files_service.dart 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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.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. final batchedFiles = uploadIDsWithMissingSize.chunks(1000);
  48. for (final batch in batchedFiles) {
  49. final Map<int, int> uploadIdToSize = await getFilesSizeFromInfo(batch);
  50. await _filesDB.updateSizeForUploadIDs(uploadIdToSize);
  51. }
  52. return Future.value(true);
  53. } catch (e, s) {
  54. _logger.severe("error during has migrated sizes", e, s);
  55. return Future.value(false);
  56. }
  57. }
  58. Future<Map<int, int>> getFilesSizeFromInfo(List<int> uploadedFileID) async {
  59. try {
  60. final response = await _enteDio.post(
  61. "/files/info",
  62. data: {"fileIDs": uploadedFileID},
  63. );
  64. final Map<int, int> idToSize = {};
  65. final List result = response.data["filesInfo"] as List;
  66. for (var fileInfo in result) {
  67. final int uploadedFileID = fileInfo["id"];
  68. final int size = fileInfo["fileInfo"]["fileSize"];
  69. idToSize[uploadedFileID] = size;
  70. }
  71. return idToSize;
  72. } catch (e, s) {
  73. _logger.severe("failed to fetch size from fileInfo", e, s);
  74. rethrow;
  75. }
  76. }
  77. // Note: this method is not used anywhere, but it is kept for future
  78. // reference when we add bulk EditTime feature
  79. Future<void> bulkEditTime(
  80. List<File> files,
  81. EditTimeSource source,
  82. ) async {
  83. final ListMatch<File> result = files.splitMatch(
  84. (element) => element.isUploaded,
  85. );
  86. final List<File> uploadedFiles = result.matched;
  87. // editTime For LocalFiles
  88. final List<File> localOnlyFiles = result.unmatched;
  89. for (File localFile in localOnlyFiles) {
  90. final timeResult = _parseTime(localFile, source);
  91. if (timeResult != null) {
  92. localFile.creationTime = timeResult;
  93. }
  94. }
  95. await _filesDB.insertMultiple(localOnlyFiles);
  96. final List<File> remoteFilesToUpdate = [];
  97. final Map<int, Map<String, int>> fileIDToUpdateMetadata = {};
  98. for (File remoteFile in uploadedFiles) {
  99. // discard files not owned by user and also dedupe already processed
  100. // files
  101. if (remoteFile.ownerID != _config.getUserID()! ||
  102. fileIDToUpdateMetadata.containsKey(remoteFile.uploadedFileID!)) {
  103. continue;
  104. }
  105. final timeResult = _parseTime(remoteFile, source);
  106. if (timeResult != null) {
  107. remoteFilesToUpdate.add(remoteFile);
  108. fileIDToUpdateMetadata[remoteFile.uploadedFileID!] = {
  109. editTimeKey: timeResult,
  110. };
  111. }
  112. }
  113. if (remoteFilesToUpdate.isNotEmpty) {
  114. await FileMagicService.instance.updatePublicMagicMetadata(
  115. remoteFilesToUpdate,
  116. null,
  117. metadataUpdateMap: fileIDToUpdateMetadata,
  118. );
  119. }
  120. }
  121. int? _parseTime(File file, EditTimeSource source) {
  122. assert(
  123. source == EditTimeSource.fileName,
  124. "edit source ${source.name} is not supported yet",
  125. );
  126. final timeResult = parseDateTimeFromFileNameV2(
  127. basenameWithoutExtension(file.title ?? ""),
  128. );
  129. return timeResult?.microsecondsSinceEpoch;
  130. }
  131. Future<void> removeIgnoredFiles(Future<FileLoadResult> result) async {
  132. final ignoredIDs = await IgnoredFilesService.instance.ignoredIDs;
  133. (await result).files.removeWhere(
  134. (f) =>
  135. f.uploadedFileID == null &&
  136. IgnoredFilesService.instance.shouldSkipUpload(ignoredIDs, f),
  137. );
  138. }
  139. }
  140. enum EditTimeSource {
  141. // parse the time from fileName
  142. fileName,
  143. // parse the time from exif data of file.
  144. exif,
  145. // use the which user provided as input
  146. manualFix,
  147. // adjust the time of selected photos by +/- time.
  148. // required for cases when the original device in which photos were taken
  149. // had incorrect time (quite common with physical cameras)
  150. manualAdjusted,
  151. }