files_db.dart 45 KB

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