@@ -26,6 +26,7 @@ class CollabFolderSyncService {
2626 final Map <String , Duration > _currentPollInterval = {}; // adaptive per group
2727 final Map <String , Timer > _uploadDebounce = {}; // debounce per file path
2828 final Set <String > _permanentlyFailed = {}; // files that failed with non-retryable errors (413, etc.)
29+ DateTime ? _lastResumeTime; // cooldown for onAppResumed
2930
3031 // Listeners for sync status changes
3132 final _statusController = StreamController <CollabSyncStatus >.broadcast ();
@@ -40,12 +41,18 @@ class CollabFolderSyncService {
4041
4142 for (final c in outgoing) {
4243 if (c.syncEnabled && c.localFolderPath != null ) {
44+ debugPrint ('[CollabFolderSync] Starting sync for outgoing "${c .name }" → ${c .localFolderPath }' );
4345 await startSync (c.id);
46+ } else {
47+ debugPrint ('[CollabFolderSync] Skipping outgoing "${c .name }" (syncEnabled=${c .syncEnabled }, folder=${c .localFolderPath })' );
4448 }
4549 }
4650 for (final c in accepted) {
4751 if (c.syncEnabled && c.localFolderPath != null ) {
52+ debugPrint ('[CollabFolderSync] Starting sync for accepted "${c .name }" → ${c .localFolderPath }' );
4853 await startSync (c.id);
54+ } else {
55+ debugPrint ('[CollabFolderSync] Skipping accepted "${c .name }" (syncEnabled=${c .syncEnabled }, folder=${c .localFolderPath })' );
4956 }
5057 }
5158 debugPrint ('[CollabFolderSync] Initialized. Active syncs: ${_watchers .length }' );
@@ -148,6 +155,24 @@ class CollabFolderSyncService {
148155 _currentPollInterval.remove (groupId);
149156 }
150157
158+ /// Restart all active watchers after app resume.
159+ /// Windows directory watchers can go stale after sleep/wake cycles.
160+ /// Debounced to avoid excessive restarts from rapid focus changes on Windows.
161+ Future <void > onAppResumed () async {
162+ final now = DateTime .now ();
163+ if (_lastResumeTime != null && now.difference (_lastResumeTime! ) < const Duration (seconds: 30 )) {
164+ return ; // cooldown — skip if resumed within last 30s
165+ }
166+ _lastResumeTime = now;
167+
168+ final activeGroupIds = _watchers.keys.toList ();
169+ if (activeGroupIds.isEmpty) return ;
170+ debugPrint ('[CollabFolderSync] onAppResumed - restarting ${activeGroupIds .length } watchers' );
171+ for (final groupId in activeGroupIds) {
172+ await startSync (groupId);
173+ }
174+ }
175+
151176 /// Trigger a manual sync (poll + download).
152177 Future <void > syncNow (String groupId) async {
153178 _currentPollInterval[groupId] = _minPollInterval; // reset to fast polling
@@ -406,22 +431,32 @@ class CollabFolderSyncService {
406431 final syncMeta = await _readSyncMeta (folderPath);
407432 final fileName = filePath.split (Platform .pathSeparator).last;
408433
409- // Skip if any entry with same fileName already uploaded
410- final alreadyUploaded = syncMeta.values.any (
411- (e) => e.fileName == fileName && e.direction == 'upload' ,
412- );
413- if (alreadyUploaded) {
414- debugPrint ('[CollabFolderSync] Skipping $fileName : already uploaded' );
415- return ;
434+ // Find any existing sync entry for this fileName
435+ String ? existingSyncId;
436+ _SyncedFileEntry ? existingEntry;
437+ for (final entry in syncMeta.entries) {
438+ if (entry.value.fileName == fileName) {
439+ existingSyncId = entry.key;
440+ existingEntry = entry.value;
441+ break ;
442+ }
416443 }
417444
418- // Also skip if the file was just downloaded (avoid re-uploading downloads)
419- final justDownloaded = syncMeta.values.any (
420- (e) => e.fileName == fileName && e.direction == 'download' ,
421- );
422- if (justDownloaded) {
423- debugPrint ('[CollabFolderSync] Skipping $fileName : was downloaded' );
424- return ;
445+ if (existingEntry != null && existingSyncId != null ) {
446+ // Check if this file was tombstoned (deleted from manifest).
447+ // If so, clear the stale sync metadata so the re-add is treated as new.
448+ final tombstoned = info.group.removedFileIds.contains (existingSyncId);
449+ if (tombstoned) {
450+ debugPrint ('[CollabFolderSync] Clearing stale sync entry for re-added file: $fileName ' );
451+ syncMeta.remove (existingSyncId);
452+ await _writeSyncMeta (folderPath, groupId, syncMeta);
453+ } else if (existingEntry.direction == 'upload' ) {
454+ debugPrint ('[CollabFolderSync] Skipping $fileName : already uploaded' );
455+ return ;
456+ } else if (existingEntry.direction == 'download' ) {
457+ debugPrint ('[CollabFolderSync] Skipping $fileName : was downloaded' );
458+ return ;
459+ }
425460 }
426461
427462 // Skip files that previously failed with non-retryable errors
0 commit comments