Compare commits

..

2 commits

Author SHA1 Message Date
Neeraj Gupta
ae61fc9c6f
Wrap add person name banner inside safeArea (#1887)
## Description

## Tests
2024-05-27 18:12:45 +05:30
Neeraj Gupta
c291fa70d3 Wrap add person name banner inside safeArea 2024-05-27 18:12:21 +05:30
15 changed files with 662 additions and 922 deletions

View file

@ -22,55 +22,61 @@ extension DeviceFiles on FilesDB {
ConflictAlgorithm conflictAlgorithm = ConflictAlgorithm.ignore,
}) async {
debugPrint("Inserting missing PathIDToLocalIDMapping");
final parameterSets = <List<Object?>>[];
final db = await database;
var batch = db.batch();
int batchCounter = 0;
for (MapEntry e in mappingToAdd.entries) {
final String pathID = e.key;
for (String localID in e.value) {
parameterSets.add([localID, pathID]);
batchCounter++;
if (batchCounter == 400) {
await _insertBatch(parameterSets, conflictAlgorithm);
parameterSets.clear();
await batch.commit(noResult: true);
batch = db.batch();
batchCounter = 0;
}
batch.insert(
"device_files",
{
"id": localID,
"path_id": pathID,
},
conflictAlgorithm: conflictAlgorithm,
);
batchCounter++;
}
}
await _insertBatch(parameterSets, conflictAlgorithm);
parameterSets.clear();
batchCounter = 0;
await batch.commit(noResult: true);
}
Future<void> deletePathIDToLocalIDMapping(
Map<String, Set<String>> mappingsToRemove,
) async {
debugPrint("removing PathIDToLocalIDMapping");
final parameterSets = <List<Object?>>[];
final db = await database;
var batch = db.batch();
int batchCounter = 0;
for (MapEntry e in mappingsToRemove.entries) {
final String pathID = e.key;
for (String localID in e.value) {
parameterSets.add([localID, pathID]);
batchCounter++;
if (batchCounter == 400) {
await _deleteBatch(parameterSets);
parameterSets.clear();
await batch.commit(noResult: true);
batch = db.batch();
batchCounter = 0;
}
batch.delete(
"device_files",
where: 'id = ? AND path_id = ?',
whereArgs: [localID, pathID],
);
batchCounter++;
}
}
await _deleteBatch(parameterSets);
parameterSets.clear();
batchCounter = 0;
await batch.commit(noResult: true);
}
Future<Map<String, int>> getDevicePathIDToImportedFileCount() async {
try {
final db = await sqliteAsyncDB;
final rows = await db.getAll(
final db = await database;
final rows = await db.rawQuery(
'''
SELECT count(*) as count, path_id
FROM device_files
@ -90,8 +96,8 @@ extension DeviceFiles on FilesDB {
Future<Map<String, Set<String>>> getDevicePathIDToLocalIDMap() async {
try {
final db = await sqliteAsyncDB;
final rows = await db.getAll(
final db = await database;
final rows = await db.rawQuery(
''' SELECT id, path_id FROM device_files; ''',
);
final result = <String, Set<String>>{};
@ -110,8 +116,8 @@ extension DeviceFiles on FilesDB {
}
Future<Set<String>> getDevicePathIDs() async {
final db = await sqliteAsyncDB;
final rows = await db.getAll(
final Database db = await database;
final rows = await db.rawQuery(
'''
SELECT id FROM device_collections
''',
@ -127,42 +133,34 @@ extension DeviceFiles on FilesDB {
List<LocalPathAsset> localPathAssets, {
bool shouldAutoBackup = false,
}) async {
final db = await sqliteAsyncDB;
final Database db = await database;
final Map<String, Set<String>> pathIDToLocalIDsMap = {};
try {
final batch = db.batch();
final Set<String> existingPathIds = await getDevicePathIDs();
final parameterSetsForUpdate = <List<Object?>>[];
final parameterSetsForInsert = <List<Object?>>[];
for (LocalPathAsset localPathAsset in localPathAssets) {
if (localPathAsset.localIDs.isNotEmpty) {
pathIDToLocalIDsMap[localPathAsset.pathID] = localPathAsset.localIDs;
}
if (existingPathIds.contains(localPathAsset.pathID)) {
parameterSetsForUpdate
.add([localPathAsset.pathName, localPathAsset.pathID]);
batch.rawUpdate(
"UPDATE device_collections SET name = ? where id = "
"?",
[localPathAsset.pathName, localPathAsset.pathID],
);
} else if (localPathAsset.localIDs.isNotEmpty) {
parameterSetsForInsert.add([
localPathAsset.pathID,
localPathAsset.pathName,
shouldAutoBackup ? _sqlBoolTrue : _sqlBoolFalse,
]);
batch.insert(
"device_collections",
{
"id": localPathAsset.pathID,
"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(
'''
UPDATE device_collections SET name = ? WHERE id = ?;
''',
parameterSetsForUpdate,
);
await batch.commit(noResult: true);
// add the mappings for localIDs
if (pathIDToLocalIDsMap.isNotEmpty) {
await insertPathIDToLocalIDMapping(pathIDToLocalIDsMap);
@ -179,7 +177,7 @@ extension DeviceFiles on FilesDB {
}) async {
bool hasUpdated = false;
try {
final db = await sqliteAsyncDB;
final Database db = await database;
final Set<String> existingPathIds = await getDevicePathIDs();
for (Tuple2<AssetPathEntity, String> tup in devicePathInfo) {
final AssetPathEntity pathEntity = tup.item1;
@ -187,42 +185,35 @@ extension DeviceFiles on FilesDB {
final String localID = tup.item2;
final bool shouldUpdate = existingPathIds.contains(pathEntity.id);
if (shouldUpdate) {
final rowUpdated = await db.writeTransaction((tx) async {
await tx.execute(
"UPDATE device_collections SET name = ?, cover_id = ?, count"
" = ? where id = ? AND (name != ? OR cover_id != ? OR count != ?)",
[
pathEntity.name,
localID,
assetCount,
pathEntity.id,
pathEntity.name,
localID,
assetCount,
],
);
final result = await tx.get("SELECT changes();");
return result["changes()"] as int;
});
final rowUpdated = await db.rawUpdate(
"UPDATE device_collections SET name = ?, cover_id = ?, count"
" = ? where id = ? AND (name != ? OR cover_id != ? OR count != ?)",
[
pathEntity.name,
localID,
assetCount,
pathEntity.id,
pathEntity.name,
localID,
assetCount,
],
);
if (rowUpdated > 0) {
_logger.fine("Updated $rowUpdated rows for ${pathEntity.name}");
hasUpdated = true;
}
} else {
hasUpdated = true;
await db.execute(
'''
INSERT INTO device_collections (id, name, count, cover_id, should_backup)
VALUES (?, ?, ?, ?, ?);
''',
[
pathEntity.id,
pathEntity.name,
assetCount,
localID,
shouldBackup ? _sqlBoolTrue : _sqlBoolFalse,
],
await db.insert(
"device_collections",
{
"id": pathEntity.id,
"name": pathEntity.name,
"count": assetCount,
"cover_id": localID,
"should_backup": shouldBackup ? _sqlBoolTrue : _sqlBoolFalse,
},
conflictAlgorithm: ConflictAlgorithm.ignore,
);
}
}
@ -240,17 +231,15 @@ extension DeviceFiles on FilesDB {
// feature, where we delete files which are backed up. Deleting such
// entries here result in us losing out on the information that
// those folders were marked for automatic backup.
await db.execute(
'''
DELETE FROM device_collections WHERE id = ? AND should_backup = $_sqlBoolFalse;
''',
[pathID],
await db.delete(
"device_collections",
where: 'id = ? and should_backup = $_sqlBoolFalse ',
whereArgs: [pathID],
);
await db.execute(
'''
DELETE FROM device_files WHERE path_id = ?;
''',
[pathID],
await db.delete(
"device_files",
where: 'path_id = ?',
whereArgs: [pathID],
);
}
}
@ -264,8 +253,8 @@ extension DeviceFiles on FilesDB {
// getDeviceSyncCollectionIDs returns the collectionIDs for the
// deviceCollections which are marked for auto-backup
Future<Set<int>> getDeviceSyncCollectionIDs() async {
final db = await sqliteAsyncDB;
final rows = await db.getAll(
final Database db = await database;
final rows = await db.rawQuery(
'''
SELECT collection_id FROM device_collections where should_backup =
$_sqlBoolTrue
@ -279,47 +268,40 @@ extension DeviceFiles on FilesDB {
return result;
}
Future<void> updateDevicePathSyncStatus(
Map<String, bool> syncStatus,
) async {
final db = await sqliteAsyncDB;
Future<void> updateDevicePathSyncStatus(Map<String, bool> syncStatus) async {
final db = await database;
var batch = db.batch();
int batchCounter = 0;
final parameterSets = <List<Object?>>[];
for (MapEntry e in syncStatus.entries) {
final String pathID = e.key;
parameterSets.add([e.value ? _sqlBoolTrue : _sqlBoolFalse, pathID]);
batchCounter++;
if (batchCounter == 400) {
await db.executeBatch(
'''
UPDATE device_collections SET should_backup = ? WHERE id = ?;
''',
parameterSets,
);
parameterSets.clear();
await batch.commit(noResult: true);
batch = db.batch();
batchCounter = 0;
}
batch.update(
"device_collections",
{
"should_backup": e.value ? _sqlBoolTrue : _sqlBoolFalse,
},
where: 'id = ?',
whereArgs: [pathID],
);
batchCounter++;
}
await db.executeBatch(
'''
UPDATE device_collections SET should_backup = ? WHERE id = ?;
''',
parameterSets,
);
await batch.commit(noResult: true);
}
Future<void> updateDeviceCollection(
String pathID,
int collectionID,
) async {
final db = await sqliteAsyncDB;
await db.execute(
'''
UPDATE device_collections SET collection_id = ? WHERE id = ?;
''',
[collectionID, pathID],
final db = await database;
await db.update(
"device_collections",
{"collection_id": collectionID},
where: 'id = ?',
whereArgs: [pathID],
);
return;
}
@ -332,7 +314,7 @@ extension DeviceFiles on FilesDB {
int? limit,
bool? asc,
}) async {
final db = await sqliteAsyncDB;
final db = await database;
final order = (asc ?? false ? 'ASC' : 'DESC');
final String rawQuery = '''
SELECT *
@ -347,7 +329,7 @@ extension DeviceFiles on FilesDB {
ORDER BY ${FilesDB.columnCreationTime} $order , ${FilesDB.columnModificationTime} $order
''' +
(limit != null ? ' limit $limit;' : ';');
final results = await db.getAll(rawQuery);
final results = await db.rawQuery(rawQuery);
final files = convertToFiles(results);
final dedupe = deduplicateByLocalID(files);
return FileLoadResult(dedupe, files.length == limit);
@ -357,7 +339,7 @@ extension DeviceFiles on FilesDB {
String pathID,
int ownerID,
) async {
final db = await sqliteAsyncDB;
final db = await database;
const String rawQuery = '''
SELECT ${FilesDB.columnLocalID}, ${FilesDB.columnUploadedFileID},
${FilesDB.columnFileSize}
@ -369,7 +351,7 @@ extension DeviceFiles on FilesDB {
${FilesDB.columnLocalID} IN
(SELECT id FROM device_files where path_id = ?)
''';
final results = await db.getAll(rawQuery, [ownerID, pathID]);
final results = await db.rawQuery(rawQuery, [ownerID, pathID]);
final localIDs = <String>{};
final uploadedIDs = <int>{};
int localSize = 0;
@ -393,17 +375,17 @@ extension DeviceFiles on FilesDB {
"$includeCoverThumbnail",
);
try {
final db = await sqliteAsyncDB;
final db = await database;
final coverFiles = <EnteFile>[];
if (includeCoverThumbnail) {
final fileRows = await db.getAll(
final fileRows = await db.rawQuery(
'''SELECT * FROM FILES where local_id in (select cover_id from device_collections) group by local_id;
''',
);
final files = convertToFiles(fileRows);
coverFiles.addAll(files);
}
final deviceCollectionRows = await db.getAll(
final deviceCollectionRows = await db.rawQuery(
'''SELECT * from device_collections''',
);
final List<DeviceCollection> deviceCollections = [];
@ -451,8 +433,8 @@ extension DeviceFiles on FilesDB {
Future<EnteFile?> getDeviceCollectionThumbnail(String pathID) async {
debugPrint("Call fallback method to get potential thumbnail");
final db = await sqliteAsyncDB;
final fileRows = await db.getAll(
final db = await database;
final fileRows = await db.rawQuery(
'''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;
''',
@ -465,28 +447,4 @@ extension DeviceFiles on FilesDB {
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,
);
}
}

View file

@ -10,78 +10,53 @@ extension EntitiesDB on FilesDB {
ConflictAlgorithm conflictAlgorithm = ConflictAlgorithm.replace,
}) async {
debugPrint("entitiesDB: upsertEntities ${data.length} entities");
final db = await sqliteAsyncDB;
final parameterSets = <List<Object?>>[];
final db = await database;
var batch = db.batch();
int batchCounter = 0;
for (LocalEntityData e in data) {
parameterSets.add([
e.id,
e.type.name,
e.ownerID,
e.data,
e.updatedAt,
]);
batchCounter++;
if (batchCounter == 400) {
await db.executeBatch(
'''
INSERT OR ${conflictAlgorithm.name.toUpperCase()}
INTO entities (id, type, ownerID, data, updatedAt)
VALUES (?, ?, ?, ?, ?)
''',
parameterSets,
);
parameterSets.clear();
await batch.commit(noResult: true);
batch = db.batch();
batchCounter = 0;
}
batch.insert(
"entities",
e.toJson(),
conflictAlgorithm: conflictAlgorithm,
);
batchCounter++;
}
await db.executeBatch(
'''
INSERT OR ${conflictAlgorithm.name.toUpperCase()}
INTO entities (id, type, ownerID, data, updatedAt)
VALUES (?, ?, ?, ?, ?)
''',
parameterSets,
);
await batch.commit(noResult: true);
}
Future<void> deleteEntities(
List<String> ids,
) async {
final db = await sqliteAsyncDB;
final parameterSets = <List<Object?>>[];
final db = await database;
var batch = db.batch();
int batchCounter = 0;
for (String id in ids) {
parameterSets.add(
[id],
);
batchCounter++;
if (batchCounter == 400) {
await db.executeBatch(
'''
DELETE FROM entities WHERE id = ?
''',
parameterSets,
);
parameterSets.clear();
await batch.commit(noResult: true);
batch = db.batch();
batchCounter = 0;
}
batch.delete(
"entities",
where: "id = ?",
whereArgs: [id],
);
batchCounter++;
}
await db.executeBatch(
'''
DELETE FROM entities WHERE id = ?
''',
parameterSets,
);
await batch.commit(noResult: true);
}
Future<List<LocalEntityData>> getEntities(EntityType type) async {
final db = await sqliteAsyncDB;
final List<Map<String, dynamic>> maps = await db.getAll(
'SELECT * FROM entities WHERE type = ?',
[type.name],
final db = await database;
final List<Map<String, dynamic>> maps = await db.query(
"entities",
where: "type = ?",
whereArgs: [type.typeToString()],
);
return List.generate(maps.length, (i) {
return LocalEntityData.fromJson(maps[i]);
@ -89,10 +64,11 @@ extension EntitiesDB on FilesDB {
}
Future<LocalEntityData?> getEntity(EntityType type, String id) async {
final db = await sqliteAsyncDB;
final List<Map<String, dynamic>> maps = await db.getAll(
'SELECT * FROM entities WHERE type = ? AND id = ?',
[type.name, id],
final db = await database;
final List<Map<String, dynamic>> maps = await db.query(
"entities",
where: "type = ? AND id = ?",
whereArgs: [type.typeToString(), id],
);
if (maps.isEmpty) {
return null;

File diff suppressed because it is too large Load diff

View file

@ -151,7 +151,9 @@ class FavoritesService {
final collectionID = await _getOrCreateFavoriteCollectionID();
final List<EnteFile> files = [file];
if (file.uploadedFileID == null) {
throw AssertionError("Can only favorite uploaded items");
file.collectionID = collectionID;
await _filesDB.insert(file);
Bus.instance.fire(CollectionUpdatedEvent(collectionID, files, "addTFav"));
} else {
await _collectionsService.addOrCopyToCollection(collectionID, files);
}

View file

@ -193,7 +193,7 @@ class LocalFileUpdateService {
} else if (e.reason == InvalidReason.imageToLivePhotoTypeChanged) {
fileType = FileType.livePhoto;
}
await FilesDB.instance.markFilesForReUpload(
final int count = await FilesDB.instance.markFilesForReUpload(
userID,
file.localID!,
file.title,
@ -202,7 +202,8 @@ class LocalFileUpdateService {
file.modificationTime!,
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 {
_logger.severe("failed to check hash: invalid file ${file.tag}", e);
}

View file

@ -21,8 +21,8 @@ import "package:photos/services/ignored_files_service.dart";
import 'package:photos/services/local/local_sync_util.dart';
import "package:photos/utils/debouncer.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:sqflite/sqflite.dart';
import 'package:tuple/tuple.dart';
class LocalSyncService {
@ -184,7 +184,7 @@ class LocalSyncService {
if (hasUnsyncedFiles) {
await _db.insertMultiple(
localDiffResult.uniqueLocalFiles!,
conflictAlgorithm: SqliteAsyncConflictAlgorithm.ignore,
conflictAlgorithm: ConflictAlgorithm.ignore,
);
_logger.info(
"Inserted ${localDiffResult.uniqueLocalFiles?.length} "
@ -321,7 +321,7 @@ class LocalSyncService {
files.removeWhere((file) => existingLocalDs.contains(file.localID));
await _db.insertMultiple(
files,
conflictAlgorithm: SqliteAsyncConflictAlgorithm.ignore,
conflictAlgorithm: ConflictAlgorithm.ignore,
);
_logger.info('Inserted ${files.length} files');
Bus.instance.fire(

View file

@ -580,9 +580,6 @@ class FaceMlService {
_isIndexingOrClusteringRunning = true;
final clusterAllImagesTime = DateTime.now();
_logger.info('Pulling remote feedback before actually clustering');
await PersonService.instance.fetchRemoteClusterFeedback();
try {
// Get a sense of the total number of faces in the database
final int totalFaces = await FaceMLDataDB.instance

View file

@ -73,7 +73,7 @@ class PersonService {
Future<void> reconcileClusters() async {
final EnteWatch? w = kDebugMode ? EnteWatch("reconcileClusters") : null;
w?.start();
await fetchRemoteClusterFeedback();
await storeRemoteFeedback();
w?.log("Stored remote feedback");
final dbPersonClusterInfo =
await faceMLDataDB.getPersonToClusterIdToFaceIds();
@ -225,7 +225,7 @@ class PersonService {
Bus.instance.fire(PeopleChangedEvent());
}
Future<void> fetchRemoteClusterFeedback() async {
Future<void> storeRemoteFeedback() async {
await entityService.syncEntities();
final entities = await entityService.getEntities(EntityType.person);
entities.sort((a, b) => a.updatedAt.compareTo(b.updatedAt));

View file

@ -193,7 +193,7 @@ class _FaceDebugSectionWidgetState extends State<FaceDebugSectionWidget> {
trailingIconIsMuted: true,
onTap: () async {
try {
await PersonService.instance.fetchRemoteClusterFeedback();
await PersonService.instance.storeRemoteFeedback();
FaceMlService.instance.debugIndexingDisabled = false;
await FaceMlService.instance
.clusterAllImages(clusterInBuckets: true);

View file

@ -371,7 +371,7 @@ class _ImageEditorPageState extends State<ImageEditorPage> {
);
}
}
newFile.generatedID = await FilesDB.instance.insertAndGetId(newFile);
newFile.generatedID = await FilesDB.instance.insert(newFile);
Bus.instance.fire(LocalPhotosUpdatedEvent([newFile], source: "editSave"));
unawaited(SyncService.instance.sync());
showShortToast(context, S.of(context).editsSaved);

View file

@ -146,8 +146,6 @@ class _LocationGalleryWidgetState extends State<LocationGalleryWidget> {
late final StreamSubscription<LocalPhotosUpdatedEvent> _filesUpdateEvent;
@override
void initState() {
super.initState();
final collectionsToHide =
CollectionsService.instance.archivedOrHiddenCollectionIds();
fileLoadResult = FilesDB.instance
@ -181,6 +179,8 @@ class _LocationGalleryWidgetState extends State<LocationGalleryWidget> {
});
galleryHeaderWidget = const GalleryHeaderWidget();
super.initState();
}
@override

View file

@ -161,43 +161,45 @@ class _ClusterPageState extends State<ClusterPage> {
),
),
showNamingBanner
? Dismissible(
key: const Key("namingBanner"),
direction: DismissDirection.horizontal,
onDismissed: (direction) {
setState(() {
userDismissedNamingBanner = true;
});
},
child: PeopleBanner(
type: PeopleBannerType.addName,
faceWidget: PersonFaceWidget(
files.first,
clusterID: widget.clusterID,
),
actionIcon: Icons.add_outlined,
text: S.of(context).addAName,
subText: S.of(context).findPeopleByName,
onTap: () async {
if (widget.personID == null) {
final result = await showAssignPersonAction(
context,
clusterID: widget.clusterID,
);
if (result != null &&
result is (PersonEntity, EnteFile)) {
Navigator.pop(context);
// ignore: unawaited_futures
routeToPage(context, PeoplePage(person: result.$1));
} else if (result != null && result is PersonEntity) {
Navigator.pop(context);
// ignore: unawaited_futures
routeToPage(context, PeoplePage(person: result));
}
} else {
showShortToast(context, "No personID or clusterID");
}
? SafeArea(
child: Dismissible(
key: const Key("namingBanner"),
direction: DismissDirection.horizontal,
onDismissed: (direction) {
setState(() {
userDismissedNamingBanner = true;
});
},
child: PeopleBanner(
type: PeopleBannerType.addName,
faceWidget: PersonFaceWidget(
files.first,
clusterID: widget.clusterID,
),
actionIcon: Icons.add_outlined,
text: S.of(context).addAName,
subText: S.of(context).findPeopleByName,
onTap: () async {
if (widget.personID == null) {
final result = await showAssignPersonAction(
context,
clusterID: widget.clusterID,
);
if (result != null &&
result is (PersonEntity, EnteFile)) {
Navigator.pop(context);
// ignore: unawaited_futures
routeToPage(context, PeoplePage(person: result.$1));
} else if (result != null && result is PersonEntity) {
Navigator.pop(context);
// ignore: unawaited_futures
routeToPage(context, PeoplePage(person: result));
}
} else {
showShortToast(context, "No personID or clusterID");
}
},
),
),
)
: const SizedBox.shrink(),

View file

@ -1,6 +0,0 @@
///This is useful when you want to pass a primitive by reference.
class PrimitiveWrapper {
var value;
PrimitiveWrapper(this.value);
}

View file

@ -1,39 +0,0 @@
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,
}

View file

@ -12,7 +12,7 @@ description: ente photos application
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.8.113+637
version: 0.8.112+636
publish_to: none
environment: