files_db.dart 48 KB

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