files_service.dart 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import 'package:dio/dio.dart';
  2. import 'package:logging/logging.dart';
  3. import 'package:path/path.dart';
  4. import 'package:photos/core/configuration.dart';
  5. import 'package:photos/core/network/network.dart';
  6. import 'package:photos/db/files_db.dart';
  7. import 'package:photos/extensions/list.dart';
  8. import 'package:photos/models/file/file.dart';
  9. import "package:photos/models/file_load_result.dart";
  10. import "package:photos/models/metadata/file_magic.dart";
  11. import 'package:photos/services/file_magic_service.dart';
  12. import "package:photos/services/ignored_files_service.dart";
  13. import 'package:photos/utils/date_time_util.dart';
  14. class FilesService {
  15. late Dio _enteDio;
  16. late Logger _logger;
  17. late FilesDB _filesDB;
  18. late Configuration _config;
  19. FilesService._privateConstructor() {
  20. _enteDio = NetworkClient.instance.enteDio;
  21. _logger = Logger("FilesService");
  22. _filesDB = FilesDB.instance;
  23. _config = Configuration.instance;
  24. }
  25. static final FilesService instance = FilesService._privateConstructor();
  26. Future<int> getFileSize(int uploadedFileID) async {
  27. try {
  28. final response = await _enteDio.post(
  29. "/files/size",
  30. data: {
  31. "fileIDs": [uploadedFileID],
  32. },
  33. );
  34. return response.data["size"];
  35. } catch (e) {
  36. _logger.severe(e);
  37. rethrow;
  38. }
  39. }
  40. Future<bool> hasMigratedSizes() async {
  41. try {
  42. final List<int> uploadIDsWithMissingSize =
  43. await _filesDB.getUploadIDsWithMissingSize(_config.getUserID()!);
  44. if (uploadIDsWithMissingSize.isEmpty) {
  45. return Future.value(true);
  46. }
  47. await backFillSizes(uploadIDsWithMissingSize);
  48. return Future.value(true);
  49. } catch (e, s) {
  50. _logger.severe("error during has migrated sizes", e, s);
  51. return Future.value(false);
  52. }
  53. }
  54. Future<void> backFillSizes(List<int> uploadIDsWithMissingSize) async {
  55. final batchedFiles = uploadIDsWithMissingSize.chunks(1000);
  56. for (final batch in batchedFiles) {
  57. final Map<int, int> uploadIdToSize = await getFilesSizeFromInfo(batch);
  58. await _filesDB.updateSizeForUploadIDs(uploadIdToSize);
  59. }
  60. }
  61. Future<Map<int, int>> getFilesSizeFromInfo(List<int> uploadedFileID) async {
  62. try {
  63. final response = await _enteDio.post(
  64. "/files/info",
  65. data: {"fileIDs": uploadedFileID},
  66. );
  67. final Map<int, int> idToSize = {};
  68. final List result = response.data["filesInfo"] as List;
  69. for (var fileInfo in result) {
  70. final int uploadedFileID = fileInfo["id"];
  71. final int size = fileInfo["fileInfo"]["fileSize"];
  72. idToSize[uploadedFileID] = size;
  73. }
  74. return idToSize;
  75. } catch (e, s) {
  76. _logger.severe("failed to fetch size from fileInfo", e, s);
  77. rethrow;
  78. }
  79. }
  80. // Note: this method is not used anywhere, but it is kept for future
  81. // reference when we add bulk EditTime feature
  82. Future<void> bulkEditTime(
  83. List<EnteFile> files,
  84. EditTimeSource source,
  85. ) async {
  86. final ListMatch<EnteFile> result = files.splitMatch(
  87. (element) => element.isUploaded,
  88. );
  89. final List<EnteFile> uploadedFiles = result.matched;
  90. // editTime For LocalFiles
  91. final List<EnteFile> localOnlyFiles = result.unmatched;
  92. for (EnteFile localFile in localOnlyFiles) {
  93. final timeResult = _parseTime(localFile, source);
  94. if (timeResult != null) {
  95. localFile.creationTime = timeResult;
  96. }
  97. }
  98. await _filesDB.insertMultiple(localOnlyFiles);
  99. final List<EnteFile> remoteFilesToUpdate = [];
  100. final Map<int, Map<String, int>> fileIDToUpdateMetadata = {};
  101. for (EnteFile remoteFile in uploadedFiles) {
  102. // discard files not owned by user and also dedupe already processed
  103. // files
  104. if (remoteFile.ownerID != _config.getUserID()! ||
  105. fileIDToUpdateMetadata.containsKey(remoteFile.uploadedFileID!)) {
  106. continue;
  107. }
  108. final timeResult = _parseTime(remoteFile, source);
  109. if (timeResult != null) {
  110. remoteFilesToUpdate.add(remoteFile);
  111. fileIDToUpdateMetadata[remoteFile.uploadedFileID!] = {
  112. editTimeKey: timeResult,
  113. };
  114. }
  115. }
  116. if (remoteFilesToUpdate.isNotEmpty) {
  117. await FileMagicService.instance.updatePublicMagicMetadata(
  118. remoteFilesToUpdate,
  119. null,
  120. metadataUpdateMap: fileIDToUpdateMetadata,
  121. );
  122. }
  123. }
  124. int? _parseTime(EnteFile file, EditTimeSource source) {
  125. assert(
  126. source == EditTimeSource.fileName,
  127. "edit source ${source.name} is not supported yet",
  128. );
  129. final timeResult = parseDateTimeFromFileNameV2(
  130. basenameWithoutExtension(file.title ?? ""),
  131. );
  132. return timeResult?.microsecondsSinceEpoch;
  133. }
  134. Future<void> removeIgnoredFiles(Future<FileLoadResult> result) async {
  135. final ignoredIDs = await IgnoredFilesService.instance.idToIgnoreReasonMap;
  136. (await result).files.removeWhere(
  137. (f) =>
  138. f.uploadedFileID == null &&
  139. IgnoredFilesService.instance.shouldSkipUpload(ignoredIDs, f),
  140. );
  141. }
  142. }
  143. enum EditTimeSource {
  144. // parse the time from fileName
  145. fileName,
  146. // parse the time from exif data of file.
  147. exif,
  148. // use the which user provided as input
  149. manualFix,
  150. // adjust the time of selected photos by +/- time.
  151. // required for cases when the original device in which photos were taken
  152. // had incorrect time (quite common with physical cameras)
  153. manualAdjusted,
  154. }