import 'package:logging/logging.dart'; import 'api_client.dart'; import 'storage_service.dart'; /// Service for synchronizing annotations between local storage and server. /// /// Handles downloading annotations on sheet open and uploading on save, /// comparing timestamps to determine which version is newer. class AnnotationSyncService { final _log = Logger('AnnotationSyncService'); final ApiClient _apiClient; final StorageService _storageService; AnnotationSyncService({ required ApiClient apiClient, required StorageService storageService, }) : _apiClient = apiClient, _storageService = storageService; /// Downloads annotations from server and merges with local storage. /// /// For each page, compares server's lastModified with local lastModified. /// If server is newer, overwrites local. Local annotations that are newer /// are preserved. Future syncFromServer(String sheetUuid) async { try { _log.info('Syncing annotations from server for sheet $sheetUuid'); // Fetch all annotations from server final serverAnnotations = await _apiClient.fetchAnnotations(sheetUuid); // Get all local annotations with metadata final localAnnotations = await _storageService .readAllAnnotationsWithMetadata(sheetUuid); int updatedCount = 0; // Process each server annotation for (final serverAnnotation in serverAnnotations) { final page = serverAnnotation.page; final localAnnotation = localAnnotations[page]; bool shouldUpdate = false; if (localAnnotation == null) { // No local annotation - use server version shouldUpdate = true; _log.fine('Page $page: No local annotation, using server version'); } else if (serverAnnotation.lastModified.isAfter( localAnnotation.lastModified, )) { // Server is newer - overwrite local shouldUpdate = true; _log.fine( 'Page $page: Server is newer ' '(server: ${serverAnnotation.lastModified}, ' 'local: ${localAnnotation.lastModified})', ); } else { _log.fine( 'Page $page: Local is newer or same, keeping local version', ); } if (shouldUpdate) { await _storageService.writeAnnotationsWithMetadata( sheetUuid, page, serverAnnotation.annotationsJson, serverAnnotation.lastModified, ); updatedCount++; } } _log.info( 'Sync complete: $updatedCount pages updated from server ' '(${serverAnnotations.length} total on server)', ); } on ApiException catch (e) { _log.warning('Failed to sync annotations from server: $e'); } catch (e) { _log.warning('Unexpected error syncing annotations: $e'); } } /// Uploads a single page's annotation to the server. /// /// Called when annotations are saved (e.g., exiting paint mode). /// Silently fails if upload fails (allows offline usage). Future uploadAnnotation({ required String sheetUuid, required int page, required String annotationsJson, required DateTime lastModified, }) async { try { _log.info('Uploading annotation for sheet $sheetUuid page $page'); await _apiClient.uploadAnnotation( sheetUuid: sheetUuid, page: page, lastModified: lastModified, annotationsJson: annotationsJson, ); _log.info('Upload successful'); return true; } on ApiException catch (e) { _log.warning('Failed to upload annotation: $e'); return false; } catch (e) { _log.warning('Unexpected error uploading annotation: $e'); return false; } } }