Refine iOS navigation, media controls, and background sync
Build APK / build (push) Successful in 5m50s
Build APK / build (push) Successful in 5m50s
This commit is contained in:
@@ -320,7 +320,13 @@ final class SyncCoordinator {
|
||||
/// exactly once - when the work ends or when the system's time does.
|
||||
private func handleBackground(_ task: BGTask) {
|
||||
scheduleBackgroundWork() // line up the next one before this one runs
|
||||
let gate = OnceGate { task.setTaskCompleted(success: $0) }
|
||||
task.expirationHandler = startBackgroundSlot { task.setTaskCompleted(success: $0) }
|
||||
}
|
||||
|
||||
/// Runs a background slot and returns its expiration handler.
|
||||
@discardableResult
|
||||
func startBackgroundSlot(completion: @escaping (Bool) -> Void) -> () -> Void {
|
||||
let gate = OnceGate(completion)
|
||||
let accountIds = backgroundConfigs().map(\.accountId)
|
||||
let work = Task { [weak self] in
|
||||
guard let self else { return gate.finish(false) }
|
||||
@@ -332,7 +338,7 @@ final class SyncCoordinator {
|
||||
}
|
||||
// The system's time is up: stop the work and the runs it started (each
|
||||
// saves what it has) and report.
|
||||
task.expirationHandler = { [weak self] in
|
||||
return { [weak self] in
|
||||
work.cancel()
|
||||
for id in accountIds { self?.cancelRun(accountId: id, invalidateQueued: false) }
|
||||
gate.finish(false)
|
||||
|
||||
@@ -89,16 +89,7 @@ enum SyncDiff {
|
||||
|
||||
for fileId in priorFolderFileIds where !seen.contains(fileId) {
|
||||
guard let prior = state[fileId] else { continue }
|
||||
// Deleted on the server. If it was also edited here since the last sync
|
||||
// that edit is the only copy left - keep it (untracked) instead of
|
||||
// deleting it along with the rest.
|
||||
if let local = LocalFS.stat(syncRoot.appendingPathComponent(prior.relPath)),
|
||||
local.mtimeMs != prior.localMTime || local.size != prior.size
|
||||
{
|
||||
actions.append(.orphan(relPath: prior.relPath, fileId: fileId))
|
||||
} else {
|
||||
actions.append(.delete(relPath: prior.relPath, fileId: fileId))
|
||||
}
|
||||
actions.append(.delete(relPath: prior.relPath, fileId: fileId))
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
@@ -38,10 +38,6 @@ enum SyncAction: Equatable {
|
||||
case upload(relPath: String, fileId: String)
|
||||
case delete(relPath: String, fileId: String)
|
||||
case conflict(SyncRemoteEntry, relPath: String)
|
||||
/// Deleted on the server, but edited on this device since the last sync:
|
||||
/// the edited copy is the only one left, so it's kept (and simply no longer
|
||||
/// tracked) instead of being deleted with the rest. Android deletes it.
|
||||
case orphan(relPath: String, fileId: String)
|
||||
}
|
||||
|
||||
/// A file changed both on the device and on the server since the last sync.
|
||||
|
||||
@@ -144,7 +144,7 @@ final class SyncStatusBus {
|
||||
/// edits with the server's copies).
|
||||
/// - Paths the server sends are checked to stay inside the mirror, and two
|
||||
/// remote files that would land on the same local file are both skipped.
|
||||
/// - A file deleted on the server but edited here is kept, not deleted.
|
||||
/// - Server deletions remove the local copy, including local edits, like Android.
|
||||
/// - A root's etag is only remembered once its whole path synced cleanly, and
|
||||
/// only if the state it relies on was actually saved.
|
||||
struct SyncRunner {
|
||||
@@ -301,11 +301,6 @@ struct SyncRunner {
|
||||
}
|
||||
state[fileId] = nil
|
||||
|
||||
case .orphan(_, let fileId):
|
||||
// Deleted on the server but edited here: the edit stays on the
|
||||
// device, no longer tracked.
|
||||
state[fileId] = nil
|
||||
|
||||
case .conflict(let entry, let rel):
|
||||
summary.conflicts.append(
|
||||
SyncConflict(
|
||||
|
||||
Reference in New Issue
Block a user