From a9312c3421351ae02a6d62a214cc95b50078623a Mon Sep 17 00:00:00 2001 From: "Yu Squire[ Yu, Tsung-Ying ]" Date: Wed, 19 Feb 2025 11:40:08 +0800 Subject: [PATCH 1/2] feat: add `timeout` parameter for downloading --- .../lib/src/cache_manager.dart | 17 ++++++++++--- .../cache_managers/base_cache_manager.dart | 11 ++++++-- .../cache_managers/image_cache_manager.dart | 10 ++++++-- .../lib/src/compat/file_service_compat.dart | 9 ++++--- .../lib/src/web/file_service.dart | 16 +++++++++--- .../lib/src/web/web_helper.dart | 25 +++++++++++-------- flutter_cache_manager/test/mock.mocks.dart | 13 ++++++++-- 7 files changed, 74 insertions(+), 27 deletions(-) diff --git a/flutter_cache_manager/lib/src/cache_manager.dart b/flutter_cache_manager/lib/src/cache_manager.dart index 82ac52a9..a8c8a9ba 100644 --- a/flutter_cache_manager/lib/src/cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_manager.dart @@ -108,11 +108,17 @@ class CacheManager implements BaseCacheManager { /// returned from the cache there will be no progress given, although the file /// might be outdated and a new file is being downloaded in the background. @override - Stream getFileStream(String url, - {String? key, Map? headers, bool withProgress = false}) { + Stream getFileStream( + String url, { + String? key, + Map? headers, + Duration? timeout, + bool withProgress = false, + }) { key ??= url; final streamController = StreamController(); - _pushFileToStream(streamController, url, key, headers, withProgress); + _pushFileToStream( + streamController, url, key, headers, timeout, withProgress); return streamController.stream; } @@ -121,6 +127,7 @@ class CacheManager implements BaseCacheManager { String url, String? key, Map? headers, + Duration? timeout, bool withProgress, ) async { key ??= url; @@ -139,7 +146,7 @@ class CacheManager implements BaseCacheManager { if (cacheFile == null || cacheFile.validTill.isBefore(DateTime.now())) { try { await for (final response - in _webHelper.downloadFile(url, key: key, authHeaders: headers)) { + in _webHelper.downloadFile(url, key: key, authHeaders: headers, timeout: timeout)) { if (response is DownloadProgress && withProgress) { streamController.add(response); } @@ -173,6 +180,7 @@ class CacheManager implements BaseCacheManager { Future downloadFile(String url, {String? key, Map? authHeaders, + Duration? timeout, bool force = false}) async { key ??= url; final fileResponse = await _webHelper @@ -180,6 +188,7 @@ class CacheManager implements BaseCacheManager { url, key: key, authHeaders: authHeaders, + timeout: timeout, ignoreMemCache: force, ) .firstWhere((r) => r is FileInfo); diff --git a/flutter_cache_manager/lib/src/cache_managers/base_cache_manager.dart b/flutter_cache_manager/lib/src/cache_managers/base_cache_manager.dart index 7449a1bc..8b06ad52 100644 --- a/flutter_cache_manager/lib/src/cache_managers/base_cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_managers/base_cache_manager.dart @@ -28,6 +28,7 @@ abstract class BaseCacheManager { /// Get the file from the cache and/or online, depending on availability and age. /// Downloaded form [url], [headers] can be used for example for authentication. + /// [timeout] can be used to specify a timeout for the download. /// The files are returned as stream. First the cached file if available, when the /// cached file is too old the newly downloaded file is returned afterwards. /// @@ -38,11 +39,17 @@ abstract class BaseCacheManager { /// returned from the cache there will be no progress given, although the file /// might be outdated and a new file is being downloaded in the background. Stream getFileStream(String url, - {String? key, Map? headers, bool withProgress}); + {String? key, + Map? headers, + Duration? timeout, + bool withProgress}); ///Download the file and add to cache Future downloadFile(String url, - {String? key, Map? authHeaders, bool force = false}); + {String? key, + Map? authHeaders, + Duration? timeout, + bool force = false}); /// Get the file from the cache. /// Specify [ignoreMemCache] to force a re-read from the database diff --git a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart index 614d7d9d..69977036 100644 --- a/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_managers/image_cache_manager.dart @@ -21,13 +21,19 @@ mixin ImageCacheManager on BaseCacheManager { String url, { String? key, Map? headers, + Duration? timeout, bool withProgress = false, int? maxHeight, int? maxWidth, }) async* { if (maxHeight == null && maxWidth == null) { - yield* getFileStream(url, - key: key, headers: headers, withProgress: withProgress); + yield* getFileStream( + url, + key: key, + headers: headers, + timeout: timeout, + withProgress: withProgress, + ); return; } key ??= url; diff --git a/flutter_cache_manager/lib/src/compat/file_service_compat.dart b/flutter_cache_manager/lib/src/compat/file_service_compat.dart index 0df201e1..c92627f4 100644 --- a/flutter_cache_manager/lib/src/compat/file_service_compat.dart +++ b/flutter_cache_manager/lib/src/compat/file_service_compat.dart @@ -11,9 +11,12 @@ class FileServiceCompat extends FileService { @override Future get(String url, - {Map? headers}) async { - final legacyResponse = await fileFetcher(url, headers: headers); - return CompatFileServiceGetResponse(legacyResponse); + {Map? headers, Duration? timeout}) async { + var legacyResponse = fileFetcher(url, headers: headers); + if (timeout != null) { + legacyResponse = legacyResponse.timeout(timeout); + } + return CompatFileServiceGetResponse(await legacyResponse); } } diff --git a/flutter_cache_manager/lib/src/web/file_service.dart b/flutter_cache_manager/lib/src/web/file_service.dart index 2dcd01d6..b8185945 100644 --- a/flutter_cache_manager/lib/src/web/file_service.dart +++ b/flutter_cache_manager/lib/src/web/file_service.dart @@ -16,7 +16,11 @@ import 'package:http/http.dart' as http; abstract class FileService { int concurrentFetches = 10; - Future get(String url, {Map? headers}); + Future get( + String url, { + Map? headers, + Duration? timeout, + }); } /// [HttpFileService] is the most common file service and the default for @@ -29,14 +33,18 @@ class HttpFileService extends FileService { @override Future get(String url, - {Map? headers}) async { + {Map? headers, Duration? timeout}) async { final req = http.Request('GET', Uri.parse(url)); if (headers != null) { req.headers.addAll(headers); } - final httpResponse = await _httpClient.send(req); - return HttpGetResponse(httpResponse); + var streamedResponse = _httpClient.send(req); + if (timeout != null) { + streamedResponse = streamedResponse.timeout(timeout); + } + + return HttpGetResponse(await streamedResponse); } } diff --git a/flutter_cache_manager/lib/src/web/web_helper.dart b/flutter_cache_manager/lib/src/web/web_helper.dart index 20685484..97db0b1a 100644 --- a/flutter_cache_manager/lib/src/web/web_helper.dart +++ b/flutter_cache_manager/lib/src/web/web_helper.dart @@ -32,13 +32,14 @@ class WebHelper { Stream downloadFile(String url, {String? key, Map? authHeaders, + Duration? timeout, bool ignoreMemCache = false}) { key ??= url; var subject = _memCache[key]; if (subject == null || ignoreMemCache) { subject = BehaviorSubject(); _memCache[key] = subject; - _downloadOrAddToQueue(url, key, authHeaders); + _downloadOrAddToQueue(url, key, authHeaders, timeout); } return subject.stream; } @@ -49,6 +50,7 @@ class WebHelper { String url, String key, Map? authHeaders, + Duration? timeout, ) async { //Add to queue if there are too many calls. if (concurrentCalls >= fileFetcher.concurrentFetches) { @@ -61,8 +63,8 @@ class WebHelper { concurrentCalls++; final subject = _memCache[key]!; try { - await for (final result - in _updateFile(url, key, authHeaders: authHeaders)) { + await for (final result in _updateFile(url, key, + authHeaders: authHeaders, timeout: timeout)) { subject.add(result); } } on Object catch (e, stackTrace) { @@ -71,19 +73,19 @@ class WebHelper { concurrentCalls--; await subject.close(); _memCache.remove(key); - _checkQueue(); + _checkQueue(timeout); } } - void _checkQueue() { + void _checkQueue(Duration? timeout) { if (_queue.isEmpty) return; final next = _queue.removeFirst(); - _downloadOrAddToQueue(next.url, next.key, next.headers); + _downloadOrAddToQueue(next.url, next.key, next.headers, timeout); } ///Download the file from the url Stream _updateFile(String url, String key, - {Map? authHeaders}) async* { + {Map? authHeaders, Duration? timeout}) async* { var cacheObject = await _store.retrieveCacheData(key); cacheObject = cacheObject == null ? CacheObject( @@ -93,12 +95,15 @@ class WebHelper { relativePath: '${const Uuid().v1()}.file', ) : cacheObject.copyWith(url: url); - final response = await _download(cacheObject, authHeaders); + final response = await _download(cacheObject, authHeaders, timeout); yield* _manageResponse(cacheObject, response); } Future _download( - CacheObject cacheObject, Map? authHeaders) { + CacheObject cacheObject, + Map? authHeaders, + Duration? timeout, + ) { final headers = {}; final etag = cacheObject.eTag; @@ -112,7 +117,7 @@ class WebHelper { headers.addAll(authHeaders); } - return fileFetcher.get(cacheObject.url, headers: headers); + return fileFetcher.get(cacheObject.url, headers: headers, timeout: timeout); } Stream _manageResponse( diff --git a/flutter_cache_manager/test/mock.mocks.dart b/flutter_cache_manager/test/mock.mocks.dart index 708ee9dd..e618131f 100644 --- a/flutter_cache_manager/test/mock.mocks.dart +++ b/flutter_cache_manager/test/mock.mocks.dart @@ -443,12 +443,16 @@ class MockFileServiceBase extends _i1.Mock implements _i3.FileService { _i4.Future<_i3.FileServiceResponse> get( String? url, { Map? headers, + Duration? timeout, }) => (super.noSuchMethod( Invocation.method( #get, [url], - {#headers: headers}, + { + #headers: headers, + #timeout: timeout, + }, ), returnValue: _i4.Future<_i3.FileServiceResponse>.value( _FakeFileServiceResponse_4( @@ -456,7 +460,10 @@ class MockFileServiceBase extends _i1.Mock implements _i3.FileService { Invocation.method( #get, [url], - {#headers: headers}, + { + #headers: headers, + #timeout: timeout, + }, ), )), ) as _i4.Future<_i3.FileServiceResponse>); @@ -499,6 +506,7 @@ class MockWebHelper extends _i1.Mock implements _i7.WebHelper { String? url, { String? key, Map? authHeaders, + Duration? timeout, bool? ignoreMemCache = false, }) => (super.noSuchMethod( @@ -508,6 +516,7 @@ class MockWebHelper extends _i1.Mock implements _i7.WebHelper { { #key: key, #authHeaders: authHeaders, + #timeout: timeout, #ignoreMemCache: ignoreMemCache, }, ), From 188c6a5a2c5e478aaef8842b0c56b4e61c3aeb37 Mon Sep 17 00:00:00 2001 From: "Yu Squire[ Yu, Tsung-Ying ]" Date: Wed, 19 Feb 2025 12:03:48 +0800 Subject: [PATCH 2/2] docs: update comment for `timeout` field --- flutter_cache_manager/lib/src/cache_manager.dart | 2 ++ .../lib/src/cache_managers/base_cache_manager.dart | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/flutter_cache_manager/lib/src/cache_manager.dart b/flutter_cache_manager/lib/src/cache_manager.dart index a8c8a9ba..86625774 100644 --- a/flutter_cache_manager/lib/src/cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_manager.dart @@ -98,6 +98,8 @@ class CacheManager implements BaseCacheManager { /// Get the file from the cache and/or online, depending on availability and age. /// Downloaded form [url], [headers] can be used for example for authentication. + /// [timeout] can be used to specify a timeout for the download, and it will throw + /// a [TimeoutException] when the download takes longer than the specified timeout. /// The files are returned as stream. First the cached file if available, when the /// cached file is too old the newly downloaded file is returned afterwards. /// diff --git a/flutter_cache_manager/lib/src/cache_managers/base_cache_manager.dart b/flutter_cache_manager/lib/src/cache_managers/base_cache_manager.dart index 8b06ad52..02b95238 100644 --- a/flutter_cache_manager/lib/src/cache_managers/base_cache_manager.dart +++ b/flutter_cache_manager/lib/src/cache_managers/base_cache_manager.dart @@ -28,7 +28,8 @@ abstract class BaseCacheManager { /// Get the file from the cache and/or online, depending on availability and age. /// Downloaded form [url], [headers] can be used for example for authentication. - /// [timeout] can be used to specify a timeout for the download. + /// [timeout] can be used to specify a timeout for the download, and it will throw + /// a [TimeoutException] when the download takes longer than the specified timeout. /// The files are returned as stream. First the cached file if available, when the /// cached file is too old the newly downloaded file is returned afterwards. ///