Skip to content

Commit 78713d8

Browse files
committed
fix(model-server): migrate legacy list of repositories at startup
1 parent c8ae9dc commit 78713d8

File tree

4 files changed

+81
-19
lines changed

4 files changed

+81
-19
lines changed

model-client/src/commonMain/kotlin/org/modelix/model/lazy/RepositoryId.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ data class RepositoryId(val id: String) {
2323
require(id.matches(VALID_ID_PATTERN)) { "Invalid repository ID: $id" }
2424
}
2525

26-
@Deprecated("Use getBranchReference().getKey()")
26+
@Deprecated("Use getBranchReference().getKey()", ReplaceWith("getBranchReference(branchName).getKey()"))
2727
fun getBranchKey(branchName: String?): String {
2828
return getBranchReference(branchName).getKey()
2929
}
3030

31-
@Deprecated("Use getBranchReference().getKey()")
31+
@Deprecated("Use getBranchReference().getKey()", ReplaceWith("getBranchReference().getKey()"))
3232
fun getBranchKey(): String = getBranchKey(null)
3333

3434
fun getBranchReference(branchName: String? = DEFAULT_BRANCH): BranchReference {

model-server/src/main/kotlin/org/modelix/model/server/handlers/HistoryHandler.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class HistoryHandler(private val client: IModelClient) {
8888
client.put(repositoryAndBranch.branchKey, newVersion.write())
8989
}
9090

91+
@Deprecated("Use RepositoriesManager")
9192
val knownRepositoryIds: Set<RepositoryAndBranch>
9293
get() {
9394
val result: MutableSet<RepositoryAndBranch> = HashSet()
@@ -326,6 +327,7 @@ class HistoryHandler(private val client: IModelClient) {
326327
return defaultValue
327328
}
328329

330+
@Deprecated("Use BranchReference")
329331
data class RepositoryAndBranch(val repository: String, val branch: String = RepositoryId.DEFAULT_BRANCH) {
330332
val branchKey: String
331333
get() = RepositoryId(repository).getBranchKey(branch)

model-server/src/main/kotlin/org/modelix/model/server/handlers/ModelReplicationServer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class ModelReplicationServer(val storeClient: IStoreClient) {
8989
}
9090
route("repositories") {
9191
get {
92-
call.respondText(repositoriesManager.getRepositoryNames().joinToString("\n"))
92+
call.respondText(repositoriesManager.getRepositories().joinToString("\n") { it.id })
9393
}
9494
route("{repository}") {
9595
fun ApplicationCall.repositoryId() = RepositoryId(parameters["repository"]!!)

model-server/src/main/kotlin/org/modelix/model/server/handlers/RepositoriesManager.kt

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ package org.modelix.model.server.handlers
1515

1616
import kotlinx.datetime.Clock
1717
import org.modelix.model.VersionMerger
18+
import org.modelix.model.api.IBranch
19+
import org.modelix.model.api.IReadTransaction
20+
import org.modelix.model.api.ITree
21+
import org.modelix.model.api.IdGeneratorDummy
22+
import org.modelix.model.api.PBranch
1823
import org.modelix.model.lazy.BranchReference
1924
import org.modelix.model.lazy.BulkQuery
2025
import org.modelix.model.lazy.CLHamtNode
@@ -24,32 +29,34 @@ import org.modelix.model.lazy.IDeserializingKeyValueStore
2429
import org.modelix.model.lazy.KVEntryReference
2530
import org.modelix.model.lazy.ObjectStoreCache
2631
import org.modelix.model.lazy.RepositoryId
32+
import org.modelix.model.metameta.MetaModelBranch
2733
import org.modelix.model.persistent.CPNode
2834
import org.modelix.model.server.store.IStoreClient
2935
import org.modelix.model.server.store.LocalModelClient
3036
import org.modelix.model.server.store.pollEntry
3137

32-
/**
33-
* Multiple instances can be used at the same time, because there is no state outside the store.
34-
*/
3538
class RepositoriesManager(val client: LocalModelClient) {
39+
init {
40+
migrateLegacyRepositoriesList()
41+
}
42+
3643
private val store: IStoreClient get() = client.store
3744

3845
fun generateClientId(repositoryId: RepositoryId): Long {
3946
return client.store.generateId("$KEY_PREFIX:${repositoryId.id}:clientId")
4047
}
4148

42-
fun getRepositoryNames(): Set<String> {
43-
return store[REPOSITORIES_LIST_KEY]?.lines()?.toSet() ?: emptySet()
49+
fun getRepositories(): Set<RepositoryId> {
50+
return store[REPOSITORIES_LIST_KEY]?.lines()?.map { RepositoryId(it) }?.toSet() ?: emptySet()
4451
}
4552

4653
fun createRepository(repositoryId: RepositoryId, userName: String?): CLVersion {
4754
var initialVersion: CLVersion? = null
4855
store.runTransaction {
4956
val masterBranch = repositoryId.getBranchReference()
50-
val existingRepositories = getRepositoryNames()
51-
if (existingRepositories.contains(repositoryId.id)) throw RepositoryAlreadyExistsException(repositoryId.id)
52-
store.put(REPOSITORIES_LIST_KEY, (existingRepositories + repositoryId.id).joinToString("\n"), false)
57+
val existingRepositories = getRepositories()
58+
if (existingRepositories.contains(repositoryId)) throw RepositoryAlreadyExistsException(repositoryId.id)
59+
store.put(REPOSITORIES_LIST_KEY, (existingRepositories + repositoryId).joinToString("\n") { it.id }, false)
5360
store.put(branchListKey(repositoryId), masterBranch.branchName, false)
5461
initialVersion = CLVersion.createRegularVersion(
5562
id = client.idGenerator.generate(),
@@ -71,19 +78,34 @@ class RepositoriesManager(val client: LocalModelClient) {
7178
/**
7279
* Must be executed inside a transaction
7380
*/
74-
private fun ensureBranchIsInList(branch: BranchReference) {
75-
val key = branchListKey(branch.repositoryId)
81+
private fun ensureRepositoriesAreInList(repositoryIds: Set<RepositoryId>) {
82+
if (repositoryIds.isEmpty()) return
83+
val key = REPOSITORIES_LIST_KEY
84+
val existingRepositories = getRepositories()
85+
val missingRepositories = repositoryIds - existingRepositories
86+
if (missingRepositories.isNotEmpty()) {
87+
store.put(key, (existingRepositories + missingRepositories).joinToString("\n") { it.id })
88+
}
89+
}
90+
91+
/**
92+
* Must be executed inside a transaction
93+
*/
94+
private fun ensureBranchesAreInList(repository: RepositoryId, branchNames: Set<String>) {
95+
if (branchNames.isEmpty()) return
96+
val key = branchListKey(repository)
7697
val existingBranches = store[key]?.lines()?.toSet() ?: emptySet()
77-
if (!existingBranches.contains(branch.branchName)) {
78-
store.put(key, (existingBranches + branch.branchName).joinToString("\n"))
98+
val missingBranches = branchNames - existingBranches
99+
if (missingBranches.isNotEmpty()) {
100+
store.put(key, (existingBranches + missingBranches).joinToString("\n"))
79101
}
80102
}
81103

82104
fun mergeChanges(branch: BranchReference, newVersionHash: String): String {
83105
var result: String? = null
84106
store.runTransaction {
85107
val branchKey = branchKey(branch)
86-
val headHash = store[branchKey] ?: store[legacyBranchKey(branch)]
108+
val headHash = getVersionHash(branch)
87109
val mergedHash = if (headHash == null) {
88110
newVersionHash
89111
} else {
@@ -97,15 +119,21 @@ class RepositoriesManager(val client: LocalModelClient) {
97119
.mergeChange(headVersion, newVersion)
98120
mergedVersion.hash
99121
}
100-
store.put(branchKey, mergedHash, false)
101-
ensureBranchIsInList(branch)
122+
putVersionHash(branch, mergedHash)
123+
ensureBranchesAreInList(branch.repositoryId, setOf(branch.branchName))
102124
result = mergedHash
103125
}
104126
return result!!
105127
}
106128

107129
fun getVersionHash(branch: BranchReference): String? {
108130
return store[branchKey(branch)]
131+
?: store[legacyBranchKey(branch)]?.also { store.put(branchKey(branch), it, true) }
132+
}
133+
134+
private fun putVersionHash(branch: BranchReference, hash: String) {
135+
store.put(branchKey(branch), hash, false)
136+
store.put(legacyBranchKey(branch), hash, false)
109137
}
110138

111139
suspend fun pollVersionHash(branch: BranchReference, lastKnown: String?): String {
@@ -181,7 +209,39 @@ class RepositoriesManager(val client: LocalModelClient) {
181209
}
182210

183211
private fun branchListKey(repositoryId: RepositoryId) = "$KEY_PREFIX:repositories:${repositoryId.id}:branches"
184-
212+
213+
fun migrateLegacyRepositoriesList() {
214+
val legacyRepositories = listLegacyRepositories().groupBy { it.repositoryId }
215+
if (legacyRepositories.isNotEmpty()) {
216+
store.runTransaction {
217+
ensureRepositoriesAreInList(legacyRepositories.keys)
218+
for ((legacyRepository, legacyBranches) in legacyRepositories) {
219+
ensureBranchesAreInList(legacyRepository, legacyBranches.map { it.branchName }.toSet())
220+
}
221+
}
222+
}
223+
}
224+
225+
private fun listLegacyRepositories(): Set<BranchReference> {
226+
val result: MutableSet<BranchReference> = HashSet()
227+
val infoVersionHash = client[RepositoryId("info").getBranchReference().getKey()] ?: return emptySet()
228+
val infoVersion = CLVersion(infoVersionHash, client.storeCache)
229+
val infoBranch: IBranch = MetaModelBranch(PBranch(infoVersion.getTree(), IdGeneratorDummy()))
230+
infoBranch.runReadT { t: IReadTransaction ->
231+
for (infoNodeId in t.getChildren(ITree.ROOT_ID, "info")) {
232+
for (repositoryNodeId in t.getChildren(infoNodeId, "repositories")) {
233+
val repositoryId = t.getProperty(repositoryNodeId, "id")?.let { RepositoryId(it) } ?: continue
234+
result.add(repositoryId.getBranchReference())
235+
for (branchNodeId in t.getChildren(repositoryNodeId, "branches")) {
236+
val branchName = t.getProperty(branchNodeId, "name") ?: continue
237+
result.add(repositoryId.getBranchReference(branchName))
238+
}
239+
}
240+
}
241+
}
242+
return result
243+
}
244+
185245
companion object {
186246
const val KEY_PREFIX = ":v2"
187247
private const val REPOSITORIES_LIST_KEY = "$KEY_PREFIX:repositories"

0 commit comments

Comments
 (0)