files_db.dart 45 KB

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