150 lines
4.7 KiB
Dart
150 lines
4.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).
|
|
/// If upload fails (e.g., offline), the annotation is queued for later sync.
|
|
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, queuing for later: $e');
|
|
await _queueForLaterUpload(
|
|
sheetUuid: sheetUuid,
|
|
page: page,
|
|
annotationsJson: annotationsJson,
|
|
lastModified: lastModified,
|
|
);
|
|
return false;
|
|
} catch (e) {
|
|
_log.warning(
|
|
'Unexpected error uploading annotation, queuing for later: $e');
|
|
await _queueForLaterUpload(
|
|
sheetUuid: sheetUuid,
|
|
page: page,
|
|
annotationsJson: annotationsJson,
|
|
lastModified: lastModified,
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Queues an annotation for later upload when connection is restored.
|
|
Future<void> _queueForLaterUpload({
|
|
required String sheetUuid,
|
|
required int page,
|
|
required String annotationsJson,
|
|
required DateTime lastModified,
|
|
}) async {
|
|
await _storageService.writePendingAnnotationUpload(
|
|
PendingAnnotationUpload(
|
|
sheetUuid: sheetUuid,
|
|
page: page,
|
|
annotationsJson: annotationsJson,
|
|
lastModified: lastModified,
|
|
),
|
|
);
|
|
_log.info('Annotation queued for later upload: $sheetUuid page $page');
|
|
}
|
|
}
|