files_db.dart 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457
  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/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 io.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> getAllPendingOrUploadedFiles(
  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 IS NULL OR $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. List<File> deduplicateByLocalID(List<File> files) {
  493. final localIDs = <String>{};
  494. final List<File> deduplicatedFiles = [];
  495. for (final file in files) {
  496. final id = file.localID;
  497. if (id != null && localIDs.contains(id)) {
  498. continue;
  499. }
  500. localIDs.add(id);
  501. deduplicatedFiles.add(file);
  502. }
  503. return deduplicatedFiles;
  504. }
  505. List<File> _deduplicatedAndFilterIgnoredFiles(
  506. List<File> files,
  507. Set<int> ignoredCollectionIDs,
  508. ) {
  509. final uploadedFileIDs = <int>{};
  510. final List<File> deduplicatedFiles = [];
  511. for (final file in files) {
  512. final id = file.uploadedFileID;
  513. if (ignoredCollectionIDs != null &&
  514. ignoredCollectionIDs.contains(file.collectionID)) {
  515. continue;
  516. }
  517. if (id != null && id != -1 && uploadedFileIDs.contains(id)) {
  518. continue;
  519. }
  520. uploadedFileIDs.add(id);
  521. deduplicatedFiles.add(file);
  522. }
  523. return deduplicatedFiles;
  524. }
  525. Future<FileLoadResult> getFilesInCollection(
  526. int collectionID,
  527. int startTime,
  528. int endTime, {
  529. int limit,
  530. bool asc,
  531. int visibility = kVisibilityVisible,
  532. }) async {
  533. final db = await instance.database;
  534. final order = (asc ?? false ? 'ASC' : 'DESC');
  535. String whereClause;
  536. List<Object> whereArgs;
  537. if (FeatureFlagService.instance.isInternalUserOrDebugBuild()) {
  538. whereClause =
  539. '$columnCollectionID = ? AND $columnCreationTime >= ? AND $columnCreationTime <= ? AND $columnMMdVisibility = ?';
  540. whereArgs = [collectionID, startTime, endTime, visibility];
  541. } else {
  542. whereClause =
  543. '$columnCollectionID = ? AND $columnCreationTime >= ? AND $columnCreationTime <= ?';
  544. whereArgs = [collectionID, startTime, endTime];
  545. }
  546. final results = await db.query(
  547. filesTable,
  548. where: whereClause,
  549. whereArgs: whereArgs,
  550. orderBy:
  551. '$columnCreationTime ' + order + ', $columnModificationTime ' + order,
  552. limit: limit,
  553. );
  554. final files = convertToFiles(results);
  555. _logger.info("Fetched " + files.length.toString() + " files");
  556. return FileLoadResult(files, files.length == limit);
  557. }
  558. Future<List<File>> getFilesCreatedWithinDurations(
  559. List<List<int>> durations,
  560. Set<int> ignoredCollectionIDs, {
  561. String order = 'ASC',
  562. }) async {
  563. if (durations.isEmpty) {
  564. return <File>[];
  565. }
  566. final db = await instance.database;
  567. String whereClause = "( ";
  568. for (int index = 0; index < durations.length; index++) {
  569. whereClause += "($columnCreationTime > " +
  570. durations[index][0].toString() +
  571. " AND $columnCreationTime < " +
  572. durations[index][1].toString() +
  573. ")";
  574. if (index != durations.length - 1) {
  575. whereClause += " OR ";
  576. }
  577. }
  578. whereClause += ") AND $columnMMdVisibility = $kVisibilityVisible";
  579. final results = await db.query(
  580. filesTable,
  581. where: whereClause,
  582. orderBy: '$columnCreationTime ' + order,
  583. );
  584. final files = convertToFiles(results);
  585. return _deduplicatedAndFilterIgnoredFiles(files, ignoredCollectionIDs);
  586. }
  587. // Files which user added to a collection manually but they are not
  588. // uploaded yet or files belonging to a collection which is marked for backup
  589. Future<List<File>> getFilesPendingForUpload() async {
  590. final db = await instance.database;
  591. final results = await db.query(
  592. filesTable,
  593. where:
  594. '($columnUploadedFileID IS NULL OR $columnUploadedFileID IS -1) AND '
  595. '$columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1 AND '
  596. '$columnLocalID IS NOT NULL AND $columnLocalID IS NOT -1',
  597. orderBy: '$columnCreationTime DESC',
  598. groupBy: columnLocalID,
  599. );
  600. final files = convertToFiles(results);
  601. // future-safe filter just to ensure that the query doesn't end up returning files
  602. // which should not be backed up
  603. files.removeWhere(
  604. (e) =>
  605. e.collectionID == null ||
  606. e.localID == null ||
  607. e.uploadedFileID != null,
  608. );
  609. return files;
  610. }
  611. Future<List<File>> getUnUploadedLocalFiles() async {
  612. final db = await instance.database;
  613. final results = await db.query(
  614. filesTable,
  615. where:
  616. '($columnUploadedFileID IS NULL OR $columnUploadedFileID IS -1) AND $columnLocalID IS NOT NULL',
  617. orderBy: '$columnCreationTime DESC',
  618. groupBy: columnLocalID,
  619. );
  620. return convertToFiles(results);
  621. }
  622. Future<List<int>> getUploadedFileIDsToBeUpdated(int ownerID) async {
  623. final db = await instance.database;
  624. final rows = await db.query(
  625. filesTable,
  626. columns: [columnUploadedFileID],
  627. where: '($columnLocalID IS NOT NULL AND $columnOwnerID = ? AND '
  628. '($columnUploadedFileID '
  629. 'IS NOT '
  630. 'NULL AND $columnUploadedFileID IS NOT -1) AND $columnUpdationTime IS NULL)',
  631. whereArgs: [ownerID],
  632. orderBy: '$columnCreationTime DESC',
  633. distinct: true,
  634. );
  635. final uploadedFileIDs = <int>[];
  636. for (final row in rows) {
  637. uploadedFileIDs.add(row[columnUploadedFileID]);
  638. }
  639. return uploadedFileIDs;
  640. }
  641. Future<File> getUploadedFileInAnyCollection(int uploadedFileID) async {
  642. final db = await instance.database;
  643. final results = await db.query(
  644. filesTable,
  645. where: '$columnUploadedFileID = ?',
  646. whereArgs: [
  647. uploadedFileID,
  648. ],
  649. limit: 1,
  650. );
  651. if (results.isEmpty) {
  652. return null;
  653. }
  654. return convertToFiles(results)[0];
  655. }
  656. Future<Set<String>> getExistingLocalFileIDs() async {
  657. final db = await instance.database;
  658. final rows = await db.query(
  659. filesTable,
  660. columns: [columnLocalID],
  661. distinct: true,
  662. where: '$columnLocalID IS NOT NULL',
  663. );
  664. final result = <String>{};
  665. for (final row in rows) {
  666. result.add(row[columnLocalID]);
  667. }
  668. return result;
  669. }
  670. Future<Set<String>> getLocalIDsMarkedForOrAlreadyUploaded(int ownerID) async {
  671. final db = await instance.database;
  672. final rows = await db.query(
  673. filesTable,
  674. columns: [columnLocalID],
  675. distinct: true,
  676. where: '$columnLocalID IS NOT NULL AND ($columnCollectionID IS NOT NULL '
  677. 'AND '
  678. '$columnCollectionID != -1) AND ($columnOwnerID = ? OR '
  679. '$columnOwnerID IS NULL)',
  680. whereArgs: [ownerID],
  681. );
  682. final result = <String>{};
  683. for (final row in rows) {
  684. result.add(row[columnLocalID]);
  685. }
  686. return result;
  687. }
  688. Future<Set<String>> getLocalFileIDsForCollection(int collectionID) async {
  689. final db = await instance.database;
  690. final rows = await db.query(
  691. filesTable,
  692. columns: [columnLocalID],
  693. where: '$columnLocalID IS NOT NULL AND $columnCollectionID = ?',
  694. whereArgs: [collectionID],
  695. );
  696. final result = <String>{};
  697. for (final row in rows) {
  698. result.add(row[columnLocalID]);
  699. }
  700. return result;
  701. }
  702. // Sets the collectionID for the files with given LocalIDs if the
  703. // corresponding file entries are not already mapped to some other collection
  704. Future<int> setCollectionIDForUnMappedLocalFiles(
  705. int collectionID,
  706. Set<String> localIDs,
  707. ) async {
  708. final db = await instance.database;
  709. String inParam = "";
  710. for (final localID in localIDs) {
  711. inParam += "'" + localID + "',";
  712. }
  713. inParam = inParam.substring(0, inParam.length - 1);
  714. return await db.rawUpdate(
  715. '''
  716. UPDATE $filesTable
  717. SET $columnCollectionID = $collectionID
  718. WHERE $columnLocalID IN ($inParam) AND ($columnCollectionID IS NULL OR
  719. $columnCollectionID = -1);
  720. ''',
  721. );
  722. }
  723. Future<int> getNumberOfUploadedFiles() async {
  724. final db = await instance.database;
  725. final rows = await db.query(
  726. filesTable,
  727. columns: [columnUploadedFileID],
  728. where:
  729. '($columnLocalID IS NOT NULL AND ($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1) AND $columnUpdationTime IS NOT NULL)',
  730. distinct: true,
  731. );
  732. return rows.length;
  733. }
  734. Future<int> updateUploadedFile(
  735. String localID,
  736. String title,
  737. Location location,
  738. int creationTime,
  739. int modificationTime,
  740. int updationTime,
  741. ) async {
  742. final db = await instance.database;
  743. return await db.update(
  744. filesTable,
  745. {
  746. columnTitle: title,
  747. columnLatitude: location.latitude,
  748. columnLongitude: location.longitude,
  749. columnCreationTime: creationTime,
  750. columnModificationTime: modificationTime,
  751. columnUpdationTime: updationTime,
  752. },
  753. where: '$columnLocalID = ?',
  754. whereArgs: [localID],
  755. );
  756. }
  757. /*
  758. This method should only return localIDs which are not uploaded yet
  759. and can be mapped to incoming remote entry
  760. */
  761. Future<List<File>> getUnlinkedLocalMatchesForRemoteFile(
  762. int ownerID,
  763. String localID,
  764. FileType fileType, {
  765. @required String title,
  766. @required String deviceFolder,
  767. }) async {
  768. final db = await instance.database;
  769. // on iOS, match using localID and fileType. title can either match or
  770. // might be null based on how the file was imported
  771. String whereClause = ''' ($columnOwnerID = ? OR $columnOwnerID IS NULL) AND
  772. $columnLocalID = ? AND $columnFileType = ? AND
  773. ($columnTitle=? OR $columnTitle IS NULL) ''';
  774. List<Object> whereArgs = [
  775. ownerID,
  776. localID,
  777. getInt(fileType),
  778. title,
  779. ];
  780. if (io.Platform.isAndroid) {
  781. whereClause = ''' ($columnOwnerID = ? OR $columnOwnerID IS NULL) AND
  782. $columnLocalID = ? AND $columnFileType = ? AND $columnTitle=? AND $columnDeviceFolder= ?
  783. ''';
  784. whereArgs = [
  785. ownerID,
  786. localID,
  787. getInt(fileType),
  788. title,
  789. deviceFolder,
  790. ];
  791. }
  792. final rows = await db.query(
  793. filesTable,
  794. where: whereClause,
  795. whereArgs: whereArgs,
  796. );
  797. return convertToFiles(rows);
  798. }
  799. Future<List<File>> getMatchingFiles(
  800. String localID,
  801. FileType fileType,
  802. String title,
  803. String deviceFolder,
  804. ) async {
  805. final db = await instance.database;
  806. final rows = await db.query(
  807. filesTable,
  808. where: '''$columnTitle=? AND $columnDeviceFolder=?''',
  809. whereArgs: [
  810. title,
  811. deviceFolder,
  812. ],
  813. );
  814. if (rows.isNotEmpty) {
  815. return convertToFiles(rows);
  816. } else {
  817. return null;
  818. }
  819. }
  820. Future<List<File>> getUploadedFilesWithHashes(
  821. FileHashData hashData,
  822. FileType fileType,
  823. int ownerID,
  824. ) async {
  825. String inParam = "'${hashData.fileHash}'";
  826. if (fileType == FileType.livePhoto && hashData.zipHash != null) {
  827. inParam += ",'${hashData.zipHash}'";
  828. }
  829. final db = await instance.database;
  830. final rows = await db.query(
  831. filesTable,
  832. where: '($columnUploadedFileID != NULL OR $columnUploadedFileID != -1) '
  833. 'AND $columnOwnerID = ? AND $columnFileType ='
  834. ' ? '
  835. 'AND $columnHash IN ($inParam)',
  836. whereArgs: [
  837. ownerID,
  838. getInt(fileType),
  839. ],
  840. );
  841. return convertToFiles(rows);
  842. }
  843. Future<int> update(File file) async {
  844. final db = await instance.database;
  845. return await db.update(
  846. filesTable,
  847. _getRowForFile(file),
  848. where: '$columnGeneratedID = ?',
  849. whereArgs: [file.generatedID],
  850. );
  851. }
  852. Future<int> updateUploadedFileAcrossCollections(File file) async {
  853. final db = await instance.database;
  854. return await db.update(
  855. filesTable,
  856. _getRowForFileWithoutCollection(file),
  857. where: '$columnUploadedFileID = ?',
  858. whereArgs: [file.uploadedFileID],
  859. );
  860. }
  861. Future<int> updateLocalIDForUploaded(int uploadedID, String localID) async {
  862. final db = await instance.database;
  863. return await db.update(
  864. filesTable,
  865. {columnLocalID: localID},
  866. where: '$columnUploadedFileID = ? AND $columnLocalID IS NULL',
  867. whereArgs: [uploadedID],
  868. );
  869. }
  870. Future<int> delete(int uploadedFileID) async {
  871. final db = await instance.database;
  872. return db.delete(
  873. filesTable,
  874. where: '$columnUploadedFileID =?',
  875. whereArgs: [uploadedFileID],
  876. );
  877. }
  878. Future<int> deleteByGeneratedID(int genID) async {
  879. final db = await instance.database;
  880. return db.delete(
  881. filesTable,
  882. where: '$columnGeneratedID =?',
  883. whereArgs: [genID],
  884. );
  885. }
  886. Future<int> deleteMultipleUploadedFiles(List<int> uploadedFileIDs) async {
  887. final db = await instance.database;
  888. return await db.delete(
  889. filesTable,
  890. where: '$columnUploadedFileID IN (${uploadedFileIDs.join(', ')})',
  891. );
  892. }
  893. Future<int> deleteMultipleByGeneratedIDs(List<int> generatedIDs) async {
  894. if (generatedIDs.isEmpty) {
  895. return 0;
  896. }
  897. final db = await instance.database;
  898. return await db.delete(
  899. filesTable,
  900. where: '$columnGeneratedID IN (${generatedIDs.join(', ')})',
  901. );
  902. }
  903. Future<int> deleteLocalFile(File file) async {
  904. final db = await instance.database;
  905. if (file.localID != null) {
  906. // delete all files with same local ID
  907. return db.delete(
  908. filesTable,
  909. where: '$columnLocalID =?',
  910. whereArgs: [file.localID],
  911. );
  912. } else {
  913. return db.delete(
  914. filesTable,
  915. where: '$columnGeneratedID =?',
  916. whereArgs: [file.generatedID],
  917. );
  918. }
  919. }
  920. Future<void> deleteLocalFiles(List<String> localIDs) async {
  921. String inParam = "";
  922. for (final localID in localIDs) {
  923. inParam += "'" + localID + "',";
  924. }
  925. inParam = inParam.substring(0, inParam.length - 1);
  926. final db = await instance.database;
  927. await db.rawQuery(
  928. '''
  929. UPDATE $filesTable
  930. SET $columnLocalID = NULL
  931. WHERE $columnLocalID IN ($inParam);
  932. ''',
  933. );
  934. }
  935. Future<List<File>> getLocalFiles(List<String> localIDs) async {
  936. String inParam = "";
  937. for (final localID in localIDs) {
  938. inParam += "'" + localID + "',";
  939. }
  940. inParam = inParam.substring(0, inParam.length - 1);
  941. final db = await instance.database;
  942. final results = await db.query(
  943. filesTable,
  944. where: '$columnLocalID IN ($inParam)',
  945. );
  946. return convertToFiles(results);
  947. }
  948. Future<int> deleteUnSyncedLocalFiles(List<String> localIDs) async {
  949. String inParam = "";
  950. for (final localID in localIDs) {
  951. inParam += "'" + localID + "',";
  952. }
  953. inParam = inParam.substring(0, inParam.length - 1);
  954. final db = await instance.database;
  955. return db.delete(
  956. filesTable,
  957. where:
  958. '($columnUploadedFileID is NULL OR $columnUploadedFileID = -1 ) AND $columnLocalID IN ($inParam)',
  959. );
  960. }
  961. Future<int> deleteFromCollection(int uploadedFileID, int collectionID) async {
  962. final db = await instance.database;
  963. return db.delete(
  964. filesTable,
  965. where: '$columnUploadedFileID = ? AND $columnCollectionID = ?',
  966. whereArgs: [uploadedFileID, collectionID],
  967. );
  968. }
  969. Future<int> deleteFilesFromCollection(
  970. int collectionID,
  971. List<int> uploadedFileIDs,
  972. ) async {
  973. final db = await instance.database;
  974. return db.delete(
  975. filesTable,
  976. where:
  977. '$columnCollectionID = ? AND $columnUploadedFileID IN (${uploadedFileIDs.join(', ')})',
  978. whereArgs: [collectionID],
  979. );
  980. }
  981. Future<int> collectionFileCount(int collectionID) async {
  982. final db = await instance.database;
  983. final count = Sqflite.firstIntValue(
  984. await db.rawQuery(
  985. 'SELECT COUNT(*) FROM $filesTable where $columnCollectionID = $collectionID',
  986. ),
  987. );
  988. return count;
  989. }
  990. Future<int> fileCountWithVisibility(int visibility, int ownerID) async {
  991. final db = await instance.database;
  992. final count = Sqflite.firstIntValue(
  993. await db.rawQuery(
  994. 'SELECT COUNT(*) FROM $filesTable where $columnMMdVisibility = $visibility AND $columnOwnerID = $ownerID',
  995. ),
  996. );
  997. return count;
  998. }
  999. Future<int> deleteCollection(int collectionID) async {
  1000. final db = await instance.database;
  1001. return db.delete(
  1002. filesTable,
  1003. where: '$columnCollectionID = ?',
  1004. whereArgs: [collectionID],
  1005. );
  1006. }
  1007. Future<int> removeFromCollection(int collectionID, List<int> fileIDs) async {
  1008. final db = await instance.database;
  1009. return db.delete(
  1010. filesTable,
  1011. where:
  1012. '$columnCollectionID =? AND $columnUploadedFileID IN (${fileIDs.join(', ')})',
  1013. whereArgs: [collectionID],
  1014. );
  1015. }
  1016. Future<List<File>> getPendingUploadForCollection(int collectionID) async {
  1017. final db = await instance.database;
  1018. final results = await db.query(
  1019. filesTable,
  1020. where: '$columnCollectionID = ? AND ($columnUploadedFileID IS NULL OR '
  1021. '$columnUploadedFileID = -1)',
  1022. whereArgs: [collectionID],
  1023. );
  1024. return convertToFiles(results);
  1025. }
  1026. Future<Set<String>> getLocalIDsPresentInEntries(
  1027. List<File> existingFiles,
  1028. int collectionID,
  1029. ) async {
  1030. String inParam = "";
  1031. for (final existingFile in existingFiles) {
  1032. inParam += "'" + existingFile.localID + "',";
  1033. }
  1034. inParam = inParam.substring(0, inParam.length - 1);
  1035. final db = await instance.database;
  1036. final rows = await db.rawQuery(
  1037. '''
  1038. SELECT $columnLocalID
  1039. FROM $filesTable
  1040. WHERE $columnLocalID IN ($inParam) AND $columnCollectionID !=
  1041. $collectionID AND $columnLocalID IS NOT NULL;
  1042. ''',
  1043. );
  1044. final result = <String>{};
  1045. for (final row in rows) {
  1046. result.add(row[columnLocalID]);
  1047. }
  1048. return result;
  1049. }
  1050. Future<List<File>> getLatestLocalFiles() async {
  1051. final db = await instance.database;
  1052. final rows = await db.rawQuery(
  1053. '''
  1054. SELECT $filesTable.*
  1055. FROM $filesTable
  1056. INNER JOIN
  1057. (
  1058. SELECT $columnDeviceFolder, MAX($columnCreationTime) AS max_creation_time
  1059. FROM $filesTable
  1060. WHERE $filesTable.$columnLocalID IS NOT NULL
  1061. GROUP BY $columnDeviceFolder
  1062. ) latest_files
  1063. ON $filesTable.$columnDeviceFolder = latest_files.$columnDeviceFolder
  1064. AND $filesTable.$columnCreationTime = latest_files.max_creation_time;
  1065. ''',
  1066. );
  1067. final files = convertToFiles(rows);
  1068. // TODO: Do this de-duplication within the SQL Query
  1069. final folderMap = <String, File>{};
  1070. for (final file in files) {
  1071. if (folderMap.containsKey(file.deviceFolder)) {
  1072. if (folderMap[file.deviceFolder].updationTime < file.updationTime) {
  1073. continue;
  1074. }
  1075. }
  1076. folderMap[file.deviceFolder] = file;
  1077. }
  1078. return folderMap.values.toList();
  1079. }
  1080. Future<List<File>> getLatestCollectionFiles() async {
  1081. debugPrint("Fetching latestCollectionFiles from db");
  1082. String query;
  1083. if (FeatureFlagService.instance.isInternalUserOrDebugBuild()) {
  1084. query = '''
  1085. SELECT $filesTable.*
  1086. FROM $filesTable
  1087. INNER JOIN
  1088. (
  1089. SELECT $columnCollectionID, MAX($columnCreationTime) AS max_creation_time
  1090. FROM $filesTable
  1091. WHERE ($columnCollectionID IS NOT NULL AND $columnCollectionID IS
  1092. NOT -1 AND $columnMMdVisibility = $kVisibilityVisible AND
  1093. $columnUploadedFileID IS NOT -1)
  1094. GROUP BY $columnCollectionID
  1095. ) latest_files
  1096. ON $filesTable.$columnCollectionID = latest_files.$columnCollectionID
  1097. AND $filesTable.$columnCreationTime = latest_files.max_creation_time;
  1098. ''';
  1099. } else {
  1100. query = '''
  1101. SELECT $filesTable.*
  1102. FROM $filesTable
  1103. INNER JOIN
  1104. (
  1105. SELECT $columnCollectionID, MAX($columnCreationTime) AS max_creation_time
  1106. FROM $filesTable
  1107. WHERE ($columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1)
  1108. GROUP BY $columnCollectionID
  1109. ) latest_files
  1110. ON $filesTable.$columnCollectionID = latest_files.$columnCollectionID
  1111. AND $filesTable.$columnCreationTime = latest_files.max_creation_time;
  1112. ''';
  1113. }
  1114. final db = await instance.database;
  1115. final rows = await db.rawQuery(
  1116. query,
  1117. );
  1118. final files = convertToFiles(rows);
  1119. // TODO: Do this de-duplication within the SQL Query
  1120. final collectionMap = <int, File>{};
  1121. for (final file in files) {
  1122. if (collectionMap.containsKey(file.collectionID)) {
  1123. if (collectionMap[file.collectionID].updationTime < file.updationTime) {
  1124. continue;
  1125. }
  1126. }
  1127. collectionMap[file.collectionID] = file;
  1128. }
  1129. return collectionMap.values.toList();
  1130. }
  1131. Future<Map<String, int>> getFileCountInDeviceFolders() async {
  1132. final db = await instance.database;
  1133. final rows = await db.rawQuery(
  1134. '''
  1135. SELECT COUNT(DISTINCT($columnLocalID)) as count, $columnDeviceFolder
  1136. FROM $filesTable
  1137. WHERE $columnLocalID IS NOT NULL
  1138. GROUP BY $columnDeviceFolder
  1139. ''',
  1140. );
  1141. final result = <String, int>{};
  1142. for (final row in rows) {
  1143. result[row[columnDeviceFolder]] = row["count"];
  1144. }
  1145. return result;
  1146. }
  1147. Future<List<String>> getLocalFilesBackedUpWithoutLocation() async {
  1148. final db = await instance.database;
  1149. final rows = await db.query(
  1150. filesTable,
  1151. columns: [columnLocalID],
  1152. distinct: true,
  1153. where:
  1154. '$columnLocalID IS NOT NULL AND ($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1) '
  1155. 'AND ($columnLatitude IS NULL OR $columnLongitude IS NULL OR $columnLongitude = 0.0 or $columnLongitude = 0.0)',
  1156. );
  1157. final result = <String>[];
  1158. for (final row in rows) {
  1159. result.add(row[columnLocalID]);
  1160. }
  1161. return result;
  1162. }
  1163. Future<void> markForReUploadIfLocationMissing(List<String> localIDs) async {
  1164. if (localIDs.isEmpty) {
  1165. return;
  1166. }
  1167. String inParam = "";
  1168. for (final localID in localIDs) {
  1169. inParam += "'" + localID + "',";
  1170. }
  1171. inParam = inParam.substring(0, inParam.length - 1);
  1172. final db = await instance.database;
  1173. await db.rawUpdate(
  1174. '''
  1175. UPDATE $filesTable
  1176. SET $columnUpdationTime = NULL
  1177. WHERE $columnLocalID IN ($inParam)
  1178. AND ($columnLatitude IS NULL OR $columnLongitude IS NULL OR $columnLongitude = 0.0 or $columnLongitude = 0.0);
  1179. ''',
  1180. );
  1181. }
  1182. Future<bool> doesFileExistInCollection(
  1183. int uploadedFileID,
  1184. int collectionID,
  1185. ) async {
  1186. final db = await instance.database;
  1187. final rows = await db.query(
  1188. filesTable,
  1189. where: '$columnUploadedFileID = ? AND $columnCollectionID = ?',
  1190. whereArgs: [uploadedFileID, collectionID],
  1191. limit: 1,
  1192. );
  1193. return rows.isNotEmpty;
  1194. }
  1195. Future<Map<int, File>> getFilesFromIDs(List<int> ids) async {
  1196. final result = <int, File>{};
  1197. if (ids.isEmpty) {
  1198. return result;
  1199. }
  1200. String inParam = "";
  1201. for (final id in ids) {
  1202. inParam += "'" + id.toString() + "',";
  1203. }
  1204. inParam = inParam.substring(0, inParam.length - 1);
  1205. final db = await instance.database;
  1206. final results = await db.query(
  1207. filesTable,
  1208. where: '$columnUploadedFileID IN ($inParam)',
  1209. );
  1210. final files = convertToFiles(results);
  1211. for (final file in files) {
  1212. result[file.uploadedFileID] = file;
  1213. }
  1214. return result;
  1215. }
  1216. Future<Set<int>> getAllCollectionIDsOfFile(
  1217. int uploadedFileID,
  1218. ) async {
  1219. final db = await instance.database;
  1220. final results = await db.query(
  1221. filesTable,
  1222. where: '$columnUploadedFileID = ? AND $columnCollectionID != -1',
  1223. columns: [columnCollectionID],
  1224. whereArgs: [uploadedFileID],
  1225. distinct: true,
  1226. );
  1227. final collectionIDsOfFile = <int>{};
  1228. for (var result in results) {
  1229. collectionIDsOfFile.add(result['collection_id']);
  1230. }
  1231. return collectionIDsOfFile;
  1232. }
  1233. List<File> convertToFiles(List<Map<String, dynamic>> results) {
  1234. final List<File> files = [];
  1235. for (final result in results) {
  1236. files.add(_getFileFromRow(result));
  1237. }
  1238. return files;
  1239. }
  1240. Future<List<File>> getAllFilesFromDB() async {
  1241. final db = await instance.database;
  1242. final List<Map<String, dynamic>> result = await db.query(filesTable);
  1243. final List<File> files = convertToFiles(result);
  1244. final List<File> deduplicatedFiles =
  1245. _deduplicatedAndFilterIgnoredFiles(files, null);
  1246. return deduplicatedFiles;
  1247. }
  1248. Map<String, dynamic> _getRowForFile(File file) {
  1249. final row = <String, dynamic>{};
  1250. if (file.generatedID != null) {
  1251. row[columnGeneratedID] = file.generatedID;
  1252. }
  1253. row[columnLocalID] = file.localID;
  1254. row[columnUploadedFileID] = file.uploadedFileID ?? -1;
  1255. row[columnOwnerID] = file.ownerID;
  1256. row[columnCollectionID] = file.collectionID ?? -1;
  1257. row[columnTitle] = file.title;
  1258. row[columnDeviceFolder] = file.deviceFolder;
  1259. if (file.location != null) {
  1260. row[columnLatitude] = file.location.latitude;
  1261. row[columnLongitude] = file.location.longitude;
  1262. }
  1263. row[columnFileType] = getInt(file.fileType);
  1264. row[columnCreationTime] = file.creationTime;
  1265. row[columnModificationTime] = file.modificationTime;
  1266. row[columnUpdationTime] = file.updationTime;
  1267. row[columnEncryptedKey] = file.encryptedKey;
  1268. row[columnKeyDecryptionNonce] = file.keyDecryptionNonce;
  1269. row[columnFileDecryptionHeader] = file.fileDecryptionHeader;
  1270. row[columnThumbnailDecryptionHeader] = file.thumbnailDecryptionHeader;
  1271. row[columnMetadataDecryptionHeader] = file.metadataDecryptionHeader;
  1272. row[columnFileSubType] = file.fileSubType ?? -1;
  1273. row[columnDuration] = file.duration ?? 0;
  1274. row[columnExif] = file.exif;
  1275. row[columnHash] = file.hash;
  1276. row[columnMetadataVersion] = file.metadataVersion;
  1277. row[columnMMdVersion] = file.mMdVersion ?? 0;
  1278. row[columnMMdEncodedJson] = file.mMdEncodedJson ?? '{}';
  1279. row[columnMMdVisibility] =
  1280. file.magicMetadata?.visibility ?? kVisibilityVisible;
  1281. row[columnPubMMdVersion] = file.pubMmdVersion ?? 0;
  1282. row[columnPubMMdEncodedJson] = file.pubMmdEncodedJson ?? '{}';
  1283. if (file.pubMagicMetadata != null &&
  1284. file.pubMagicMetadata.editedTime != null) {
  1285. // override existing creationTime to avoid re-writing all queries related
  1286. // to loading the gallery
  1287. row[columnCreationTime] = file.pubMagicMetadata.editedTime;
  1288. }
  1289. return row;
  1290. }
  1291. Map<String, dynamic> _getRowForFileWithoutCollection(File file) {
  1292. final row = <String, dynamic>{};
  1293. row[columnLocalID] = file.localID;
  1294. row[columnUploadedFileID] = file.uploadedFileID ?? -1;
  1295. row[columnOwnerID] = file.ownerID;
  1296. row[columnTitle] = file.title;
  1297. row[columnDeviceFolder] = file.deviceFolder;
  1298. if (file.location != null) {
  1299. row[columnLatitude] = file.location.latitude;
  1300. row[columnLongitude] = file.location.longitude;
  1301. }
  1302. row[columnFileType] = getInt(file.fileType);
  1303. row[columnCreationTime] = file.creationTime;
  1304. row[columnModificationTime] = file.modificationTime;
  1305. row[columnUpdationTime] = file.updationTime;
  1306. row[columnFileDecryptionHeader] = file.fileDecryptionHeader;
  1307. row[columnThumbnailDecryptionHeader] = file.thumbnailDecryptionHeader;
  1308. row[columnMetadataDecryptionHeader] = file.metadataDecryptionHeader;
  1309. row[columnFileSubType] = file.fileSubType ?? -1;
  1310. row[columnDuration] = file.duration ?? 0;
  1311. row[columnExif] = file.exif;
  1312. row[columnHash] = file.hash;
  1313. row[columnMetadataVersion] = file.metadataVersion;
  1314. row[columnMMdVersion] = file.mMdVersion ?? 0;
  1315. row[columnMMdEncodedJson] = file.mMdEncodedJson ?? '{}';
  1316. row[columnMMdVisibility] =
  1317. file.magicMetadata?.visibility ?? kVisibilityVisible;
  1318. row[columnPubMMdVersion] = file.pubMmdVersion ?? 0;
  1319. row[columnPubMMdEncodedJson] = file.pubMmdEncodedJson ?? '{}';
  1320. if (file.pubMagicMetadata != null &&
  1321. file.pubMagicMetadata.editedTime != null) {
  1322. // override existing creationTime to avoid re-writing all queries related
  1323. // to loading the gallery
  1324. row[columnCreationTime] = file.pubMagicMetadata.editedTime;
  1325. }
  1326. return row;
  1327. }
  1328. File _getFileFromRow(Map<String, dynamic> row) {
  1329. final file = File();
  1330. file.generatedID = row[columnGeneratedID];
  1331. file.localID = row[columnLocalID];
  1332. file.uploadedFileID =
  1333. row[columnUploadedFileID] == -1 ? null : row[columnUploadedFileID];
  1334. file.ownerID = row[columnOwnerID];
  1335. file.collectionID =
  1336. row[columnCollectionID] == -1 ? null : row[columnCollectionID];
  1337. file.title = row[columnTitle];
  1338. file.deviceFolder = row[columnDeviceFolder];
  1339. if (row[columnLatitude] != null && row[columnLongitude] != null) {
  1340. file.location = Location(row[columnLatitude], row[columnLongitude]);
  1341. }
  1342. file.fileType = getFileType(row[columnFileType]);
  1343. file.creationTime = row[columnCreationTime];
  1344. file.modificationTime = row[columnModificationTime];
  1345. file.updationTime = row[columnUpdationTime] ?? -1;
  1346. file.encryptedKey = row[columnEncryptedKey];
  1347. file.keyDecryptionNonce = row[columnKeyDecryptionNonce];
  1348. file.fileDecryptionHeader = row[columnFileDecryptionHeader];
  1349. file.thumbnailDecryptionHeader = row[columnThumbnailDecryptionHeader];
  1350. file.metadataDecryptionHeader = row[columnMetadataDecryptionHeader];
  1351. file.fileSubType = row[columnFileSubType] ?? -1;
  1352. file.duration = row[columnDuration] ?? 0;
  1353. file.exif = row[columnExif];
  1354. file.hash = row[columnHash];
  1355. file.metadataVersion = row[columnMetadataVersion] ?? 0;
  1356. file.mMdVersion = row[columnMMdVersion] ?? 0;
  1357. file.mMdEncodedJson = row[columnMMdEncodedJson] ?? '{}';
  1358. file.pubMmdVersion = row[columnPubMMdVersion] ?? 0;
  1359. file.pubMmdEncodedJson = row[columnPubMMdEncodedJson] ?? '{}';
  1360. return file;
  1361. }
  1362. }