files_db.dart 47 KB

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