files_db.dart 41 KB

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