files_db.dart 43 KB

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