From 2b3427e40bf7765cda11227f217c942adc03512e Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 24 Jun 2025 11:55:28 +0530 Subject: [PATCH] Track shared assets separately --- mobile/lib/db/files_db.dart | 1 - mobile/lib/db/local/schema.dart | 11 +++- mobile/lib/db/local/table/shared_assets.dart | 56 +++++++++++++++++++ mobile/lib/models/local/shared_asset.dart | 43 ++++++++++++++ .../collection/collection_file_actions.dart | 12 ++-- mobile/lib/utils/file_uploader.dart | 5 +- mobile/lib/utils/share_util.dart | 48 +++++++++------- 7 files changed, 145 insertions(+), 31 deletions(-) create mode 100644 mobile/lib/db/local/table/shared_assets.dart create mode 100644 mobile/lib/models/local/shared_asset.dart diff --git a/mobile/lib/db/files_db.dart b/mobile/lib/db/files_db.dart index 2472fa493c..dd74f85d95 100644 --- a/mobile/lib/db/files_db.dart +++ b/mobile/lib/db/files_db.dart @@ -16,7 +16,6 @@ import 'package:photos/models/file_load_result.dart'; import 'package:photos/models/location/location.dart'; import "package:photos/models/metadata/common_keys.dart"; import "package:photos/services/filter/db_filters.dart"; -import 'package:photos/utils/file_uploader_util.dart'; import 'package:sqlite_async/sqlite_async.dart'; class FilesDB with SqlDbBase { diff --git a/mobile/lib/db/local/schema.dart b/mobile/lib/db/local/schema.dart index 0ec24ef4d2..a29481d917 100644 --- a/mobile/lib/db/local/schema.dart +++ b/mobile/lib/db/local/schema.dart @@ -141,14 +141,19 @@ class LocalDBMigration { ''', ''' CREATE TABLE shared_assets ( - id TEXT PRIMARY KEY, + dest_collection_id INTEGER NOT NULL, + id TEXT NOT NULL, + name TEXT NOT NULL, type INTEGER NOT NULL, created_at INTEGER NOT NULL, duration_in_sec INTEGER DEFAULT 0, - dest_collection_id INTEGER NOT NULL, - owner_id INTEGER NOT NULL + owner_id INTEGER NOT NULL, + PRIMARY KEY (dest_collection_id, id), ); ''', + ''' + CREATE INDEX IF NOT EXISTS sa_collection_owner ON shared_assets(dest_collection_id, owner_id); + ''', ''' CREATE TABLE device_path ( path_id TEXT PRIMARY KEY, diff --git a/mobile/lib/db/local/table/shared_assets.dart b/mobile/lib/db/local/table/shared_assets.dart new file mode 100644 index 0000000000..ebb6cd649b --- /dev/null +++ b/mobile/lib/db/local/table/shared_assets.dart @@ -0,0 +1,56 @@ +import "package:collection/collection.dart"; +import "package:photos/db/local/db.dart"; +import "package:photos/models/local/shared_asset.dart"; + +extension SharedAssetsTable on LocalDB { + Future> getSharedAssetsID() async { + final result = await sqliteDB.getAll('SELECT id FROM shared_assets'); + return Set.unmodifiable(result.map((row) => row['id'] as String)); + } + + Future insertSharedAssets(List assets) async { + if (assets.isEmpty) return; + await Future.forEach( + assets.slices(LocalDB.batchInsertMaxCount), + (slice) async { + final List> values = + slice.map((e) => e.rowProps).toList(); + await sqliteDB.executeBatch( + 'INSERT INTO shared_assets (id, name, type, creation_time, duration_in_seconds, dest_collection_id, owner_id) VALUES (?, ?, ?, ?, ?, ?, ?)', + values, + ); + }, + ); + } + + Future> getSharedAssets() async { + final result = await sqliteDB.getAll( + 'SELECT * FROM shared_assets ORDER BY creation_time DESC', + ); + return result.map((row) => SharedAsset.fromRow(row)).toList(); + } + + Future> getSharedAssetsByCollection( + int collectionID, + ) async { + final result = await sqliteDB.getAll( + 'SELECT * FROM shared_assets WHERE dest_collection_id = ? ORDER BY creation_time DESC', + [collectionID], + ); + return result.map((row) => SharedAsset.fromRow(row)).toList(); + } + + Future deleteSharedAssetsByCollection(int collectionID) async { + await sqliteDB.execute( + 'DELETE FROM shared_assets WHERE dest_collection_id = ?', + [collectionID], + ); + } + + Future deleteSharedAsset(String assetID) async { + await sqliteDB.execute( + 'DELETE FROM shared_assets WHERE id = ?', + [assetID], + ); + } +} diff --git a/mobile/lib/models/local/shared_asset.dart b/mobile/lib/models/local/shared_asset.dart new file mode 100644 index 0000000000..bc056a862c --- /dev/null +++ b/mobile/lib/models/local/shared_asset.dart @@ -0,0 +1,43 @@ +import "package:photos/models/file/file_type.dart"; + +class SharedAsset { + final String id; + final String name; + final FileType type; + final int creationTime; + final int durationInSeconds; + final int destCollectionID; + final int ownerID; + + SharedAsset({ + required this.id, + required this.name, + required this.type, + required this.creationTime, + required this.durationInSeconds, + required this.destCollectionID, + required this.ownerID, + }); + + List get rowProps => [ + id, + name, + getInt(type), + creationTime, + durationInSeconds, + destCollectionID, + ownerID, + ]; + + factory SharedAsset.fromRow(Map map) { + return SharedAsset( + id: map['id'] as String, + name: map['name'] as String, + type: getFileType(['type'] as int), + creationTime: map['creation_time'] as int, + durationInSeconds: map['duration_in_seconds'] as int, + destCollectionID: map['dest_collection_id'] as int, + ownerID: map['owner_id'] as int, + ); + } +} diff --git a/mobile/lib/ui/actions/collection/collection_file_actions.dart b/mobile/lib/ui/actions/collection/collection_file_actions.dart index 237db6825b..799171df00 100644 --- a/mobile/lib/ui/actions/collection/collection_file_actions.dart +++ b/mobile/lib/ui/actions/collection/collection_file_actions.dart @@ -5,11 +5,13 @@ 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/shared_assets.dart"; import "package:photos/events/collection_updated_event.dart"; import "package:photos/generated/l10n.dart"; import 'package:photos/models/collection/collection.dart'; import 'package:photos/models/file/file.dart'; import 'package:photos/models/selected_files.dart'; +import "package:photos/service_locator.dart"; import "package:photos/services/collections_service.dart"; import 'package:photos/services/favorites_service.dart'; import "package:photos/services/hidden_service.dart"; @@ -203,12 +205,12 @@ extension CollectionFileActions on CollectionActions { final List filesPendingUpload = []; final int currentUserID = Configuration.instance.getUserID()!; if (sharedFiles != null) { - filesPendingUpload.addAll( - await convertIncomingSharedMediaToFile( - sharedFiles, - collectionID, - ), + final sharedAssets = await convertIncomingSharedMediaToFile( + sharedFiles, + collectionID, + Configuration.instance.getUserID()!, ); + await localDB.insertSharedAssets(sharedAssets); } else if (picketAssets != null) { filesPendingUpload.addAll( await convertPicketAssets( diff --git a/mobile/lib/utils/file_uploader.dart b/mobile/lib/utils/file_uploader.dart index 8c32fc73cd..c653ffbdaf 100644 --- a/mobile/lib/utils/file_uploader.dart +++ b/mobile/lib/utils/file_uploader.dart @@ -17,6 +17,7 @@ import 'package:photos/core/errors.dart'; import 'package:photos/core/event_bus.dart'; import 'package:photos/core/network/network.dart'; import 'package:photos/db/files_db.dart'; +import "package:photos/db/local/table/shared_assets.dart"; import 'package:photos/db/upload_locks_db.dart'; import "package:photos/events/backup_updated_event.dart"; import "package:photos/events/file_uploaded_event.dart"; @@ -394,9 +395,7 @@ class FileUploader { final sharedFiles = await Directory(sharedMediaDir).list().toList(); if (sharedFiles.isNotEmpty) { _logger.info('Shared media directory cleanup ${sharedFiles.length}'); - final int ownerID = Configuration.instance.getUserID()!; - final existingLocalFileIDs = - await FilesDB.instance.getExistingLocalFileIDs(ownerID); + final existingLocalFileIDs = await localDB.getSharedAssetsID(); final Set trackedSharedFilePaths = {}; for (String localID in existingLocalFileIDs) { if (localID.contains(sharedMediaIdentifier)) { diff --git a/mobile/lib/utils/share_util.dart b/mobile/lib/utils/share_util.dart index 3d84ba0e31..126d43c4bb 100644 --- a/mobile/lib/utils/share_util.dart +++ b/mobile/lib/utils/share_util.dart @@ -8,12 +8,12 @@ import 'package:path/path.dart'; import "package:photo_manager/photo_manager.dart"; import 'package:photos/core/configuration.dart'; import 'package:photos/core/constants.dart'; -import "package:photos/db/files_db.dart"; import "package:photos/db/remote/schema.dart"; import "package:photos/generated/l10n.dart"; import "package:photos/models/collection/collection.dart"; import 'package:photos/models/file/file.dart'; import 'package:photos/models/file/file_type.dart'; +import "package:photos/models/local/shared_asset.dart"; import "package:photos/service_locator.dart"; import "package:photos/ui/sharing/show_images_prevew.dart"; import 'package:photos/utils/dialog_util.dart'; @@ -113,11 +113,12 @@ Future shareText( } } -Future> convertIncomingSharedMediaToFile( +Future> convertIncomingSharedMediaToFile( List sharedMedia, int collectionID, + int ownerID, ) async { - final List localFiles = []; + final List sharedAssets = []; for (var media in sharedMedia) { if (!(media.type == SharedMediaType.image || media.type == SharedMediaType.video)) { @@ -129,7 +130,11 @@ Future> convertIncomingSharedMediaToFile( final enteFile = EnteFile(); final sharedLocalId = const Uuid().v4(); // fileName: img_x.jpg - enteFile.title = basename(media.path); + final String name = basename(media.path); + int creationTime = 0; + int durationInSeconds = 0; + final fileType = + media.type == SharedMediaType.image ? FileType.image : FileType.video; var ioFile = File(media.path); try { ioFile = ioFile.renameSync( @@ -156,32 +161,37 @@ Future> convertIncomingSharedMediaToFile( rethrow; } } - enteFile.localID = sharedMediaIdentifier + sharedLocalId; - enteFile.collectionID = collectionID; - enteFile.fileType = - media.type == SharedMediaType.image ? FileType.image : FileType.video; - if (enteFile.fileType == FileType.image) { + + if (fileType == FileType.image) { final dateResult = await tryParseExifDateTime(ioFile, null); if (dateResult != null && dateResult.time != null) { - enteFile.creationTime = dateResult.time!.microsecondsSinceEpoch; + creationTime = dateResult.time!.microsecondsSinceEpoch; } - } else if (enteFile.fileType == FileType.video) { - enteFile.duration = (media.duration ?? 0) ~/ 1000; + } else if (fileType == FileType.video) { + durationInSeconds = (media.duration ?? 0) ~/ 1000; } - if (enteFile.creationTime == null || enteFile.creationTime == 0) { + if (creationTime == 0) { final parsedDateTime = parseDateTimeFromFileNameV2(basenameWithoutExtension(media.path)); if (parsedDateTime != null) { - enteFile.creationTime = parsedDateTime.microsecondsSinceEpoch; + creationTime = parsedDateTime.microsecondsSinceEpoch; } else { - enteFile.creationTime = DateTime.now().microsecondsSinceEpoch; + creationTime = DateTime.now().microsecondsSinceEpoch; } } - enteFile.modificationTime = enteFile.creationTime; - enteFile.metadataVersion = EnteFile.kCurrentMetadataVersion; - localFiles.add(enteFile); + sharedAssets.add( + SharedAsset( + id: sharedLocalId, + name: name, + type: fileType, + creationTime: creationTime, + durationInSeconds: durationInSeconds, + destCollectionID: collectionID, + ownerID: ownerID, + ), + ); } - return localFiles; + return sharedAssets; } Future> convertPicketAssets(