v1.0.28: fix sync rewrite/delete loop, Avast-inspired icon
Sync loop root-cause fixes (three independent bugs): 1. Folder change clears stale file states (AddPairViewModel): when localPath or remotePath changes on an existing pair, all SyncFileStateEntity records are wiped. Previously those stale records caused every sync to attempt DELETE_REMOTE on the old folder's files and to treat all new-folder files as changed — causing both the "deleting 32 files" loop and rewrites on every run. 2. Download stores null localModifiedAt (SyncEngine): SAF document cursors can return a stale mtime immediately after a write. Storing null forces the SKIP reconciliation pass on the next sync to read the actual walkFiles cursor value, breaking the download->changed-> download loop caused by mtime inconsistency. 3. Second-precision mtime comparison (syncDecide): WebDAV RFC-1123 has 1-second precision; FAT32 has 2-second precision. Comparing at millisecond level caused phantom "changed" detections after syncing to/from these systems. Now uses epochSecond for both local and remote. Icon: three bold teal/red/yellow teardrop streaks (Avast palette) flying into a white cloud centre, on dark charcoal background. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.syncflow.data.db.CloudAccountDao
|
||||
import com.syncflow.data.db.SyncFileStateDao
|
||||
import com.syncflow.data.db.SyncPairDao
|
||||
import com.syncflow.data.db.entities.CloudAccountEntity
|
||||
import com.syncflow.data.db.entities.SyncPairEntity
|
||||
@@ -58,6 +59,7 @@ data class AddPairUiState(
|
||||
@HiltViewModel
|
||||
class AddPairViewModel @Inject constructor(
|
||||
private val syncPairDao: SyncPairDao,
|
||||
private val fileStateDao: SyncFileStateDao,
|
||||
private val accountDao: CloudAccountDao,
|
||||
@ApplicationContext private val context: Context,
|
||||
savedState: SavedStateHandle,
|
||||
@@ -148,7 +150,20 @@ class AddPairViewModel @Inject constructor(
|
||||
notifyOnComplete = s.notifyOnComplete, notifyOnError = s.notifyOnError,
|
||||
isEnabled = true, lastSyncAt = null, lastSyncResult = SyncStatus.IDLE, pendingConflicts = 0,
|
||||
)
|
||||
if (editPairId == null) syncPairDao.insert(entity) else syncPairDao.update(entity)
|
||||
if (editPairId == null) {
|
||||
syncPairDao.insert(entity)
|
||||
} else {
|
||||
val existing = syncPairDao.getById(editPairId)
|
||||
syncPairDao.update(entity)
|
||||
// If local or remote folder changed, old file-state records no longer
|
||||
// correspond to any real path — wipe them so the next sync starts fresh
|
||||
// instead of trying to delete/re-upload stale paths.
|
||||
if (existing != null &&
|
||||
(existing.localPath != entity.localPath || existing.remotePath != entity.remotePath)
|
||||
) {
|
||||
fileStateDao.deleteForPair(editPairId)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onSuccess {
|
||||
if (s.scheduleType == ScheduleType.ON_CHANGE) FileWatchService.start(context)
|
||||
|
||||
Reference in New Issue
Block a user