Throttle the maximum number of concurrent thumbnail downloads
This commit is contained in:
parent
15018b64ca
commit
2af6516c90
1 changed files with 89 additions and 42 deletions
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:io' as io;
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
@ -161,8 +163,26 @@ Future<Uint8List> getBytesFromDisk(File file, {int quality = 100}) async {
|
|||
final Map<int, Future<io.File>> fileDownloadsInProgress =
|
||||
Map<int, Future<io.File>>();
|
||||
|
||||
final Map<int, Future<io.File>> thumbnailDownloadsInProgress =
|
||||
Map<int, Future<io.File>>();
|
||||
final _thumbnailQueue = LinkedHashMap<int, FileDownloadItem>();
|
||||
int _currentlyDownloading = 0;
|
||||
int kMaximumConcurrentDownloads = 32;
|
||||
|
||||
class FileDownloadItem {
|
||||
final File file;
|
||||
final Completer<io.File> completer;
|
||||
DownloadStatus status;
|
||||
|
||||
FileDownloadItem(
|
||||
this.file,
|
||||
this.completer, {
|
||||
this.status = DownloadStatus.not_started,
|
||||
});
|
||||
}
|
||||
|
||||
enum DownloadStatus {
|
||||
not_started,
|
||||
in_progress,
|
||||
}
|
||||
|
||||
Future<io.File> getFileFromServer(File file,
|
||||
{ProgressCallback progressCallback}) async {
|
||||
|
@ -190,14 +210,15 @@ Future<io.File> getThumbnailFromServer(File file) async {
|
|||
.getFileFromCache(file.getThumbnailUrl())
|
||||
.then((info) {
|
||||
if (info == null) {
|
||||
if (!thumbnailDownloadsInProgress.containsKey(file.uploadedFileID)) {
|
||||
thumbnailDownloadsInProgress[file.uploadedFileID] =
|
||||
_downloadAndDecryptThumbnail(file).then((data) {
|
||||
ThumbnailFileLruCache.put(file, data);
|
||||
return data;
|
||||
});
|
||||
if (!_thumbnailQueue.containsKey(file.uploadedFileID)) {
|
||||
final completer = Completer<io.File>();
|
||||
_thumbnailQueue[file.uploadedFileID] =
|
||||
FileDownloadItem(file, completer);
|
||||
_pollQueue();
|
||||
return completer.future;
|
||||
} else {
|
||||
return _thumbnailQueue[file.uploadedFileID].completer.future;
|
||||
}
|
||||
return thumbnailDownloadsInProgress[file.uploadedFileID];
|
||||
} else {
|
||||
ThumbnailFileLruCache.put(file, info.file);
|
||||
return info.file;
|
||||
|
@ -205,6 +226,39 @@ Future<io.File> getThumbnailFromServer(File file) async {
|
|||
});
|
||||
}
|
||||
|
||||
void removePendingGetThumbnailRequestIfAny(File file) {
|
||||
if (_thumbnailQueue[file.uploadedFileID] != null &&
|
||||
_thumbnailQueue[file.uploadedFileID].status ==
|
||||
DownloadStatus.not_started) {
|
||||
_thumbnailQueue.remove(file.uploadedFileID);
|
||||
}
|
||||
}
|
||||
|
||||
void _pollQueue() async {
|
||||
if (_thumbnailQueue.length > 0 &&
|
||||
_currentlyDownloading < kMaximumConcurrentDownloads) {
|
||||
final firstPendingEntry = _thumbnailQueue.entries.firstWhere(
|
||||
(entry) => entry.value.status == DownloadStatus.not_started,
|
||||
orElse: () => null);
|
||||
if (firstPendingEntry != null) {
|
||||
final item = firstPendingEntry.value;
|
||||
_currentlyDownloading++;
|
||||
item.status = DownloadStatus.in_progress;
|
||||
try {
|
||||
final data = await _downloadAndDecryptThumbnail(item.file);
|
||||
ThumbnailFileLruCache.put(item.file, data);
|
||||
item.completer.complete(data);
|
||||
} catch (e) {
|
||||
_logger.severe("Downloading thumbnail failed", e);
|
||||
item.completer.completeError(e);
|
||||
}
|
||||
_currentlyDownloading--;
|
||||
_thumbnailQueue.remove(firstPendingEntry.key);
|
||||
_pollQueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<io.File> _downloadAndDecrypt(File file, BaseCacheManager cacheManager,
|
||||
{ProgressCallback progressCallback}) async {
|
||||
_logger.info("Downloading file " + file.uploadedFileID.toString());
|
||||
|
@ -272,47 +326,40 @@ Future<io.File> _downloadAndDecrypt(File file, BaseCacheManager cacheManager,
|
|||
}
|
||||
|
||||
Future<io.File> _downloadAndDecryptThumbnail(File file) async {
|
||||
_logger.info("Downloading thumbnail for " + file.title);
|
||||
final temporaryPath = Configuration.instance.getTempDirectory() +
|
||||
file.generatedID.toString() +
|
||||
"_thumbnail.decrypted";
|
||||
return Network.instance
|
||||
.getDio()
|
||||
.download(
|
||||
await Network.instance.getDio().download(
|
||||
file.getThumbnailUrl(),
|
||||
temporaryPath,
|
||||
options: Options(
|
||||
headers: {"X-Auth-Token": Configuration.instance.getToken()},
|
||||
),
|
||||
)
|
||||
.then((_) async {
|
||||
final encryptedFile = io.File(temporaryPath);
|
||||
final thumbnailDecryptionKey = decryptFileKey(file);
|
||||
var data = CryptoUtil.decryptChaCha(
|
||||
encryptedFile.readAsBytesSync(),
|
||||
thumbnailDecryptionKey,
|
||||
Sodium.base642bin(file.thumbnailDecryptionHeader),
|
||||
);
|
||||
final thumbnailSize = data.length;
|
||||
if (thumbnailSize > THUMBNAIL_DATA_LIMIT) {
|
||||
data = await compressThumbnail(data);
|
||||
_logger.info("Compressed thumbnail from " +
|
||||
thumbnailSize.toString() +
|
||||
" to " +
|
||||
data.length.toString());
|
||||
}
|
||||
encryptedFile.deleteSync();
|
||||
final cachedThumbnail = ThumbnailCacheManager().putFile(
|
||||
file.getThumbnailUrl(),
|
||||
data,
|
||||
eTag: file.getThumbnailUrl(),
|
||||
maxAge: Duration(days: 365),
|
||||
);
|
||||
thumbnailDownloadsInProgress.remove(file.uploadedFileID);
|
||||
return cachedThumbnail;
|
||||
}).catchError((e, s) {
|
||||
_logger.severe("Error downloading thumbnail ", e, s);
|
||||
thumbnailDownloadsInProgress.remove(file.uploadedFileID);
|
||||
});
|
||||
);
|
||||
final encryptedFile = io.File(temporaryPath);
|
||||
final thumbnailDecryptionKey = decryptFileKey(file);
|
||||
var data = CryptoUtil.decryptChaCha(
|
||||
encryptedFile.readAsBytesSync(),
|
||||
thumbnailDecryptionKey,
|
||||
Sodium.base642bin(file.thumbnailDecryptionHeader),
|
||||
);
|
||||
final thumbnailSize = data.length;
|
||||
if (thumbnailSize > THUMBNAIL_DATA_LIMIT) {
|
||||
data = await compressThumbnail(data);
|
||||
_logger.info("Compressed thumbnail from " +
|
||||
thumbnailSize.toString() +
|
||||
" to " +
|
||||
data.length.toString());
|
||||
}
|
||||
encryptedFile.deleteSync();
|
||||
final cachedThumbnail = ThumbnailCacheManager().putFile(
|
||||
file.getThumbnailUrl(),
|
||||
data,
|
||||
eTag: file.getThumbnailUrl(),
|
||||
maxAge: Duration(days: 365),
|
||||
);
|
||||
return cachedThumbnail;
|
||||
}
|
||||
|
||||
Uint8List decryptFileKey(File file) {
|
||||
|
|
Loading…
Add table
Reference in a new issue