ente/mobile/lib/db/file_updation_db.dart
Ashil f8febe12df
[mobile][photos] Reupload files with missing GPS data (#1263)
## Description

- Fixes corrupt files (missing GPS data) that were uploaded due to [this
issue](https://github.com/ente-io/ente/pull/1261)
- Refactor

## Tests

Tested and working
- Uploaded two file from a build that has missing permission for
`ACCESS_MEDIA_LOCATION` and GPS data is missing.
- Created a new build with changes in this PR.
- Deleted the file from device. 
- Remote file has GPS data when checked from file info.

---------

Co-authored-by: Neeraj Gupta <254676+ua741@users.noreply.github.com>
2024-04-01 16:41:33 +05:30

187 lines
5.2 KiB
Dart

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:sqflite_migration/sqflite_migration.dart';
class FileUpdationDB {
static const _databaseName = "ente.files_migration.db";
static final Logger _logger = Logger((FileUpdationDB).toString());
static const tableName = 're_upload_tracker';
static const columnLocalID = 'local_id';
static const columnReason = 'reason';
static const livePhotoCheck = 'livePhotoCheck';
static const androidMissingGPS = 'androidMissingGPS';
static const modificationTimeUpdated = 'modificationTimeUpdated';
// SQL code to create the database table
static List<String> _createTable() {
return [
'''
CREATE TABLE $tableName (
$columnLocalID TEXT NOT NULL,
UNIQUE($columnLocalID)
);
''',
];
}
static List<String> addReasonColumn() {
return [
'''
ALTER TABLE $tableName ADD COLUMN $columnReason TEXT;
''',
'''
UPDATE $tableName SET $columnReason = '$modificationTimeUpdated';
''',
];
}
static final initializationScript = [..._createTable()];
static final migrationScripts = [
...addReasonColumn(),
];
final dbConfig = MigrationConfig(
initializationScript: initializationScript,
migrationScripts: migrationScripts,
);
FileUpdationDB._privateConstructor();
static final FileUpdationDB instance = FileUpdationDB._privateConstructor();
// only have a single app-wide reference to the database
static Future<Database>? _dbFuture;
Future<Database> get database async {
// lazily instantiate the db the first time it is accessed
_dbFuture ??= _initDatabase();
return _dbFuture!;
}
// this opens the database (and creates it if it doesn't exist)
Future<Database> _initDatabase() async {
final Directory documentsDirectory =
await getApplicationDocumentsDirectory();
final String path = join(documentsDirectory.path, _databaseName);
debugPrint("DB path " + path);
return await openDatabaseWithMigration(path, dbConfig);
}
Future<void> clearTable() async {
final db = await instance.database;
await db.delete(tableName);
}
Future<void> insertMultiple(
List<String> fileLocalIDs,
String reason,
) async {
final startTime = DateTime.now();
final db = await instance.database;
var batch = db.batch();
int batchCounter = 0;
for (String localID in fileLocalIDs) {
if (batchCounter == 400) {
await batch.commit(noResult: true);
batch = db.batch();
batchCounter = 0;
}
batch.insert(
tableName,
_getRowForReUploadTable(localID, reason),
conflictAlgorithm: ConflictAlgorithm.replace,
);
batchCounter++;
}
await batch.commit(noResult: true);
final endTime = DateTime.now();
final duration = Duration(
microseconds:
endTime.microsecondsSinceEpoch - startTime.microsecondsSinceEpoch,
);
_logger.info(
"Batch insert of ${fileLocalIDs.length} updated files due to $reason "
"took ${duration.inMilliseconds} ms.",
);
}
Future<void> deleteByLocalIDs(List<String> localIDs, String reason) async {
if (localIDs.isEmpty) {
return;
}
String inParam = "";
for (final localID in localIDs) {
inParam += "'" + localID + "',";
}
inParam = inParam.substring(0, inParam.length - 1);
final db = await instance.database;
await db.rawQuery(
'''
DELETE FROM $tableName
WHERE $columnLocalID IN ($inParam) AND $columnReason = '$reason';
''',
);
}
// check if entry existing for given localID and reason
Future<bool> isExisting(String localID, String reason) async {
final db = await instance.database;
final String whereClause =
'$columnLocalID = "$localID" AND $columnReason = "$reason"';
final rows = await db.query(
tableName,
where: whereClause,
);
return rows.isNotEmpty;
}
Future<List<String>> getLocalIDsForPotentialReUpload(
int limit,
String reason,
) async {
final db = await instance.database;
final String whereClause = '$columnReason = "$reason"';
final rows = await db.query(
tableName,
limit: limit,
where: whereClause,
);
final result = <String>[];
for (final row in rows) {
result.add(row[columnLocalID] as String);
}
return result;
}
// delete entries for given list of reasons
Future<void> deleteByReasons(List<String> reasons) async {
if (reasons.isEmpty) {
return;
}
String inParam = "";
for (final reason in reasons) {
inParam += "'" + reason + "',";
}
inParam = inParam.substring(0, inParam.length - 1);
final db = await instance.database;
await db.rawQuery(
'''
DELETE FROM $tableName
WHERE $columnReason IN ($inParam);
''',
);
}
Map<String, dynamic> _getRowForReUploadTable(String localID, String reason) {
final row = <String, dynamic>{};
row[columnLocalID] = localID;
row[columnReason] = reason;
return row;
}
}