diff --git a/lib/Helpers/DatabaseHelper.dart b/lib/Helpers/DatabaseHelper.dart index c5775b7..71bb397 100644 --- a/lib/Helpers/DatabaseHelper.dart +++ b/lib/Helpers/DatabaseHelper.dart @@ -19,7 +19,7 @@ enum DatabaseTableType { class DatabaseHelper { static const _databaseName = "visit_database.db"; - static const _databaseVersion = 3; + static const _databaseVersion = 4; static const mainTable = 'visitAppContext'; static const columnLanguage = 'language'; @@ -55,6 +55,7 @@ class DatabaseHelper { static const resourcesTable = 'resources'; static const columnSource = 'source'; + static const columnDateUpdate = 'dateUpdate'; static const articleReadTable = 'articleRead'; static const columnLastTimeOpen = 'readTime'; @@ -96,6 +97,12 @@ class DatabaseHelper { await db.execute('ALTER TABLE $configurationsTable ADD COLUMN $columnGridColSpan INT'); await db.execute('ALTER TABLE $configurationsTable ADD COLUMN $columnGridRowSpan INT'); } + if (oldVersion < 4) { + // Les lignes existantes restent à NULL : un fichier déjà téléchargé sans date + // connue est considéré à jour, sinon la montée de version déclencherait un + // re-téléchargement intégral de toutes les visites sur le réseau du visiteur. + await db.execute('ALTER TABLE $resourcesTable ADD COLUMN $columnDateUpdate TEXT'); + } } // SQL code to create the database table @@ -228,7 +235,8 @@ class DatabaseHelper { $columnId TEXT NOT NULL PRIMARY KEY, $columnPath TEXT NOT NULL, $columnSource TEXT NOT NULL, - $columnType INT NOT NULL + $columnType INT NOT NULL, + $columnDateUpdate TEXT ) '''); break; @@ -439,7 +447,8 @@ class DatabaseHelper { id: element["id"], path: element["path"], source: element["source"], - type: ResourceType.values[element["type"]] + type: ResourceType.values[element["type"]], + dateUpdate: element["dateUpdate"] == null ? null : DateTime.tryParse(element["dateUpdate"]) ); } diff --git a/lib/Models/resourceModel.dart b/lib/Models/resourceModel.dart index 24459b2..17d4498 100644 --- a/lib/Models/resourceModel.dart +++ b/lib/Models/resourceModel.dart @@ -7,15 +7,17 @@ class ResourceModel { String? source = ""; String? label = ""; ResourceType? type; + DateTime? dateUpdate; - ResourceModel({this.id, this.path, this.source, this.type}); + ResourceModel({this.id, this.path, this.source, this.type, this.dateUpdate}); Map toMap() { return { 'id': id, 'path': path, 'source': source, - 'type': type?.value + 'type': type?.value, + 'dateUpdate': dateUpdate?.toUtc().toIso8601String() }; } @@ -24,12 +26,13 @@ class ResourceModel { id: json['id'] as String, path: json['path'] as String, source: json['source'] as String, - type: json['type'] as ResourceType + type: json['type'] as ResourceType, + dateUpdate: json['dateUpdate'] == null ? null : DateTime.parse(json['dateUpdate'] as String) ); } @override String toString() { - return 'ResourceModel{id: $id, type: $type, source: $source, path: $path, label: $label}'; + return 'ResourceModel{id: $id, type: $type, source: $source, path: $path, label: $label, dateUpdate: $dateUpdate}'; } } \ No newline at end of file diff --git a/lib/Services/downloadConfiguration.dart b/lib/Services/downloadConfiguration.dart index dec881a..005941b 100644 --- a/lib/Services/downloadConfiguration.dart +++ b/lib/Services/downloadConfiguration.dart @@ -104,7 +104,9 @@ class _DownloadConfigurationWidgetState extends State resource.type != ResourceType.ImageUrl && resource.type != ResourceType.VideoUrl && resource.type != ResourceType.JsonUrl && resource.url != null && !fileList.any((fileL) => fileL.uri.pathSegments.last.contains(resource.id!))); + Map localResourceDates = await readLocalResourceDates(); + + var resourcesToDownload = exportConfigurationDTO.resources!.where((resource) => resource.type != ResourceType.ImageUrl && resource.type != ResourceType.VideoUrl && resource.type != ResourceType.JsonUrl && resource.url != null && isResourceOutdated(resource, fileList, localResourceDates[resource.id])); currentResourceNbr.value = resourcesToDownload.length; @@ -115,9 +117,10 @@ class _DownloadConfigurationWidgetState extends State downloadResource(VisitAppContext visitAppContext, ConfigurationD } } +/// Date de dernière mise à jour connue pour chaque ressource déjà sur le device. +/// Une valeur nulle signifie « fichier téléchargé avant la v4 de la base locale, +/// date inconnue » — pas « périmé ». +Future> readLocalResourceDates() async { + List> rows = await DatabaseHelper.instance.queryAllRows(DatabaseTableType.resources); + return { + for (var row in rows) row["id"] as String: DatabaseHelper.instance.getResourceFromDB(row).dateUpdate + }; +} + +bool isResourceOutdated(ResourceDTO resource, List fileList, DateTime? localDateUpdate) { + var localFile = fileList.where((fileL) => fileL.uri.pathSegments.last.contains(resource.id!)); + + if (localFile.isEmpty) return true; + + // Fichier écrit avec une extension que la table ne savait pas déduire — c'était le + // cas de tous les MP3 avant D3. Il est illisible, et sa date ne le dira jamais : + // le serveur n'a pas changé, c'est le client qui l'avait mal enregistré. + if (localFile.first.uri.pathSegments.last.endsWith(".unknown")) return true; + + if (localDateUpdate == null || resource.dateUpdate == null) return false; + + return resource.dateUpdate!.isAfter(localDateUpdate); +} + String _getExtensionFromContentType(String contentType) { Map contentTypeToExtension = { "image/jpeg": "jpg", "image/jpg": "jpg", "image/png": "png", "image/gif": "gif", + "audio/mpeg": "mp3", "audio/mp3": "mp3", "video/mp4": "mp4", "video/webm": "webm",