Skip to content

Commit ef6893a

Browse files
committed
refactor: optimize folder copying to be atomic and linear in time
1 parent c96887c commit ef6893a

2 files changed

Lines changed: 85 additions & 107 deletions

File tree

src/main/kotlin/com/sakethh/linkora/data/repository/MultiActionRepoImpl.kt

Lines changed: 80 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,31 @@ import com.sakethh.linkora.domain.Result
66
import com.sakethh.linkora.domain.Route
77
import com.sakethh.linkora.domain.dto.*
88
import com.sakethh.linkora.domain.dto.folder.MarkItemsRegularDTO
9+
import com.sakethh.linkora.domain.model.Folder
910
import com.sakethh.linkora.domain.model.WebSocketEvent
1011
import com.sakethh.linkora.domain.repository.FoldersRepo
1112
import com.sakethh.linkora.domain.repository.MultiActionRepo
1213
import com.sakethh.linkora.domain.tables.FoldersTable
14+
import com.sakethh.linkora.domain.tables.FoldersTable.folderName
15+
import com.sakethh.linkora.domain.tables.FoldersTable.isFolderArchived
16+
import com.sakethh.linkora.domain.tables.FoldersTable.lastModified
17+
import com.sakethh.linkora.domain.tables.FoldersTable.note
18+
import com.sakethh.linkora.domain.tables.FoldersTable.parentFolderID
1319
import com.sakethh.linkora.domain.tables.LinkTagTable
1420
import com.sakethh.linkora.domain.tables.LinksTable
1521
import com.sakethh.linkora.domain.tables.helper.TombStoneHelper
1622
import com.sakethh.linkora.utils.checkForLWWConflictAndThrow
17-
import com.sakethh.linkora.utils.insertNewLinks
18-
import com.sakethh.linkora.utils.insertNewFolders
1923
import com.sakethh.linkora.utils.getSystemEpochSeconds
24+
import com.sakethh.linkora.utils.insertNewLinks
2025
import kotlinx.coroutines.async
2126
import kotlinx.coroutines.awaitAll
2227
import kotlinx.coroutines.coroutineScope
2328
import kotlinx.serialization.encodeToString
2429
import kotlinx.serialization.json.Json
2530
import kotlinx.serialization.json.encodeToJsonElement
2631
import org.jetbrains.exposed.v1.core.SqlExpressionBuilder.inList
27-
import org.jetbrains.exposed.v1.jdbc.batchInsert
28-
import org.jetbrains.exposed.v1.jdbc.deleteWhere
29-
import org.jetbrains.exposed.v1.jdbc.selectAll
32+
import org.jetbrains.exposed.v1.jdbc.*
3033
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
31-
import org.jetbrains.exposed.v1.jdbc.update
3234

