Track shared assets separately

This commit is contained in:
Neeraj Gupta
2025-06-24 11:55:28 +05:30
parent a57c9e881d
commit 2b3427e40b
7 changed files with 145 additions and 31 deletions

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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<Set<String>> getSharedAssetsID() async {
final result = await sqliteDB.getAll('SELECT id FROM shared_assets');
return Set.unmodifiable(result.map<String>((row) => row['id'] as String));
}
Future<void> insertSharedAssets(List<SharedAsset> assets) async {
if (assets.isEmpty) return;
await Future.forEach(
assets.slices(LocalDB.batchInsertMaxCount),
(slice) async {
final List<List<Object?>> 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<List<SharedAsset>> 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<List<SharedAsset>> 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<void> deleteSharedAssetsByCollection(int collectionID) async {
await sqliteDB.execute(
'DELETE FROM shared_assets WHERE dest_collection_id = ?',
[collectionID],
);
}
Future<void> deleteSharedAsset(String assetID) async {
await sqliteDB.execute(
'DELETE FROM shared_assets WHERE id = ?',
[assetID],
);
}
}

View File

@@ -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<Object?> get rowProps => [
id,
name,
getInt(type),
creationTime,
durationInSeconds,
destCollectionID,
ownerID,
];
factory SharedAsset.fromRow(Map<String, dynamic> 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,
);
}
}

View File

@@ -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<EnteFile> 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(

View File

@@ -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<String> trackedSharedFilePaths = {};
for (String localID in existingLocalFileIDs) {
if (localID.contains(sharedMediaIdentifier)) {

View File

@@ -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<ShareResult> shareText(
}
}
Future<List<EnteFile>> convertIncomingSharedMediaToFile(
Future<List<SharedAsset>> convertIncomingSharedMediaToFile(
List<SharedMediaFile> sharedMedia,
int collectionID,
int ownerID,
) async {
final List<EnteFile> localFiles = [];
final List<SharedAsset> sharedAssets = [];
for (var media in sharedMedia) {
if (!(media.type == SharedMediaType.image ||
media.type == SharedMediaType.video)) {
@@ -129,7 +130,11 @@ Future<List<EnteFile>> 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<List<EnteFile>> 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<List<EnteFile>> convertPicketAssets(