files_db.dart 41 KB

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