refactor(sync): перевёл журнал на map по пути и убрал цикл debounce-sync

Журнал хранится как словарь path→entry, служебные пути исключены из sync.
Apply пишет файлы без записи в журнал; bootstrap не триггерит sync во время работы.
This commit is contained in:
2026-05-21 22:05:57 +03:00
parent 51e6f40587
commit d0f490a3fd
11 changed files with 275 additions and 153 deletions

View File

@@ -0,0 +1,59 @@
package com.github.nullptroma.wallenc.domain.datatypes
/** Журнал синхронизации: один актуальный [StorageSyncJournalEntry] на нормализованный путь. */
typealias StorageSyncJournal = Map<String, StorageSyncJournalEntry>
object StorageSyncPaths {
fun normalize(path: String): String = if (path.startsWith("/")) path else "/$path"
/**
* Пути, которые участвуют в sync и попадают в журнал при пользовательских операциях.
* Служебные каталоги, lock/journal/meta и файлы внутри *-enc-dir исключены.
*/
fun isSyncableUserPath(path: String): Boolean {
val p = normalize(path)
if (p == "/" || p.isBlank()) return false
if (p == "/wallenc-yandex-system" || p.startsWith("/wallenc-yandex-system/")) return false
if (p.contains("-enc-dir/") || p.endsWith("-enc-dir")) return false
val name = p.substringAfterLast('/')
if (name == "sync-journal.json" || name == "sync-lock.json") return false
if (name.endsWith(".enc-meta") || name.endsWith(".storage-info")) return false
return true
}
}
object StorageSyncJournalMerge {
fun merge(into: StorageSyncJournal, entries: Map<String, StorageSyncJournalEntry>): StorageSyncJournal {
if (entries.isEmpty()) return into
val result = into.toMutableMap()
for ((rawPath, entry) in entries) {
val path = StorageSyncPaths.normalize(rawPath)
if (!StorageSyncPaths.isSyncableUserPath(path)) continue
val normalizedEntry = entry.copy(path = path)
val current = result[path]
if (current == null || compareEntries(normalizedEntry, current) > 0) {
result[path] = normalizedEntry
}
}
return result
}
fun merge(into: StorageSyncJournal, entry: StorageSyncJournalEntry): StorageSyncJournal =
merge(into, mapOf(entry.path to entry))
fun mergeAll(journals: Collection<StorageSyncJournal>): StorageSyncJournal {
var acc: StorageSyncJournal = emptyMap()
for (journal in journals) {
acc = merge(acc, journal)
}
return acc
}
private fun compareEntries(a: StorageSyncJournalEntry, b: StorageSyncJournalEntry): Int {
val seqCmp = a.revision.sequence.compareTo(b.revision.sequence)
if (seqCmp != 0) return seqCmp
val actorCmp = a.revision.actorId.compareTo(b.revision.actorId)
if (actorCmp != 0) return actorCmp
return a.revision.createdAt.compareTo(b.revision.createdAt)
}
}

View File

@@ -1,7 +1,7 @@
package com.github.nullptroma.wallenc.domain.interfaces
import com.github.nullptroma.wallenc.domain.datatypes.DataPage
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournalEntry
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournal
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncLock
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
@@ -40,7 +40,7 @@ interface IStorageAccessor {
suspend fun touchFile(path: String)
suspend fun touchDir(path: String)
suspend fun delete(path: String)
suspend fun openWrite(path: String): OutputStream
suspend fun openWrite(path: String, recordSyncJournal: Boolean = true): OutputStream
suspend fun openRead(path: String): InputStream
suspend fun moveToTrash(path: String)
@@ -52,9 +52,8 @@ interface IStorageAccessor {
suspend fun openReadSystemFile(name: String): InputStream
suspend fun openWriteSystemFile(name: String): OutputStream
suspend fun readSyncJournal(): List<StorageSyncJournalEntry>
suspend fun appendSyncJournal(entries: List<StorageSyncJournalEntry>)
suspend fun rewriteSyncJournal(entries: List<StorageSyncJournalEntry>)
suspend fun readSyncJournal(): StorageSyncJournal
suspend fun putSyncJournalEntries(entries: StorageSyncJournal)
suspend fun readSyncLock(): StorageSyncLock?
suspend fun tryAcquireSyncLock(holderId: String, leaseUntil: Instant): Boolean