ente/lib/services/files_service.dart

246 lines
7.8 KiB
Dart
Raw Normal View History

import 'package:dio/dio.dart';
import "package:flutter/material.dart";
import "package:latlong2/latlong.dart";
import 'package:logging/logging.dart';
2022-11-14 08:33:49 +00:00
import 'package:path/path.dart';
import 'package:photos/core/configuration.dart';
2023-02-03 07:39:04 +00:00
import 'package:photos/core/network/network.dart';
2022-11-14 08:33:49 +00:00
import 'package:photos/db/files_db.dart';
import 'package:photos/extensions/list.dart';
import "package:photos/generated/l10n.dart";
2023-08-25 04:39:30 +00:00
import 'package:photos/models/file/file.dart';
import "package:photos/models/file_load_result.dart";
import "package:photos/models/metadata/file_magic.dart";
2022-11-14 08:33:49 +00:00
import 'package:photos/services/file_magic_service.dart';
import "package:photos/services/ignored_files_service.dart";
import "package:photos/ui/components/action_sheet_widget.dart";
import "package:photos/ui/components/buttons/button_widget.dart";
import "package:photos/ui/components/models/button_type.dart";
2022-11-14 08:33:49 +00:00
import 'package:photos/utils/date_time_util.dart';
class FilesService {
2022-10-14 15:03:55 +00:00
late Dio _enteDio;
late Logger _logger;
2022-11-14 08:33:49 +00:00
late FilesDB _filesDB;
late Configuration _config;
FilesService._privateConstructor() {
2023-02-03 07:39:04 +00:00
_enteDio = NetworkClient.instance.enteDio;
_logger = Logger("FilesService");
2022-11-14 08:33:49 +00:00
_filesDB = FilesDB.instance;
_config = Configuration.instance;
}
2022-11-14 08:33:49 +00:00
static final FilesService instance = FilesService._privateConstructor();
Future<int> getFileSize(int uploadedFileID) async {
try {
2022-10-14 15:03:55 +00:00
final response = await _enteDio.post(
"/files/size",
data: {
2023-08-19 11:39:56 +00:00
"fileIDs": [uploadedFileID],
},
);
return response.data["size"];
} catch (e) {
_logger.severe(e);
rethrow;
}
}
2022-11-14 08:33:49 +00:00
Future<bool> hasMigratedSizes() async {
try {
final List<int> uploadIDsWithMissingSize =
await _filesDB.getUploadIDsWithMissingSize(_config.getUserID()!);
if (uploadIDsWithMissingSize.isEmpty) {
return Future.value(true);
}
await backFillSizes(uploadIDsWithMissingSize);
return Future.value(true);
} catch (e, s) {
2023-05-31 09:57:01 +00:00
_logger.severe("error during has migrated sizes", e, s);
return Future.value(false);
}
}
Future<void> backFillSizes(List<int> uploadIDsWithMissingSize) async {
final batchedFiles = uploadIDsWithMissingSize.chunks(1000);
for (final batch in batchedFiles) {
final Map<int, int> uploadIdToSize = await getFilesSizeFromInfo(batch);
await _filesDB.updateSizeForUploadIDs(uploadIdToSize);
}
}
2023-05-30 15:33:02 +00:00
Future<Map<int, int>> getFilesSizeFromInfo(List<int> uploadedFileID) async {
try {
final response = await _enteDio.post(
"/files/info",
data: {"fileIDs": uploadedFileID},
2023-05-30 15:33:02 +00:00
);
final Map<int, int> idToSize = {};
final List result = response.data["filesInfo"] as List;
for (var fileInfo in result) {
final int uploadedFileID = fileInfo["id"];
final int size = fileInfo["fileInfo"]["fileSize"];
idToSize[uploadedFileID] = size;
}
return idToSize;
2023-05-31 09:57:01 +00:00
} catch (e, s) {
_logger.severe("failed to fetch size from fileInfo", e, s);
2023-05-30 15:33:02 +00:00
rethrow;
}
}
Future<void> bulkEditLocationData(
List<EnteFile> files,
LatLng location,
BuildContext context,
) async {
2023-12-15 17:12:06 +00:00
final List<EnteFile> uploadedFiles =
files.where((element) => element.uploadedFileID != null).toList();
final List<EnteFile> remoteFilesToUpdate = [];
final Map<int, Map<String, dynamic>> fileIDToUpdateMetadata = {};
2023-12-15 11:23:44 +00:00
await showActionSheet(
context: context,
2023-12-16 01:23:10 +00:00
body: S.of(context).changeLocationOfSelectedItems,
2023-12-15 11:23:44 +00:00
buttons: [
ButtonWidget(
2023-12-16 01:23:10 +00:00
labelText: S.of(context).yes,
2023-12-15 11:23:44 +00:00
buttonType: ButtonType.neutral,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.first,
shouldSurfaceExecutionStates: true,
isInAlert: true,
onTap: () async {
await _editLocationData(
uploadedFiles,
fileIDToUpdateMetadata,
remoteFilesToUpdate,
location,
);
},
),
ButtonWidget(
labelText: S.of(context).cancel,
buttonType: ButtonType.secondary,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.cancel,
isInAlert: true,
),
],
);
}
Future<void> _editLocationData(
List<EnteFile> uploadedFiles,
Map<int, Map<String, dynamic>> fileIDToUpdateMetadata,
List<EnteFile> remoteFilesToUpdate,
LatLng location,
) async {
for (EnteFile remoteFile in uploadedFiles) {
// discard files not owned by user and also dedupe already processed
// files
if (remoteFile.ownerID != _config.getUserID()! ||
fileIDToUpdateMetadata.containsKey(remoteFile.uploadedFileID!)) {
continue;
}
2023-12-14 06:13:20 +00:00
remoteFilesToUpdate.add(remoteFile);
fileIDToUpdateMetadata[remoteFile.uploadedFileID!] = {
latKey: location.latitude,
longKey: location.longitude,
};
}
if (remoteFilesToUpdate.isNotEmpty) {
await FileMagicService.instance.updatePublicMagicMetadata(
remoteFilesToUpdate,
null,
metadataUpdateMap: fileIDToUpdateMetadata,
);
}
}
2023-05-24 06:43:00 +00:00
// Note: this method is not used anywhere, but it is kept for future
// reference when we add bulk EditTime feature
2022-11-14 08:33:49 +00:00
Future<void> bulkEditTime(
2023-08-24 16:56:24 +00:00
List<EnteFile> files,
2022-11-14 08:33:49 +00:00
EditTimeSource source,
) async {
2023-08-24 16:56:24 +00:00
final ListMatch<EnteFile> result = files.splitMatch(
2022-11-14 08:33:49 +00:00
(element) => element.isUploaded,
);
2023-08-24 16:56:24 +00:00
final List<EnteFile> uploadedFiles = result.matched;
2022-11-14 08:33:49 +00:00
// editTime For LocalFiles
2023-08-24 16:56:24 +00:00
final List<EnteFile> localOnlyFiles = result.unmatched;
for (EnteFile localFile in localOnlyFiles) {
2022-11-14 08:41:42 +00:00
final timeResult = _parseTime(localFile, source);
2022-11-14 08:33:49 +00:00
if (timeResult != null) {
2022-11-14 08:41:42 +00:00
localFile.creationTime = timeResult;
2022-11-14 08:33:49 +00:00
}
}
await _filesDB.insertMultiple(localOnlyFiles);
2023-08-24 16:56:24 +00:00
final List<EnteFile> remoteFilesToUpdate = [];
2022-11-14 08:33:49 +00:00
final Map<int, Map<String, int>> fileIDToUpdateMetadata = {};
2023-08-24 16:56:24 +00:00
for (EnteFile remoteFile in uploadedFiles) {
2022-11-14 08:33:49 +00:00
// discard files not owned by user and also dedupe already processed
// files
if (remoteFile.ownerID != _config.getUserID()! ||
2022-11-14 11:26:32 +00:00
fileIDToUpdateMetadata.containsKey(remoteFile.uploadedFileID!)) {
2022-11-14 08:33:49 +00:00
continue;
}
2022-11-14 08:41:42 +00:00
final timeResult = _parseTime(remoteFile, source);
2022-11-14 08:33:49 +00:00
if (timeResult != null) {
remoteFilesToUpdate.add(remoteFile);
fileIDToUpdateMetadata[remoteFile.uploadedFileID!] = {
editTimeKey: timeResult,
2022-11-14 08:33:49 +00:00
};
}
}
if (remoteFilesToUpdate.isNotEmpty) {
await FileMagicService.instance.updatePublicMagicMetadata(
remoteFilesToUpdate,
null,
metadataUpdateMap: fileIDToUpdateMetadata,
);
}
}
2022-11-14 08:41:42 +00:00
2023-08-24 16:56:24 +00:00
int? _parseTime(EnteFile file, EditTimeSource source) {
2022-11-14 08:41:42 +00:00
assert(
source == EditTimeSource.fileName,
"edit source ${source.name} is not supported yet",
);
final timeResult = parseDateTimeFromFileNameV2(
basenameWithoutExtension(file.title ?? ""),
);
return timeResult?.microsecondsSinceEpoch;
}
Future<void> removeIgnoredFiles(Future<FileLoadResult> result) async {
2023-08-23 08:56:06 +00:00
final ignoredIDs = await IgnoredFilesService.instance.idToIgnoreReasonMap;
(await result).files.removeWhere(
(f) =>
f.uploadedFileID == null &&
IgnoredFilesService.instance.shouldSkipUpload(ignoredIDs, f),
);
}
}
2022-11-14 07:16:51 +00:00
enum EditTimeSource {
// parse the time from fileName
fileName,
// parse the time from exif data of file.
exif,
// use the which user provided as input
manualFix,
// adjust the time of selected photos by +/- time.
// required for cases when the original device in which photos were taken
// had incorrect time (quite common with physical cameras)
manualAdjusted,
}