files_db.dart 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431
  1. import 'dart:io' as io;
  2. import 'dart:io';
  3. import 'package:flutter/foundation.dart';
  4. import 'package:logging/logging.dart';
  5. import 'package:path/path.dart';
  6. import 'package:path_provider/path_provider.dart';
  7. import 'package:photos/models/backup_status.dart';
  8. import 'package:photos/models/file.dart';
  9. import 'package:photos/models/file_load_result.dart';
  10. import 'package:photos/models/file_type.dart';
  11. import 'package:photos/models/location.dart';
  12. import 'package:photos/models/magic_metadata.dart';
  13. import 'package:photos/services/feature_flag_service.dart';
  14. import 'package:photos/utils/file_uploader_util.dart';
  15. import 'package:sqflite/sqflite.dart';
  16. import 'package:sqflite_migration/sqflite_migration.dart';
  17. class FilesDB {
  18. /*
  19. Note: columnUploadedFileID and columnCollectionID have to be compared against
  20. both NULL and -1 because older clients might have entries where the DEFAULT
  21. was unset, and a migration script to set the DEFAULT would break in case of
  22. duplicate entries for un-uploaded files that were created due to a collision
  23. in background and foreground syncs.
  24. */
  25. static const _databaseName = "ente.files.db";
  26. static final Logger _logger = Logger("FilesDB");
  27. static const filesTable = 'files';
  28. static const tempTable = 'temp_files';
  29. static const columnGeneratedID = '_id';
  30. static const columnUploadedFileID = 'uploaded_file_id';
  31. static const columnOwnerID = 'owner_id';
  32. static const columnCollectionID = 'collection_id';
  33. static const columnLocalID = 'local_id';
  34. static const columnTitle = 'title';
  35. static const columnDeviceFolder = 'device_folder';
  36. static const columnLatitude = 'latitude';
  37. static const columnLongitude = 'longitude';
  38. static const columnFileType = 'file_type';
  39. static const columnFileSubType = 'file_sub_type';
  40. static const columnDuration = 'duration';
  41. static const columnExif = 'exif';
  42. static const columnHash = 'hash';
  43. static const columnMetadataVersion = 'metadata_version';
  44. static const columnIsDeleted = 'is_deleted';
  45. static const columnCreationTime = 'creation_time';
  46. static const columnModificationTime = 'modification_time';
  47. static const columnUpdationTime = 'updation_time';
  48. static const columnEncryptedKey = 'encrypted_key';
  49. static const columnKeyDecryptionNonce = 'key_decryption_nonce';
  50. static const columnFileDecryptionHeader = 'file_decryption_header';
  51. static const columnThumbnailDecryptionHeader = 'thumbnail_decryption_header';
  52. static const columnMetadataDecryptionHeader = 'metadata_decryption_header';
  53. // MMD -> Magic Metadata
  54. static const columnMMdEncodedJson = 'mmd_encoded_json';
  55. static const columnMMdVersion = 'mmd_ver';
  56. static const columnPubMMdEncodedJson = 'pub_mmd_encoded_json';
  57. static const columnPubMMdVersion = 'pub_mmd_ver';
  58. // part of magic metadata
  59. // Only parse & store selected fields from JSON in separate columns if
  60. // we need to write query based on that field
  61. static const columnMMdVisibility = 'mmd_visibility';
  62. static final initializationScript = [...createTable(filesTable)];
  63. static final migrationScripts = [
  64. ...alterDeviceFolderToAllowNULL(),
  65. ...alterTimestampColumnTypes(),
  66. ...addIndices(),
  67. ...addMetadataColumns(),
  68. ...addMagicMetadataColumns(),
  69. ...addUniqueConstraintOnCollectionFiles(),
  70. ...addPubMagicMetadataColumns(),
  71. ...createOnDeviceFilesAndPathCollection(),
  72. ];
  73. final dbConfig = MigrationConfig(
  74. initializationScript: initializationScript,
  75. migrationScripts: migrationScripts,
  76. );
  77. // make this a singleton class
  78. FilesDB._privateConstructor();
  79. static final FilesDB instance = FilesDB._privateConstructor();
  80. // only have a single app-wide reference to the database
  81. static Future<Database> _dbFuture;
  82. Future<Database> get database async {
  83. // lazily instantiate the db the first time it is accessed
  84. _dbFuture ??= _initDatabase();
  85. return _dbFuture;
  86. }
  87. // this opens the database (and creates it if it doesn't exist)
  88. Future<Database> _initDatabase() async {
  89. final Directory documentsDirectory =
  90. await getApplicationDocumentsDirectory();
  91. final String path = join(documentsDirectory.path, _databaseName);
  92. _logger.info("DB path " + path);
  93. return await openDatabaseWithMigration(path, dbConfig);
  94. }
  95. // SQL code to create the database table
  96. static List<String> createTable(String tableName) {
  97. return [
  98. '''
  99. CREATE TABLE $tableName (
  100. $columnGeneratedID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
  101. $columnLocalID TEXT,
  102. $columnUploadedFileID INTEGER DEFAULT -1,
  103. $columnOwnerID INTEGER,
  104. $columnCollectionID INTEGER DEFAULT -1,
  105. $columnTitle TEXT NOT NULL,
  106. $columnDeviceFolder TEXT,
  107. $columnLatitude REAL,
  108. $columnLongitude REAL,
  109. $columnFileType INTEGER,
  110. $columnModificationTime TEXT NOT NULL,
  111. $columnEncryptedKey TEXT,
  112. $columnKeyDecryptionNonce TEXT,
  113. $columnFileDecryptionHeader TEXT,
  114. $columnThumbnailDecryptionHeader TEXT,
  115. $columnMetadataDecryptionHeader TEXT,
  116. $columnIsDeleted INTEGER DEFAULT 0,
  117. $columnCreationTime TEXT NOT NULL,
  118. $columnUpdationTime TEXT,
  119. UNIQUE($columnLocalID, $columnUploadedFileID, $columnCollectionID)
  120. );
  121. ''',
  122. ];
  123. }
  124. static List<String> addIndices() {
  125. return [
  126. '''
  127. CREATE INDEX IF NOT EXISTS collection_id_index ON $filesTable($columnCollectionID);
  128. ''',
  129. '''
  130. CREATE INDEX IF NOT EXISTS device_folder_index ON $filesTable($columnDeviceFolder);
  131. ''',
  132. '''
  133. CREATE INDEX IF NOT EXISTS creation_time_index ON $filesTable($columnCreationTime);
  134. ''',
  135. '''
  136. CREATE INDEX IF NOT EXISTS updation_time_index ON $filesTable($columnUpdationTime);
  137. '''
  138. ];
  139. }
  140. static List<String> alterDeviceFolderToAllowNULL() {
  141. return [
  142. ...createTable(tempTable),
  143. '''
  144. INSERT INTO $tempTable
  145. SELECT *
  146. FROM $filesTable;
  147. DROP TABLE $filesTable;
  148. ALTER TABLE $tempTable
  149. RENAME TO $filesTable;
  150. '''
  151. ];
  152. }
  153. static List<String> alterTimestampColumnTypes() {
  154. return [
  155. '''
  156. DROP TABLE IF EXISTS $tempTable;
  157. ''',
  158. '''
  159. CREATE TABLE $tempTable (
  160. $columnGeneratedID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
  161. $columnLocalID TEXT,
  162. $columnUploadedFileID INTEGER DEFAULT -1,
  163. $columnOwnerID INTEGER,
  164. $columnCollectionID INTEGER DEFAULT -1,
  165. $columnTitle TEXT NOT NULL,
  166. $columnDeviceFolder TEXT,
  167. $columnLatitude REAL,
  168. $columnLongitude REAL,
  169. $columnFileType INTEGER,
  170. $columnModificationTime INTEGER NOT NULL,
  171. $columnEncryptedKey TEXT,
  172. $columnKeyDecryptionNonce TEXT,
  173. $columnFileDecryptionHeader TEXT,
  174. $columnThumbnailDecryptionHeader TEXT,
  175. $columnMetadataDecryptionHeader TEXT,
  176. $columnCreationTime INTEGER NOT NULL,
  177. $columnUpdationTime INTEGER,
  178. UNIQUE($columnLocalID, $columnUploadedFileID, $columnCollectionID)
  179. );
  180. ''',
  181. '''
  182. INSERT INTO $tempTable
  183. SELECT
  184. $columnGeneratedID,
  185. $columnLocalID,
  186. $columnUploadedFileID,
  187. $columnOwnerID,
  188. $columnCollectionID,
  189. $columnTitle,
  190. $columnDeviceFolder,
  191. $columnLatitude,
  192. $columnLongitude,
  193. $columnFileType,
  194. CAST($columnModificationTime AS INTEGER),
  195. $columnEncryptedKey,
  196. $columnKeyDecryptionNonce,
  197. $columnFileDecryptionHeader,
  198. $columnThumbnailDecryptionHeader,
  199. $columnMetadataDecryptionHeader,
  200. CAST($columnCreationTime AS INTEGER),
  201. CAST($columnUpdationTime AS INTEGER)
  202. FROM $filesTable;
  203. ''',
  204. '''
  205. DROP TABLE $filesTable;
  206. ''',
  207. '''
  208. ALTER TABLE $tempTable
  209. RENAME TO $filesTable;
  210. ''',
  211. ];
  212. }
  213. static List<String> addMetadataColumns() {
  214. return [
  215. '''
  216. ALTER TABLE $filesTable ADD COLUMN $columnFileSubType INTEGER;
  217. ''',
  218. '''
  219. ALTER TABLE $filesTable ADD COLUMN $columnDuration INTEGER;
  220. ''',
  221. '''
  222. ALTER TABLE $filesTable ADD COLUMN $columnExif TEXT;
  223. ''',
  224. '''
  225. ALTER TABLE $filesTable ADD COLUMN $columnHash TEXT;
  226. ''',
  227. '''
  228. ALTER TABLE $filesTable ADD COLUMN $columnMetadataVersion INTEGER;
  229. ''',
  230. ];
  231. }
  232. static List<String> addMagicMetadataColumns() {
  233. return [
  234. '''
  235. ALTER TABLE $filesTable ADD COLUMN $columnMMdEncodedJson TEXT DEFAULT '{}';
  236. ''',
  237. '''
  238. ALTER TABLE $filesTable ADD COLUMN $columnMMdVersion INTEGER DEFAULT 0;
  239. ''',
  240. '''
  241. ALTER TABLE $filesTable ADD COLUMN $columnMMdVisibility INTEGER DEFAULT $kVisibilityVisible;
  242. '''
  243. ];
  244. }
  245. static List<String> addUniqueConstraintOnCollectionFiles() {
  246. return [
  247. '''
  248. DELETE from $filesTable where $columnCollectionID || '-' || $columnUploadedFileID IN
  249. (SELECT $columnCollectionID || '-' || $columnUploadedFileID from $filesTable WHERE
  250. $columnCollectionID is not NULL AND $columnUploadedFileID is NOT NULL
  251. AND $columnCollectionID != -1 AND $columnUploadedFileID != -1
  252. GROUP BY ($columnCollectionID || '-' || $columnUploadedFileID) HAVING count(*) > 1)
  253. AND ($columnCollectionID || '-' || $columnUploadedFileID || '-' || $columnGeneratedID) NOT IN
  254. (SELECT $columnCollectionID || '-' || $columnUploadedFileID || '-' || max($columnGeneratedID)
  255. from $filesTable WHERE
  256. $columnCollectionID is not NULL AND $columnUploadedFileID is NOT NULL
  257. AND $columnCollectionID != -1 AND $columnUploadedFileID != -1 GROUP BY
  258. ($columnCollectionID || '-' || $columnUploadedFileID) HAVING count(*) > 1);
  259. ''',
  260. '''
  261. CREATE UNIQUE INDEX IF NOT EXISTS cid_uid ON $filesTable ($columnCollectionID, $columnUploadedFileID)
  262. WHERE $columnCollectionID is not NULL AND $columnUploadedFileID is not NULL
  263. AND $columnCollectionID != -1 AND $columnUploadedFileID != -1;
  264. '''
  265. ];
  266. }
  267. static List<String> addPubMagicMetadataColumns() {
  268. return [
  269. '''
  270. ALTER TABLE $filesTable ADD COLUMN $columnPubMMdEncodedJson TEXT DEFAULT '{}';
  271. ''',
  272. '''
  273. ALTER TABLE $filesTable ADD COLUMN $columnPubMMdVersion INTEGER DEFAULT 0;
  274. '''
  275. ];
  276. }
  277. static List<String> createOnDeviceFilesAndPathCollection() {
  278. return [
  279. '''
  280. CREATE TABLE IF NOT EXISTS device_files (
  281. id TEXT NOT NULL,
  282. path_id TEXT NOT NULL,
  283. UNIQUE(id, path_id)
  284. );
  285. ''',
  286. '''
  287. CREATE TABLE IF NOT EXISTS device_collections (
  288. id TEXT PRIMARY KEY NOT NULL,
  289. name TEXT,
  290. modified_at INTEGER NOT NULL DEFAULT 0,
  291. should_backup INTEGER NOT NULL DEFAULT 0,
  292. count INTEGER NOT NULL DEFAULT 0,
  293. collection_id INTEGER DEFAULT -1,
  294. cover_id TEXT
  295. );
  296. ''',
  297. '''
  298. CREATE INDEX IF NOT EXISTS df_id_idx ON device_files (id);
  299. ''',
  300. '''
  301. CREATE INDEX IF NOT EXISTS df_path_id_idx ON device_files (path_id);
  302. ''',
  303. ];
  304. }
  305. Future<void> clearTable() async {
  306. final db = await instance.database;
  307. await db.delete(filesTable);
  308. }
  309. Future<void> deleteDB() async {
  310. if (kDebugMode) {
  311. debugPrint("Deleting files db");
  312. final io.Directory documentsDirectory =
  313. await getApplicationDocumentsDirectory();
  314. final String path = join(documentsDirectory.path, _databaseName);
  315. io.File(path).deleteSync(recursive: true);
  316. _dbFuture = null;
  317. }
  318. }
  319. Future<void> insertMultiple(
  320. List<File> files, {
  321. ConflictAlgorithm conflictAlgorithm = ConflictAlgorithm.replace,
  322. }) async {
  323. final startTime = DateTime.now();
  324. final db = await instance.database;
  325. var batch = db.batch();
  326. int batchCounter = 0;
  327. for (File file in files) {
  328. if (batchCounter == 400) {
  329. await batch.commit(noResult: true);
  330. batch = db.batch();
  331. batchCounter = 0;
  332. }
  333. batch.insert(
  334. filesTable,
  335. _getRowForFile(file),
  336. conflictAlgorithm: conflictAlgorithm,
  337. );
  338. batchCounter++;
  339. }
  340. await batch.commit(noResult: true);
  341. final endTime = DateTime.now();
  342. final duration = Duration(
  343. microseconds:
  344. endTime.microsecondsSinceEpoch - startTime.microsecondsSinceEpoch,
  345. );
  346. _logger.info(
  347. "Batch insert of " +
  348. files.length.toString() +
  349. " took " +
  350. duration.inMilliseconds.toString() +
  351. "ms.",
  352. );
  353. }
  354. Future<int> insert(File file) async {
  355. final db = await instance.database;
  356. return db.insert(
  357. filesTable,
  358. _getRowForFile(file),
  359. conflictAlgorithm: ConflictAlgorithm.replace,
  360. );
  361. }
  362. Future<File> getFile(int generatedID) async {
  363. final db = await instance.database;
  364. final results = await db.query(
  365. filesTable,
  366. where: '$columnGeneratedID = ?',
  367. whereArgs: [generatedID],
  368. );
  369. if (results.isEmpty) {
  370. return null;
  371. }
  372. return convertToFiles(results)[0];
  373. }
  374. Future<File> getUploadedFile(int uploadedID, int collectionID) async {
  375. final db = await instance.database;
  376. final results = await db.query(
  377. filesTable,
  378. where: '$columnUploadedFileID = ? AND $columnCollectionID = ?',
  379. whereArgs: [
  380. uploadedID,
  381. collectionID,
  382. ],
  383. );
  384. if (results.isEmpty) {
  385. return null;
  386. }
  387. return convertToFiles(results)[0];
  388. }
  389. Future<Set<int>> getUploadedFileIDs(int collectionID) async {
  390. final db = await instance.database;
  391. final results = await db.query(
  392. filesTable,
  393. columns: [columnUploadedFileID],
  394. where: '$columnCollectionID = ?',
  395. whereArgs: [
  396. collectionID,
  397. ],
  398. );
  399. final ids = <int>{};
  400. for (final result in results) {
  401. ids.add(result[columnUploadedFileID]);
  402. }
  403. return ids;
  404. }
  405. Future<BackedUpFileIDs> getBackedUpIDs() async {
  406. final db = await instance.database;
  407. final results = await db.query(
  408. filesTable,
  409. columns: [columnLocalID, columnUploadedFileID],
  410. where:
  411. '$columnLocalID IS NOT NULL AND ($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1)',
  412. );
  413. final localIDs = <String>{};
  414. final uploadedIDs = <int>{};
  415. for (final result in results) {
  416. localIDs.add(result[columnLocalID]);
  417. uploadedIDs.add(result[columnUploadedFileID]);
  418. }
  419. return BackedUpFileIDs(localIDs.toList(), uploadedIDs.toList());
  420. }
  421. Future<FileLoadResult> getAllUploadedFiles(
  422. int startTime,
  423. int endTime,
  424. int ownerID, {
  425. int limit,
  426. bool asc,
  427. int visibility = kVisibilityVisible,
  428. Set<int> ignoredCollectionIDs,
  429. }) async {
  430. final db = await instance.database;
  431. final order = (asc ?? false ? 'ASC' : 'DESC');
  432. final results = await db.query(
  433. filesTable,
  434. where:
  435. '$columnCreationTime >= ? AND $columnCreationTime <= ? AND $columnOwnerID = ? AND ($columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1)'
  436. ' AND $columnMMdVisibility = ?',
  437. whereArgs: [startTime, endTime, ownerID, visibility],
  438. orderBy:
  439. '$columnCreationTime ' + order + ', $columnModificationTime ' + order,
  440. limit: limit,
  441. );
  442. final files = convertToFiles(results);
  443. final List<File> deduplicatedFiles =
  444. _deduplicatedAndFilterIgnoredFiles(files, ignoredCollectionIDs);
  445. return FileLoadResult(deduplicatedFiles, files.length == limit);
  446. }
  447. Future<Set<int>> getCollectionIDsOfHiddenFiles(
  448. int ownerID, {
  449. int visibility = kVisibilityArchive,
  450. }) async {
  451. final db = await instance.database;
  452. final results = await db.query(
  453. filesTable,
  454. where:
  455. '$columnOwnerID = ? AND $columnMMdVisibility = ? AND $columnCollectionID != -1',
  456. columns: [columnCollectionID],
  457. whereArgs: [ownerID, visibility],
  458. distinct: true,
  459. );
  460. final Set<int> collectionIDsOfHiddenFiles = {};
  461. for (var result in results) {
  462. collectionIDsOfHiddenFiles.add(result['collection_id']);
  463. }
  464. return collectionIDsOfHiddenFiles;
  465. }
  466. Future<FileLoadResult> getAllLocalAndUploadedFiles(
  467. int startTime,
  468. int endTime,
  469. int ownerID, {
  470. int limit,
  471. bool asc,
  472. Set<int> ignoredCollectionIDs,
  473. }) async {
  474. final db = await instance.database;
  475. final order = (asc ?? false ? 'ASC' : 'DESC');
  476. final results = await db.query(
  477. filesTable,
  478. where:
  479. '$columnCreationTime >= ? AND $columnCreationTime <= ? AND ($columnOwnerID IS NULL OR $columnOwnerID = ?) AND ($columnMMdVisibility IS NULL OR $columnMMdVisibility = ?)'
  480. ' AND ($columnLocalID IS NOT NULL OR ($columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1))',
  481. whereArgs: [startTime, endTime, ownerID, kVisibilityVisible],
  482. orderBy:
  483. '$columnCreationTime ' + order + ', $columnModificationTime ' + order,
  484. limit: limit,
  485. );
  486. final files = convertToFiles(results);
  487. final List<File> deduplicatedFiles =
  488. _deduplicatedAndFilterIgnoredFiles(files, ignoredCollectionIDs);
  489. return FileLoadResult(deduplicatedFiles, files.length == limit);
  490. }
  491. Future<FileLoadResult> getImportantFiles(
  492. int startTime,
  493. int endTime,
  494. int ownerID,
  495. List<String> paths, {
  496. int limit,
  497. bool asc,
  498. Set<int> ignoredCollectionIDs,
  499. }) async {
  500. final db = await instance.database;
  501. String inParam = "";
  502. for (final path in paths) {
  503. inParam += "'" + path.replaceAll("'", "''") + "',";
  504. }
  505. inParam = inParam.substring(0, inParam.length - 1);
  506. final order = (asc ?? false ? 'ASC' : 'DESC');
  507. final results = await db.query(
  508. filesTable,
  509. where:
  510. '$columnCreationTime >= ? AND $columnCreationTime <= ? AND ($columnOwnerID IS NULL OR $columnOwnerID = ?) AND ($columnMMdVisibility IS NULL OR $columnMMdVisibility = ?)'
  511. 'AND (($columnLocalID IS NOT NULL AND $columnDeviceFolder IN ($inParam)) OR ($columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1))',
  512. whereArgs: [startTime, endTime, ownerID, kVisibilityVisible],
  513. orderBy:
  514. '$columnCreationTime ' + order + ', $columnModificationTime ' + order,
  515. limit: limit,
  516. );
  517. final files = convertToFiles(results);
  518. final List<File> deduplicatedFiles =
  519. _deduplicatedAndFilterIgnoredFiles(files, ignoredCollectionIDs);
  520. return FileLoadResult(deduplicatedFiles, files.length == limit);
  521. }
  522. List<File> deduplicateByLocalID(List<File> files) {
  523. final localIDs = <String>{};
  524. final List<File> deduplicatedFiles = [];
  525. for (final file in files) {
  526. final id = file.localID;
  527. if (id != null && localIDs.contains(id)) {
  528. continue;
  529. }
  530. localIDs.add(id);
  531. deduplicatedFiles.add(file);
  532. }
  533. return deduplicatedFiles;
  534. }
  535. List<File> _deduplicatedAndFilterIgnoredFiles(
  536. List<File> files,
  537. Set<int> ignoredCollectionIDs,
  538. ) {
  539. final uploadedFileIDs = <int>{};
  540. final List<File> deduplicatedFiles = [];
  541. for (final file in files) {
  542. final id = file.uploadedFileID;
  543. if (ignoredCollectionIDs != null &&
  544. ignoredCollectionIDs.contains(file.collectionID)) {
  545. continue;
  546. }
  547. if (id != null && id != -1 && uploadedFileIDs.contains(id)) {
  548. continue;
  549. }
  550. uploadedFileIDs.add(id);
  551. deduplicatedFiles.add(file);
  552. }
  553. return deduplicatedFiles;
  554. }
  555. Future<FileLoadResult> getFilesInCollection(
  556. int collectionID,
  557. int startTime,
  558. int endTime, {
  559. int limit,
  560. bool asc,
  561. int visibility = kVisibilityVisible,
  562. }) async {
  563. final db = await instance.database;
  564. final order = (asc ?? false ? 'ASC' : 'DESC');
  565. String whereClause;
  566. List<Object> whereArgs;
  567. if (FeatureFlagService.instance.isInternalUserOrDebugBuild()) {
  568. whereClause =
  569. '$columnCollectionID = ? AND $columnCreationTime >= ? AND $columnCreationTime <= ? AND $columnMMdVisibility = ?';
  570. whereArgs = [collectionID, startTime, endTime, visibility];
  571. } else {
  572. whereClause =
  573. '$columnCollectionID = ? AND $columnCreationTime >= ? AND $columnCreationTime <= ?';
  574. whereArgs = [collectionID, startTime, endTime];
  575. }
  576. final results = await db.query(
  577. filesTable,
  578. where: whereClause,
  579. whereArgs: whereArgs,
  580. orderBy:
  581. '$columnCreationTime ' + order + ', $columnModificationTime ' + order,
  582. limit: limit,
  583. );
  584. final files = convertToFiles(results);
  585. _logger.info("Fetched " + files.length.toString() + " files");
  586. return FileLoadResult(files, files.length == limit);
  587. }
  588. Future<FileLoadResult> getFilesInPath(
  589. String path,
  590. int startTime,
  591. int endTime, {
  592. int limit,
  593. bool asc,
  594. }) async {
  595. final db = await instance.database;
  596. final order = (asc ?? false ? 'ASC' : 'DESC');
  597. final results = await db.query(
  598. filesTable,
  599. where:
  600. '$columnDeviceFolder = ? AND $columnCreationTime >= ? AND $columnCreationTime <= ? AND $columnLocalID IS NOT NULL',
  601. whereArgs: [path, startTime, endTime],
  602. orderBy:
  603. '$columnCreationTime ' + order + ', $columnModificationTime ' + order,
  604. groupBy: columnLocalID,
  605. limit: limit,
  606. );
  607. final files = convertToFiles(results);
  608. return FileLoadResult(files, files.length == limit);
  609. }
  610. Future<FileLoadResult> getLocalDeviceFiles(
  611. int startTime,
  612. int endTime, {
  613. int limit,
  614. bool asc,
  615. }) async {
  616. final db = await instance.database;
  617. final order = (asc ?? false ? 'ASC' : 'DESC');
  618. final results = await db.query(
  619. filesTable,
  620. where:
  621. '$columnCreationTime >= ? AND $columnCreationTime <= ? AND $columnLocalID IS NOT NULL',
  622. whereArgs: [startTime, endTime],
  623. orderBy:
  624. '$columnCreationTime ' + order + ', $columnModificationTime ' + order,
  625. limit: limit,
  626. );
  627. final files = convertToFiles(results);
  628. final result = deduplicateByLocalID(files);
  629. return FileLoadResult(result, files.length == limit);
  630. }
  631. Future<List<File>> getAllVideos() async {
  632. final db = await instance.database;
  633. final results = await db.query(
  634. filesTable,
  635. where: '$columnLocalID IS NOT NULL AND $columnFileType = 1',
  636. orderBy: '$columnCreationTime DESC',
  637. );
  638. return convertToFiles(results);
  639. }
  640. Future<List<File>> getFilesCreatedWithinDurations(
  641. List<List<int>> durations,
  642. Set<int> ignoredCollectionIDs, {
  643. String order = 'ASC',
  644. }) async {
  645. final db = await instance.database;
  646. String whereClause = "( ";
  647. for (int index = 0; index < durations.length; index++) {
  648. whereClause += "($columnCreationTime > " +
  649. durations[index][0].toString() +
  650. " AND $columnCreationTime < " +
  651. durations[index][1].toString() +
  652. ")";
  653. if (index != durations.length - 1) {
  654. whereClause += " OR ";
  655. }
  656. }
  657. whereClause += ") AND $columnMMdVisibility = $kVisibilityVisible";
  658. final results = await db.query(
  659. filesTable,
  660. where: whereClause,
  661. orderBy: '$columnCreationTime ' + order,
  662. );
  663. final files = convertToFiles(results);
  664. return _deduplicatedAndFilterIgnoredFiles(files, ignoredCollectionIDs);
  665. }
  666. Future<List<File>> getFilesToBeUploadedWithinFolders(
  667. Set<String> folders,
  668. ) async {
  669. if (folders.isEmpty) {
  670. return [];
  671. }
  672. final db = await instance.database;
  673. String inParam = "";
  674. for (final folder in folders) {
  675. inParam += "'" + folder.replaceAll("'", "''") + "',";
  676. }
  677. inParam = inParam.substring(0, inParam.length - 1);
  678. final results = await db.query(
  679. filesTable,
  680. where:
  681. '($columnUploadedFileID IS NULL OR $columnUploadedFileID IS -1) AND $columnDeviceFolder IN ($inParam)',
  682. orderBy: '$columnCreationTime DESC',
  683. groupBy: columnLocalID,
  684. );
  685. return convertToFiles(results);
  686. }
  687. // Files which user added to a collection manually but they are not uploaded yet.
  688. Future<List<File>> getPendingManualUploads() async {
  689. final db = await instance.database;
  690. final results = await db.query(
  691. filesTable,
  692. where:
  693. '($columnUploadedFileID IS NULL OR $columnUploadedFileID IS -1) AND '
  694. '$columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1 AND '
  695. '$columnLocalID IS NOT NULL AND $columnLocalID IS NOT -1',
  696. orderBy: '$columnCreationTime DESC',
  697. groupBy: columnLocalID,
  698. );
  699. final files = convertToFiles(results);
  700. // future-safe filter just to ensure that the query doesn't end up returning files
  701. // which should not be backed up
  702. files.removeWhere(
  703. (e) =>
  704. e.collectionID == null ||
  705. e.localID == null ||
  706. e.uploadedFileID != null,
  707. );
  708. return files;
  709. }
  710. Future<List<File>> getUnUploadedLocalFiles() async {
  711. final db = await instance.database;
  712. final results = await db.query(
  713. filesTable,
  714. where:
  715. '($columnUploadedFileID IS NULL OR $columnUploadedFileID IS -1) AND $columnLocalID IS NOT NULL',
  716. orderBy: '$columnCreationTime DESC',
  717. groupBy: columnLocalID,
  718. );
  719. return convertToFiles(results);
  720. }
  721. Future<List<int>> getUploadedFileIDsToBeUpdated(int ownerID) async {
  722. final db = await instance.database;
  723. final rows = await db.query(
  724. filesTable,
  725. columns: [columnUploadedFileID],
  726. where: '($columnLocalID IS NOT NULL AND $columnOwnerID = ? AND '
  727. '($columnUploadedFileID '
  728. 'IS NOT '
  729. 'NULL AND $columnUploadedFileID IS NOT -1) AND $columnUpdationTime IS NULL)',
  730. whereArgs: [ownerID],
  731. orderBy: '$columnCreationTime DESC',
  732. distinct: true,
  733. );
  734. final uploadedFileIDs = <int>[];
  735. for (final row in rows) {
  736. uploadedFileIDs.add(row[columnUploadedFileID]);
  737. }
  738. return uploadedFileIDs;
  739. }
  740. Future<File> getUploadedFileInAnyCollection(int uploadedFileID) async {
  741. final db = await instance.database;
  742. final results = await db.query(
  743. filesTable,
  744. where: '$columnUploadedFileID = ?',
  745. whereArgs: [
  746. uploadedFileID,
  747. ],
  748. limit: 1,
  749. );
  750. if (results.isEmpty) {
  751. return null;
  752. }
  753. return convertToFiles(results)[0];
  754. }
  755. Future<Set<String>> getExistingLocalFileIDs() async {
  756. final db = await instance.database;
  757. final rows = await db.query(
  758. filesTable,
  759. columns: [columnLocalID],
  760. distinct: true,
  761. where: '$columnLocalID IS NOT NULL',
  762. );
  763. final result = <String>{};
  764. for (final row in rows) {
  765. result.add(row[columnLocalID]);
  766. }
  767. return result;
  768. }
  769. Future<Set<String>> getLocalFileIDsForCollection(int collectionID) async {
  770. final db = await instance.database;
  771. final rows = await db.query(
  772. filesTable,
  773. columns: [columnLocalID],
  774. where: '$columnLocalID IS NOT NULL AND $columnCollectionID = ?',
  775. whereArgs: [collectionID],
  776. );
  777. final result = <String>{};
  778. for (final row in rows) {
  779. if (kDebugMode && result.contains(row[columnLocalID])) {
  780. debugPrint(
  781. "Two mappings existing for same localID ${row[columnLocalID]} ");
  782. }
  783. result.add(row[columnLocalID]);
  784. }
  785. return result;
  786. }
  787. // Sets the collectionID for the files with given LocalIDs if the
  788. // corresponding file entries are not already mapped to some other collection
  789. Future<int> setCollectionIDForUnMappedLocalFiles(
  790. int collectionID,
  791. Set<String> localIDs,
  792. ) async {
  793. final db = await instance.database;
  794. String inParam = "";
  795. for (final localID in localIDs) {
  796. inParam += "'" + localID + "',";
  797. }
  798. inParam = inParam.substring(0, inParam.length - 1);
  799. return await db.rawUpdate(
  800. '''
  801. UPDATE $filesTable
  802. SET $columnCollectionID = $collectionID
  803. WHERE $columnLocalID IN ($inParam) AND ($columnCollectionID IS NULL OR
  804. $columnCollectionID = -1);
  805. ''',
  806. );
  807. }
  808. Future<int> getNumberOfUploadedFiles() async {
  809. final db = await instance.database;
  810. final rows = await db.query(
  811. filesTable,
  812. columns: [columnUploadedFileID],
  813. where:
  814. '($columnLocalID IS NOT NULL AND ($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1) AND $columnUpdationTime IS NOT NULL)',
  815. distinct: true,
  816. );
  817. return rows.length;
  818. }
  819. Future<int> updateUploadedFile(
  820. String localID,
  821. String title,
  822. Location location,
  823. int creationTime,
  824. int modificationTime,
  825. int updationTime,
  826. ) async {
  827. final db = await instance.database;
  828. return await db.update(
  829. filesTable,
  830. {
  831. columnTitle: title,
  832. columnLatitude: location.latitude,
  833. columnLongitude: location.longitude,
  834. columnCreationTime: creationTime,
  835. columnModificationTime: modificationTime,
  836. columnUpdationTime: updationTime,
  837. },
  838. where: '$columnLocalID = ?',
  839. whereArgs: [localID],
  840. );
  841. }
  842. Future<List<File>> getMatchingFiles(
  843. String title,
  844. String deviceFolder,
  845. ) async {
  846. final db = await instance.database;
  847. final rows = await db.query(
  848. filesTable,
  849. where: '''$columnTitle=? AND $columnDeviceFolder=?''',
  850. whereArgs: [
  851. title,
  852. deviceFolder,
  853. ],
  854. );
  855. if (rows.isNotEmpty) {
  856. return convertToFiles(rows);
  857. } else {
  858. return null;
  859. }
  860. }
  861. Future<List<File>> getUploadedFilesWithHashes(
  862. FileHashData hashData,
  863. FileType fileType,
  864. int ownerID,
  865. ) async {
  866. String inParam = "'${hashData.fileHash}'";
  867. if (fileType == FileType.livePhoto && hashData.zipHash != null) {
  868. inParam += ",'${hashData.zipHash}'";
  869. }
  870. final db = await instance.database;
  871. final rows = await db.query(
  872. filesTable,
  873. where: '($columnUploadedFileID != NULL OR $columnUploadedFileID != -1) '
  874. 'AND $columnOwnerID = ? AND $columnFileType ='
  875. ' ? '
  876. 'AND $columnHash IN ($inParam)',
  877. whereArgs: [
  878. ownerID,
  879. getInt(fileType),
  880. ],
  881. );
  882. return convertToFiles(rows);
  883. }
  884. Future<int> update(File file) async {
  885. final db = await instance.database;
  886. return await db.update(
  887. filesTable,
  888. _getRowForFile(file),
  889. where: '$columnGeneratedID = ?',
  890. whereArgs: [file.generatedID],
  891. );
  892. }
  893. Future<int> updateUploadedFileAcrossCollections(File file) async {
  894. final db = await instance.database;
  895. return await db.update(
  896. filesTable,
  897. _getRowForFileWithoutCollection(file),
  898. where: '$columnUploadedFileID = ?',
  899. whereArgs: [file.uploadedFileID],
  900. );
  901. }
  902. Future<int> updateLocalIDForUploaded(int uploadedID, String localID) async {
  903. final db = await instance.database;
  904. return await db.update(
  905. table,
  906. {columnLocalID: localID},
  907. where: '$columnUploadedFileID = ? AND $columnLocalID IS NULL',
  908. whereArgs: [uploadedID],
  909. );
  910. }
  911. Future<int> delete(int uploadedFileID) async {
  912. final db = await instance.database;
  913. return db.delete(
  914. filesTable,
  915. where: '$columnUploadedFileID =?',
  916. whereArgs: [uploadedFileID],
  917. );
  918. }
  919. Future<int> deleteByGeneratedID(int genID) async {
  920. final db = await instance.database;
  921. return db.delete(
  922. filesTable,
  923. where: '$columnGeneratedID =?',
  924. whereArgs: [genID],
  925. );
  926. }
  927. Future<int> deleteMultipleUploadedFiles(List<int> uploadedFileIDs) async {
  928. final db = await instance.database;
  929. return await db.delete(
  930. filesTable,
  931. where: '$columnUploadedFileID IN (${uploadedFileIDs.join(', ')})',
  932. );
  933. }
  934. Future<int> deleteLocalFile(File file) async {
  935. final db = await instance.database;
  936. if (file.localID != null) {
  937. // delete all files with same local ID
  938. return db.delete(
  939. filesTable,
  940. where: '$columnLocalID =?',
  941. whereArgs: [file.localID],
  942. );
  943. } else {
  944. return db.delete(
  945. filesTable,
  946. where: '$columnGeneratedID =?',
  947. whereArgs: [file.generatedID],
  948. );
  949. }
  950. }
  951. Future<void> deleteLocalFiles(List<String> localIDs) async {
  952. String inParam = "";
  953. for (final localID in localIDs) {
  954. inParam += "'" + localID + "',";
  955. }
  956. inParam = inParam.substring(0, inParam.length - 1);
  957. final db = await instance.database;
  958. await db.rawQuery(
  959. '''
  960. UPDATE $filesTable
  961. SET $columnLocalID = NULL
  962. WHERE $columnLocalID IN ($inParam);
  963. ''',
  964. );
  965. }
  966. Future<List<File>> getLocalFiles(List<String> localIDs) async {
  967. String inParam = "";
  968. for (final localID in localIDs) {
  969. inParam += "'" + localID + "',";
  970. }
  971. inParam = inParam.substring(0, inParam.length - 1);
  972. final db = await instance.database;
  973. final results = await db.query(
  974. filesTable,
  975. where: '$columnLocalID IN ($inParam)',
  976. );
  977. return convertToFiles(results);
  978. }
  979. Future<int> deleteUnSyncedLocalFiles(List<String> localIDs) async {
  980. String inParam = "";
  981. for (final localID in localIDs) {
  982. inParam += "'" + localID + "',";
  983. }
  984. inParam = inParam.substring(0, inParam.length - 1);
  985. final db = await instance.database;
  986. return db.delete(
  987. filesTable,
  988. where:
  989. '($columnUploadedFileID is NULL OR $columnUploadedFileID = -1 ) AND $columnLocalID IN ($inParam)',
  990. );
  991. }
  992. Future<int> deleteFromCollection(int uploadedFileID, int collectionID) async {
  993. final db = await instance.database;
  994. return db.delete(
  995. filesTable,
  996. where: '$columnUploadedFileID = ? AND $columnCollectionID = ?',
  997. whereArgs: [uploadedFileID, collectionID],
  998. );
  999. }
  1000. Future<int> deleteFilesFromCollection(
  1001. int collectionID,
  1002. List<int> uploadedFileIDs,
  1003. ) async {
  1004. final db = await instance.database;
  1005. return db.delete(
  1006. filesTable,
  1007. where:
  1008. '$columnCollectionID = ? AND $columnUploadedFileID IN (${uploadedFileIDs.join(', ')})',
  1009. whereArgs: [collectionID],
  1010. );
  1011. }
  1012. Future<int> collectionFileCount(int collectionID) async {
  1013. final db = await instance.database;
  1014. final count = Sqflite.firstIntValue(
  1015. await db.rawQuery(
  1016. 'SELECT COUNT(*) FROM $filesTable where $columnCollectionID = $collectionID',
  1017. ),
  1018. );
  1019. return count;
  1020. }
  1021. Future<int> fileCountWithVisibility(int visibility, int ownerID) async {
  1022. final db = await instance.database;
  1023. final count = Sqflite.firstIntValue(
  1024. await db.rawQuery(
  1025. 'SELECT COUNT(*) FROM $filesTable where $columnMMdVisibility = $visibility AND $columnOwnerID = $ownerID',
  1026. ),
  1027. );
  1028. return count;
  1029. }
  1030. Future<int> deleteCollection(int collectionID) async {
  1031. final db = await instance.database;
  1032. return db.delete(
  1033. filesTable,
  1034. where: '$columnCollectionID = ?',
  1035. whereArgs: [collectionID],
  1036. );
  1037. }
  1038. Future<int> removeFromCollection(int collectionID, List<int> fileIDs) async {
  1039. final db = await instance.database;
  1040. return db.delete(
  1041. filesTable,
  1042. where:
  1043. '$columnCollectionID =? AND $columnUploadedFileID IN (${fileIDs.join(', ')})',
  1044. whereArgs: [collectionID],
  1045. );
  1046. }
  1047. Future<List<File>> getLatestLocalFiles() async {
  1048. final db = await instance.database;
  1049. final rows = await db.rawQuery(
  1050. '''
  1051. SELECT $filesTable.*
  1052. FROM $filesTable
  1053. INNER JOIN
  1054. (
  1055. SELECT $columnDeviceFolder, MAX($columnCreationTime) AS max_creation_time
  1056. FROM $filesTable
  1057. WHERE $filesTable.$columnLocalID IS NOT NULL
  1058. GROUP BY $columnDeviceFolder
  1059. ) latest_files
  1060. ON $filesTable.$columnDeviceFolder = latest_files.$columnDeviceFolder
  1061. AND $filesTable.$columnCreationTime = latest_files.max_creation_time;
  1062. ''',
  1063. );
  1064. final files = convertToFiles(rows);
  1065. // TODO: Do this de-duplication within the SQL Query
  1066. final folderMap = <String, File>{};
  1067. for (final file in files) {
  1068. if (folderMap.containsKey(file.deviceFolder)) {
  1069. if (folderMap[file.deviceFolder].updationTime < file.updationTime) {
  1070. continue;
  1071. }
  1072. }
  1073. folderMap[file.deviceFolder] = file;
  1074. }
  1075. return folderMap.values.toList();
  1076. }
  1077. Future<List<File>> getLatestCollectionFiles() async {
  1078. debugPrint("Fetching latestCollectionFiles from db");
  1079. String query;
  1080. if (FeatureFlagService.instance.isInternalUserOrDebugBuild()) {
  1081. query = '''
  1082. SELECT $filesTable.*
  1083. FROM $filesTable
  1084. INNER JOIN
  1085. (
  1086. SELECT $columnCollectionID, MAX($columnCreationTime) AS max_creation_time
  1087. FROM $filesTable
  1088. WHERE ($columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1 AND $columnMMdVisibility = $kVisibilityVisible)
  1089. GROUP BY $columnCollectionID
  1090. ) latest_files
  1091. ON $filesTable.$columnCollectionID = latest_files.$columnCollectionID
  1092. AND $filesTable.$columnCreationTime = latest_files.max_creation_time;
  1093. ''';
  1094. } else {
  1095. query = '''
  1096. SELECT $filesTable.*
  1097. FROM $filesTable
  1098. INNER JOIN
  1099. (
  1100. SELECT $columnCollectionID, MAX($columnCreationTime) AS max_creation_time
  1101. FROM $filesTable
  1102. WHERE ($columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1)
  1103. GROUP BY $columnCollectionID
  1104. ) latest_files
  1105. ON $filesTable.$columnCollectionID = latest_files.$columnCollectionID
  1106. AND $filesTable.$columnCreationTime = latest_files.max_creation_time;
  1107. ''';
  1108. }
  1109. final db = await instance.database;
  1110. final rows = await db.rawQuery(
  1111. query,
  1112. );
  1113. final files = convertToFiles(rows);
  1114. // TODO: Do this de-duplication within the SQL Query
  1115. final collectionMap = <int, File>{};
  1116. for (final file in files) {
  1117. if (collectionMap.containsKey(file.collectionID)) {
  1118. if (collectionMap[file.collectionID].updationTime < file.updationTime) {
  1119. continue;
  1120. }
  1121. }
  1122. collectionMap[file.collectionID] = file;
  1123. }
  1124. return collectionMap.values.toList();
  1125. }
  1126. Future<Map<String, int>> getFileCountInDeviceFolders() async {
  1127. final db = await instance.database;
  1128. final rows = await db.rawQuery(
  1129. '''
  1130. SELECT COUNT(DISTINCT($columnLocalID)) as count, $columnDeviceFolder
  1131. FROM $filesTable
  1132. WHERE $columnLocalID IS NOT NULL
  1133. GROUP BY $columnDeviceFolder
  1134. ''',
  1135. );
  1136. final result = <String, int>{};
  1137. for (final row in rows) {
  1138. result[row[columnDeviceFolder]] = row["count"];
  1139. }
  1140. return result;
  1141. }
  1142. Future<List<String>> getLocalFilesBackedUpWithoutLocation() async {
  1143. final db = await instance.database;
  1144. final rows = await db.query(
  1145. filesTable,
  1146. columns: [columnLocalID],
  1147. distinct: true,
  1148. where:
  1149. '$columnLocalID IS NOT NULL AND ($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1) '
  1150. 'AND ($columnLatitude IS NULL OR $columnLongitude IS NULL OR $columnLongitude = 0.0 or $columnLongitude = 0.0)',
  1151. );
  1152. final result = <String>[];
  1153. for (final row in rows) {
  1154. result.add(row[columnLocalID]);
  1155. }
  1156. return result;
  1157. }
  1158. Future<void> markForReUploadIfLocationMissing(List<String> localIDs) async {
  1159. if (localIDs.isEmpty) {
  1160. return;
  1161. }
  1162. String inParam = "";
  1163. for (final localID in localIDs) {
  1164. inParam += "'" + localID + "',";
  1165. }
  1166. inParam = inParam.substring(0, inParam.length - 1);
  1167. final db = await instance.database;
  1168. await db.rawUpdate(
  1169. '''
  1170. UPDATE $filesTable
  1171. SET $columnUpdationTime = NULL
  1172. WHERE $columnLocalID IN ($inParam)
  1173. AND ($columnLatitude IS NULL OR $columnLongitude IS NULL OR $columnLongitude = 0.0 or $columnLongitude = 0.0);
  1174. ''',
  1175. );
  1176. }
  1177. Future<bool> doesFileExistInCollection(
  1178. int uploadedFileID,
  1179. int collectionID,
  1180. ) async {
  1181. final db = await instance.database;
  1182. final rows = await db.query(
  1183. filesTable,
  1184. where: '$columnUploadedFileID = ? AND $columnCollectionID = ?',
  1185. whereArgs: [uploadedFileID, collectionID],
  1186. limit: 1,
  1187. );
  1188. return rows.isNotEmpty;
  1189. }
  1190. Future<Map<int, File>> getFilesFromIDs(List<int> ids) async {
  1191. final result = <int, File>{};
  1192. if (ids.isEmpty) {
  1193. return result;
  1194. }
  1195. String inParam = "";
  1196. for (final id in ids) {
  1197. inParam += "'" + id.toString() + "',";
  1198. }
  1199. inParam = inParam.substring(0, inParam.length - 1);
  1200. final db = await instance.database;
  1201. final results = await db.query(
  1202. filesTable,
  1203. where: '$columnUploadedFileID IN ($inParam)',
  1204. );
  1205. final files = convertToFiles(results);
  1206. for (final file in files) {
  1207. result[file.uploadedFileID] = file;
  1208. }
  1209. return result;
  1210. }
  1211. List<File> convertToFiles(List<Map<String, dynamic>> results) {
  1212. final List<File> files = [];
  1213. for (final result in results) {
  1214. files.add(_getFileFromRow(result));
  1215. }
  1216. return files;
  1217. }
  1218. Future<List<File>> getAllFilesFromDB() async {
  1219. final db = await instance.database;
  1220. final List<Map<String, dynamic>> result = await db.query(filesTable);
  1221. final List<File> files = convertToFiles(result);
  1222. final List<File> deduplicatedFiles =
  1223. _deduplicatedAndFilterIgnoredFiles(files, null);
  1224. return deduplicatedFiles;
  1225. }
  1226. Map<String, dynamic> _getRowForFile(File file) {
  1227. final row = <String, dynamic>{};
  1228. if (file.generatedID != null) {
  1229. row[columnGeneratedID] = file.generatedID;
  1230. }
  1231. row[columnLocalID] = file.localID;
  1232. row[columnUploadedFileID] = file.uploadedFileID ?? -1;
  1233. row[columnOwnerID] = file.ownerID;
  1234. row[columnCollectionID] = file.collectionID ?? -1;
  1235. row[columnTitle] = file.title;
  1236. row[columnDeviceFolder] = file.deviceFolder;
  1237. if (file.location != null) {
  1238. row[columnLatitude] = file.location.latitude;
  1239. row[columnLongitude] = file.location.longitude;
  1240. }
  1241. row[columnFileType] = getInt(file.fileType);
  1242. row[columnCreationTime] = file.creationTime;
  1243. row[columnModificationTime] = file.modificationTime;
  1244. row[columnUpdationTime] = file.updationTime;
  1245. row[columnEncryptedKey] = file.encryptedKey;
  1246. row[columnKeyDecryptionNonce] = file.keyDecryptionNonce;
  1247. row[columnFileDecryptionHeader] = file.fileDecryptionHeader;
  1248. row[columnThumbnailDecryptionHeader] = file.thumbnailDecryptionHeader;
  1249. row[columnMetadataDecryptionHeader] = file.metadataDecryptionHeader;
  1250. row[columnFileSubType] = file.fileSubType ?? -1;
  1251. row[columnDuration] = file.duration ?? 0;
  1252. row[columnExif] = file.exif;
  1253. row[columnHash] = file.hash;
  1254. row[columnMetadataVersion] = file.metadataVersion;
  1255. row[columnMMdVersion] = file.mMdVersion ?? 0;
  1256. row[columnMMdEncodedJson] = file.mMdEncodedJson ?? '{}';
  1257. row[columnMMdVisibility] =
  1258. file.magicMetadata?.visibility ?? kVisibilityVisible;
  1259. row[columnPubMMdVersion] = file.pubMmdVersion ?? 0;
  1260. row[columnPubMMdEncodedJson] = file.pubMmdEncodedJson ?? '{}';
  1261. if (file.pubMagicMetadata != null &&
  1262. file.pubMagicMetadata.editedTime != null) {
  1263. // override existing creationTime to avoid re-writing all queries related
  1264. // to loading the gallery
  1265. row[columnCreationTime] = file.pubMagicMetadata.editedTime;
  1266. }
  1267. return row;
  1268. }
  1269. Map<String, dynamic> _getRowForFileWithoutCollection(File file) {
  1270. final row = <String, dynamic>{};
  1271. row[columnLocalID] = file.localID;
  1272. row[columnUploadedFileID] = file.uploadedFileID ?? -1;
  1273. row[columnOwnerID] = file.ownerID;
  1274. row[columnTitle] = file.title;
  1275. row[columnDeviceFolder] = file.deviceFolder;
  1276. if (file.location != null) {
  1277. row[columnLatitude] = file.location.latitude;
  1278. row[columnLongitude] = file.location.longitude;
  1279. }
  1280. row[columnFileType] = getInt(file.fileType);
  1281. row[columnCreationTime] = file.creationTime;
  1282. row[columnModificationTime] = file.modificationTime;
  1283. row[columnUpdationTime] = file.updationTime;
  1284. row[columnFileDecryptionHeader] = file.fileDecryptionHeader;
  1285. row[columnThumbnailDecryptionHeader] = file.thumbnailDecryptionHeader;
  1286. row[columnMetadataDecryptionHeader] = file.metadataDecryptionHeader;
  1287. row[columnFileSubType] = file.fileSubType ?? -1;
  1288. row[columnDuration] = file.duration ?? 0;
  1289. row[columnExif] = file.exif;
  1290. row[columnHash] = file.hash;
  1291. row[columnMetadataVersion] = file.metadataVersion;
  1292. row[columnMMdVersion] = file.mMdVersion ?? 0;
  1293. row[columnMMdEncodedJson] = file.mMdEncodedJson ?? '{}';
  1294. row[columnMMdVisibility] =
  1295. file.magicMetadata?.visibility ?? kVisibilityVisible;
  1296. row[columnPubMMdVersion] = file.pubMmdVersion ?? 0;
  1297. row[columnPubMMdEncodedJson] = file.pubMmdEncodedJson ?? '{}';
  1298. if (file.pubMagicMetadata != null &&
  1299. file.pubMagicMetadata.editedTime != null) {
  1300. // override existing creationTime to avoid re-writing all queries related
  1301. // to loading the gallery
  1302. row[columnCreationTime] = file.pubMagicMetadata.editedTime;
  1303. }
  1304. return row;
  1305. }
  1306. File _getFileFromRow(Map<String, dynamic> row) {
  1307. final file = File();
  1308. file.generatedID = row[columnGeneratedID];
  1309. file.localID = row[columnLocalID];
  1310. file.uploadedFileID =
  1311. row[columnUploadedFileID] == -1 ? null : row[columnUploadedFileID];
  1312. file.ownerID = row[columnOwnerID];
  1313. file.collectionID =
  1314. row[columnCollectionID] == -1 ? null : row[columnCollectionID];
  1315. file.title = row[columnTitle];
  1316. file.deviceFolder = row[columnDeviceFolder];
  1317. if (row[columnLatitude] != null && row[columnLongitude] != null) {
  1318. file.location = Location(row[columnLatitude], row[columnLongitude]);
  1319. }
  1320. file.fileType = getFileType(row[columnFileType]);
  1321. file.creationTime = row[columnCreationTime];
  1322. file.modificationTime = row[columnModificationTime];
  1323. file.updationTime = row[columnUpdationTime] ?? -1;
  1324. file.encryptedKey = row[columnEncryptedKey];
  1325. file.keyDecryptionNonce = row[columnKeyDecryptionNonce];
  1326. file.fileDecryptionHeader = row[columnFileDecryptionHeader];
  1327. file.thumbnailDecryptionHeader = row[columnThumbnailDecryptionHeader];
  1328. file.metadataDecryptionHeader = row[columnMetadataDecryptionHeader];
  1329. file.fileSubType = row[columnFileSubType] ?? -1;
  1330. file.duration = row[columnDuration] ?? 0;
  1331. file.exif = row[columnExif];
  1332. file.hash = row[columnHash];
  1333. file.metadataVersion = row[columnMetadataVersion] ?? 0;
  1334. file.mMdVersion = row[columnMMdVersion] ?? 0;
  1335. file.mMdEncodedJson = row[columnMMdEncodedJson] ?? '{}';
  1336. file.pubMmdVersion = row[columnPubMMdVersion] ?? 0;
  1337. file.pubMmdEncodedJson = row[columnPubMMdEncodedJson] ?? '{}';
  1338. return file;
  1339. }
  1340. }