files_db.dart 44 KB

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