|
@@ -1,5 +1,3 @@
|
|
|
-// @dart=2.9
|
|
|
-
|
|
|
import 'dart:async';
|
|
|
import 'dart:collection';
|
|
|
import 'dart:convert';
|
|
@@ -7,6 +5,7 @@ import 'dart:io' as io;
|
|
|
import 'dart:math';
|
|
|
import 'dart:typed_data';
|
|
|
|
|
|
+import 'package:collection/collection.dart';
|
|
|
import 'package:connectivity/connectivity.dart';
|
|
|
import 'package:dio/dio.dart';
|
|
|
import 'package:flutter/foundation.dart';
|
|
@@ -60,9 +59,9 @@ class FileUploader {
|
|
|
// _uploadCounter indicates number of uploads which are currently in progress
|
|
|
int _uploadCounter = 0;
|
|
|
int _videoUploadCounter = 0;
|
|
|
- ProcessType _processType;
|
|
|
- bool _isBackground;
|
|
|
- SharedPreferences _prefs;
|
|
|
+ late ProcessType _processType;
|
|
|
+ late bool _isBackground;
|
|
|
+ late SharedPreferences _prefs;
|
|
|
|
|
|
FileUploader._privateConstructor() {
|
|
|
Bus.instance.on<SubscriptionPurchasedEvent>().listen((event) {
|
|
@@ -206,9 +205,8 @@ class FileUploader {
|
|
|
}
|
|
|
if (_uploadCounter < kMaximumConcurrentUploads) {
|
|
|
var pendingEntry = _queue.entries
|
|
|
- .firstWhere(
|
|
|
+ .firstWhereOrNull(
|
|
|
(entry) => entry.value.status == UploadStatus.notStarted,
|
|
|
- orElse: () => null,
|
|
|
)
|
|
|
?.value;
|
|
|
|
|
@@ -217,11 +215,10 @@ class FileUploader {
|
|
|
_videoUploadCounter >= kMaximumConcurrentVideoUploads) {
|
|
|
// check if there's any non-video entry which can be queued for upload
|
|
|
pendingEntry = _queue.entries
|
|
|
- .firstWhere(
|
|
|
+ .firstWhereOrNull(
|
|
|
(entry) =>
|
|
|
entry.value.status == UploadStatus.notStarted &&
|
|
|
entry.value.file.fileType != FileType.video,
|
|
|
- orElse: () => null,
|
|
|
)
|
|
|
?.value;
|
|
|
}
|
|
@@ -235,7 +232,7 @@ class FileUploader {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- Future<File> _encryptAndUploadFileToCollection(
|
|
|
+ Future<File?> _encryptAndUploadFileToCollection(
|
|
|
File file,
|
|
|
int collectionID, {
|
|
|
bool forcedUpload = false,
|
|
@@ -286,10 +283,11 @@ class FileUploader {
|
|
|
if (!canUploadUnderCurrentNetworkConditions && !forcedUpload) {
|
|
|
throw WiFiUnavailableError();
|
|
|
}
|
|
|
- final fileOnDisk = await FilesDB.instance.getFile(file.generatedID);
|
|
|
- final wasAlreadyUploaded = fileOnDisk.uploadedFileID != null &&
|
|
|
- fileOnDisk.updationTime != -1 &&
|
|
|
- fileOnDisk.collectionID == collectionID;
|
|
|
+ final fileOnDisk = await FilesDB.instance.getFile(file.generatedID!);
|
|
|
+ final wasAlreadyUploaded = fileOnDisk != null &&
|
|
|
+ fileOnDisk.uploadedFileID != null &&
|
|
|
+ (fileOnDisk.updationTime ?? -1) != -1 &&
|
|
|
+ (fileOnDisk.collectionID ?? -1) == collectionID;
|
|
|
if (wasAlreadyUploaded) {
|
|
|
debugPrint("File is already uploaded ${fileOnDisk.tag}");
|
|
|
return fileOnDisk;
|
|
@@ -297,7 +295,7 @@ class FileUploader {
|
|
|
|
|
|
try {
|
|
|
await _uploadLocks.acquireLock(
|
|
|
- file.localID,
|
|
|
+ file.localID!,
|
|
|
_processType.toString(),
|
|
|
DateTime.now().microsecondsSinceEpoch,
|
|
|
);
|
|
@@ -316,7 +314,7 @@ class FileUploader {
|
|
|
"_thumbnail" +
|
|
|
(_isBackground ? "_bg" : "") +
|
|
|
".encrypted";
|
|
|
- MediaUploadData mediaUploadData;
|
|
|
+ MediaUploadData? mediaUploadData;
|
|
|
var uploadCompleted = false;
|
|
|
// This flag is used to decide whether to clear the iOS origin file cache
|
|
|
// or not.
|
|
@@ -339,7 +337,7 @@ class FileUploader {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- Uint8List key;
|
|
|
+ Uint8List? key;
|
|
|
final bool isUpdatedFile =
|
|
|
file.uploadedFileID != null && file.updationTime == -1;
|
|
|
if (isUpdatedFile) {
|
|
@@ -351,7 +349,7 @@ class FileUploader {
|
|
|
// uploaded file. If map is found, it also returns the corresponding
|
|
|
// mapped or update file entry.
|
|
|
final result = await _mapToExistingUploadWithSameHash(
|
|
|
- mediaUploadData,
|
|
|
+ mediaUploadData!,
|
|
|
file,
|
|
|
collectionID,
|
|
|
);
|
|
@@ -370,20 +368,22 @@ class FileUploader {
|
|
|
}
|
|
|
final encryptedFile = io.File(encryptedFilePath);
|
|
|
final fileAttributes = await CryptoUtil.encryptFile(
|
|
|
- mediaUploadData.sourceFile.path,
|
|
|
+ mediaUploadData!.sourceFile!.path,
|
|
|
encryptedFilePath,
|
|
|
key: key,
|
|
|
);
|
|
|
final thumbnailData = mediaUploadData.thumbnail;
|
|
|
|
|
|
- final encryptedThumbnailData =
|
|
|
- await CryptoUtil.encryptChaCha(thumbnailData, fileAttributes.key);
|
|
|
+ final encryptedThumbnailData = await CryptoUtil.encryptChaCha(
|
|
|
+ thumbnailData as Uint8List,
|
|
|
+ fileAttributes.key as Uint8List,
|
|
|
+ );
|
|
|
if (io.File(encryptedThumbnailPath).existsSync()) {
|
|
|
await io.File(encryptedThumbnailPath).delete();
|
|
|
}
|
|
|
final encryptedThumbnailFile = io.File(encryptedThumbnailPath);
|
|
|
await encryptedThumbnailFile
|
|
|
- .writeAsBytes(encryptedThumbnailData.encryptedData);
|
|
|
+ .writeAsBytes(encryptedThumbnailData.encryptedData as Uint8List);
|
|
|
|
|
|
final thumbnailUploadURL = await _getUploadURL();
|
|
|
final String thumbnailObjectKey =
|
|
@@ -394,16 +394,17 @@ class FileUploader {
|
|
|
|
|
|
final metadata = await file.getMetadataForUpload(mediaUploadData);
|
|
|
final encryptedMetadataData = await CryptoUtil.encryptChaCha(
|
|
|
- utf8.encode(jsonEncode(metadata)),
|
|
|
- fileAttributes.key,
|
|
|
+ utf8.encode(jsonEncode(metadata)) as Uint8List,
|
|
|
+ fileAttributes.key as Uint8List,
|
|
|
);
|
|
|
- final fileDecryptionHeader = Sodium.bin2base64(fileAttributes.header);
|
|
|
+ final fileDecryptionHeader =
|
|
|
+ Sodium.bin2base64(fileAttributes.header as Uint8List);
|
|
|
final thumbnailDecryptionHeader =
|
|
|
- Sodium.bin2base64(encryptedThumbnailData.header);
|
|
|
+ Sodium.bin2base64(encryptedThumbnailData.header as Uint8List);
|
|
|
final encryptedMetadata =
|
|
|
- Sodium.bin2base64(encryptedMetadataData.encryptedData);
|
|
|
+ Sodium.bin2base64(encryptedMetadataData.encryptedData as Uint8List);
|
|
|
final metadataDecryptionHeader =
|
|
|
- Sodium.bin2base64(encryptedMetadataData.header);
|
|
|
+ Sodium.bin2base64(encryptedMetadataData.header as Uint8List);
|
|
|
if (SyncService.instance.shouldStopSync()) {
|
|
|
throw SyncStopRequestedError();
|
|
|
}
|
|
@@ -424,13 +425,13 @@ class FileUploader {
|
|
|
await FilesDB.instance.updateUploadedFileAcrossCollections(remoteFile);
|
|
|
} else {
|
|
|
final encryptedFileKeyData = CryptoUtil.encryptSync(
|
|
|
- fileAttributes.key,
|
|
|
+ fileAttributes.key as Uint8List,
|
|
|
CollectionsService.instance.getCollectionKey(collectionID),
|
|
|
);
|
|
|
final encryptedKey =
|
|
|
- Sodium.bin2base64(encryptedFileKeyData.encryptedData);
|
|
|
+ Sodium.bin2base64(encryptedFileKeyData.encryptedData as Uint8List);
|
|
|
final keyDecryptionNonce =
|
|
|
- Sodium.bin2base64(encryptedFileKeyData.nonce);
|
|
|
+ Sodium.bin2base64(encryptedFileKeyData.nonce as Uint8List);
|
|
|
remoteFile = await _uploadFile(
|
|
|
file,
|
|
|
collectionID,
|
|
@@ -524,20 +525,20 @@ class FileUploader {
|
|
|
|
|
|
final List<File> existingUploadedFiles =
|
|
|
await FilesDB.instance.getUploadedFilesWithHashes(
|
|
|
- mediaUploadData.hashData,
|
|
|
+ mediaUploadData.hashData!,
|
|
|
fileToUpload.fileType,
|
|
|
- Configuration.instance.getUserID(),
|
|
|
+ Configuration.instance.getUserID()!,
|
|
|
);
|
|
|
- if (existingUploadedFiles?.isEmpty ?? true) {
|
|
|
+ if (existingUploadedFiles.isEmpty) {
|
|
|
// continueUploading this file
|
|
|
return Tuple2(false, fileToUpload);
|
|
|
}
|
|
|
|
|
|
// case a
|
|
|
- final File sameLocalSameCollection = existingUploadedFiles.firstWhere(
|
|
|
+ final File? sameLocalSameCollection =
|
|
|
+ existingUploadedFiles.firstWhereOrNull(
|
|
|
(e) =>
|
|
|
e.collectionID == toCollectionID && e.localID == fileToUpload.localID,
|
|
|
- orElse: () => null,
|
|
|
);
|
|
|
if (sameLocalSameCollection != null) {
|
|
|
_logger.fine(
|
|
@@ -545,7 +546,7 @@ class FileUploader {
|
|
|
"\n existing: ${sameLocalSameCollection.tag}",
|
|
|
);
|
|
|
// should delete the fileToUploadEntry
|
|
|
- await FilesDB.instance.deleteByGeneratedID(fileToUpload.generatedID);
|
|
|
+ await FilesDB.instance.deleteByGeneratedID(fileToUpload.generatedID!);
|
|
|
|
|
|
Bus.instance.fire(
|
|
|
LocalPhotosUpdatedEvent(
|
|
@@ -558,10 +559,9 @@ class FileUploader {
|
|
|
}
|
|
|
|
|
|
// case b
|
|
|
- final File fileMissingLocalButSameCollection =
|
|
|
- existingUploadedFiles.firstWhere(
|
|
|
+ final File? fileMissingLocalButSameCollection =
|
|
|
+ existingUploadedFiles.firstWhereOrNull(
|
|
|
(e) => e.collectionID == toCollectionID && e.localID == null,
|
|
|
- orElse: () => null,
|
|
|
);
|
|
|
if (fileMissingLocalButSameCollection != null) {
|
|
|
// update the local id of the existing file and delete the fileToUpload
|
|
@@ -573,10 +573,10 @@ class FileUploader {
|
|
|
fileMissingLocalButSameCollection.localID = fileToUpload.localID;
|
|
|
// set localID for the given uploadedID across collections
|
|
|
await FilesDB.instance.updateLocalIDForUploaded(
|
|
|
- fileMissingLocalButSameCollection.uploadedFileID,
|
|
|
- fileToUpload.localID,
|
|
|
+ fileMissingLocalButSameCollection.uploadedFileID!,
|
|
|
+ fileToUpload.localID!,
|
|
|
);
|
|
|
- await FilesDB.instance.deleteByGeneratedID(fileToUpload.generatedID);
|
|
|
+ await FilesDB.instance.deleteByGeneratedID(fileToUpload.generatedID!);
|
|
|
Bus.instance.fire(
|
|
|
LocalPhotosUpdatedEvent(
|
|
|
[fileToUpload],
|
|
@@ -588,10 +588,9 @@ class FileUploader {
|
|
|
}
|
|
|
|
|
|
// case c and d
|
|
|
- final File fileExistsButDifferentCollection =
|
|
|
- existingUploadedFiles.firstWhere(
|
|
|
+ final File? fileExistsButDifferentCollection =
|
|
|
+ existingUploadedFiles.firstWhereOrNull(
|
|
|
(e) => e.collectionID != toCollectionID,
|
|
|
- orElse: () => null,
|
|
|
);
|
|
|
if (fileExistsButDifferentCollection != null) {
|
|
|
_logger.fine(
|
|
@@ -610,7 +609,7 @@ class FileUploader {
|
|
|
.where(
|
|
|
(e) => e.localID != null,
|
|
|
)
|
|
|
- .map((e) => e.localID)
|
|
|
+ .map((e) => e.localID!)
|
|
|
.toSet();
|
|
|
_logger.fine(
|
|
|
"Found hashMatch but probably with diff localIDs "
|
|
@@ -621,7 +620,7 @@ class FileUploader {
|
|
|
}
|
|
|
|
|
|
Future<void> _onUploadDone(
|
|
|
- MediaUploadData mediaUploadData,
|
|
|
+ MediaUploadData? mediaUploadData,
|
|
|
bool uploadCompleted,
|
|
|
bool uploadHardFailure,
|
|
|
File file,
|
|
@@ -636,7 +635,7 @@ class FileUploader {
|
|
|
// succeeds.
|
|
|
if ((io.Platform.isIOS && (uploadCompleted || uploadHardFailure)) ||
|
|
|
(uploadCompleted && file.isSharedMediaToAppSandbox)) {
|
|
|
- await mediaUploadData.sourceFile.delete();
|
|
|
+ await mediaUploadData.sourceFile?.delete();
|
|
|
}
|
|
|
}
|
|
|
if (io.File(encryptedFilePath).existsSync()) {
|
|
@@ -645,11 +644,11 @@ class FileUploader {
|
|
|
if (io.File(encryptedThumbnailPath).existsSync()) {
|
|
|
await io.File(encryptedThumbnailPath).delete();
|
|
|
}
|
|
|
- await _uploadLocks.releaseLock(file.localID, _processType.toString());
|
|
|
+ await _uploadLocks.releaseLock(file.localID!, _processType.toString());
|
|
|
}
|
|
|
|
|
|
Future _onInvalidFileError(File file, InvalidFileError e) async {
|
|
|
- final String ext = file.title == null ? "no title" : extension(file.title);
|
|
|
+ final String ext = file.title == null ? "no title" : extension(file.title!);
|
|
|
_logger.severe(
|
|
|
"Invalid file: (ext: $ext) encountered: " + file.toString(),
|
|
|
e,
|
|
@@ -813,7 +812,7 @@ class FileUploader {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- Future<void> _uploadURLFetchInProgress;
|
|
|
+ Future<void>? _uploadURLFetchInProgress;
|
|
|
|
|
|
Future<void> fetchUploadURLs(int fileCount) async {
|
|
|
_uploadURLFetchInProgress ??= Future<void>(() async {
|
|
@@ -830,11 +829,11 @@ class FileUploader {
|
|
|
_uploadURLs.addAll(urls);
|
|
|
} on DioError catch (e, s) {
|
|
|
if (e.response != null) {
|
|
|
- if (e.response.statusCode == 402) {
|
|
|
+ if (e.response!.statusCode == 402) {
|
|
|
final error = NoActiveSubscriptionError();
|
|
|
clearQueue(error);
|
|
|
throw error;
|
|
|
- } else if (e.response.statusCode == 426) {
|
|
|
+ } else if (e.response!.statusCode == 426) {
|
|
|
final error = StorageLimitExceededError();
|
|
|
clearQueue(error);
|
|
|
throw error;
|
|
@@ -858,7 +857,7 @@ class FileUploader {
|
|
|
Future<String> _putFile(
|
|
|
UploadURL uploadURL,
|
|
|
io.File file, {
|
|
|
- int contentLength,
|
|
|
+ int? contentLength,
|
|
|
int attempt = 1,
|
|
|
}) async {
|
|
|
final fileSize = contentLength ?? await file.length();
|
|
@@ -930,7 +929,7 @@ class FileUploader {
|
|
|
final completer = _queue.remove(upload.key).completer;
|
|
|
final dbFile =
|
|
|
await FilesDB.instance.getFile(upload.value.file.generatedID);
|
|
|
- if (dbFile.uploadedFileID != null) {
|
|
|
+ if (dbFile?.uploadedFileID != null) {
|
|
|
_logger.info("Background upload success detected");
|
|
|
completer.complete(dbFile);
|
|
|
} else {
|