Skip to content

Commit da90e2a

Browse files
committed
resolved sync issues
1 parent bf14cab commit da90e2a

2 files changed

Lines changed: 52 additions & 14 deletions

File tree

lib/app/app.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:fula_files/core/services/fula_api_service.dart';
1313
import 'package:fula_files/core/services/local_storage_service.dart';
1414
import 'package:fula_files/core/services/secure_storage_service.dart';
1515
import 'package:fula_files/core/services/sync_service.dart';
16+
import 'package:fula_files/core/services/collab_folder_sync_service.dart';
1617
import 'package:fula_files/core/services/folder_watch_service.dart';
1718
import 'package:fula_files/core/services/wallet_service.dart' show walletNavigatorKey;
1819
import 'package:fula_files/core/models/sync_state.dart';
@@ -129,6 +130,8 @@ class _FulaFilesAppState extends ConsumerState<FulaFilesApp>
129130
SyncService.instance.resumeIfPending();
130131
// Restart file watchers and scan for changes missed while backgrounded
131132
FolderWatchService.instance.onAppResumed();
133+
// Restart collab folder watchers (stale after sleep/wake on Windows)
134+
CollabFolderSyncService.instance.onAppResumed();
132135
}
133136
}
134137

lib/core/services/collab_folder_sync_service.dart

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)