diff --git a/base/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/base/http/ProjectApi.kt b/base/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/base/http/ProjectApi.kt index 6a99d1c31..a72ed2764 100644 --- a/base/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/base/http/ProjectApi.kt +++ b/base/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/base/http/ProjectApi.kt @@ -25,6 +25,7 @@ data class ProjectSynchronizationBegan( val syncId: String, val lastSync: Instant, val lastId: Int, + val idSequence: List, val deletedIds: Set ) diff --git a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projectsync/ClientProjectSynchronizer.kt b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projectsync/ClientProjectSynchronizer.kt index 9ae8aa4b2..a00b8b993 100644 --- a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projectsync/ClientProjectSynchronizer.kt +++ b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projectsync/ClientProjectSynchronizer.kt @@ -425,7 +425,17 @@ class ClientProjectSynchronizer( onConflict: EntityConflictHandler ): Boolean { var allSuccess = true - for (thisId in 1..maxId) { + + val maxServerId = serverSyncData.idSequence.max() + // Add local IDs on top of server sequence + val combinedSequence = if(maxId > maxServerId) { + val localIds = (maxServerId+1 .. maxId).toList() + serverSyncData.idSequence + localIds + } else { + serverSyncData.idSequence + } + + for (thisId in combinedSequence) { if (thisId in combinedDeletions) { Napier.d("Skipping deleted ID $thisId") continue diff --git a/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/ProjectRepository.kt b/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/ProjectRepository.kt index a5ee51142..7517c010b 100644 --- a/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/ProjectRepository.kt +++ b/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/ProjectRepository.kt @@ -99,10 +99,12 @@ class ProjectRepository( ) } + val updateSequence = getUpdateSequence(userId, projectDef) val syncBegan = ProjectSynchronizationBegan( syncId = newSyncId, lastId = projectSyncData.lastId, lastSync = projectSyncData.lastSync, + idSequence = updateSequence, deletedIds = projectSyncData.deletedIds, ) Result.success(syncBegan) @@ -352,6 +354,17 @@ class ProjectRepository( sessionManager.validateSyncId(userId, syncId, true) } + private suspend fun getUpdateSequence(userId: Long, projectDef: ProjectDefinition): List { + val updateSequence = mutableSetOf() + updateSequence += sceneSynchronizer.getUpdateSequence(userId, projectDef) + updateSequence += sceneDraftSynchronizer.getUpdateSequence(userId, projectDef) + updateSequence += noteSynchronizer.getUpdateSequence(userId, projectDef) + updateSequence += timelineEventSynchronizer.getUpdateSequence(userId, projectDef) + updateSequence += encyclopediaSynchronizer.getUpdateSequence(userId, projectDef) + + return updateSequence.toList() + } + companion object { const val SYNC_DATA_FILE = "syncData.json" const val ENTITY_DIR = "entities" diff --git a/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerEncyclopediaSynchronizer.kt b/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerEncyclopediaSynchronizer.kt index c10fc06c4..d856486db 100644 --- a/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerEncyclopediaSynchronizer.kt +++ b/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerEncyclopediaSynchronizer.kt @@ -20,6 +20,7 @@ class ServerEncyclopediaSynchronizer( ) } + override val entityType = ApiProjectEntity.Type.ENCYCLOPEDIA_ENTRY override val entityClazz = ApiProjectEntity.EncyclopediaEntryEntity::class override val pathStub = ApiProjectEntity.Type.ENCYCLOPEDIA_ENTRY.name.lowercase() } \ No newline at end of file diff --git a/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerEntitySynchronizer.kt b/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerEntitySynchronizer.kt index ea4ec27f1..20671071e 100644 --- a/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerEntitySynchronizer.kt +++ b/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerEntitySynchronizer.kt @@ -18,6 +18,7 @@ abstract class ServerEntitySynchronizer( protected val fileSystem: FileSystem, protected val json: Json ) { + abstract val entityType: ApiProjectEntity.Type abstract fun hashEntity(entity: T): String protected abstract val entityClazz: KClass protected abstract val pathStub: String @@ -123,6 +124,19 @@ abstract class ServerEntitySynchronizer( fileSystem.delete(path, false) } + protected fun getEntityDefs(userId: Long, projectDef: ProjectDefinition): List { + val entityDir = ProjectRepository.getEntityDirectory(userId, projectDef, fileSystem) + val entities = fileSystem.list(entityDir).mapNotNull{ + parseEntityFilename(it) + }.filter { it.type == entityType } + return entities + } + + open fun getUpdateSequence(userId: Long, projectDef: ProjectDefinition): List { + val entities = getEntityDefs(userId, projectDef) + return entities.map { it.id } + } + companion object { val ENTITY_FILENAME_REGEX = Regex("^([0-9]+)-([a-zA-Z_]+).json$") diff --git a/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerNoteSynchronizer.kt b/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerNoteSynchronizer.kt index 1a7f15469..091b8d17c 100644 --- a/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerNoteSynchronizer.kt +++ b/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerNoteSynchronizer.kt @@ -17,6 +17,7 @@ class ServerNoteSynchronizer( ) } + override val entityType = ApiProjectEntity.Type.NOTE override val entityClazz = ApiProjectEntity.NoteEntity::class override val pathStub = ApiProjectEntity.Type.NOTE.name.lowercase() } \ No newline at end of file diff --git a/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerSceneDraftSynchronizer.kt b/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerSceneDraftSynchronizer.kt index 986d2abd0..82d3b579f 100644 --- a/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerSceneDraftSynchronizer.kt +++ b/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerSceneDraftSynchronizer.kt @@ -18,6 +18,7 @@ class ServerSceneDraftSynchronizer( ) } + override val entityType = ApiProjectEntity.Type.SCENE_DRAFT override val entityClazz = ApiProjectEntity.SceneDraftEntity::class override val pathStub = ApiProjectEntity.Type.SCENE_DRAFT.name.lowercase() } \ No newline at end of file diff --git a/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerSceneSynchronizer.kt b/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerSceneSynchronizer.kt index 52199c97b..7c4cf5fc3 100644 --- a/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerSceneSynchronizer.kt +++ b/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerSceneSynchronizer.kt @@ -2,6 +2,7 @@ package com.darkrockstudios.apps.hammer.project.synchronizers import com.darkrockstudios.apps.hammer.base.http.ApiProjectEntity import com.darkrockstudios.apps.hammer.base.http.synchronizer.EntityHash +import com.darkrockstudios.apps.hammer.project.ProjectDefinition import kotlinx.serialization.json.Json import okio.FileSystem @@ -20,6 +21,27 @@ class ServerSceneSynchronizer( ) } + override fun getUpdateSequence(userId: Long, projectDef: ProjectDefinition): List { + val entities = getEntityDefs(userId, projectDef) + + // Sort by SceneType, we want directories first + val entityIds = entities.mapNotNull { def -> + val entityResult = loadEntity(userId, projectDef, def.id) + return@mapNotNull if (entityResult.isSuccess) { + val entity = entityResult.getOrThrow() + Pair(def.id, entity.sceneType) + } else { + println("Failed to get entity $def.id: ${entityResult.exceptionOrNull()?.message}") + null + } + } + .sortedByDescending { it.second.ordinal } + .map { it.first } + + return entityIds + } + + override val entityType = ApiProjectEntity.Type.SCENE override val entityClazz = ApiProjectEntity.SceneEntity::class override val pathStub = ApiProjectEntity.Type.SCENE.name.lowercase() } \ No newline at end of file diff --git a/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerTimelineSynchronizer.kt b/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerTimelineSynchronizer.kt index e221bf57c..672e9ba16 100644 --- a/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerTimelineSynchronizer.kt +++ b/server/src/main/kotlin/com/darkrockstudios/apps/hammer/project/synchronizers/ServerTimelineSynchronizer.kt @@ -18,6 +18,7 @@ class ServerTimelineSynchronizer( ) } + override val entityType = ApiProjectEntity.Type.TIMELINE_EVENT override val entityClazz = ApiProjectEntity.TimelineEventEntity::class override val pathStub = ApiProjectEntity.Type.TIMELINE_EVENT.name.lowercase() } \ No newline at end of file diff --git a/server/src/test/kotlin/com/darkrockstudios/apps/hammer/ProjectRepositoryTest.kt b/server/src/test/kotlin/com/darkrockstudios/apps/hammer/ProjectRepositoryTest.kt index 2123cae07..95f1d119f 100644 --- a/server/src/test/kotlin/com/darkrockstudios/apps/hammer/ProjectRepositoryTest.kt +++ b/server/src/test/kotlin/com/darkrockstudios/apps/hammer/ProjectRepositoryTest.kt @@ -65,6 +65,12 @@ class ProjectRepositoryTest : BaseTest() { encyclopediaSynchronizer = mockk() sceneDraftSynchronizer = mockk() + coEvery { sceneSynchronizer.getUpdateSequence(userId, projectDefinition) } returns emptyList() + coEvery { noteSynchronizer.getUpdateSequence(userId, projectDefinition) } returns emptyList() + coEvery { timelineEventSynchronizer.getUpdateSequence(userId, projectDefinition) } returns emptyList() + coEvery { encyclopediaSynchronizer.getUpdateSequence(userId, projectDefinition) } returns emptyList() + coEvery { sceneDraftSynchronizer.getUpdateSequence(userId, projectDefinition) } returns emptyList() + val testModule = module { single { fileSystem } bind FileSystem::class single { Json } bind Json::class