ente/lib/utils/file_download_util.dart

127 lines
4.3 KiB
Dart
Raw Normal View History

2023-08-24 16:56:24 +00:00
import 'dart:io';
2021-08-09 06:21:09 +00:00
import "package:computer/computer.dart";
import 'package:dio/dio.dart';
import "package:flutter/foundation.dart";
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
2023-02-03 07:39:04 +00:00
import 'package:photos/core/network/network.dart';
2023-08-25 04:39:30 +00:00
import 'package:photos/models/file/file.dart';
import "package:photos/models/file/file_type.dart";
import 'package:photos/services/collections_service.dart';
2021-10-30 05:51:23 +00:00
import 'package:photos/utils/crypto_util.dart';
2023-08-11 10:45:36 +00:00
import "package:photos/utils/data_util.dart";
import "package:photos/utils/fake_progress.dart";
final _logger = Logger("file_download_util");
2023-08-24 16:56:24 +00:00
Future<File?> downloadAndDecrypt(
EnteFile file, {
2022-12-29 13:36:35 +00:00
ProgressCallback? progressCallback,
}) async {
2023-08-11 10:45:36 +00:00
final String logPrefix = 'File-${file.uploadedFileID}:';
_logger
.info('$logPrefix starting download ${formatBytes(file.fileSize ?? 0)}');
final String tempDir = Configuration.instance.getTempDirectory();
final String encryptedFilePath = "$tempDir${file.generatedID}.encrypted";
2023-08-24 16:56:24 +00:00
final encryptedFile = File(encryptedFilePath);
final startTime = DateTime.now().millisecondsSinceEpoch;
try {
final response = await NetworkClient.instance.getDio().download(
file.downloadUrl,
encryptedFilePath,
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
onReceiveProgress: (a, b) {
if (kDebugMode && a >= 0 && b >= 0) {
_logger.fine(
"$logPrefix download progress: ${formatBytes(a)} / ${formatBytes(b)}",
);
}
progressCallback?.call(a, b);
},
);
if (response.statusCode != 200 || !encryptedFile.existsSync()) {
_logger.warning('$logPrefix download failed ${response.toString()}');
return null;
}
final int sizeInBytes = file.fileSize ?? await encryptedFile.length();
final double elapsedSeconds =
(DateTime.now().millisecondsSinceEpoch - startTime) / 1000;
final double speedInKBps = sizeInBytes / 1024.0 / elapsedSeconds;
2022-06-11 08:23:52 +00:00
_logger.info(
'$logPrefix download completed: ${formatBytes(sizeInBytes)}, avg speed: ${speedInKBps.toStringAsFixed(2)} KB/s',
);
2023-08-11 10:36:56 +00:00
final String decryptedFilePath = "$tempDir${file.generatedID}.decrypted";
// As decryption can take time, emit fake progress for large files during
// decryption
final FakePeriodicProgress? fakeProgress = file.fileType == FileType.video
? FakePeriodicProgress(
callback: (count) {
progressCallback?.call(sizeInBytes, sizeInBytes);
},
duration: const Duration(milliseconds: 5000),
)
: null;
2023-08-11 06:14:19 +00:00
try {
// Start the periodic callback after initial 5 seconds
fakeProgress?.start();
2023-08-11 06:14:19 +00:00
await CryptoUtil.decryptFile(
encryptedFilePath,
decryptedFilePath,
CryptoUtil.base642bin(file.fileDecryptionHeader!),
getFileKey(file),
);
fakeProgress?.stop();
_logger.info('$logPrefix decryption completed');
2023-08-11 06:14:19 +00:00
} catch (e, s) {
fakeProgress?.stop();
_logger.severe("Critical: $logPrefix failed to decrypt", e, s);
2023-08-11 06:14:19 +00:00
return null;
}
2021-08-09 06:21:09 +00:00
await encryptedFile.delete();
2023-08-24 16:56:24 +00:00
return File(decryptedFilePath);
} catch (e, s) {
_logger.severe("$logPrefix failed to download or decrypt", e, s);
return null;
}
}
2023-08-24 16:56:24 +00:00
Uint8List getFileKey(EnteFile file) {
2023-02-03 04:41:45 +00:00
final encryptedKey = CryptoUtil.base642bin(file.encryptedKey!);
final nonce = CryptoUtil.base642bin(file.keyDecryptionNonce!);
final collectionKey =
2022-12-29 13:36:35 +00:00
CollectionsService.instance.getCollectionKey(file.collectionID!);
return CryptoUtil.decryptSync(encryptedKey, collectionKey, nonce);
}
2023-08-24 16:56:24 +00:00
Future<Uint8List> getFileKeyUsingBgWorker(EnteFile file) async {
final collectionKey =
CollectionsService.instance.getCollectionKey(file.collectionID!);
return await Computer.shared().compute(
_decryptFileKey,
param: <String, dynamic>{
"encryptedKey": file.encryptedKey,
"keyDecryptionNonce": file.keyDecryptionNonce,
"collectionKey": collectionKey,
},
);
}
Uint8List _decryptFileKey(Map<String, dynamic> args) {
final encryptedKey = CryptoUtil.base642bin(args["encryptedKey"]);
final nonce = CryptoUtil.base642bin(args["keyDecryptionNonce"]);
return CryptoUtil.decryptSync(
encryptedKey,
args["collectionKey"],
nonce,
);
}