FileUploader: Move AssetEntity related logic in helper class
This commit is contained in:
parent
759c894a88
commit
1c1fb43eda
|
@ -20,11 +20,11 @@ import 'package:photos/events/subscription_purchased_event.dart';
|
||||||
import 'package:photos/main.dart';
|
import 'package:photos/main.dart';
|
||||||
import 'package:photos/models/encryption_result.dart';
|
import 'package:photos/models/encryption_result.dart';
|
||||||
import 'package:photos/models/file.dart';
|
import 'package:photos/models/file.dart';
|
||||||
import 'package:photos/models/location.dart';
|
|
||||||
import 'package:photos/models/upload_url.dart';
|
import 'package:photos/models/upload_url.dart';
|
||||||
import 'package:photos/services/collections_service.dart';
|
import 'package:photos/services/collections_service.dart';
|
||||||
import 'package:photos/services/local_sync_service.dart';
|
import 'package:photos/services/local_sync_service.dart';
|
||||||
import 'package:photos/services/sync_service.dart';
|
import 'package:photos/services/sync_service.dart';
|
||||||
|
import 'package:photos/utils/file_uploader_util.dart';
|
||||||
import 'package:photos/utils/crypto_util.dart';
|
import 'package:photos/utils/crypto_util.dart';
|
||||||
import 'package:photos/utils/file_util.dart';
|
import 'package:photos/utils/file_util.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
@ -267,41 +267,20 @@ class FileUploader {
|
||||||
"_thumbnail" +
|
"_thumbnail" +
|
||||||
(_isBackground ? "_bg" : "") +
|
(_isBackground ? "_bg" : "") +
|
||||||
".encrypted";
|
".encrypted";
|
||||||
io.File sourceFile;
|
MediaUploadData mediaUploadData;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_logger.info("Trying to upload " +
|
_logger.info("Trying to upload " +
|
||||||
file.toString() +
|
file.toString() +
|
||||||
", isForced: " +
|
", isForced: " +
|
||||||
forcedUpload.toString());
|
forcedUpload.toString());
|
||||||
// The timeouts are to safeguard against https://github.com/CaiJingLong/flutter_photo_manager/issues/467
|
mediaUploadData = await getUploadDataFromEnteFile(file).catchError((e) async {
|
||||||
final asset = await file
|
if (e is InvalidFileError) {
|
||||||
.getAsset()
|
_onInvalidFileError(file);
|
||||||
.timeout(Duration(seconds: 3))
|
|
||||||
.catchError((e) async {
|
|
||||||
if (e is TimeoutException) {
|
|
||||||
_logger.info("Asset fetch timed out for " + file.toString());
|
|
||||||
return await file.getAsset();
|
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (asset == null) {
|
|
||||||
await _onInvalidFileError(file);
|
|
||||||
}
|
|
||||||
sourceFile = await asset.originFile
|
|
||||||
.timeout(Duration(seconds: 3))
|
|
||||||
.catchError((e) async {
|
|
||||||
if (e is TimeoutException) {
|
|
||||||
_logger.info("Origin file fetch timed out for " + file.toString());
|
|
||||||
return await asset.originFile;
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!sourceFile.existsSync()) {
|
|
||||||
await _onInvalidFileError(file);
|
|
||||||
}
|
|
||||||
var key;
|
var key;
|
||||||
var isAlreadyUploadedFile = file.uploadedFileID != null;
|
var isAlreadyUploadedFile = file.uploadedFileID != null;
|
||||||
if (isAlreadyUploadedFile) {
|
if (isAlreadyUploadedFile) {
|
||||||
|
@ -314,30 +293,12 @@ class FileUploader {
|
||||||
io.File(encryptedFilePath).deleteSync();
|
io.File(encryptedFilePath).deleteSync();
|
||||||
}
|
}
|
||||||
final encryptedFile = io.File(encryptedFilePath);
|
final encryptedFile = io.File(encryptedFilePath);
|
||||||
|
|
||||||
final fileAttributes = await CryptoUtil.encryptFile(
|
final fileAttributes = await CryptoUtil.encryptFile(
|
||||||
sourceFile.path,
|
mediaUploadData.sourceFile.path,
|
||||||
encryptedFilePath,
|
encryptedFilePath,
|
||||||
key: key,
|
key: key,
|
||||||
);
|
);
|
||||||
|
var thumbnailData = mediaUploadData.thumbnail;
|
||||||
var thumbnailData = await asset.thumbDataWithSize(
|
|
||||||
THUMBNAIL_LARGE_SIZE,
|
|
||||||
THUMBNAIL_LARGE_SIZE,
|
|
||||||
quality: 50,
|
|
||||||
);
|
|
||||||
if (thumbnailData == null) {
|
|
||||||
await _onInvalidFileError(file);
|
|
||||||
}
|
|
||||||
int compressionAttempts = 0;
|
|
||||||
while (thumbnailData.length > THUMBNAIL_DATA_LIMIT &&
|
|
||||||
compressionAttempts < kMaximumThumbnailCompressionAttempts) {
|
|
||||||
_logger.info("Thumbnail size " + thumbnailData.length.toString());
|
|
||||||
thumbnailData = await compressThumbnail(thumbnailData);
|
|
||||||
_logger.info(
|
|
||||||
"Compressed thumbnail size " + thumbnailData.length.toString());
|
|
||||||
compressionAttempts++;
|
|
||||||
}
|
|
||||||
|
|
||||||
final encryptedThumbnailData =
|
final encryptedThumbnailData =
|
||||||
await CryptoUtil.encryptChaCha(thumbnailData, fileAttributes.key);
|
await CryptoUtil.encryptChaCha(thumbnailData, fileAttributes.key);
|
||||||
|
@ -355,18 +316,6 @@ class FileUploader {
|
||||||
final fileUploadURL = await _getUploadURL();
|
final fileUploadURL = await _getUploadURL();
|
||||||
String fileObjectKey = await _putFile(fileUploadURL, encryptedFile);
|
String fileObjectKey = await _putFile(fileUploadURL, encryptedFile);
|
||||||
|
|
||||||
// h4ck to fetch location data if missing (thank you Android Q+) lazily only during uploads
|
|
||||||
if (file.location == null ||
|
|
||||||
(file.location.latitude == 0 && file.location.longitude == 0)) {
|
|
||||||
final latLong = await asset.latlngAsync();
|
|
||||||
file.location = Location(latLong.latitude, latLong.longitude);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file.title == null || file.title.isEmpty) {
|
|
||||||
_logger.severe("Title was missing");
|
|
||||||
file.title = await asset.titleAsync;
|
|
||||||
}
|
|
||||||
|
|
||||||
final encryptedMetadataData = await CryptoUtil.encryptChaCha(
|
final encryptedMetadataData = await CryptoUtil.encryptChaCha(
|
||||||
utf8.encode(jsonEncode(file.getMetadata())), fileAttributes.key);
|
utf8.encode(jsonEncode(file.getMetadata())), fileAttributes.key);
|
||||||
final fileDecryptionHeader = Sodium.bin2base64(fileAttributes.header);
|
final fileDecryptionHeader = Sodium.bin2base64(fileAttributes.header);
|
||||||
|
@ -404,7 +353,7 @@ class FileUploader {
|
||||||
encryptedMetadata,
|
encryptedMetadata,
|
||||||
metadataDecryptionHeader,
|
metadataDecryptionHeader,
|
||||||
);
|
);
|
||||||
if (asset == null || !(await asset.exists)) {
|
if (mediaUploadData.isDeleted) {
|
||||||
_logger.info("File found to be deleted");
|
_logger.info("File found to be deleted");
|
||||||
remoteFile.localID = null;
|
remoteFile.localID = null;
|
||||||
}
|
}
|
||||||
|
@ -421,8 +370,8 @@ class FileUploader {
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
if (io.Platform.isIOS && sourceFile != null) {
|
if (io.Platform.isIOS && mediaUploadData != null && mediaUploadData.sourceFile != null) {
|
||||||
sourceFile.deleteSync();
|
mediaUploadData.sourceFile.deleteSync();
|
||||||
}
|
}
|
||||||
if (io.File(encryptedFilePath).existsSync()) {
|
if (io.File(encryptedFilePath).existsSync()) {
|
||||||
io.File(encryptedFilePath).deleteSync();
|
io.File(encryptedFilePath).deleteSync();
|
||||||
|
|
95
lib/utils/file_uploader_util.dart
Normal file
95
lib/utils/file_uploader_util.dart
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io' as io;
|
||||||
|
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
import 'package:photos/core/constants.dart';
|
||||||
|
import 'package:photos/core/errors.dart';
|
||||||
|
import 'package:photos/models/location.dart';
|
||||||
|
import 'package:photos/models/file.dart' as ente;
|
||||||
|
|
||||||
|
import 'file_util.dart';
|
||||||
|
|
||||||
|
final _logger = Logger("FileUtil");
|
||||||
|
const kMaximumThumbnailCompressionAttempts = 2;
|
||||||
|
|
||||||
|
class MediaUploadData {
|
||||||
|
final io.File sourceFile;
|
||||||
|
final Uint8List thumbnail;
|
||||||
|
final bool isDeleted;
|
||||||
|
|
||||||
|
MediaUploadData(this.sourceFile, this.thumbnail, this.isDeleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<MediaUploadData> getUploadDataFromEnteFile(ente.File file) async {
|
||||||
|
// todo: add local to get data from either Asset/photoManager or app cache
|
||||||
|
return await _getMediaUploadDataFromAssetFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<MediaUploadData> _getMediaUploadDataFromAssetFile(ente.File file) async {
|
||||||
|
io.File sourceFile;
|
||||||
|
Uint8List thumbnailData;
|
||||||
|
bool isDeleted;
|
||||||
|
|
||||||
|
// The timeouts are to safeguard against https://github.com/CaiJingLong/flutter_photo_manager/issues/467
|
||||||
|
final asset =
|
||||||
|
await file.getAsset().timeout(Duration(seconds: 3)).catchError((e) async {
|
||||||
|
if (e is TimeoutException) {
|
||||||
|
_logger.info("Asset fetch timed out for " + file.toString());
|
||||||
|
return await file.getAsset();
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (asset == null) {
|
||||||
|
throw InvalidFileError();
|
||||||
|
}
|
||||||
|
sourceFile = await asset.originFile
|
||||||
|
.timeout(Duration(seconds: 3))
|
||||||
|
.catchError((e) async {
|
||||||
|
if (e is TimeoutException) {
|
||||||
|
_logger.info("Origin file fetch timed out for " + file.toString());
|
||||||
|
return await asset.originFile;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!sourceFile.existsSync()) {
|
||||||
|
throw InvalidFileError();
|
||||||
|
}
|
||||||
|
thumbnailData = await asset.thumbDataWithSize(
|
||||||
|
THUMBNAIL_SMALL_SIZE,
|
||||||
|
THUMBNAIL_SMALL_SIZE,
|
||||||
|
quality: THUMBNAIL_QUALITY,
|
||||||
|
);
|
||||||
|
int compressionAttempts = 0;
|
||||||
|
while (thumbnailData.length > THUMBNAIL_DATA_LIMIT &&
|
||||||
|
compressionAttempts < kMaximumThumbnailCompressionAttempts) {
|
||||||
|
_logger.info("Thumbnail size " + thumbnailData.length.toString());
|
||||||
|
thumbnailData = await compressThumbnail(thumbnailData);
|
||||||
|
_logger
|
||||||
|
.info("Compressed thumbnail size " + thumbnailData.length.toString());
|
||||||
|
compressionAttempts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDeleted = asset == null || !(await asset.exists);
|
||||||
|
// h4ck to fetch location data if missing (thank you Android Q+) lazily only during uploads
|
||||||
|
await _decorateEnteFileData(file, asset);
|
||||||
|
return MediaUploadData(sourceFile, thumbnailData, isDeleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _decorateEnteFileData(ente.File file, AssetEntity asset) async {
|
||||||
|
// h4ck to fetch location data if missing (thank you Android Q+) lazily only during uploads
|
||||||
|
if (file.location == null ||
|
||||||
|
(file.location.latitude == 0 && file.location.longitude == 0)) {
|
||||||
|
final latLong = await asset.latlngAsync();
|
||||||
|
file.location = Location(latLong.latitude, latLong.longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.title == null || file.title.isEmpty) {
|
||||||
|
_logger.severe("Title was missing");
|
||||||
|
file.title = await asset.titleAsync;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue