From 5e3e3b4427bdff3fbdad10fb75b9834d99006bb5 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 28 Jul 2025 16:53:57 +0530 Subject: [PATCH] Clean up --- mobile/apps/photos/lib/db/files_db.dart | 126 ------------------ mobile/apps/photos/lib/db/local/db.dart | 28 ++++ mobile/apps/photos/lib/db/local/schema.dart | 11 ++ .../db/local/table/upload_queue_table.dart | 8 ++ .../photos/lib/module/upload/model/item.dart | 2 + .../module/upload/service/file_uploader.dart | 17 +-- .../services/album_home_widget_service.dart | 28 ++-- .../lib/services/filter/db_filters.dart | 19 +++ .../services/people_home_widget_service.dart | 12 +- .../services/sync/remote_sync_service.dart | 8 +- .../lib/ui/home/home_gallery_widget_v2.dart | 4 +- .../lib/ui/tools/editor/editor_utils.dart | 40 +++--- 12 files changed, 125 insertions(+), 178 deletions(-) diff --git a/mobile/apps/photos/lib/db/files_db.dart b/mobile/apps/photos/lib/db/files_db.dart index af8beafd0e..76b3159c2c 100644 --- a/mobile/apps/photos/lib/db/files_db.dart +++ b/mobile/apps/photos/lib/db/files_db.dart @@ -421,36 +421,6 @@ class FilesDB with SqlDbBase { await db.execute('DELETE FROM entities'); } - // blocked upload queue (shared assets rendering) - Future insertAndGetId(EnteFile file) async { - _logger.info("Inserting $file"); - final db = await instance.sqliteAsyncDB; - final columnsAndPlaceholders = - _generateColumnsAndPlaceholdersForInsert(fileGenId: file.generatedID); - final values = _getParameterSetForFile(file); - return await db.writeTransaction((tx) async { - await tx.execute( - 'INSERT OR REPLACE INTO $filesTable (${columnsAndPlaceholders["columns"]}) VALUES (${columnsAndPlaceholders["placeholders"]})', - values, - ); - final result = await tx.get('SELECT last_insert_rowid()'); - return result["last_insert_rowid()"] as int; - }); - } - - // upload queue - Future getFile(int generatedID) async { - final db = await instance.sqliteAsyncDB; - final results = await db.getAll( - 'SELECT * FROM $filesTable WHERE $columnGeneratedID = ?', - [generatedID], - ); - if (results.isEmpty) { - return null; - } - return convertToFiles(results)[0]; - } - Future getBackedUpIDs() async { final db = await instance.sqliteAsyncDB; final results = await db.getAll( @@ -616,23 +586,6 @@ class FilesDB with SqlDbBase { return files; } - // todo:rewrite (upload related) - // Future> getUploadedFileIDsToBeUpdated(int ownerID) async { - // final db = await instance.sqliteAsyncDB; - // final rows = await db.getAll( - // 'SELECT DISTINCT $columnUploadedFileID FROM $filesTable WHERE ' - // '($columnLocalID IS NOT NULL AND $columnOwnerID = ? AND ' - // '($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1) ' - // 'AND $columnUpdationTime IS NULL) ORDER BY $columnCreationTime DESC ', - // [ownerID], - // ); - // final uploadedFileIDs = []; - // for (final row in rows) { - // uploadedFileIDs.add(row[columnUploadedFileID] as int); - // } - // return uploadedFileIDs; - // } - // todo:rewrite (upload related) Future markFilesForReUpload( int ownerID, @@ -835,25 +788,6 @@ class FilesDB with SqlDbBase { return deduplicatedFiles; } - Map _generateColumnsAndPlaceholdersForInsert({ - required int? fileGenId, - }) { - final columnNames = []; - - for (String columnName in _columnNames) { - if (columnName == columnGeneratedID && fileGenId == null) { - continue; - } - - columnNames.add(columnName); - } - - return { - "columns": columnNames.join(","), - "placeholders": List.filled(columnNames.length, "?").join(","), - }; - } - List convertToFilesForIsolate(Map args) { final List files = []; for (final result in args["result"]) { @@ -870,66 +804,6 @@ class FilesDB with SqlDbBase { return files; } - List _getParameterSetForFile( - EnteFile file, { - bool omitCollectionId = false, - }) { - final values = []; - - double? latitude = file.location?.latitude; - double? longitude = file.location?.longitude; - - int? creationTime = file.creationTime; - - if (file.generatedID != null) { - values.add(file.generatedID); - } - values.addAll([ - file.localID, - file.uploadedFileID ?? -1, - file.ownerID, - file.collectionID ?? -1, - file.title, - file.deviceFolder, - latitude, - longitude, - getInt(file.fileType), - file.modificationTime, - // file.encryptedKey, - 'no_encrypted_key', // encryptedKey is not used in this context - // file.keyDecryptionNonce, - 'no_key_decryption_nonce', // keyDecryptionNonce is not used in this context - // file.fileDecryptionHeader, - 'no_file_decryption_header', // fileDecryptionHeader is not used in this context - // file.thumbnailDecryptionHeader, - 'no_thumbnail_decryption_header', // thumbnailDecryptionHeader is not used in this context - 'na', - creationTime, - file.updationTime, - file.fileSubType ?? -1, - file.duration ?? 0, - file.exif, - file.hash, - file.metadataVersion, - // file.mMdEncodedJson ?? '{}', - {}, // mMdEncodedJson is not used in this context - 0, // version - 0, // default visibility - '{}', // pubMmdEncodedJson is not used in this context - 0, // pubMmdVersion is not used in this context - // file.pubMmdEncodedJson ?? '{}', - // file.pubMmdVersion, - file.fileSize, - DateTime.now().microsecondsSinceEpoch, // added Time - ]); - - if (omitCollectionId) { - values.removeAt(3); - } - - return values; - } - EnteFile _getFileFromRow(Map row) { final file = EnteFile(); file.generatedID = row[columnGeneratedID]; diff --git a/mobile/apps/photos/lib/db/local/db.dart b/mobile/apps/photos/lib/db/local/db.dart index f32dccf217..470c42d4b5 100644 --- a/mobile/apps/photos/lib/db/local/db.dart +++ b/mobile/apps/photos/lib/db/local/db.dart @@ -49,6 +49,34 @@ class LocalDB with SqlDbBase { ); } +// Store time and location metadata inside edited_assets + Future trackEdit( + String id, + int createdAt, + int modifiedAt, + double? lat, + double? lng, + ) async { + final stopwatch = Stopwatch()..start(); + await _sqliteDB.execute( + 'INSERT INTO edited_assets (id, created_at, modified_at, latitude, longitude) VALUES (?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET created_at = ?, modified_at = ?, latitude = ?, longitude = ?', + [id, createdAt, modifiedAt, lat, lng, createdAt, modifiedAt, lat, lng], + ); + debugPrint( + '$runtimeType editCopy complete in ${stopwatch.elapsed.inMilliseconds}ms for $id', + ); + } + ) async { + final stopwatch = Stopwatch()..start(); + await _sqliteDB.execute( + 'INSERT INTO edited_assets (id, created_at, modified_at, latitude, longitude) VALUES (?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET created_at = ?, modified_at = ?, latitude = ?, longitude = ?', + [id, createdAt, modifiedAt, lat, lng, createdAt, modifiedAt, lat, lng], + ); + debugPrint( + '$runtimeType editCopy complete in ${stopwatch.elapsed.inMilliseconds}ms for $id', + ); + } + Future updateMetadata( String id, { DroidMetadata? droid, diff --git a/mobile/apps/photos/lib/db/local/schema.dart b/mobile/apps/photos/lib/db/local/schema.dart index 6a7affd0aa..1c6d3d1cec 100644 --- a/mobile/apps/photos/lib/db/local/schema.dart +++ b/mobile/apps/photos/lib/db/local/schema.dart @@ -215,6 +215,17 @@ class LocalDBMigration { ''' CREATE INDEX IF NOT EXISTS assets_created_at_desc ON assets(created_at DESC); ''', + ''' + CREATE TABLE edited_assets ( + id String NOT NULL, + created_at INTEGER NOT NULL, + modified_at INTEGER NOT NULL, + latitude REAL, + longitude REAL, + PRIMARY KEY (id) + FOREIGN KEY (id) REFERENCES assets(id) ON DELETE CASCADE + ); + ''', ]; static Future migrate( diff --git a/mobile/apps/photos/lib/db/local/table/upload_queue_table.dart b/mobile/apps/photos/lib/db/local/table/upload_queue_table.dart index 0b846b26dd..99aac093e3 100644 --- a/mobile/apps/photos/lib/db/local/table/upload_queue_table.dart +++ b/mobile/apps/photos/lib/db/local/table/upload_queue_table.dart @@ -43,6 +43,14 @@ extension UploadQueueTable on LocalDB { } } + Future existsQueueEntry(AssetUploadQueue entry) async { + final result = await sqliteDB.getAll( + 'SELECT 1 FROM asset_upload_queue WHERE asset_id = ? AND owner_id = ? AND dest_collection_id = ?', + [entry.id, entry.ownerId, entry.destCollectionId], + ); + return result.isNotEmpty; + } + Future delete(AssetUploadQueue entry) async { final stopwatch = Stopwatch()..start(); final result = await sqliteDB.execute( diff --git a/mobile/apps/photos/lib/module/upload/model/item.dart b/mobile/apps/photos/lib/module/upload/model/item.dart index 54618a3f3f..70c433b710 100644 --- a/mobile/apps/photos/lib/module/upload/model/item.dart +++ b/mobile/apps/photos/lib/module/upload/model/item.dart @@ -17,6 +17,8 @@ class FileUploadItem { this.assetQueue, this.status = UploadStatus.notStarted, }); + + String get lockKey => assetQueue?.id ?? file.localID!; } enum UploadStatus { notStarted, inProgress, inBackground, completed } diff --git a/mobile/apps/photos/lib/module/upload/service/file_uploader.dart b/mobile/apps/photos/lib/module/upload/service/file_uploader.dart index e1d739f2f7..06b95e6c49 100644 --- a/mobile/apps/photos/lib/module/upload/service/file_uploader.dart +++ b/mobile/apps/photos/lib/module/upload/service/file_uploader.dart @@ -51,6 +51,7 @@ import "package:photos/utils/file_key.dart"; import "package:photos/utils/network_util.dart"; import 'package:photos/utils/standalone/data.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import "package:timezone/timezone.dart"; import "package:uuid/uuid.dart"; class FileUploader { @@ -1153,20 +1154,20 @@ class FileUploader { .where((e) => e.value.status == UploadStatus.inBackground) .toList(); for (final upload in blockedUploads) { - final file = upload.value.file; + final String lockKey = upload.value.lockKey; final isStillLocked = await _uploadLocks.isLocked( - file.localID!, + lockKey, ProcessType.background.toString(), ); - if (!isStillLocked) { + if (!isStillLocked && upload.value.assetQueue != null) { final completer = _queue.remove(upload.key)?.completer; - final dbFile = - await FilesDB.instance.getFile(upload.value.file.generatedID!); - if (dbFile?.uploadedFileID != null) { + final exists = await localDB.existsQueueEntry(upload.value.assetQueue!); + if (exists) { _logger.info( "Background upload success detected ${upload.value.file.tag}", ); - completer?.complete(dbFile); + // todo:neeraj Instead of cancel, fire event + completer?.completeError(SilentlyCancelUploadsError()); _allBackups[upload.key] = _allBackups[upload.key]! .copyWith(status: BackupItemStatus.uploaded); } else { @@ -1177,7 +1178,7 @@ class FileUploader { // by the background process. Release any lock taken by the foreground process // and complete the completer with error. await _uploadLocks.releaseLock( - file.localID!, + lockKey, ProcessType.foreground.toString(), ); completer?.completeError(SilentlyCancelUploadsError()); diff --git a/mobile/apps/photos/lib/services/album_home_widget_service.dart b/mobile/apps/photos/lib/services/album_home_widget_service.dart index 554520f346..052e40d8a6 100644 --- a/mobile/apps/photos/lib/services/album_home_widget_service.dart +++ b/mobile/apps/photos/lib/services/album_home_widget_service.dart @@ -191,23 +191,23 @@ class AlbumHomeWidgetService { await remoteCache.geFilesForCollection(collection.id); // Then open the specific file - final file = await FilesDB.instance.getFile(fileId); + // final file = await FilesDB.instance.getFile(fileId); + final file = null; if (file == null) { _logger.warning("Cannot launch widget: file with ID $fileId not found"); - return; - } - - routeToPage( - context, - DetailPage( - DetailPageConfiguration( - getAllFilesCollection, - getAllFilesCollection.indexOf(file), - "albumwidget", + } else { + routeToPage( + context, + DetailPage( + DetailPageConfiguration( + getAllFilesCollection, + getAllFilesCollection.indexOf(file), + "albumwidget", + ), ), - ), - forceCustomPageRoute: true, - ).ignore(); + forceCustomPageRoute: true, + ).ignore(); + } await _refreshAlbumsWidget(); } diff --git a/mobile/apps/photos/lib/services/filter/db_filters.dart b/mobile/apps/photos/lib/services/filter/db_filters.dart index a4ec0bce9d..df579e83f3 100644 --- a/mobile/apps/photos/lib/services/filter/db_filters.dart +++ b/mobile/apps/photos/lib/services/filter/db_filters.dart @@ -31,6 +31,25 @@ class DBFilterOptions { this.ignoreSharedItems = false, }); + // CopyWith method to create a new instance with some options changed + DBFilterOptions copyWith({ + Set? ignoredCollectionIDs, + bool? hideIgnoredForUpload, + bool? dedupeUploadID, + bool? ignoreSavedFiles, + bool? onlyUploadedFiles, + bool? ignoreSharedItems, + }) { + return DBFilterOptions( + ignoredCollectionIDs: ignoredCollectionIDs ?? this.ignoredCollectionIDs, + hideIgnoredForUpload: hideIgnoredForUpload ?? this.hideIgnoredForUpload, + dedupeUploadID: dedupeUploadID ?? this.dedupeUploadID, + ignoreSavedFiles: ignoreSavedFiles ?? this.ignoreSavedFiles, + onlyUploadedFiles: onlyUploadedFiles ?? this.onlyUploadedFiles, + ignoreSharedItems: ignoreSharedItems ?? this.ignoreSharedItems, + ); + } + static DBFilterOptions dedupeOption = DBFilterOptions( dedupeUploadID: true, ); diff --git a/mobile/apps/photos/lib/services/people_home_widget_service.dart b/mobile/apps/photos/lib/services/people_home_widget_service.dart index b86de3f1bc..4bd08e4e1f 100644 --- a/mobile/apps/photos/lib/services/people_home_widget_service.dart +++ b/mobile/apps/photos/lib/services/people_home_widget_service.dart @@ -162,12 +162,6 @@ class PeopleHomeWidgetService { String personId, BuildContext context, ) async { - final file = await FilesDB.instance.getFile(fileId); - if (file == null) { - _logger.warning("Cannot launch widget: file with ID $fileId not found"); - return; - } - final person = await PersonService.instance.getPerson(personId); if (person == null) { _logger @@ -184,6 +178,12 @@ class PeopleHomeWidgetService { forceCustomPageRoute: true, ).ignore(); + // final file = await FilesDB.instance.getFile(fileId); + final file = null; + if (file == null) { + _logger.warning("Cannot launch widget: file with ID $fileId not found"); + return; + } final clusterFiles = await SearchService.instance.getClusterFilesForPersonID( personId, diff --git a/mobile/apps/photos/lib/services/sync/remote_sync_service.dart b/mobile/apps/photos/lib/services/sync/remote_sync_service.dart index 1c204df192..b0b725d55b 100644 --- a/mobile/apps/photos/lib/services/sync/remote_sync_service.dart +++ b/mobile/apps/photos/lib/services/sync/remote_sync_service.dart @@ -310,11 +310,7 @@ class RemoteSyncService { try { final uploadedFile = await _uploader .upload(file, queueEntry.destCollectionId, queue: queueEntry); - await _collectionsService.addOrCopyToCollection( - queueEntry.destCollectionId, - [uploadedFile], - ); - _onFileUploaded(uploadedFile, queueEntry: queueEntry); + _onFileUploaded(uploadedFile); } catch (error, stackTrace) { _onFileUploadError(error, stackTrace, file); } @@ -322,7 +318,7 @@ class RemoteSyncService { void _onFileUploaded( EnteFile file, { - AssetUploadQueue? queueEntry, + }) { Bus.instance.fire( CollectionUpdatedEvent(file.collectionID, [file], "fileUpload"), diff --git a/mobile/apps/photos/lib/ui/home/home_gallery_widget_v2.dart b/mobile/apps/photos/lib/ui/home/home_gallery_widget_v2.dart index 1c92957155..b3b1178357 100644 --- a/mobile/apps/photos/lib/ui/home/home_gallery_widget_v2.dart +++ b/mobile/apps/photos/lib/ui/home/home_gallery_widget_v2.dart @@ -104,7 +104,9 @@ class _HomeGalleryWidgetV2State extends State { final List allFiles = await merge( localFiles: localFiles, remoteFiles: enteFiles, - filterOptions: homeGalleryFilters, + filterOptions: homeGalleryFilters.copyWith( + ignoreSharedItems: _shouldHideSharedItems, + ), ); _logger.info( "Merged files: ${allFiles.length} (local: ${localFiles.length}, remote: ${enteFiles.length}) files $tl, total ${tl.elapsed}", diff --git a/mobile/apps/photos/lib/ui/tools/editor/editor_utils.dart b/mobile/apps/photos/lib/ui/tools/editor/editor_utils.dart index 816cadc4b6..f1d8c9a424 100644 --- a/mobile/apps/photos/lib/ui/tools/editor/editor_utils.dart +++ b/mobile/apps/photos/lib/ui/tools/editor/editor_utils.dart @@ -4,12 +4,13 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:photo_manager/photo_manager.dart'; +import "package:photos/core/configuration.dart"; import 'package:photos/core/event_bus.dart'; -import 'package:photos/db/files_db.dart'; +import "package:photos/db/local/table/upload_queue_table.dart"; import 'package:photos/events/local_photos_updated_event.dart'; import 'package:photos/generated/l10n.dart'; import 'package:photos/models/file/file.dart'; -import 'package:photos/models/location/location.dart'; +import "package:photos/service_locator.dart"; import 'package:photos/services/sync/sync_service.dart'; import 'package:photos/ui/notification/toast.dart'; import 'package:photos/ui/viewer/file/detail_page.dart'; @@ -37,32 +38,33 @@ Future saveAsset({ originalFile.deviceFolder ?? '', newAsset, ); - newFile.creationTime = originalFile.creationTime; newFile.collectionID = originalFile.collectionID; newFile.location = originalFile.location; - if (!newFile.hasLocation && originalFile.localID != null) { - final assetEntity = await originalFile.getAsset; - if (assetEntity != null) { - final latLong = await assetEntity.latlngAsync(); - newFile.location = Location( - latitude: latLong.latitude, - longitude: latLong.longitude, - ); - } + await localDB.trackEdit( + newAsset.id, + originalFile.creationTime!, + originalFile.modificationTime!, + originalFile.location?.latitude, + originalFile.location?.longitude, + ); + if (originalFile.collectionID != null) { + await localDB.insertOrUpdateQueue( + {newAsset.id}, + originalFile.collectionID!, + Configuration.instance.getUserID()!, + ); } - newFile.generatedID = await FilesDB.instance.insertAndGetId(newFile); + Bus.instance.fire(LocalPhotosUpdatedEvent([newFile], source: "editSave")); - unawaited(SyncService.instance.sync()); showShortToast(context, S.of(context).editsSaved); logger.info("Original file " + originalFile.toString()); logger.info("Saved edits to file " + newFile.toString()); final files = detailPageConfig.files; - // the index could be -1 if the files fetched doesn't contain the newly // edited files - int selectionIndex = - files.indexWhere((file) => file.generatedID == newFile.generatedID); + int selectionIndex = files.indexWhere((file) => + originalFile.localID != null && file.localID == newFile.localID); if (selectionIndex == -1) { files.add(newFile); selectionIndex = files.length - 1; @@ -83,5 +85,9 @@ Future saveAsset({ logger.severe(e, s); } finally { await PhotoManager.startChangeNotify(); + Future.delayed( + const Duration(seconds: 2), + () => SyncService.instance.sync().ignore(), + ); } }