Compare commits
54 commits
main
...
migrate_fi
Author | SHA1 | Date | |
---|---|---|---|
5e18ae1938 | |||
37d3776e28 | |||
05579ef368 | |||
90e467c7c0 | |||
99cf23d286 | |||
7a6fa1cd80 | |||
022448155d | |||
ed830dc387 | |||
a79d11c263 | |||
a470ed4dfa | |||
500d7da306 | |||
637adb4617 | |||
320f79bb52 | |||
484d2dc6cb | |||
a63558a309 | |||
7aa26a950d | |||
b74be0b8f1 | |||
8caa559812 | |||
22fc67c8c3 | |||
cb9ac0d939 | |||
f513473362 | |||
4fb9e75394 | |||
ee348f5585 | |||
eaca151a9f | |||
e3ea22f479 | |||
5a017616f5 | |||
159fdf83ad | |||
b2a359ca59 | |||
cae3748995 | |||
49e64b3d4c | |||
a7e0f3df7b | |||
ab9cef689d | |||
18d68bbdf3 | |||
48436694eb | |||
16178b6f09 | |||
c2b6032b6f | |||
a44e5f9505 | |||
28ddb93747 | |||
2b0fa9bae6 | |||
16d54645bc | |||
dec7c45310 | |||
1a360d3ee7 | |||
584a37d2a2 | |||
cd023b621a | |||
7fdc2b5e66 | |||
1e7779a819 | |||
56478fcb8a | |||
e179d351d9 | |||
25554209ec | |||
d1a5921c27 | |||
ff14eb1d5a | |||
8fcd05b95f | |||
3a0882a1a9 | |||
5bd845d32b |
|
@ -22,61 +22,55 @@ extension DeviceFiles on FilesDB {
|
||||||
ConflictAlgorithm conflictAlgorithm = ConflictAlgorithm.ignore,
|
ConflictAlgorithm conflictAlgorithm = ConflictAlgorithm.ignore,
|
||||||
}) async {
|
}) async {
|
||||||
debugPrint("Inserting missing PathIDToLocalIDMapping");
|
debugPrint("Inserting missing PathIDToLocalIDMapping");
|
||||||
final db = await database;
|
final parameterSets = <List<Object?>>[];
|
||||||
var batch = db.batch();
|
|
||||||
int batchCounter = 0;
|
int batchCounter = 0;
|
||||||
for (MapEntry e in mappingToAdd.entries) {
|
for (MapEntry e in mappingToAdd.entries) {
|
||||||
final String pathID = e.key;
|
final String pathID = e.key;
|
||||||
for (String localID in e.value) {
|
for (String localID in e.value) {
|
||||||
|
parameterSets.add([localID, pathID]);
|
||||||
|
batchCounter++;
|
||||||
|
|
||||||
if (batchCounter == 400) {
|
if (batchCounter == 400) {
|
||||||
await batch.commit(noResult: true);
|
await _insertBatch(parameterSets, conflictAlgorithm);
|
||||||
batch = db.batch();
|
parameterSets.clear();
|
||||||
batchCounter = 0;
|
batchCounter = 0;
|
||||||
}
|
}
|
||||||
batch.insert(
|
|
||||||
"device_files",
|
|
||||||
{
|
|
||||||
"id": localID,
|
|
||||||
"path_id": pathID,
|
|
||||||
},
|
|
||||||
conflictAlgorithm: conflictAlgorithm,
|
|
||||||
);
|
|
||||||
batchCounter++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await batch.commit(noResult: true);
|
await _insertBatch(parameterSets, conflictAlgorithm);
|
||||||
|
parameterSets.clear();
|
||||||
|
batchCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deletePathIDToLocalIDMapping(
|
Future<void> deletePathIDToLocalIDMapping(
|
||||||
Map<String, Set<String>> mappingsToRemove,
|
Map<String, Set<String>> mappingsToRemove,
|
||||||
) async {
|
) async {
|
||||||
debugPrint("removing PathIDToLocalIDMapping");
|
debugPrint("removing PathIDToLocalIDMapping");
|
||||||
final db = await database;
|
final parameterSets = <List<Object?>>[];
|
||||||
var batch = db.batch();
|
|
||||||
int batchCounter = 0;
|
int batchCounter = 0;
|
||||||
for (MapEntry e in mappingsToRemove.entries) {
|
for (MapEntry e in mappingsToRemove.entries) {
|
||||||
final String pathID = e.key;
|
final String pathID = e.key;
|
||||||
|
|
||||||
for (String localID in e.value) {
|
for (String localID in e.value) {
|
||||||
|
parameterSets.add([localID, pathID]);
|
||||||
|
batchCounter++;
|
||||||
|
|
||||||
if (batchCounter == 400) {
|
if (batchCounter == 400) {
|
||||||
await batch.commit(noResult: true);
|
await _deleteBatch(parameterSets);
|
||||||
batch = db.batch();
|
parameterSets.clear();
|
||||||
batchCounter = 0;
|
batchCounter = 0;
|
||||||
}
|
}
|
||||||
batch.delete(
|
|
||||||
"device_files",
|
|
||||||
where: 'id = ? AND path_id = ?',
|
|
||||||
whereArgs: [localID, pathID],
|
|
||||||
);
|
|
||||||
batchCounter++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await batch.commit(noResult: true);
|
await _deleteBatch(parameterSets);
|
||||||
|
parameterSets.clear();
|
||||||
|
batchCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, int>> getDevicePathIDToImportedFileCount() async {
|
Future<Map<String, int>> getDevicePathIDToImportedFileCount() async {
|
||||||
try {
|
try {
|
||||||
final db = await database;
|
final db = await sqliteAsyncDB;
|
||||||
final rows = await db.rawQuery(
|
final rows = await db.getAll(
|
||||||
'''
|
'''
|
||||||
SELECT count(*) as count, path_id
|
SELECT count(*) as count, path_id
|
||||||
FROM device_files
|
FROM device_files
|
||||||
|
@ -96,8 +90,8 @@ extension DeviceFiles on FilesDB {
|
||||||
|
|
||||||
Future<Map<String, Set<String>>> getDevicePathIDToLocalIDMap() async {
|
Future<Map<String, Set<String>>> getDevicePathIDToLocalIDMap() async {
|
||||||
try {
|
try {
|
||||||
final db = await database;
|
final db = await sqliteAsyncDB;
|
||||||
final rows = await db.rawQuery(
|
final rows = await db.getAll(
|
||||||
''' SELECT id, path_id FROM device_files; ''',
|
''' SELECT id, path_id FROM device_files; ''',
|
||||||
);
|
);
|
||||||
final result = <String, Set<String>>{};
|
final result = <String, Set<String>>{};
|
||||||
|
@ -116,8 +110,8 @@ extension DeviceFiles on FilesDB {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Set<String>> getDevicePathIDs() async {
|
Future<Set<String>> getDevicePathIDs() async {
|
||||||
final Database db = await database;
|
final db = await sqliteAsyncDB;
|
||||||
final rows = await db.rawQuery(
|
final rows = await db.getAll(
|
||||||
'''
|
'''
|
||||||
SELECT id FROM device_collections
|
SELECT id FROM device_collections
|
||||||
''',
|
''',
|
||||||
|
@ -133,34 +127,42 @@ extension DeviceFiles on FilesDB {
|
||||||
List<LocalPathAsset> localPathAssets, {
|
List<LocalPathAsset> localPathAssets, {
|
||||||
bool shouldAutoBackup = false,
|
bool shouldAutoBackup = false,
|
||||||
}) async {
|
}) async {
|
||||||
final Database db = await database;
|
final db = await sqliteAsyncDB;
|
||||||
final Map<String, Set<String>> pathIDToLocalIDsMap = {};
|
final Map<String, Set<String>> pathIDToLocalIDsMap = {};
|
||||||
try {
|
try {
|
||||||
final batch = db.batch();
|
|
||||||
final Set<String> existingPathIds = await getDevicePathIDs();
|
final Set<String> existingPathIds = await getDevicePathIDs();
|
||||||
|
final parameterSetsForUpdate = <List<Object?>>[];
|
||||||
|
final parameterSetsForInsert = <List<Object?>>[];
|
||||||
for (LocalPathAsset localPathAsset in localPathAssets) {
|
for (LocalPathAsset localPathAsset in localPathAssets) {
|
||||||
if (localPathAsset.localIDs.isNotEmpty) {
|
if (localPathAsset.localIDs.isNotEmpty) {
|
||||||
pathIDToLocalIDsMap[localPathAsset.pathID] = localPathAsset.localIDs;
|
pathIDToLocalIDsMap[localPathAsset.pathID] = localPathAsset.localIDs;
|
||||||
}
|
}
|
||||||
if (existingPathIds.contains(localPathAsset.pathID)) {
|
if (existingPathIds.contains(localPathAsset.pathID)) {
|
||||||
batch.rawUpdate(
|
parameterSetsForUpdate
|
||||||
"UPDATE device_collections SET name = ? where id = "
|
.add([localPathAsset.pathName, localPathAsset.pathID]);
|
||||||
"?",
|
|
||||||
[localPathAsset.pathName, localPathAsset.pathID],
|
|
||||||
);
|
|
||||||
} else if (localPathAsset.localIDs.isNotEmpty) {
|
} else if (localPathAsset.localIDs.isNotEmpty) {
|
||||||
batch.insert(
|
parameterSetsForInsert.add([
|
||||||
"device_collections",
|
localPathAsset.pathID,
|
||||||
{
|
localPathAsset.pathName,
|
||||||
"id": localPathAsset.pathID,
|
shouldAutoBackup ? _sqlBoolTrue : _sqlBoolFalse,
|
||||||
"name": localPathAsset.pathName,
|
]);
|
||||||
"should_backup": shouldAutoBackup ? _sqlBoolTrue : _sqlBoolFalse,
|
}
|
||||||
},
|
}
|
||||||
conflictAlgorithm: ConflictAlgorithm.ignore,
|
|
||||||
|
await db.executeBatch(
|
||||||
|
'''
|
||||||
|
INSERT OR IGNORE INTO device_collections (id, name, should_backup) VALUES (?, ?, ?);
|
||||||
|
''',
|
||||||
|
parameterSetsForInsert,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
await db.executeBatch(
|
||||||
await batch.commit(noResult: true);
|
'''
|
||||||
|
UPDATE device_collections SET name = ? WHERE id = ?;
|
||||||
|
''',
|
||||||
|
parameterSetsForUpdate,
|
||||||
|
);
|
||||||
|
|
||||||
// add the mappings for localIDs
|
// add the mappings for localIDs
|
||||||
if (pathIDToLocalIDsMap.isNotEmpty) {
|
if (pathIDToLocalIDsMap.isNotEmpty) {
|
||||||
await insertPathIDToLocalIDMapping(pathIDToLocalIDsMap);
|
await insertPathIDToLocalIDMapping(pathIDToLocalIDsMap);
|
||||||
|
@ -177,7 +179,7 @@ extension DeviceFiles on FilesDB {
|
||||||
}) async {
|
}) async {
|
||||||
bool hasUpdated = false;
|
bool hasUpdated = false;
|
||||||
try {
|
try {
|
||||||
final Database db = await database;
|
final db = await sqliteAsyncDB;
|
||||||
final Set<String> existingPathIds = await getDevicePathIDs();
|
final Set<String> existingPathIds = await getDevicePathIDs();
|
||||||
for (Tuple2<AssetPathEntity, String> tup in devicePathInfo) {
|
for (Tuple2<AssetPathEntity, String> tup in devicePathInfo) {
|
||||||
final AssetPathEntity pathEntity = tup.item1;
|
final AssetPathEntity pathEntity = tup.item1;
|
||||||
|
@ -185,7 +187,8 @@ extension DeviceFiles on FilesDB {
|
||||||
final String localID = tup.item2;
|
final String localID = tup.item2;
|
||||||
final bool shouldUpdate = existingPathIds.contains(pathEntity.id);
|
final bool shouldUpdate = existingPathIds.contains(pathEntity.id);
|
||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
final rowUpdated = await db.rawUpdate(
|
final rowUpdated = await db.writeTransaction((tx) async {
|
||||||
|
await tx.execute(
|
||||||
"UPDATE device_collections SET name = ?, cover_id = ?, count"
|
"UPDATE device_collections SET name = ?, cover_id = ?, count"
|
||||||
" = ? where id = ? AND (name != ? OR cover_id != ? OR count != ?)",
|
" = ? where id = ? AND (name != ? OR cover_id != ? OR count != ?)",
|
||||||
[
|
[
|
||||||
|
@ -198,22 +201,28 @@ extension DeviceFiles on FilesDB {
|
||||||
assetCount,
|
assetCount,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
final result = await tx.get("SELECT changes();");
|
||||||
|
return result["changes()"] as int;
|
||||||
|
});
|
||||||
|
|
||||||
if (rowUpdated > 0) {
|
if (rowUpdated > 0) {
|
||||||
_logger.fine("Updated $rowUpdated rows for ${pathEntity.name}");
|
_logger.fine("Updated $rowUpdated rows for ${pathEntity.name}");
|
||||||
hasUpdated = true;
|
hasUpdated = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hasUpdated = true;
|
hasUpdated = true;
|
||||||
await db.insert(
|
await db.execute(
|
||||||
"device_collections",
|
'''
|
||||||
{
|
INSERT INTO device_collections (id, name, count, cover_id, should_backup)
|
||||||
"id": pathEntity.id,
|
VALUES (?, ?, ?, ?, ?);
|
||||||
"name": pathEntity.name,
|
''',
|
||||||
"count": assetCount,
|
[
|
||||||
"cover_id": localID,
|
pathEntity.id,
|
||||||
"should_backup": shouldBackup ? _sqlBoolTrue : _sqlBoolFalse,
|
pathEntity.name,
|
||||||
},
|
assetCount,
|
||||||
conflictAlgorithm: ConflictAlgorithm.ignore,
|
localID,
|
||||||
|
shouldBackup ? _sqlBoolTrue : _sqlBoolFalse,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,15 +240,17 @@ extension DeviceFiles on FilesDB {
|
||||||
// feature, where we delete files which are backed up. Deleting such
|
// feature, where we delete files which are backed up. Deleting such
|
||||||
// entries here result in us losing out on the information that
|
// entries here result in us losing out on the information that
|
||||||
// those folders were marked for automatic backup.
|
// those folders were marked for automatic backup.
|
||||||
await db.delete(
|
await db.execute(
|
||||||
"device_collections",
|
'''
|
||||||
where: 'id = ? and should_backup = $_sqlBoolFalse ',
|
DELETE FROM device_collections WHERE id = ? AND should_backup = $_sqlBoolFalse;
|
||||||
whereArgs: [pathID],
|
''',
|
||||||
|
[pathID],
|
||||||
);
|
);
|
||||||
await db.delete(
|
await db.execute(
|
||||||
"device_files",
|
'''
|
||||||
where: 'path_id = ?',
|
DELETE FROM device_files WHERE path_id = ?;
|
||||||
whereArgs: [pathID],
|
''',
|
||||||
|
[pathID],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -253,8 +264,8 @@ extension DeviceFiles on FilesDB {
|
||||||
// getDeviceSyncCollectionIDs returns the collectionIDs for the
|
// getDeviceSyncCollectionIDs returns the collectionIDs for the
|
||||||
// deviceCollections which are marked for auto-backup
|
// deviceCollections which are marked for auto-backup
|
||||||
Future<Set<int>> getDeviceSyncCollectionIDs() async {
|
Future<Set<int>> getDeviceSyncCollectionIDs() async {
|
||||||
final Database db = await database;
|
final db = await sqliteAsyncDB;
|
||||||
final rows = await db.rawQuery(
|
final rows = await db.getAll(
|
||||||
'''
|
'''
|
||||||
SELECT collection_id FROM device_collections where should_backup =
|
SELECT collection_id FROM device_collections where should_backup =
|
||||||
$_sqlBoolTrue
|
$_sqlBoolTrue
|
||||||
|
@ -268,40 +279,47 @@ extension DeviceFiles on FilesDB {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateDevicePathSyncStatus(Map<String, bool> syncStatus) async {
|
Future<void> updateDevicePathSyncStatus(
|
||||||
final db = await database;
|
Map<String, bool> syncStatus,
|
||||||
var batch = db.batch();
|
) async {
|
||||||
|
final db = await sqliteAsyncDB;
|
||||||
int batchCounter = 0;
|
int batchCounter = 0;
|
||||||
|
final parameterSets = <List<Object?>>[];
|
||||||
for (MapEntry e in syncStatus.entries) {
|
for (MapEntry e in syncStatus.entries) {
|
||||||
final String pathID = e.key;
|
final String pathID = e.key;
|
||||||
|
parameterSets.add([e.value ? _sqlBoolTrue : _sqlBoolFalse, pathID]);
|
||||||
|
batchCounter++;
|
||||||
|
|
||||||
if (batchCounter == 400) {
|
if (batchCounter == 400) {
|
||||||
await batch.commit(noResult: true);
|
await db.executeBatch(
|
||||||
batch = db.batch();
|
'''
|
||||||
|
UPDATE device_collections SET should_backup = ? WHERE id = ?;
|
||||||
|
''',
|
||||||
|
parameterSets,
|
||||||
|
);
|
||||||
|
parameterSets.clear();
|
||||||
batchCounter = 0;
|
batchCounter = 0;
|
||||||
}
|
}
|
||||||
batch.update(
|
|
||||||
"device_collections",
|
|
||||||
{
|
|
||||||
"should_backup": e.value ? _sqlBoolTrue : _sqlBoolFalse,
|
|
||||||
},
|
|
||||||
where: 'id = ?',
|
|
||||||
whereArgs: [pathID],
|
|
||||||
);
|
|
||||||
batchCounter++;
|
|
||||||
}
|
}
|
||||||
await batch.commit(noResult: true);
|
|
||||||
|
await db.executeBatch(
|
||||||
|
'''
|
||||||
|
UPDATE device_collections SET should_backup = ? WHERE id = ?;
|
||||||
|
''',
|
||||||
|
parameterSets,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateDeviceCollection(
|
Future<void> updateDeviceCollection(
|
||||||
String pathID,
|
String pathID,
|
||||||
int collectionID,
|
int collectionID,
|
||||||
) async {
|
) async {
|
||||||
final db = await database;
|
final db = await sqliteAsyncDB;
|
||||||
await db.update(
|
await db.execute(
|
||||||
"device_collections",
|
'''
|
||||||
{"collection_id": collectionID},
|
UPDATE device_collections SET collection_id = ? WHERE id = ?;
|
||||||
where: 'id = ?',
|
''',
|
||||||
whereArgs: [pathID],
|
[collectionID, pathID],
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -314,7 +332,7 @@ extension DeviceFiles on FilesDB {
|
||||||
int? limit,
|
int? limit,
|
||||||
bool? asc,
|
bool? asc,
|
||||||
}) async {
|
}) async {
|
||||||
final db = await database;
|
final db = await sqliteAsyncDB;
|
||||||
final order = (asc ?? false ? 'ASC' : 'DESC');
|
final order = (asc ?? false ? 'ASC' : 'DESC');
|
||||||
final String rawQuery = '''
|
final String rawQuery = '''
|
||||||
SELECT *
|
SELECT *
|
||||||
|
@ -329,7 +347,7 @@ extension DeviceFiles on FilesDB {
|
||||||
ORDER BY ${FilesDB.columnCreationTime} $order , ${FilesDB.columnModificationTime} $order
|
ORDER BY ${FilesDB.columnCreationTime} $order , ${FilesDB.columnModificationTime} $order
|
||||||
''' +
|
''' +
|
||||||
(limit != null ? ' limit $limit;' : ';');
|
(limit != null ? ' limit $limit;' : ';');
|
||||||
final results = await db.rawQuery(rawQuery);
|
final results = await db.getAll(rawQuery);
|
||||||
final files = convertToFiles(results);
|
final files = convertToFiles(results);
|
||||||
final dedupe = deduplicateByLocalID(files);
|
final dedupe = deduplicateByLocalID(files);
|
||||||
return FileLoadResult(dedupe, files.length == limit);
|
return FileLoadResult(dedupe, files.length == limit);
|
||||||
|
@ -339,7 +357,7 @@ extension DeviceFiles on FilesDB {
|
||||||
String pathID,
|
String pathID,
|
||||||
int ownerID,
|
int ownerID,
|
||||||
) async {
|
) async {
|
||||||
final db = await database;
|
final db = await sqliteAsyncDB;
|
||||||
const String rawQuery = '''
|
const String rawQuery = '''
|
||||||
SELECT ${FilesDB.columnLocalID}, ${FilesDB.columnUploadedFileID},
|
SELECT ${FilesDB.columnLocalID}, ${FilesDB.columnUploadedFileID},
|
||||||
${FilesDB.columnFileSize}
|
${FilesDB.columnFileSize}
|
||||||
|
@ -351,7 +369,7 @@ extension DeviceFiles on FilesDB {
|
||||||
${FilesDB.columnLocalID} IN
|
${FilesDB.columnLocalID} IN
|
||||||
(SELECT id FROM device_files where path_id = ?)
|
(SELECT id FROM device_files where path_id = ?)
|
||||||
''';
|
''';
|
||||||
final results = await db.rawQuery(rawQuery, [ownerID, pathID]);
|
final results = await db.getAll(rawQuery, [ownerID, pathID]);
|
||||||
final localIDs = <String>{};
|
final localIDs = <String>{};
|
||||||
final uploadedIDs = <int>{};
|
final uploadedIDs = <int>{};
|
||||||
int localSize = 0;
|
int localSize = 0;
|
||||||
|
@ -375,17 +393,17 @@ extension DeviceFiles on FilesDB {
|
||||||
"$includeCoverThumbnail",
|
"$includeCoverThumbnail",
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
final db = await database;
|
final db = await sqliteAsyncDB;
|
||||||
final coverFiles = <EnteFile>[];
|
final coverFiles = <EnteFile>[];
|
||||||
if (includeCoverThumbnail) {
|
if (includeCoverThumbnail) {
|
||||||
final fileRows = await db.rawQuery(
|
final fileRows = await db.getAll(
|
||||||
'''SELECT * FROM FILES where local_id in (select cover_id from device_collections) group by local_id;
|
'''SELECT * FROM FILES where local_id in (select cover_id from device_collections) group by local_id;
|
||||||
''',
|
''',
|
||||||
);
|
);
|
||||||
final files = convertToFiles(fileRows);
|
final files = convertToFiles(fileRows);
|
||||||
coverFiles.addAll(files);
|
coverFiles.addAll(files);
|
||||||
}
|
}
|
||||||
final deviceCollectionRows = await db.rawQuery(
|
final deviceCollectionRows = await db.getAll(
|
||||||
'''SELECT * from device_collections''',
|
'''SELECT * from device_collections''',
|
||||||
);
|
);
|
||||||
final List<DeviceCollection> deviceCollections = [];
|
final List<DeviceCollection> deviceCollections = [];
|
||||||
|
@ -433,8 +451,8 @@ extension DeviceFiles on FilesDB {
|
||||||
|
|
||||||
Future<EnteFile?> getDeviceCollectionThumbnail(String pathID) async {
|
Future<EnteFile?> getDeviceCollectionThumbnail(String pathID) async {
|
||||||
debugPrint("Call fallback method to get potential thumbnail");
|
debugPrint("Call fallback method to get potential thumbnail");
|
||||||
final db = await database;
|
final db = await sqliteAsyncDB;
|
||||||
final fileRows = await db.rawQuery(
|
final fileRows = await db.getAll(
|
||||||
'''SELECT * FROM FILES f JOIN device_files df on f.local_id = df.id
|
'''SELECT * FROM FILES f JOIN device_files df on f.local_id = df.id
|
||||||
and df.path_id= ? order by f.creation_time DESC limit 1;
|
and df.path_id= ? order by f.creation_time DESC limit 1;
|
||||||
''',
|
''',
|
||||||
|
@ -447,4 +465,28 @@ extension DeviceFiles on FilesDB {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _insertBatch(
|
||||||
|
List<List<Object?>> parameterSets,
|
||||||
|
ConflictAlgorithm conflictAlgorithm,
|
||||||
|
) async {
|
||||||
|
final db = await sqliteAsyncDB;
|
||||||
|
await db.executeBatch(
|
||||||
|
'''
|
||||||
|
INSERT OR ${conflictAlgorithm.name.toUpperCase()}
|
||||||
|
INTO device_files (id, path_id) VALUES (?, ?);
|
||||||
|
''',
|
||||||
|
parameterSets,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _deleteBatch(List<List<Object?>> parameterSets) async {
|
||||||
|
final db = await sqliteAsyncDB;
|
||||||
|
await db.executeBatch(
|
||||||
|
'''
|
||||||
|
DELETE FROM device_files WHERE id = ? AND path_id = ?;
|
||||||
|
''',
|
||||||
|
parameterSets,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,53 +10,78 @@ extension EntitiesDB on FilesDB {
|
||||||
ConflictAlgorithm conflictAlgorithm = ConflictAlgorithm.replace,
|
ConflictAlgorithm conflictAlgorithm = ConflictAlgorithm.replace,
|
||||||
}) async {
|
}) async {
|
||||||
debugPrint("entitiesDB: upsertEntities ${data.length} entities");
|
debugPrint("entitiesDB: upsertEntities ${data.length} entities");
|
||||||
final db = await database;
|
final db = await sqliteAsyncDB;
|
||||||
var batch = db.batch();
|
final parameterSets = <List<Object?>>[];
|
||||||
int batchCounter = 0;
|
int batchCounter = 0;
|
||||||
for (LocalEntityData e in data) {
|
for (LocalEntityData e in data) {
|
||||||
|
parameterSets.add([
|
||||||
|
e.id,
|
||||||
|
e.type.name,
|
||||||
|
e.ownerID,
|
||||||
|
e.data,
|
||||||
|
e.updatedAt,
|
||||||
|
]);
|
||||||
|
batchCounter++;
|
||||||
|
|
||||||
if (batchCounter == 400) {
|
if (batchCounter == 400) {
|
||||||
await batch.commit(noResult: true);
|
await db.executeBatch(
|
||||||
batch = db.batch();
|
'''
|
||||||
|
INSERT OR ${conflictAlgorithm.name.toUpperCase()}
|
||||||
|
INTO entities (id, type, ownerID, data, updatedAt)
|
||||||
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
''',
|
||||||
|
parameterSets,
|
||||||
|
);
|
||||||
|
parameterSets.clear();
|
||||||
batchCounter = 0;
|
batchCounter = 0;
|
||||||
}
|
}
|
||||||
batch.insert(
|
|
||||||
"entities",
|
|
||||||
e.toJson(),
|
|
||||||
conflictAlgorithm: conflictAlgorithm,
|
|
||||||
);
|
|
||||||
batchCounter++;
|
|
||||||
}
|
}
|
||||||
await batch.commit(noResult: true);
|
await db.executeBatch(
|
||||||
|
'''
|
||||||
|
INSERT OR ${conflictAlgorithm.name.toUpperCase()}
|
||||||
|
INTO entities (id, type, ownerID, data, updatedAt)
|
||||||
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
''',
|
||||||
|
parameterSets,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteEntities(
|
Future<void> deleteEntities(
|
||||||
List<String> ids,
|
List<String> ids,
|
||||||
) async {
|
) async {
|
||||||
final db = await database;
|
final db = await sqliteAsyncDB;
|
||||||
var batch = db.batch();
|
final parameterSets = <List<Object?>>[];
|
||||||
int batchCounter = 0;
|
int batchCounter = 0;
|
||||||
for (String id in ids) {
|
for (String id in ids) {
|
||||||
if (batchCounter == 400) {
|
parameterSets.add(
|
||||||
await batch.commit(noResult: true);
|
[id],
|
||||||
batch = db.batch();
|
|
||||||
batchCounter = 0;
|
|
||||||
}
|
|
||||||
batch.delete(
|
|
||||||
"entities",
|
|
||||||
where: "id = ?",
|
|
||||||
whereArgs: [id],
|
|
||||||
);
|
);
|
||||||
batchCounter++;
|
batchCounter++;
|
||||||
|
|
||||||
|
if (batchCounter == 400) {
|
||||||
|
await db.executeBatch(
|
||||||
|
'''
|
||||||
|
DELETE FROM entities WHERE id = ?
|
||||||
|
''',
|
||||||
|
parameterSets,
|
||||||
|
);
|
||||||
|
parameterSets.clear();
|
||||||
|
batchCounter = 0;
|
||||||
}
|
}
|
||||||
await batch.commit(noResult: true);
|
}
|
||||||
|
await db.executeBatch(
|
||||||
|
'''
|
||||||
|
DELETE FROM entities WHERE id = ?
|
||||||
|
''',
|
||||||
|
parameterSets,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<LocalEntityData>> getEntities(EntityType type) async {
|
Future<List<LocalEntityData>> getEntities(EntityType type) async {
|
||||||
final db = await database;
|
final db = await sqliteAsyncDB;
|
||||||
final List<Map<String, dynamic>> maps = await db.query(
|
final List<Map<String, dynamic>> maps = await db.getAll(
|
||||||
"entities",
|
'SELECT * FROM entities WHERE type = ?',
|
||||||
where: "type = ?",
|
[type.name],
|
||||||
whereArgs: [type.typeToString()],
|
|
||||||
);
|
);
|
||||||
return List.generate(maps.length, (i) {
|
return List.generate(maps.length, (i) {
|
||||||
return LocalEntityData.fromJson(maps[i]);
|
return LocalEntityData.fromJson(maps[i]);
|
||||||
|
@ -64,11 +89,10 @@ extension EntitiesDB on FilesDB {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<LocalEntityData?> getEntity(EntityType type, String id) async {
|
Future<LocalEntityData?> getEntity(EntityType type, String id) async {
|
||||||
final db = await database;
|
final db = await sqliteAsyncDB;
|
||||||
final List<Map<String, dynamic>> maps = await db.query(
|
final List<Map<String, dynamic>> maps = await db.getAll(
|
||||||
"entities",
|
'SELECT * FROM entities WHERE type = ? AND id = ?',
|
||||||
where: "type = ? AND id = ?",
|
[type.name, id],
|
||||||
whereArgs: [type.typeToString(), id],
|
|
||||||
);
|
);
|
||||||
if (maps.isEmpty) {
|
if (maps.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -151,9 +151,7 @@ class FavoritesService {
|
||||||
final collectionID = await _getOrCreateFavoriteCollectionID();
|
final collectionID = await _getOrCreateFavoriteCollectionID();
|
||||||
final List<EnteFile> files = [file];
|
final List<EnteFile> files = [file];
|
||||||
if (file.uploadedFileID == null) {
|
if (file.uploadedFileID == null) {
|
||||||
file.collectionID = collectionID;
|
throw AssertionError("Can only favorite uploaded items");
|
||||||
await _filesDB.insert(file);
|
|
||||||
Bus.instance.fire(CollectionUpdatedEvent(collectionID, files, "addTFav"));
|
|
||||||
} else {
|
} else {
|
||||||
await _collectionsService.addOrCopyToCollection(collectionID, files);
|
await _collectionsService.addOrCopyToCollection(collectionID, files);
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,7 +193,7 @@ class LocalFileUpdateService {
|
||||||
} else if (e.reason == InvalidReason.imageToLivePhotoTypeChanged) {
|
} else if (e.reason == InvalidReason.imageToLivePhotoTypeChanged) {
|
||||||
fileType = FileType.livePhoto;
|
fileType = FileType.livePhoto;
|
||||||
}
|
}
|
||||||
final int count = await FilesDB.instance.markFilesForReUpload(
|
await FilesDB.instance.markFilesForReUpload(
|
||||||
userID,
|
userID,
|
||||||
file.localID!,
|
file.localID!,
|
||||||
file.title,
|
file.title,
|
||||||
|
@ -202,8 +202,7 @@ class LocalFileUpdateService {
|
||||||
file.modificationTime!,
|
file.modificationTime!,
|
||||||
fileType,
|
fileType,
|
||||||
);
|
);
|
||||||
_logger.fine('fileType changed for ${file.tag} to ${e.reason} for '
|
_logger.fine('fileType changed for ${file.tag} to ${e.reason} for ');
|
||||||
'$count files');
|
|
||||||
} else {
|
} else {
|
||||||
_logger.severe("failed to check hash: invalid file ${file.tag}", e);
|
_logger.severe("failed to check hash: invalid file ${file.tag}", e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@ import "package:photos/services/ignored_files_service.dart";
|
||||||
import 'package:photos/services/local/local_sync_util.dart';
|
import 'package:photos/services/local/local_sync_util.dart';
|
||||||
import "package:photos/utils/debouncer.dart";
|
import "package:photos/utils/debouncer.dart";
|
||||||
import "package:photos/utils/photo_manager_util.dart";
|
import "package:photos/utils/photo_manager_util.dart";
|
||||||
|
import "package:photos/utils/sqlite_util.dart";
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:sqflite/sqflite.dart';
|
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
class LocalSyncService {
|
class LocalSyncService {
|
||||||
|
@ -184,7 +184,7 @@ class LocalSyncService {
|
||||||
if (hasUnsyncedFiles) {
|
if (hasUnsyncedFiles) {
|
||||||
await _db.insertMultiple(
|
await _db.insertMultiple(
|
||||||
localDiffResult.uniqueLocalFiles!,
|
localDiffResult.uniqueLocalFiles!,
|
||||||
conflictAlgorithm: ConflictAlgorithm.ignore,
|
conflictAlgorithm: SqliteAsyncConflictAlgorithm.ignore,
|
||||||
);
|
);
|
||||||
_logger.info(
|
_logger.info(
|
||||||
"Inserted ${localDiffResult.uniqueLocalFiles?.length} "
|
"Inserted ${localDiffResult.uniqueLocalFiles?.length} "
|
||||||
|
@ -321,7 +321,7 @@ class LocalSyncService {
|
||||||
files.removeWhere((file) => existingLocalDs.contains(file.localID));
|
files.removeWhere((file) => existingLocalDs.contains(file.localID));
|
||||||
await _db.insertMultiple(
|
await _db.insertMultiple(
|
||||||
files,
|
files,
|
||||||
conflictAlgorithm: ConflictAlgorithm.ignore,
|
conflictAlgorithm: SqliteAsyncConflictAlgorithm.ignore,
|
||||||
);
|
);
|
||||||
_logger.info('Inserted ${files.length} files');
|
_logger.info('Inserted ${files.length} files');
|
||||||
Bus.instance.fire(
|
Bus.instance.fire(
|
||||||
|
|
|
@ -580,6 +580,9 @@ class FaceMlService {
|
||||||
_isIndexingOrClusteringRunning = true;
|
_isIndexingOrClusteringRunning = true;
|
||||||
final clusterAllImagesTime = DateTime.now();
|
final clusterAllImagesTime = DateTime.now();
|
||||||
|
|
||||||
|
_logger.info('Pulling remote feedback before actually clustering');
|
||||||
|
await PersonService.instance.fetchRemoteClusterFeedback();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get a sense of the total number of faces in the database
|
// Get a sense of the total number of faces in the database
|
||||||
final int totalFaces = await FaceMLDataDB.instance
|
final int totalFaces = await FaceMLDataDB.instance
|
||||||
|
|
|
@ -73,7 +73,7 @@ class PersonService {
|
||||||
Future<void> reconcileClusters() async {
|
Future<void> reconcileClusters() async {
|
||||||
final EnteWatch? w = kDebugMode ? EnteWatch("reconcileClusters") : null;
|
final EnteWatch? w = kDebugMode ? EnteWatch("reconcileClusters") : null;
|
||||||
w?.start();
|
w?.start();
|
||||||
await storeRemoteFeedback();
|
await fetchRemoteClusterFeedback();
|
||||||
w?.log("Stored remote feedback");
|
w?.log("Stored remote feedback");
|
||||||
final dbPersonClusterInfo =
|
final dbPersonClusterInfo =
|
||||||
await faceMLDataDB.getPersonToClusterIdToFaceIds();
|
await faceMLDataDB.getPersonToClusterIdToFaceIds();
|
||||||
|
@ -225,7 +225,7 @@ class PersonService {
|
||||||
Bus.instance.fire(PeopleChangedEvent());
|
Bus.instance.fire(PeopleChangedEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeRemoteFeedback() async {
|
Future<void> fetchRemoteClusterFeedback() async {
|
||||||
await entityService.syncEntities();
|
await entityService.syncEntities();
|
||||||
final entities = await entityService.getEntities(EntityType.person);
|
final entities = await entityService.getEntities(EntityType.person);
|
||||||
entities.sort((a, b) => a.updatedAt.compareTo(b.updatedAt));
|
entities.sort((a, b) => a.updatedAt.compareTo(b.updatedAt));
|
||||||
|
|
|
@ -193,7 +193,7 @@ class _FaceDebugSectionWidgetState extends State<FaceDebugSectionWidget> {
|
||||||
trailingIconIsMuted: true,
|
trailingIconIsMuted: true,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
try {
|
try {
|
||||||
await PersonService.instance.storeRemoteFeedback();
|
await PersonService.instance.fetchRemoteClusterFeedback();
|
||||||
FaceMlService.instance.debugIndexingDisabled = false;
|
FaceMlService.instance.debugIndexingDisabled = false;
|
||||||
await FaceMlService.instance
|
await FaceMlService.instance
|
||||||
.clusterAllImages(clusterInBuckets: true);
|
.clusterAllImages(clusterInBuckets: true);
|
||||||
|
|
|
@ -371,7 +371,7 @@ class _ImageEditorPageState extends State<ImageEditorPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newFile.generatedID = await FilesDB.instance.insert(newFile);
|
newFile.generatedID = await FilesDB.instance.insertAndGetId(newFile);
|
||||||
Bus.instance.fire(LocalPhotosUpdatedEvent([newFile], source: "editSave"));
|
Bus.instance.fire(LocalPhotosUpdatedEvent([newFile], source: "editSave"));
|
||||||
unawaited(SyncService.instance.sync());
|
unawaited(SyncService.instance.sync());
|
||||||
showShortToast(context, S.of(context).editsSaved);
|
showShortToast(context, S.of(context).editsSaved);
|
||||||
|
|
|
@ -146,6 +146,8 @@ class _LocationGalleryWidgetState extends State<LocationGalleryWidget> {
|
||||||
late final StreamSubscription<LocalPhotosUpdatedEvent> _filesUpdateEvent;
|
late final StreamSubscription<LocalPhotosUpdatedEvent> _filesUpdateEvent;
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
final collectionsToHide =
|
final collectionsToHide =
|
||||||
CollectionsService.instance.archivedOrHiddenCollectionIds();
|
CollectionsService.instance.archivedOrHiddenCollectionIds();
|
||||||
fileLoadResult = FilesDB.instance
|
fileLoadResult = FilesDB.instance
|
||||||
|
@ -179,8 +181,6 @@ class _LocationGalleryWidgetState extends State<LocationGalleryWidget> {
|
||||||
});
|
});
|
||||||
|
|
||||||
galleryHeaderWidget = const GalleryHeaderWidget();
|
galleryHeaderWidget = const GalleryHeaderWidget();
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
6
mobile/lib/utils/primitive_wrapper.dart
Normal file
6
mobile/lib/utils/primitive_wrapper.dart
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
///This is useful when you want to pass a primitive by reference.
|
||||||
|
|
||||||
|
class PrimitiveWrapper {
|
||||||
|
var value;
|
||||||
|
PrimitiveWrapper(this.value);
|
||||||
|
}
|
39
mobile/lib/utils/sqlite_util.dart
Normal file
39
mobile/lib/utils/sqlite_util.dart
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
enum SqliteAsyncConflictAlgorithm {
|
||||||
|
/// When a constraint violation occurs, an immediate ROLLBACK occurs,
|
||||||
|
/// thus ending the current transaction, and the command aborts with a
|
||||||
|
/// return code of SQLITE_CONSTRAINT. If no transaction is active
|
||||||
|
/// (other than the implied transaction that is created on every command)
|
||||||
|
/// then this algorithm works the same as ABORT.
|
||||||
|
rollback,
|
||||||
|
|
||||||
|
/// When a constraint violation occurs,no ROLLBACK is executed
|
||||||
|
/// so changes from prior commands within the same transaction
|
||||||
|
/// are preserved. This is the default behavior.
|
||||||
|
abort,
|
||||||
|
|
||||||
|
/// When a constraint violation occurs, the command aborts with a return
|
||||||
|
/// code SQLITE_CONSTRAINT. But any changes to the database that
|
||||||
|
/// the command made prior to encountering the constraint violation
|
||||||
|
/// are preserved and are not backed out.
|
||||||
|
fail,
|
||||||
|
|
||||||
|
/// When a constraint violation occurs, the one row that contains
|
||||||
|
/// the constraint violation is not inserted or changed.
|
||||||
|
/// But the command continues executing normally. Other rows before and
|
||||||
|
/// after the row that contained the constraint violation continue to be
|
||||||
|
/// inserted or updated normally. No error is returned.
|
||||||
|
ignore,
|
||||||
|
|
||||||
|
/// When a UNIQUE constraint violation occurs, the pre-existing rows that
|
||||||
|
/// are causing the constraint violation are removed prior to inserting
|
||||||
|
/// or updating the current row. Thus the insert or update always occurs.
|
||||||
|
/// The command continues executing normally. No error is returned.
|
||||||
|
/// If a NOT NULL constraint violation occurs, the NULL value is replaced
|
||||||
|
/// by the default value for that column. If the column has no default
|
||||||
|
/// value, then the ABORT algorithm is used. If a CHECK constraint
|
||||||
|
/// violation occurs then the IGNORE algorithm is used. When this conflict
|
||||||
|
/// resolution strategy deletes rows in order to satisfy a constraint,
|
||||||
|
/// it does not invoke delete triggers on those rows.
|
||||||
|
/// This behavior might change in a future release.
|
||||||
|
replace,
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ description: ente photos application
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
|
|
||||||
version: 0.8.112+636
|
version: 0.8.113+637
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|
Loading…
Reference in a new issue