Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d7a8b5f3d |
@@ -70,6 +70,7 @@ class SyncEngine @Inject constructor(
|
|||||||
val localFiles = accessor.walkFiles(pair)
|
val localFiles = accessor.walkFiles(pair)
|
||||||
|
|
||||||
val allPaths = (localFiles.keys + remoteFiles.keys + knownStates.keys).toSet()
|
val allPaths = (localFiles.keys + remoteFiles.keys + knownStates.keys).toSet()
|
||||||
|
val hasPriorSyncState = knownStates.isNotEmpty()
|
||||||
val semaphore = Semaphore(4)
|
val semaphore = Semaphore(4)
|
||||||
|
|
||||||
// Each async block returns its outcome; no shared mutable state across coroutines.
|
// Each async block returns its outcome; no shared mutable state across coroutines.
|
||||||
@@ -87,7 +88,7 @@ class SyncEngine @Inject constructor(
|
|||||||
val local = localFiles[rel]
|
val local = localFiles[rel]
|
||||||
val remote = remoteFiles[rel]
|
val remote = remoteFiles[rel]
|
||||||
val known = knownStates[rel]
|
val known = knownStates[rel]
|
||||||
val decision = syncDecide(pair.syncDirection, pair.conflictStrategy, pair.deleteBehavior, local, remote, known)
|
val decision = syncDecide(pair.syncDirection, pair.conflictStrategy, pair.deleteBehavior, local, remote, known, hasPriorSyncState)
|
||||||
|
|
||||||
when (decision) {
|
when (decision) {
|
||||||
SyncDecision.UPLOAD -> {
|
SyncDecision.UPLOAD -> {
|
||||||
@@ -228,6 +229,7 @@ internal fun syncDecide(
|
|||||||
local: LocalFileInfo?,
|
local: LocalFileInfo?,
|
||||||
remote: RemoteFile?,
|
remote: RemoteFile?,
|
||||||
known: SyncFileStateEntity?,
|
known: SyncFileStateEntity?,
|
||||||
|
hasPriorSyncState: Boolean = false,
|
||||||
): SyncDecision {
|
): SyncDecision {
|
||||||
val localExists = local != null
|
val localExists = local != null
|
||||||
val remoteExists = remote != null
|
val remoteExists = remote != null
|
||||||
@@ -257,10 +259,22 @@ internal fun syncDecide(
|
|||||||
}
|
}
|
||||||
|
|
||||||
!localExists && remoteExists -> when {
|
!localExists && remoteExists -> when {
|
||||||
known == null -> when (direction) {
|
known == null -> if (!hasPriorSyncState) {
|
||||||
|
// Initial sync: no history at all — remote files are new, download them.
|
||||||
|
when (direction) {
|
||||||
SyncDirection.DOWNLOAD_ONLY, SyncDirection.TWO_WAY -> SyncDecision.DOWNLOAD
|
SyncDirection.DOWNLOAD_ONLY, SyncDirection.TWO_WAY -> SyncDecision.DOWNLOAD
|
||||||
else -> SyncDecision.SKIP
|
else -> SyncDecision.SKIP
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Pair has been synced before but this file has no state record
|
||||||
|
// (e.g. uploaded before state-tracking was fixed). Treat the same
|
||||||
|
// as a known remote-deletion: apply mirror/keep behavior.
|
||||||
|
when {
|
||||||
|
deleteBehavior == DeleteBehavior.KEEP -> SyncDecision.SKIP
|
||||||
|
direction == SyncDirection.UPLOAD_ONLY || direction == SyncDirection.TWO_WAY -> SyncDecision.DELETE_REMOTE
|
||||||
|
else -> SyncDecision.SKIP
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> when {
|
else -> when {
|
||||||
deleteBehavior == DeleteBehavior.KEEP -> SyncDecision.SKIP
|
deleteBehavior == DeleteBehavior.KEEP -> SyncDecision.SKIP
|
||||||
direction == SyncDirection.UPLOAD_ONLY || direction == SyncDirection.TWO_WAY -> SyncDecision.DELETE_REMOTE
|
direction == SyncDirection.UPLOAD_ONLY || direction == SyncDirection.TWO_WAY -> SyncDecision.DELETE_REMOTE
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ class SyncDecideTest {
|
|||||||
dir: SyncDirection = SyncDirection.TWO_WAY,
|
dir: SyncDirection = SyncDirection.TWO_WAY,
|
||||||
conflict: ConflictStrategy = ConflictStrategy.KEEP_NEWEST,
|
conflict: ConflictStrategy = ConflictStrategy.KEEP_NEWEST,
|
||||||
delete: DeleteBehavior = DeleteBehavior.MIRROR,
|
delete: DeleteBehavior = DeleteBehavior.MIRROR,
|
||||||
) = syncDecide(dir, conflict, delete, local, remote, known)
|
hasPriorState: Boolean = known != null,
|
||||||
|
) = syncDecide(dir, conflict, delete, local, remote, known, hasPriorState)
|
||||||
|
|
||||||
// ── first sync (no known state) ───────────────────────────────────────────
|
// ── first sync (no known state) ───────────────────────────────────────────
|
||||||
|
|
||||||
@@ -126,6 +127,18 @@ class SyncDecideTest {
|
|||||||
assertEquals(SyncDecision.SKIP,
|
assertEquals(SyncDecision.SKIP,
|
||||||
decide(null, remote(), state(), dir = SyncDirection.DOWNLOAD_ONLY))
|
decide(null, remote(), state(), dir = SyncDirection.DOWNLOAD_ONLY))
|
||||||
|
|
||||||
|
// ── local deleted, no state record (uploaded in broken version) ──────────
|
||||||
|
|
||||||
|
@Test fun `local deleted no known state but pair has prior history deletes remote`() =
|
||||||
|
// hasPriorState=true means the pair has been synced before; file has no state
|
||||||
|
// because it was uploaded when getFileMetadata was broken. Should still mirror deletion.
|
||||||
|
assertEquals(SyncDecision.DELETE_REMOTE,
|
||||||
|
decide(null, remote(), known = null, delete = DeleteBehavior.MIRROR, hasPriorState = true))
|
||||||
|
|
||||||
|
@Test fun `initial sync remote only no prior state downloads`() =
|
||||||
|
assertEquals(SyncDecision.DOWNLOAD,
|
||||||
|
decide(null, remote(), known = null, hasPriorState = false))
|
||||||
|
|
||||||
// ── first-seen SKIP saves baseline so later deletions are detected ────────
|
// ── first-seen SKIP saves baseline so later deletions are detected ────────
|
||||||
|
|
||||||
@Test fun `first sync both exist same mtime uploads local wins tie`() =
|
@Test fun `first sync both exist same mtime uploads local wins tie`() =
|
||||||
|
|||||||
+2
-2
@@ -1,2 +1,2 @@
|
|||||||
VERSION_NAME=1.0.11
|
VERSION_NAME=1.0.12
|
||||||
VERSION_CODE=12
|
VERSION_CODE=13
|
||||||
|
|||||||
Reference in New Issue
Block a user