diff --git a/lib/data/holidays.dart b/lib/data/holidays.dart index c8ed8f8b0..4601cd92c 100644 --- a/lib/data/holidays.dart +++ b/lib/data/holidays.dart @@ -1,12 +1,26 @@ +import "package:flutter/cupertino.dart"; + class HolidayData { final String name; final int month; final int day; - const HolidayData(this.name, {required this.month, required this.day}); + HolidayData(this.name, {required this.month, required this.day}); +} + +// Based on the locale, this return holidays that have fixed date as per the +// Gregorian calendar. For example, Christmas is always on December 25th. +List getHolidays(BuildContext context) { + final locale = Localizations.localeOf(context); + if (localeToHolidays.containsKey(locale.toLanguageTag())) { + return localeToHolidays[locale.toLanguageTag()]!; + } else if (localeToHolidays.containsKey(locale.languageCode)) { + return localeToHolidays[locale.languageCode]!; + } + return _defaultHolidays; } -const List allHolidays = [ +List _defaultHolidays = [ HolidayData('New Year', month: 1, day: 1), HolidayData('Epiphany', month: 1, day: 6), HolidayData('Pongal', month: 1, day: 14), @@ -18,9 +32,101 @@ const List allHolidays = [ HolidayData('May Day', month: 5, day: 1), HolidayData('Midsummer\'s Eve', month: 6, day: 24), HolidayData('Midsummer Day', month: 6, day: 25), - HolidayData('Christmas Eve', month: 12, day: 24), HolidayData('Halloween', month: 10, day: 31), + HolidayData('Christmas Eve', month: 12, day: 24), HolidayData('Christmas', month: 12, day: 25), HolidayData('Boxing Day', month: 12, day: 26), HolidayData('New Year\'s Eve', month: 12, day: 31), ]; +Map> localeToHolidays = { + 'it': [ + HolidayData('Capodanno', month: 1, day: 1), + // New Year's Day + HolidayData('Epifania', month: 1, day: 6), + // Epiphany + HolidayData('San Valentino', month: 2, day: 14), + // Valentine's Day + HolidayData('Festa della Liberazione', month: 4, day: 25), + // Liberation Day + HolidayData('Primo Maggio', month: 5, day: 1), + // Labor Day + HolidayData('Festa della Repubblica', month: 6, day: 2), + // Republic Day + HolidayData('Ferragosto', month: 8, day: 15), + // Assumption of Mary + HolidayData('Halloween', month: 10, day: 31), + // Halloween + HolidayData('Ognissanti', month: 11, day: 1), + // All Saints' Day + HolidayData('Immacolata Concezione', month: 12, day: 8), + // Immaculate Conception + HolidayData('Natale', month: 12, day: 25), + // Christmas Day + HolidayData('Vigilia di Capodanno', month: 12, day: 31), + // New Year's Eve + ], + 'fr': [ + HolidayData('Jour de l\'An', month: 1, day: 1), // New Year's Day + HolidayData('Fête du Travail', month: 5, day: 1), // Labour Day + HolidayData('Fête Nationale', month: 7, day: 14), // Bastille Day + HolidayData('Assomption', month: 8, day: 15), // Assumption of Mary + HolidayData('Halloween', month: 10, day: 31), // Halloween + HolidayData('Toussaint', month: 11, day: 1), // All Saints' Day + HolidayData('Jour de l\'Armistice', month: 11, day: 11), // Armistice Day + HolidayData('Noël', month: 12, day: 25), // Christmas + HolidayData('Lendemain de Noël', month: 12, day: 26), // Boxing Day + HolidayData('Saint-Sylvestre', month: 12, day: 31), // New Year's Eve + ], + 'de': [ + HolidayData('Neujahrstag', month: 1, day: 1), + // New Year + HolidayData('Valentinstag', month: 2, day: 14), + // Valentine's Day + HolidayData('Tag der Arbeit', month: 5, day: 1), + // Labor Day + HolidayData('Tag der Deutschen Einheit', month: 10, day: 3), + // German Unity Day + HolidayData('Halloween', month: 10, day: 31), + // Halloween + HolidayData('Erster Weihnachtstag', month: 12, day: 25), + // First Christmas Day + HolidayData('Zweiter Weihnachtstag', month: 12, day: 26), + // Second Christmas Day (Boxing Day) + HolidayData('Silvester', month: 12, day: 31), + // New Year's Eve + ], + 'nl': [ + HolidayData('Nieuwjaarsdag', month: 1, day: 1), // New Year's Day + HolidayData('Valentijnsdag', month: 2, day: 14), // Valentine's Day + HolidayData('Koningsdag', month: 4, day: 27), // King's Day + HolidayData('Bevrijdingsdag', month: 5, day: 5), // Liberation Day + HolidayData('Hemelvaartsdag', month: 5, day: 26), // Ascension Day + HolidayData('Halloween', month: 10, day: 31), // Halloween + HolidayData('Sinterklaas', month: 12, day: 5), // Sinterklaas + HolidayData('Eerste Kerstdag', month: 12, day: 25), // First Christmas Day + HolidayData('Tweede Kerstdag', month: 12, day: 26), // Second Christmas Day + HolidayData('Oudejaarsdag', month: 12, day: 31), // New Year's Eve + ], + 'es': [ + HolidayData('Año Nuevo', month: 1, day: 1), + // New Year's Day + HolidayData('San Valentín', month: 2, day: 14), + // Valentine's Day + HolidayData('Día del Trabajador', month: 5, day: 1), + // Labor Day + HolidayData('Día de la Hispanidad', month: 10, day: 12), + // Hispanic Day + HolidayData('Halloween', month: 10, day: 31), + // Halloween + HolidayData('Día de Todos los Santos', month: 11, day: 1), + // All Saints' Day + HolidayData('Día de la Constitución', month: 12, day: 6), + // Constitution Day + HolidayData('La Inmaculada Concepción', month: 12, day: 8), + // Immaculate Conception + HolidayData('Navidad', month: 12, day: 25), + // Christmas Day + HolidayData('Nochevieja', month: 12, day: 31), + // New Year's Eve + ], +}; diff --git a/lib/data/months.dart b/lib/data/months.dart index 530a869c1..4000b9a30 100644 --- a/lib/data/months.dart +++ b/lib/data/months.dart @@ -1,17 +1,20 @@ -List allMonths = [ - MonthData('January', 1), - MonthData('February', 2), - MonthData('March', 3), - MonthData('April', 4), - MonthData('May', 5), - MonthData('June', 6), - MonthData('July', 7), - MonthData('August', 8), - MonthData('September', 9), - MonthData('October', 10), - MonthData('November', 11), - MonthData('December', 12), -]; +import "package:flutter/widgets.dart"; +import "package:intl/intl.dart"; + +final Map> _cache = {}; + +List getMonthData(BuildContext context) { + final locale = Localizations.localeOf(context).toString(); + + if (!_cache.containsKey(locale)) { + final dateSymbols = DateFormat('MMMM', locale).dateSymbols; + _cache[locale] = List.generate( + 12, + (index) => MonthData(dateSymbols.MONTHS[index], index + 1), + ); + } + return _cache[locale]!; +} class MonthData { final String name; diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index 0b2e15a2d..d4e31a2ec 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -13,6 +13,7 @@ const List appSupportedLocales = [ Locale('en'), Locale('es'), Locale('de'), + Locale('fr'), Locale('it'), Locale("nl"), Locale("zh", "CN"), diff --git a/lib/services/search_service.dart b/lib/services/search_service.dart index 4331eca05..30a71cbf9 100644 --- a/lib/services/search_service.dart +++ b/lib/services/search_service.dart @@ -1,3 +1,4 @@ +import "package:flutter/cupertino.dart"; import 'package:logging/logging.dart'; import 'package:photos/core/event_bus.dart'; import 'package:photos/data/holidays.dart'; @@ -93,7 +94,8 @@ class SearchService { final List searchResults = []; for (var yearData in YearsData.instance.yearsData) { if (yearData.year.startsWith(yearFromQuery)) { - final List filesInYear = await _getFilesInYear(yearData.duration); + final List filesInYear = + await _getFilesInYear(yearData.duration); if (filesInYear.isNotEmpty) { searchResults.add( GenericSearchResult( @@ -109,11 +111,16 @@ class SearchService { } Future> getHolidaySearchResults( + BuildContext context, String query, ) async { final List searchResults = []; + if (query.isEmpty) { + return searchResults; + } + final holidays = getHolidays(context); - for (var holiday in allHolidays) { + for (var holiday in holidays) { if (holiday.name.toLowerCase().contains(query.toLowerCase())) { final matchedFiles = await FilesDB.instance.getFilesCreatedWithinDurations( @@ -314,9 +321,12 @@ class SearchService { return searchResults; } - Future> getMonthSearchResults(String query) async { + Future> getMonthSearchResults( + BuildContext context, + String query, + ) async { final List searchResults = []; - for (var month in _getMatchingMonths(query)) { + for (var month in _getMatchingMonths(context, query)) { final matchedFiles = await FilesDB.instance.getFilesCreatedWithinDurations( _getDurationsOfMonthInEveryYear(month.monthNumber), @@ -337,10 +347,11 @@ class SearchService { } Future> getDateResults( + BuildContext context, String query, ) async { final List searchResults = []; - final potentialDates = _getPossibleEventDate(query); + final potentialDates = _getPossibleEventDate(context, query); for (var potentialDate in potentialDates) { final int day = potentialDate.item1; @@ -365,8 +376,8 @@ class SearchService { return searchResults; } - List _getMatchingMonths(String query) { - return allMonths + List _getMatchingMonths(BuildContext context, String query) { + return getMonthData(context) .where( (monthData) => monthData.name.toLowerCase().startsWith(query.toLowerCase()), @@ -391,7 +402,7 @@ class SearchService { final int startYear = year ?? searchStartYear; final int endYear = year ?? currentYear; for (var yr = startYear; yr <= endYear; yr++) { - if (isValidDate(day: day, month: month, year: yr)) { + if (isValidGregorianDate(day: day, month: month, year: yr)) { durationsOfHolidayInEveryYear.add([ DateTime(yr, month, day).microsecondsSinceEpoch, DateTime(yr, month, day + 1).microsecondsSinceEpoch, @@ -414,7 +425,10 @@ class SearchService { return durationsOfMonthInEveryYear; } - List> _getPossibleEventDate(String query) { + List> _getPossibleEventDate( + BuildContext context, + String query, + ) { final List> possibleEvents = []; if (query.trim().isEmpty) { return possibleEvents; @@ -434,8 +448,9 @@ class SearchService { if (day == null || day < 1 || day > 31) { return possibleEvents; } - final List potentialMonth = - resultCount > 1 ? _getMatchingMonths(result[1]) : allMonths; + final List potentialMonth = resultCount > 1 + ? _getMatchingMonths(context, result[1]) + : getMonthData(context); final int? parsedYear = resultCount >= 3 ? int.tryParse(result[2]) : null; final List matchingYears = []; if (parsedYear != null) { diff --git a/lib/ui/home/preserve_footer_widget.dart b/lib/ui/home/preserve_footer_widget.dart index c2367a2d2..a9a3019c8 100644 --- a/lib/ui/home/preserve_footer_widget.dart +++ b/lib/ui/home/preserve_footer_widget.dart @@ -2,10 +2,8 @@ import 'dart:async'; import "dart:io"; import 'package:flutter/material.dart'; -import "package:flutter/services.dart"; import "package:logging/logging.dart"; import 'package:photo_manager/photo_manager.dart'; -import "package:photos/core/configuration.dart"; import "package:photos/generated/l10n.dart"; import 'package:photos/services/local_sync_service.dart'; import 'package:photos/ui/common/gradient_button.dart'; diff --git a/lib/ui/viewer/file/file_app_bar.dart b/lib/ui/viewer/file/file_app_bar.dart index ed8bd5dd2..ac666198b 100644 --- a/lib/ui/viewer/file/file_app_bar.dart +++ b/lib/ui/viewer/file/file_app_bar.dart @@ -11,6 +11,7 @@ import 'package:photos/core/event_bus.dart'; import 'package:photos/db/files_db.dart'; import 'package:photos/events/local_photos_updated_event.dart'; import "package:photos/generated/l10n.dart"; +import "package:photos/l10n/l10n.dart"; import "package:photos/models/file/extensions/file_props.dart"; import 'package:photos/models/file/file.dart'; import 'package:photos/models/file/file_type.dart'; @@ -307,7 +308,11 @@ class FileAppBarState extends State { } Future _download(EnteFile file) async { - final dialog = createProgressDialog(context, "Downloading..."); + final dialog = createProgressDialog( + context, + context.l10n.downloading, + isDismissible: true, + ); await dialog.show(); try { final FileType type = file.fileType; diff --git a/lib/ui/viewer/file/video_widget.dart b/lib/ui/viewer/file/video_widget.dart index a273ba8ed..806e4dc63 100644 --- a/lib/ui/viewer/file/video_widget.dart +++ b/lib/ui/viewer/file/video_widget.dart @@ -92,6 +92,9 @@ class _VideoWidgetState extends State { getFileFromServer( widget.file, progressCallback: (count, total) { + if(!mounted) { + return; + } _progressNotifier.value = count / (widget.file.fileSize ?? total); if (_progressNotifier.value == 1) { if (mounted) { @@ -100,18 +103,24 @@ class _VideoWidgetState extends State { } }, ).then((file) { - if (file != null) { + if (file != null && mounted) { _setVideoPlayerController(file: file); } }).onError((error, stackTrace) { - showErrorDialog(context, "Error", S.of(context).failedToDownloadVideo); + if(mounted) { + showErrorDialog(context, "Error", S + .of(context).failedToDownloadVideo,); + } }); } @override void dispose() { + removeCallBack(widget.file); _videoPlayerController?.dispose(); _chewieController?.dispose(); + _progressNotifier.dispose(); + if (_wakeLockEnabledHere) { unawaited( WakelockPlus.enabled.then((isEnabled) { diff --git a/lib/ui/viewer/search/search_widget.dart b/lib/ui/viewer/search/search_widget.dart index a704bd6a1..c37e453d2 100644 --- a/lib/ui/viewer/search/search_widget.dart +++ b/lib/ui/viewer/search/search_widget.dart @@ -141,7 +141,7 @@ class _SearchWidgetState extends State { onChanged: (value) async { _query = value; final List allResults = - await getSearchResultsForQuery(value); + await getSearchResultsForQuery(context, value); /*checking if _query == value to make sure that the results are from the current query and not from the previous query (race condition).*/ if (mounted && _query == value) { @@ -174,12 +174,15 @@ class _SearchWidgetState extends State { super.dispose(); } - Future> getSearchResultsForQuery(String query) async { + Future> getSearchResultsForQuery( + BuildContext context, + String query, + ) async { final Completer> completer = Completer(); _debouncer.run( () { - return _getSearchResultsFromService(query, completer); + return _getSearchResultsFromService(context, query, completer); }, ); @@ -187,6 +190,7 @@ class _SearchWidgetState extends State { } Future _getSearchResultsFromService( + BuildContext context, String query, Completer completer, ) async { @@ -202,7 +206,7 @@ class _SearchWidgetState extends State { } final holidayResults = - await _searchService.getHolidaySearchResults(query); + await _searchService.getHolidaySearchResults(context, query); allResults.addAll(holidayResults); final fileTypeSearchResults = @@ -224,10 +228,12 @@ class _SearchWidgetState extends State { await _searchService.getCollectionSearchResults(query); allResults.addAll(collectionResults); - final monthResults = await _searchService.getMonthSearchResults(query); + final monthResults = + await _searchService.getMonthSearchResults(context, query); allResults.addAll(monthResults); - final possibleEvents = await _searchService.getDateResults(query); + final possibleEvents = + await _searchService.getDateResults(context, query); allResults.addAll(possibleEvents); } catch (e, s) { _logger.severe("error during search", e, s); diff --git a/lib/utils/date_time_util.dart b/lib/utils/date_time_util.dart index ce510398b..942896859 100644 --- a/lib/utils/date_time_util.dart +++ b/lib/utils/date_time_util.dart @@ -2,8 +2,6 @@ import "package:flutter/cupertino.dart"; import 'package:flutter/foundation.dart'; import 'package:intl/intl.dart'; -const Set monthWith31Days = {1, 3, 5, 7, 8, 10, 12}; -const Set monthWith30Days = {4, 6, 9, 11}; Map _months = { 1: "Jan", 2: "Feb", @@ -123,22 +121,22 @@ String secondsToHHMMSS(int value) { return result; } -bool isValidDate({ +bool isValidGregorianDate({ required int day, required int month, required int year, }) { - if (day < 0 || day > 31 || month < 0 || month > 12 || year < 0) { + if (day <= 0 || day > 31 || month <= 0 || month > 12 || year < 0) { return false; } - if (monthWith30Days.contains(month) && day > 30) { + if ((month == 4 || month == 6 || month == 9 || month == 11) && day > 30) { return false; } if (month == 2) { if (day > 29) { return false; } - if (day == 29 && year % 4 != 0) { + if (day == 29 && (year % 4 != 0 || (year % 100 == 0 && year % 400 != 0))) { return false; } } diff --git a/lib/utils/fake_progress.dart b/lib/utils/fake_progress.dart new file mode 100644 index 000000000..adcc3ee58 --- /dev/null +++ b/lib/utils/fake_progress.dart @@ -0,0 +1,44 @@ +import 'dart:async'; + +import "package:flutter/foundation.dart"; + +typedef FakeProgressCallback = void Function(int count); + +class FakePeriodicProgress { + final FakeProgressCallback? callback; + final Duration duration; + late Timer _timer; + bool _shouldRun = true; + int runCount = 0; + + FakePeriodicProgress({ + required this.callback, + required this.duration, + }); + + void start() { + assert(_shouldRun, "Cannot start a stopped FakePeriodicProgress"); + Future.delayed(duration, _invokePeriodically); + } + + void stop() { + if (_shouldRun) { + _shouldRun = false; + _timer.cancel(); + } + } + + void _invokePeriodically() { + if (_shouldRun) { + try { + runCount++; + callback?.call(runCount); + } catch (e) { + debugPrint("Error in FakePeriodicProgress callback: $e"); + stop(); + return; + } + _timer = Timer(duration, _invokePeriodically); + } + } +} diff --git a/lib/utils/file_download_util.dart b/lib/utils/file_download_util.dart index 0913a7acd..527018f67 100644 --- a/lib/utils/file_download_util.dart +++ b/lib/utils/file_download_util.dart @@ -1,75 +1,97 @@ import 'dart:io'; -import 'dart:typed_data'; 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'; import 'package:photos/core/network/network.dart'; import 'package:photos/models/file/file.dart'; +import "package:photos/models/file/file_type.dart"; import 'package:photos/services/collections_service.dart'; import 'package:photos/utils/crypto_util.dart'; import "package:photos/utils/data_util.dart"; +import "package:photos/utils/fake_progress.dart"; final _logger = Logger("file_download_util"); Future downloadAndDecrypt( EnteFile file, { ProgressCallback? progressCallback, -}) { +}) async { final String logPrefix = 'File-${file.uploadedFileID}:'; - _logger.info('$logPrefix starting download'); - final encryptedFilePath = Configuration.instance.getTempDirectory() + - file.generatedID.toString() + - ".encrypted"; + _logger + .info('$logPrefix starting download ${formatBytes(file.fileSize ?? 0)}'); + + final String tempDir = Configuration.instance.getTempDirectory(); + final String encryptedFilePath = "$tempDir${file.generatedID}.encrypted"; final encryptedFile = File(encryptedFilePath); + final startTime = DateTime.now().millisecondsSinceEpoch; - return NetworkClient.instance - .getDio() - .download( - file.downloadUrl, - encryptedFilePath, - options: Options( - headers: {"X-Auth-Token": Configuration.instance.getToken()}, - ), - onReceiveProgress: progressCallback, - ) - .then((response) async { - if (response.statusCode != 200) { - _logger.warning('$logPrefix download failed ${response.toString()}'); - return null; - } else if (!encryptedFile.existsSync()) { - _logger.warning('$logPrefix incomplete download, file not found'); + + 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) { + _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 ?? 0) > 0) - ? file.fileSize! - : await encryptedFile.length(); - final double speedInKBps = sizeInBytes / - 1024.0 / - ((DateTime.now().millisecondsSinceEpoch - startTime) / 1000); + + final int sizeInBytes = file.fileSize ?? await encryptedFile.length(); + final double elapsedSeconds = + (DateTime.now().millisecondsSinceEpoch - startTime) / 1000; + final double speedInKBps = sizeInBytes / 1024.0 / elapsedSeconds; + _logger.info( - "$logPrefix download completed: ${formatBytes(sizeInBytes)}, avg speed: ${speedInKBps.toStringAsFixed(2)} KB/s", + '$logPrefix download completed: ${formatBytes(sizeInBytes)}, avg speed: ${speedInKBps.toStringAsFixed(2)} KB/s', ); - final decryptedFilePath = Configuration.instance.getTempDirectory() + - file.generatedID.toString() + - ".decrypted"; + 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; try { + // Start the periodic callback after initial 5 seconds + fakeProgress?.start(); await CryptoUtil.decryptFile( encryptedFilePath, decryptedFilePath, CryptoUtil.base642bin(file.fileDecryptionHeader!), getFileKey(file), ); + fakeProgress?.stop(); + _logger.info('$logPrefix decryption completed'); } catch (e, s) { - _logger.severe("failed to decrypt file", e, s); + fakeProgress?.stop(); + _logger.severe("Critical: $logPrefix failed to decrypt", e, s); return null; } - _logger.info('$logPrefix decryption completed'); await encryptedFile.delete(); return File(decryptedFilePath); - }); + } catch (e, s) { + _logger.severe("$logPrefix failed to download or decrypt", e, s); + return null; + } } Uint8List getFileKey(EnteFile file) { diff --git a/lib/utils/file_util.dart b/lib/utils/file_util.dart index c9a7548fb..123a4327d 100644 --- a/lib/utils/file_util.dart +++ b/lib/utils/file_util.dart @@ -14,6 +14,7 @@ import 'package:photos/core/cache/thumbnail_in_memory_cache.dart'; import 'package:photos/core/cache/video_cache_manager.dart'; import 'package:photos/core/configuration.dart'; import 'package:photos/core/constants.dart'; +import "package:photos/models/file/extensions/file_props.dart"; import 'package:photos/models/file/file.dart'; import 'package:photos/models/file/file_type.dart'; import 'package:photos/utils/file_download_util.dart'; @@ -108,8 +109,21 @@ void preloadThumbnail(EnteFile file) { } } -final Map> fileDownloadsInProgress = +final Map> _fileDownloadsInProgress = >{}; +Map _progressCallbacks = {}; + +void removeCallBack(EnteFile file) { + if (!file.isUploaded) { + return; + } + String id = file.uploadedFileID.toString() + false.toString(); + _progressCallbacks.remove(id); + if (file.isLivePhoto) { + id = file.uploadedFileID.toString() + true.toString(); + _progressCallbacks.remove(id); + } +} Future getFileFromServer( EnteFile file, { @@ -124,30 +138,44 @@ Future getFileFromServer( return fileFromCache.file; } final downloadID = file.uploadedFileID.toString() + liveVideo.toString(); - if (!fileDownloadsInProgress.containsKey(downloadID)) { + + if (progressCallback != null) { + _progressCallbacks[downloadID] = progressCallback; + } + + if (!_fileDownloadsInProgress.containsKey(downloadID)) { + final completer = Completer(); + _fileDownloadsInProgress[downloadID] = completer.future; + + Future downloadFuture; if (file.fileType == FileType.livePhoto) { - fileDownloadsInProgress[downloadID] = _getLivePhotoFromServer( + downloadFuture = _getLivePhotoFromServer( file, - progressCallback: progressCallback, + progressCallback: (count, total) { + _progressCallbacks[downloadID]?.call(count, total); + }, needLiveVideo: liveVideo, ); - fileDownloadsInProgress[downloadID]!.whenComplete(() { - fileDownloadsInProgress.remove(downloadID); - }); } else { - fileDownloadsInProgress[downloadID] = _downloadAndCache( + downloadFuture = _downloadAndCache( file, cacheManager, - progressCallback: progressCallback, + progressCallback: (count, total) { + _progressCallbacks[downloadID]?.call(count, total); + }, ); - fileDownloadsInProgress[downloadID]!.whenComplete(() { - fileDownloadsInProgress.remove(downloadID); - }); } + downloadFuture.then((downloadedFile) { + completer.complete(downloadedFile); + _fileDownloadsInProgress.remove(downloadID); + _progressCallbacks.remove(downloadID); + }); } - return fileDownloadsInProgress[downloadID]; + return _fileDownloadsInProgress[downloadID]; } + + Future isFileCached(EnteFile file, {bool liveVideo = false}) async { final cacheManager = (file.fileType == FileType.video || liveVideo) ? VideoCacheManager.instance @@ -237,9 +265,9 @@ Future<_LivePhoto?> _downloadLivePhoto( final videoFile = File(decodePath); await videoFile.create(recursive: true); await videoFile.writeAsBytes(data); - videoFileCache = await VideoCacheManager.instance.putFile( + videoFileCache = await VideoCacheManager.instance.putFileStream( file.downloadUrl, - await videoFile.readAsBytes(), + videoFile.openRead(), eTag: file.downloadUrl, maxAge: const Duration(days: 365), fileExtension: fileExtension, @@ -263,7 +291,7 @@ Future<_LivePhoto?> _downloadLivePhoto( Future _downloadAndCache( EnteFile file, BaseCacheManager cacheManager, { - ProgressCallback? progressCallback, + required ProgressCallback progressCallback, }) async { return downloadAndDecrypt(file, progressCallback: progressCallback) .then((decryptedFile) async { @@ -287,9 +315,9 @@ Future _downloadAndCache( } await decryptedFile.delete(); } - final cachedFile = await cacheManager.putFile( + final cachedFile = await cacheManager.putFileStream( file.downloadUrl, - await outputFile.readAsBytes(), + outputFile.openRead(), eTag: file.downloadUrl, maxAge: const Duration(days: 365), fileExtension: fileExtension, diff --git a/pubspec.yaml b/pubspec.yaml index d100a7874..c39790f37 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.7.89+489 +version: 0.7.90+490 environment: sdk: ">=3.0.0 <4.0.0"