@@ -61,20 +61,40 @@ class CollabFolderSyncService {
6161 await dir.create (recursive: true );
6262 }
6363
64+ // Clear stale sync metadata from a previous collab group on this folder
65+ await _resetSyncMetaIfDifferentGroup (groupId, folderPath);
66+
6467 // Update persistence
6568 await CollaborationService .instance.updateFolderAssignment (
6669 groupId,
6770 folderPath: folderPath,
6871 syncEnabled: true ,
6972 );
7073
71- // Initial download of existing files
72- await _pollForNewFiles (groupId);
73-
74- // Start watching + polling
74+ // Start watching + polling immediately so the caller isn't blocked
7575 await startSync (groupId);
7676
77- _emitStatus (groupId, CollabSyncState .synced);
77+ // Download remote files and upload existing local files in the background.
78+ // The collab link is valid immediately — files appear as they're synced.
79+ unawaited (_initialSyncInBackground (groupId, folderPath));
80+ }
81+
82+ /// Runs the initial download + upload scan without blocking [assignFolder] .
83+ Future <void > _initialSyncInBackground (String groupId, String folderPath) async {
84+ try {
85+ _emitStatus (groupId, CollabSyncState .syncing);
86+
87+ // Download any existing remote files first
88+ await _pollForNewFiles (groupId);
89+
90+ // Then upload local files not yet in the manifest
91+ await _scanAndUploadExistingFiles (groupId, folderPath);
92+
93+ _emitStatus (groupId, CollabSyncState .synced);
94+ } catch (e) {
95+ debugPrint ('[CollabFolderSync] Background initial sync error ($groupId ): $e ' );
96+ _emitStatus (groupId, CollabSyncState .error);
97+ }
7898 }
7999
80100 /// Remove folder assignment and stop sync.
@@ -248,6 +268,26 @@ class CollabFolderSyncService {
248268 // UPLOAD SYNC (local → remote)
249269 // ============================================================================
250270
271+ /// Scan existing files in the folder and upload any not yet in the manifest.
272+ Future <void > _scanAndUploadExistingFiles (String groupId, String folderPath) async {
273+ final dir = Directory (folderPath);
274+ if (! await dir.exists ()) return ;
275+
276+ debugPrint ('[CollabFolderSync] Scanning existing files in $folderPath ' );
277+ int uploaded = 0 ;
278+
279+ await for (final entity in dir.list (recursive: true , followLinks: false )) {
280+ if (entity is ! File ) continue ;
281+ final fileName = entity.path.split (Platform .pathSeparator).last;
282+ if (fileName == _syncMetaFile || fileName.startsWith ('.' )) continue ;
283+
284+ await _uploadLocalFileIfNew (groupId, entity.path);
285+ uploaded++ ;
286+ }
287+
288+ debugPrint ('[CollabFolderSync] Scan complete: processed $uploaded files' );
289+ }
290+
251291 void _handleLocalFileChange (String groupId, FileSystemEvent event) {
252292 // Only handle file creation/modification
253293 if (event is ! FileSystemCreateEvent && event is ! FileSystemModifyEvent ) return ;
@@ -261,6 +301,8 @@ class CollabFolderSyncService {
261301 // Skip directories
262302 if (FileSystemEntity .isDirectorySync (path)) return ;
263303
304+ debugPrint ('[CollabFolderSync] File change detected: $fileName (${event .runtimeType }) for group $groupId ' );
305+
264306 // Debounce: cancel any pending upload for this path, reschedule
265307 _uploadDebounce[path]? .cancel ();
266308 _uploadDebounce[path] = Timer (const Duration (seconds: 3 ), () {
@@ -272,7 +314,10 @@ class CollabFolderSyncService {
272314 Future <void > _uploadLocalFileIfNew (String groupId, String filePath) async {
273315 try {
274316 final info = await _getGroupInfo (groupId);
275- if (info == null || info.folderPath == null || info.linkSecretKey == null ) return ;
317+ if (info == null || info.folderPath == null || info.linkSecretKey == null ) {
318+ debugPrint ('[CollabFolderSync] Skipping upload: group info missing (info=${info != null }, folder=${info ?.folderPath != null }, key=${info ?.linkSecretKey != null })' );
319+ return ;
320+ }
276321
277322 final folderPath = info.folderPath! ;
278323 final file = File (filePath);
@@ -292,13 +337,19 @@ class CollabFolderSyncService {
292337 final alreadyUploaded = syncMeta.values.any (
293338 (e) => e.fileName == fileName && e.direction == 'upload' ,
294339 );
295- if (alreadyUploaded) return ;
340+ if (alreadyUploaded) {
341+ debugPrint ('[CollabFolderSync] Skipping $fileName : already uploaded' );
342+ return ;
343+ }
296344
297345 // Also skip if the file was just downloaded (avoid re-uploading downloads)
298346 final justDownloaded = syncMeta.values.any (
299347 (e) => e.fileName == fileName && e.direction == 'download' ,
300348 );
301- if (justDownloaded) return ;
349+ if (justDownloaded) {
350+ debugPrint ('[CollabFolderSync] Skipping $fileName : was downloaded' );
351+ return ;
352+ }
302353
303354 // Skip files that previously failed with non-retryable errors
304355 if (_permanentlyFailed.contains (filePath)) return ;
@@ -386,6 +437,24 @@ class CollabFolderSyncService {
386437 return null ;
387438 }
388439
440+ /// Clear sync metadata if it belongs to a different collab group.
441+ /// This prevents stale entries from a previous group on the same folder
442+ /// from blocking uploads for the new group.
443+ Future <void > _resetSyncMetaIfDifferentGroup (String groupId, String folderPath) async {
444+ final file = File ('$folderPath ${Platform .pathSeparator }$_syncMetaFile ' );
445+ if (! await file.exists ()) return ;
446+ try {
447+ final json = jsonDecode (await file.readAsString ()) as Map <String , dynamic >;
448+ final storedGroupId = json['groupId' ] as String ? ;
449+ if (storedGroupId != null && storedGroupId != groupId) {
450+ debugPrint ('[CollabFolderSync] Clearing stale sync meta (was group $storedGroupId , now $groupId )' );
451+ await file.delete ();
452+ }
453+ } catch (e) {
454+ debugPrint ('[CollabFolderSync] Failed to check sync meta group: $e ' );
455+ }
456+ }
457+
389458 Future <Map <String , _SyncedFileEntry >> _readSyncMeta (String folderPath) async {
390459 final file = File ('$folderPath ${Platform .pathSeparator }$_syncMetaFile ' );
391460 if (! await file.exists ()) return {};
0 commit comments