3335
class MultiActionRepoImpl(
3436
private val foldersRepo: FoldersRepo
@@ -46,7 +48,7 @@ class MultiActionRepoImpl(
4648
FoldersTable.checkForLWWConflictAndThrow(
4749
id = archiveMultipleItemsDTO.folderIds.last(),
4850
timeStamp = archiveMultipleItemsDTO.eventTimestamp,
49-
lastModifiedColumn = FoldersTable.lastModified
51+
lastModifiedColumn = lastModified
5052
)
5153
}
5254
val eventTimestamp = getSystemEpochSeconds()
@@ -127,7 +129,7 @@ class MultiActionRepoImpl(
127129
FoldersTable.checkForLWWConflictAndThrow(
128130
id = moveItemsDTO.folderIds.last(),
129131
timeStamp = moveItemsDTO.eventTimestamp,
130-
lastModifiedColumn = FoldersTable.lastModified
132+
lastModifiedColumn = lastModified
131133
)
132134
}
133135
if (moveItemsDTO.linkIds.isNotEmpty()) {
@@ -175,6 +177,8 @@ class MultiActionRepoImpl(
175177
lateinit var globalLinksOldToNewIdsMap: Map<Long, Long>
176178
val copiedFolderResponse = mutableListOf<CopiedFolderResponse>()
177179
transaction {
180+
181+
// 1. Copy the root links that aren't connected to anything
178182
val sourceGlobalSelectedLinks = LinksTable.selectAll().where {
179183
LinksTable.id inList copyItemsDTO.linkIds.values
180184
}.toList()
@@ -188,126 +192,98 @@ class MultiActionRepoImpl(
188192

189193
insertNewLinkTags(oldToNewLinkIdsMap = globalLinksOldToNewIdsMap)
190194

191-
// initially, we'll insert the root folders
192-
val sourceRootFolders = FoldersTable.selectAll().where {
193-
FoldersTable.id.inList(copyItemsDTO.folders.map { it.currentFolder.remoteId })
194-
}.toList()
195+
val localToNewFolderCreationMap = mutableMapOf<Long, Long>() // local to remote
195196

196-
val copiedRootFolderOldToNewIdsMap = FoldersTable.insertNewFolders(
197-
source = sourceRootFolders,
198-
eventTimestamp = eventTimestamp,
199-
parentFolderId = copyItemsDTO.newParentFolderId
200-
)
197+
copyItemsDTO.folders.forEach { (currentFolder, links) ->
201198

202-
copyItemsDTO.folders.forEach { (currentFolder, _, _) ->
203-
copiedFolderResponse.add(
204-
CopiedFolderResponse(
205-
currentFolder = CurrentFolder(
206-
localId = currentFolder.localId,
207-
remoteId = copiedRootFolderOldToNewIdsMap.getValue(currentFolder.remoteId)
208-
), links = emptyList()
199+
val sourceFolder = FoldersTable.selectAll().where {
200+
FoldersTable.id.eq(currentFolder.sourceRemoteId)
201+
}.limit(1).map {
202+
Folder(
203+
id = it[FoldersTable.id].value,
204+
name = it[folderName],
205+
note = it[note],
206+
parentFolderId = it[parentFolderID],
207+
isArchived = it[isFolderArchived],
208+
eventTimestamp = it[lastModified]
209209
)
210-
)
211-
}
212-
213-
// insert links of root folders
214-
copyItemsDTO.folders.forEachIndexed { folderIndex, folder ->
215-
val sourceLinksOfRootFolder = LinksTable.selectAll().where {
216-
LinksTable.id.inList(folder.links.map { it.remoteId })
217-
}.toList()
218-
219-
val newRootFolderLinkIds = LinksTable.insertNewLinks(
220-
source = sourceLinksOfRootFolder,
221-
eventTimestamp = eventTimestamp,
222-
parentFolderId = copiedRootFolderOldToNewIdsMap[folder.currentFolder.remoteId]
223-
)
224-
225-
insertNewLinkTags(oldToNewLinkIdsMap = newRootFolderLinkIds)
210+
}.first()
226211

227-
val folderLinks = folder.links.mapNotNull {
228-
val newRemoteId = newRootFolderLinkIds[it.remoteId]
229-
if (newRemoteId != null) {
230-
FolderLink(localId = it.localId, remoteId = newRemoteId)
231-
} else {
232-
println("folder.links.mapNotNull hit null")
233-
null
234-
}
212+
val parentFolderOfCopiedFolder = if (currentFolder.isRootFolderForTheDestination) {
213+
copyItemsDTO.newParentFolderId
214+
} else {
215+
localToNewFolderCreationMap.getOrElse(
216+
key = currentFolder.parentOfNewlyCopiedLocalId, defaultValue = {
217+
null
218+
})
235219
}
236-
copiedFolderResponse[folderIndex] = copiedFolderResponse[folderIndex].copy(
237-
links = folderLinks
238-
)
239-
}
240-
241-
fun insertChildFolders(
242-
parentFolderId: Long,
243-
childFolders: List<CopyFolderDTO>,
244-
) {
245-
val sourceFolders = FoldersTable.selectAll().where {
246-
FoldersTable.id.inList(childFolders.map { it.currentFolder.remoteId })
247-
}.toList()
248220

249-
val oldToNewChildFolderMap =
250-
FoldersTable.insertNewFolders(source = sourceFolders, eventTimestamp, parentFolderId)
221+
val copiedFolderId = FoldersTable.insertAndGetId { folder ->
222+
folder[lastModified] = eventTimestamp
223+
folder[folderName] = sourceFolder.name
224+
folder[note] = sourceFolder.note
225+
folder[parentFolderID] = parentFolderOfCopiedFolder
226+
folder[isFolderArchived] = sourceFolder.isArchived
227+
}.value
251228

252-
childFolders.forEach { childFolder ->
253-
val oldFolderId = childFolder.currentFolder.remoteId
254-
val newFolderId = oldToNewChildFolderMap.getValue(oldFolderId)
229+
localToNewFolderCreationMap[currentFolder.newlyCopiedLocalId] = copiedFolderId
255230

256-
val sourceCurrentFolderLinks = LinksTable.selectAll().where {
257-
LinksTable.id.inList(childFolder.links.map { it.remoteId })
258-
}
259-
val newChildFolderLinksMap = LinksTable.insertNewLinks(
260-
source = sourceCurrentFolderLinks.toList(),
261-
eventTimestamp = eventTimestamp,
262-
parentFolderId = newFolderId
263-
)
231+
val sourceLinks = LinksTable.selectAll().where {
232+
LinksTable.id.inList(links.map {
233+
it.sourceRemoteId
234+
})
235+
}.toList()
264236

265-
insertNewLinkTags(oldToNewLinkIdsMap = newChildFolderLinksMap)
237+
val oldToNewLinkIdsMap = LinksTable.insertNewLinks(
238+
source = sourceLinks,
239+
eventTimestamp = eventTimestamp,
240+
parentFolderId = copiedFolderId,
241+
newLinkType = LinkType.FOLDER_LINK
242+
)
266243

267-
val folderLinks = childFolder.links.mapNotNull { link ->
268-
val newRemoteId = newChildFolderLinksMap[link.remoteId]
269-
if (newRemoteId != null) {
270-
FolderLink(localId = link.localId, remoteId = newRemoteId)
271-
} else {
272-
println("childFolder.links.mapNotNull hit null")
273-
null
274-
}
275-
}
244+
insertNewLinkTags(oldToNewLinkIdsMap)
276245

277-
copiedFolderResponse.add(
278-
CopiedFolderResponse(
279-
currentFolder = CurrentFolder(
280-
localId = childFolder.currentFolder.localId, remoteId = newFolderId
281-
), links = folderLinks
282-
)
283-
)
284-
insertChildFolders(
285-
parentFolderId = newFolderId, childFolder.childFolders
286-
)
287-
}
288-
}
246+
copiedFolderResponse.add(
247+
CopiedFolderResponse(
248+
currentFolder = CurrentFolder(
249+
newlyCopiedLocalId = currentFolder.newlyCopiedLocalId,
250+
sourceRemoteId = copiedFolderId,
251+
sourceRemoteParentId = -45454, // the client doesn't give a damn about this value
252+
isRootFolderForTheDestination = false, // the client doesn't give a damn about this value
253+
parentOfNewlyCopiedLocalId = -45454, // the client doesn't give a damn about this value
254+
), links = links.mapNotNull { link ->
255+
val newRemoteId =
256+
oldToNewLinkIdsMap[link.sourceRemoteId] ?: return@mapNotNull null
289257

290-
// insert child folders
291-
copyItemsDTO.folders.forEach { parentFolder ->
292-
insertChildFolders(
293-
parentFolderId = copiedRootFolderOldToNewIdsMap.getValue(parentFolder.currentFolder.remoteId),
294-
childFolders = parentFolder.childFolders
258+
FolderLink(
259+
newlyCopiedLocalId = link.newlyCopiedLocalId,
260+
sourceRemoteId = newRemoteId,
261+
sourceRemoteParentId = -454545, // the client doesn't give a damn about this value
262+
isRootFolderForTheDestination = false, // the client doesn't give a damn about this value
263+
parentOfNewlyCopiedLocalId = -45454, // the client doesn't give a damn about this value
264+
)
265+
})
295266
)
296267
}
297268
}
298269
Result.Success(
299270
response = CopyItemsHTTPResponseDTO(
300271
folders = copiedFolderResponse.toList(),
301-
linkIds = globalLinksOldToNewIdsMap,
272+
linkIds = copyItemsDTO.linkIds.mapNotNull { (localId, oldRemoteId) ->
273+
globalLinksOldToNewIdsMap[oldRemoteId]?.let { newRemoteId ->
274+
localId to newRemoteId
275+
}
276+
}.toMap(),
302277
correlation = copyItemsDTO.correlation,
303278
eventTimestamp = eventTimestamp
304-
), webSocketEvent = WebSocketEvent(
279+
),
280+
webSocketEvent = WebSocketEvent(
305281
operation = Route.COPY_EXISTING_ITEMS.name, payload = Json.encodeToJsonElement(
306282
CopyItemsSocketResponseDTO(
307283
eventTimestamp = eventTimestamp, correlation = copyItemsDTO.correlation
308284
)
309285
)
310-
)
286+
),
311287
)
312288
} catch (e: Exception) {
313289
Result.Failure(e)
@@ -345,7 +321,7 @@ class MultiActionRepoImpl(
345321
FoldersTable.checkForLWWConflictAndThrow(
346322
id = markItemsRegularDTO.foldersIds.random(),
347323
timeStamp = markItemsRegularDTO.eventTimestamp,
348-
lastModifiedColumn = FoldersTable.lastModified
324+
lastModifiedColumn = lastModified
349325
)
350326
}
351327
if (markItemsRegularDTO.linkIds.isNotEmpty()) {

src/main/kotlin/com/sakethh/linkora/domain/dto/CopyItemsDTO.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@ data class CopyItemsDTO(
1010
val linkType: LinkType, val newParentFolderId: Long, val correlation: Correlation, val eventTimestamp: Long
1111
)
1212

13-
1413
@Serializable
1514
data class CopyFolderDTO(
1615
val currentFolder: CurrentFolder,
1716
val links: List<FolderLink>,
18-
val childFolders: List<CopyFolderDTO>
1917
)
2018

2119
@Serializable
2220
data class CurrentFolder(
23-
val localId: Long, val remoteId: Long
21+
val newlyCopiedLocalId: Long,
22+
val parentOfNewlyCopiedLocalId: Long,
23+
val sourceRemoteId: Long,
24+
val sourceRemoteParentId: Long?,
25+
val isRootFolderForTheDestination: Boolean
2426
)
2527

2628
typealias FolderLink = CurrentFolder

0 commit comments

Comments
 (0)