refactor(sync): перевёл журнал на map по пути и убрал цикл debounce-sync
Журнал хранится как словарь path→entry, служебные пути исключены из sync. Apply пишет файлы без записи в журнал; bootstrap не триггерит sync во время работы.
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
package com.github.nullptroma.wallenc.usecases
|
||||
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournal
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournalEntry
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournalMerge
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncOperation
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncPaths
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorageSyncEngine
|
||||
@@ -113,8 +116,8 @@ class StorageSyncEngine @Inject constructor(
|
||||
lockedAccessors.add(storage.accessor)
|
||||
}
|
||||
|
||||
val latestByPath = mutableMapOf<String, StorageSyncJournalEntry>()
|
||||
val entriesByStorage = mutableMapOf<UUID, Map<String, StorageSyncJournalEntry>>()
|
||||
val mergedByPath = mutableMapOf<String, StorageSyncJournalEntry>()
|
||||
val entriesByStorage = mutableMapOf<UUID, StorageSyncJournal>()
|
||||
|
||||
reportProgress(null, TaskProgressLabel.SyncGroupReadingJournals(groupId))
|
||||
for ((journalIndex, storage) in storages.withIndex()) {
|
||||
@@ -132,17 +135,14 @@ class StorageSyncEngine @Inject constructor(
|
||||
null,
|
||||
TaskProgressLabel.SyncGroupJournalProgress(groupId, journalIndex + 1, storages.size),
|
||||
)
|
||||
val latestEntries = latestByPath(storage.accessor.readSyncJournal())
|
||||
entriesByStorage[storage.uuid] = latestEntries
|
||||
for ((path, entry) in latestEntries) {
|
||||
val current = latestByPath[path]
|
||||
if (current == null || compareEntries(entry, current) > 0) {
|
||||
latestByPath[path] = entry
|
||||
}
|
||||
}
|
||||
val journal = filterSyncableJournal(storage.accessor.readSyncJournal())
|
||||
entriesByStorage[storage.uuid] = journal
|
||||
mergedByPath.putAll(
|
||||
StorageSyncJournalMerge.merge(mergedByPath, journal),
|
||||
)
|
||||
}
|
||||
|
||||
val mergedEntries = latestByPath.entries.toList()
|
||||
val mergedEntries = mergedByPath.entries.toList()
|
||||
if (mergedEntries.isEmpty()) {
|
||||
reportProgress(null, TaskProgressLabel.SyncGroupNoJournalEntries(groupId))
|
||||
return
|
||||
@@ -191,13 +191,12 @@ class StorageSyncEngine @Inject constructor(
|
||||
entry = winnerEntry,
|
||||
)
|
||||
if (applied) {
|
||||
target.accessor.appendSyncJournal(listOf(winnerEntry))
|
||||
target.accessor.putSyncJournalEntries(mapOf(path to winnerEntry))
|
||||
} else {
|
||||
applyFailures++
|
||||
}
|
||||
}
|
||||
}
|
||||
compactSyncJournals(storages)
|
||||
if (applyFailures > 0) {
|
||||
reportProgress(
|
||||
null,
|
||||
@@ -215,6 +214,10 @@ class StorageSyncEngine @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun filterSyncableJournal(journal: StorageSyncJournal): StorageSyncJournal {
|
||||
return journal.filterKeys { StorageSyncPaths.isSyncableUserPath(it) }
|
||||
}
|
||||
|
||||
private suspend fun renewLocksIfNeeded(
|
||||
groupId: String,
|
||||
lockedAccessors: List<IStorageAccessor>,
|
||||
@@ -243,20 +246,9 @@ class StorageSyncEngine @Inject constructor(
|
||||
return uuids.mapNotNull(findStorageUseCase::find)
|
||||
}
|
||||
|
||||
private fun latestByPath(entries: List<StorageSyncJournalEntry>): Map<String, StorageSyncJournalEntry> {
|
||||
val map = mutableMapOf<String, StorageSyncJournalEntry>()
|
||||
for (entry in entries) {
|
||||
val current = map[entry.path]
|
||||
if (current == null || compareEntries(entry, current) > 0) {
|
||||
map[entry.path] = entry
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
private fun findSourceStorage(
|
||||
storages: List<IStorage>,
|
||||
entriesByStorage: Map<UUID, Map<String, StorageSyncJournalEntry>>,
|
||||
entriesByStorage: Map<UUID, StorageSyncJournal>,
|
||||
path: String,
|
||||
winnerEntry: StorageSyncJournalEntry,
|
||||
): IStorage? {
|
||||
@@ -273,16 +265,6 @@ class StorageSyncEngine @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun compactSyncJournals(storages: List<IStorage>) {
|
||||
for (storage in storages) {
|
||||
val entries = storage.accessor.readSyncJournal()
|
||||
val compacted = latestByPath(entries).values.toList()
|
||||
if (compacted.size < entries.size) {
|
||||
storage.accessor.rewriteSyncJournal(compacted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun applyEntry(
|
||||
source: IStorage?,
|
||||
target: IStorage,
|
||||
@@ -305,7 +287,7 @@ class StorageSyncEngine @Inject constructor(
|
||||
val sourceAccessor = source?.accessor ?: return false
|
||||
runCatching {
|
||||
sourceAccessor.openRead(entry.path).use { input ->
|
||||
target.accessor.openWrite(entry.path).use { output ->
|
||||
target.accessor.openWrite(entry.path, recordSyncJournal = false).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.github.nullptroma.wallenc.usecases
|
||||
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournalEntry
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournalMerge
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncOperation
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncPaths
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncRevision
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.time.Instant
|
||||
|
||||
class StorageSyncJournalMergeTest {
|
||||
|
||||
@Test
|
||||
fun mergeKeepsSingleEntryPerPath() {
|
||||
val older = entry(path = "/a.txt", sequence = 1L)
|
||||
val newer = entry(path = "/a.txt", sequence = 2L)
|
||||
val merged = StorageSyncJournalMerge.merge(
|
||||
mapOf("/a.txt" to older),
|
||||
mapOf("/a.txt" to newer),
|
||||
)
|
||||
assertEquals(1, merged.size)
|
||||
assertEquals(2L, merged["/a.txt"]?.revision?.sequence)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isSyncableUserPathExcludesEncDirAndJournal() {
|
||||
assertFalse(StorageSyncPaths.isSyncableUserPath("/88a048ed-enc-dir/sync-journal.json"))
|
||||
assertFalse(StorageSyncPaths.isSyncableUserPath("/wallenc-yandex-system/sync-journal.json"))
|
||||
assertTrue(StorageSyncPaths.isSyncableUserPath("/wallenc-data/text-secrets.json"))
|
||||
}
|
||||
|
||||
private fun entry(path: String, sequence: Long): StorageSyncJournalEntry =
|
||||
StorageSyncJournalEntry(
|
||||
path = path,
|
||||
operation = StorageSyncOperation.UPSERT,
|
||||
revision = StorageSyncRevision(
|
||||
sequence = sequence,
|
||||
actorId = "actor",
|
||||
createdAt = Instant.EPOCH,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.github.nullptroma.wallenc.usecases.fakes
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageMetaLoadState
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournalEntry
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournalMerge
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorageMetaInfo
|
||||
@@ -46,10 +47,13 @@ class FakeStorage(
|
||||
fun fileBytes(path: String): ByteArray? = accessorImpl.dataFiles[path]
|
||||
|
||||
fun addSyncJournalEntry(entry: StorageSyncJournalEntry) {
|
||||
accessorImpl.syncJournal.add(entry)
|
||||
accessorImpl.syncJournal = StorageSyncJournalMerge.merge(
|
||||
accessorImpl.syncJournal,
|
||||
mapOf(entry.path to entry),
|
||||
)
|
||||
}
|
||||
|
||||
fun syncJournalEntries(): List<StorageSyncJournalEntry> = accessorImpl.syncJournal.toList()
|
||||
fun syncJournalEntries(): List<StorageSyncJournalEntry> = accessorImpl.syncJournal.values.toList()
|
||||
|
||||
fun setAcquireLockResult(result: Boolean) {
|
||||
accessorImpl.acquireLockResult = result
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.github.nullptroma.wallenc.usecases.fakes
|
||||
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.DataPage
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournal
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournalEntry
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournalMerge
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncLock
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IDirectory
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IFile
|
||||
@@ -25,7 +27,7 @@ class FakeStorageAccessor : IStorageAccessor {
|
||||
private val systemFiles: MutableMap<String, ByteArray> = mutableMapOf()
|
||||
private val _filesUpdates = MutableSharedFlow<DataPage<IFile>>(extraBufferCapacity = 16)
|
||||
|
||||
var syncJournal: MutableList<StorageSyncJournalEntry> = mutableListOf()
|
||||
var syncJournal: StorageSyncJournal = emptyMap()
|
||||
var syncLock: StorageSyncLock? = null
|
||||
var acquireLockResult: Boolean = true
|
||||
|
||||
@@ -65,7 +67,7 @@ class FakeStorageAccessor : IStorageAccessor {
|
||||
dataFiles.remove(path)
|
||||
}
|
||||
|
||||
override suspend fun openWrite(path: String): OutputStream {
|
||||
override suspend fun openWrite(path: String, recordSyncJournal: Boolean): OutputStream {
|
||||
return object : ByteArrayOutputStream() {
|
||||
override fun close() {
|
||||
dataFiles[path] = toByteArray()
|
||||
@@ -104,15 +106,10 @@ class FakeStorageAccessor : IStorageAccessor {
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun readSyncJournal(): List<StorageSyncJournalEntry> = syncJournal.toList()
|
||||
override suspend fun readSyncJournal(): StorageSyncJournal = syncJournal
|
||||
|
||||
override suspend fun appendSyncJournal(entries: List<StorageSyncJournalEntry>) {
|
||||
syncJournal.addAll(entries)
|
||||
}
|
||||
|
||||
override suspend fun rewriteSyncJournal(entries: List<StorageSyncJournalEntry>) {
|
||||
syncJournal.clear()
|
||||
syncJournal.addAll(entries)
|
||||
override suspend fun putSyncJournalEntries(entries: StorageSyncJournal) {
|
||||
syncJournal = StorageSyncJournalMerge.merge(syncJournal, entries)
|
||||
}
|
||||
|
||||
override suspend fun readSyncLock(): StorageSyncLock? = syncLock
|
||||
|
||||
Reference in New Issue
Block a user