123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- import 'dart:async';
- import 'dart:collection';
- import 'dart:typed_data';
- import 'package:dio/dio.dart';
- import 'package:flutter_sodium/flutter_sodium.dart';
- import 'package:logging/logging.dart';
- import 'package:photos/core/cache/thumbnail_cache.dart';
- import 'package:photos/core/configuration.dart';
- import 'package:photos/core/constants.dart';
- import 'package:photos/core/errors.dart';
- import 'package:photos/core/network.dart';
- import 'package:photos/models/file.dart';
- import 'package:photos/utils/crypto_util.dart';
- import 'package:photos/utils/file_util.dart';
- import 'dart:io' as io;
- final _logger = Logger("ThumbnailUtil");
- final _map = LinkedHashMap<int, FileDownloadItem>();
- final _queue = Queue<int>();
- const int kMaximumConcurrentDownloads = 500;
- class FileDownloadItem {
- final File file;
- final Completer<Uint8List> completer;
- final CancelToken cancelToken;
- int counter = 0; // number of times file download was requested
- FileDownloadItem(this.file, this.completer, this.cancelToken, this.counter);
- }
- Future<Uint8List> getThumbnailFromServer(File file) async {
- final cachedThumbnail = getCachedThumbnail(file);
- if (cachedThumbnail.existsSync()) {
- final data = cachedThumbnail.readAsBytesSync();
- ThumbnailLruCache.put(file, data);
- return data;
- }
- if (!_map.containsKey(file.uploadedFileID)) {
- if (_queue.length > kMaximumConcurrentDownloads) {
- final id = _queue.removeFirst();
- final item = _map.remove(id);
- item.cancelToken.cancel();
- item.completer.completeError(RequestCancelledError());
- }
- final item =
- FileDownloadItem(file, Completer<Uint8List>(), CancelToken(), 1);
- _map[file.uploadedFileID] = item;
- _queue.add(file.uploadedFileID);
- _downloadItem(item);
- return item.completer.future;
- } else {
- _map[file.uploadedFileID].counter++;
- return _map[file.uploadedFileID].completer.future;
- }
- }
- void removePendingGetThumbnailRequestIfAny(File file) {
- if (_map.containsKey(file.uploadedFileID)) {
- final item = _map[file.uploadedFileID];
- item.counter--;
- if (item.counter <= 0) {
- _map.remove(file.uploadedFileID);
- item.cancelToken.cancel();
- _queue.removeWhere((element) => element == file.uploadedFileID);
- }
- }
- }
- void _downloadItem(FileDownloadItem item) async {
- try {
- await _downloadAndDecryptThumbnail(item);
- } catch (e, s) {
- _logger.severe(
- "Failed to download thumbnail " + item.file.toString(), e, s);
- item.completer.completeError(e);
- }
- _queue.removeWhere((element) => element == item.file.uploadedFileID);
- _map.remove(item.file.uploadedFileID);
- }
- Future<void> _downloadAndDecryptThumbnail(FileDownloadItem item) async {
- final file = item.file;
- var encryptedThumbnail;
- try {
- encryptedThumbnail = (await Network.instance.getDio().get(
- file.getThumbnailUrl(),
- options: Options(
- headers: {"X-Auth-Token": Configuration.instance.getToken()},
- responseType: ResponseType.bytes,
- ),
- cancelToken: item.cancelToken,
- ))
- .data;
- } catch (e) {
- if (e is DioError && CancelToken.isCancel(e)) {
- return;
- }
- throw e;
- }
- if (!_map.containsKey(file.uploadedFileID)) {
- return;
- }
- final thumbnailDecryptionKey = decryptFileKey(file);
- var data = await CryptoUtil.decryptChaCha(
- encryptedThumbnail,
- thumbnailDecryptionKey,
- Sodium.base642bin(file.thumbnailDecryptionHeader),
- );
- final thumbnailSize = data.length;
- if (thumbnailSize > THUMBNAIL_DATA_LIMIT) {
- data = await compressThumbnail(data);
- }
- ThumbnailLruCache.put(item.file, data);
- final cachedThumbnail = getCachedThumbnail(item.file);
- if (cachedThumbnail.existsSync()) {
- cachedThumbnail.deleteSync();
- }
- cachedThumbnail.writeAsBytes(data);
- if (_map.containsKey(file.uploadedFileID)) {
- try {
- item.completer.complete(data);
- } catch (e) {
- _logger.severe("Error while completing request for " +
- file.uploadedFileID.toString());
- }
- }
- }
- io.File getCachedThumbnail(File file) {
- final thumbnailCacheDirectory =
- Configuration.instance.getThumbnailCacheDirectory();
- return io.File(thumbnailCacheDirectory + "/" + file.uploadedFileID.toString());
- }
|