Files
sheetless/lib/core/services/annotation_sync_service.dart

119 lines
3.7 KiB
Dart

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<void> 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<bool> 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;
}
}
}