Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions flutter_cache_manager/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## [3.5.0] - 2026-10-03

* Adds an optional `timeout` parameter to `getFileStream`, `downloadFile`, `getImageFile`, and `FileService.get`, so a download that exceeds it throws a `TimeoutException` instead of running unchecked.

## [3.4.5] - 2026-09-18

* Prevents image resize decoding from completing more than once and propagates decoding errors ([#400](https://github.com/Baseflow/flutter_cache_manager/issues/400)).
Expand Down
16 changes: 15 additions & 1 deletion flutter_cache_manager/lib/src/cache_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,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.
///
Expand All @@ -113,11 +115,19 @@ class CacheManager implements BaseCacheManager {
String url, {
String? key,
Map<String, String>? headers,
Duration? timeout,
bool withProgress = false,
}) {
key ??= url;
final streamController = StreamController<FileResponse>();
_pushFileToStream(streamController, url, key, headers, withProgress);
_pushFileToStream(
streamController,
url,
key,
headers,
timeout,
withProgress,
);
return streamController.stream;
}

Expand All @@ -126,6 +136,7 @@ class CacheManager implements BaseCacheManager {
String url,
String? key,
Map<String, String>? headers,
Duration? timeout,
bool withProgress,
) async {
key ??= url;
Expand All @@ -148,6 +159,7 @@ class CacheManager implements BaseCacheManager {
url,
key: key,
authHeaders: headers,
timeout: timeout,
)) {
if (response is DownloadProgress && withProgress) {
streamController.add(response);
Expand Down Expand Up @@ -184,6 +196,7 @@ class CacheManager implements BaseCacheManager {
String url, {
String? key,
Map<String, String>? authHeaders,
Duration? timeout,
bool force = false,
}) async {
key ??= url;
Expand All @@ -192,6 +205,7 @@ class CacheManager implements BaseCacheManager {
url,
key: key,
authHeaders: authHeaders,
timeout: timeout,
ignoreMemCache: force,
)
.firstWhere((r) => r is FileInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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, 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.
///
Expand All @@ -44,6 +46,7 @@ abstract class BaseCacheManager {
String url, {
String? key,
Map<String, String>? headers,
Duration? timeout,
bool withProgress,
});

Expand All @@ -52,6 +55,7 @@ abstract class BaseCacheManager {
String url, {
String? key,
Map<String, String>? authHeaders,
Duration? timeout,
bool force = false,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ mixin ImageCacheManager on BaseCacheManager {
/// When the resized file is not found in the cache the original is fetched
/// from the cache or online and stored in the cache. Then it is resized
/// and returned to the caller.
///
/// [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.
Stream<FileResponse> getImageFile(
String url, {
String? key,
Map<String, String>? headers,
Duration? timeout,
bool withProgress = false,
int? maxHeight,
int? maxWidth,
Expand All @@ -30,6 +35,7 @@ mixin ImageCacheManager on BaseCacheManager {
url,
key: key,
headers: headers,
timeout: timeout,
withProgress: withProgress,
);
return;
Expand All @@ -50,6 +56,8 @@ mixin ImageCacheManager on BaseCacheManager {
}
var runningResize = _runningResizes[resizedKey];
if (runningResize == null) {
// Only the first caller for a given [resizedKey] creates this stream;
// concurrent callers share it and therefore inherit its [timeout].
runningResize = _fetchedResizedFile(
url,
key,
Expand All @@ -58,6 +66,7 @@ mixin ImageCacheManager on BaseCacheManager {
withProgress,
maxWidth: maxWidth,
maxHeight: maxHeight,
timeout: timeout,
).asBroadcastStream();
_runningResizes[resizedKey] = runningResize;
}
Expand Down Expand Up @@ -130,11 +139,13 @@ mixin ImageCacheManager on BaseCacheManager {
bool withProgress, {
int? maxWidth,
int? maxHeight,
Duration? timeout,
}) async* {
await for (final response in getFileStream(
url,
key: originalKey,
headers: headers,
timeout: timeout,
withProgress: withProgress,
)) {
if (response is DownloadProgress) {
Expand Down
8 changes: 6 additions & 2 deletions flutter_cache_manager/lib/src/compat/file_service_compat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ class FileServiceCompat extends FileService {
Future<FileServiceResponse> get(
String url, {
Map<String, String>? headers,
Duration? timeout,
}) async {
final legacyResponse = await fileFetcher(url, headers: headers);
return CompatFileServiceGetResponse(legacyResponse);
var legacyResponse = fileFetcher(url, headers: headers);
if (timeout != null) {
legacyResponse = legacyResponse.timeout(timeout);
}
return CompatFileServiceGetResponse(await legacyResponse);
}
}

Expand Down
15 changes: 12 additions & 3 deletions flutter_cache_manager/lib/src/web/file_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import 'package:http/http.dart' as http;
abstract class FileService {
int concurrentFetches = 10;

Future<FileServiceResponse> get(String url, {Map<String, String>? headers});
Future<FileServiceResponse> get(
String url, {
Map<String, String>? headers,
Duration? timeout,
});
}

/// [HttpFileService] is the most common file service and the default for
Expand All @@ -31,14 +35,19 @@ class HttpFileService extends FileService {
Future<FileServiceResponse> get(
String url, {
Map<String, String>? 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);
}
}

Expand Down
17 changes: 11 additions & 6 deletions flutter_cache_manager/lib/src/web/web_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,15 @@ class WebHelper {
String url, {
String? key,
Map<String, String>? authHeaders,
Duration? timeout,
bool ignoreMemCache = false,
}) {
key ??= url;
var subject = _memCache[key];
if (subject == null || ignoreMemCache) {
subject = BehaviorSubject<FileResponse>();
_memCache[key] = subject;
_downloadOrAddToQueue(url, key, authHeaders);
_downloadOrAddToQueue(url, key, authHeaders, timeout);
}
return subject.stream;
}
Expand All @@ -51,6 +52,7 @@ class WebHelper {
String url,
String key,
Map<String, String>? authHeaders,
Duration? timeout,
) async {
//Add to queue if there are too many calls.
if (concurrentCalls >= fileFetcher.concurrentFetches) {
Expand All @@ -69,6 +71,7 @@ class WebHelper {
url,
key,
authHeaders: authHeaders,
timeout: timeout,
)) {
subject.add(result);
}
Expand All @@ -78,21 +81,22 @@ 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<FileResponse> _updateFile(
String url,
String key, {
Map<String, String>? authHeaders,
Duration? timeout,
}) async* {
var cacheObject = await _store.retrieveCacheData(key);
cacheObject = cacheObject == null
Expand All @@ -103,13 +107,14 @@ 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<FileServiceResponse> _download(
CacheObject cacheObject,
Map<String, String>? authHeaders,
Duration? timeout,
) {
final headers = <String, String>{};

Expand All @@ -124,7 +129,7 @@ class WebHelper {
headers.addAll(authHeaders);
}

return fileFetcher.get(cacheObject.url, headers: headers);
return fileFetcher.get(cacheObject.url, headers: headers, timeout: timeout);
}

Stream<FileResponse> _manageResponse(
Expand Down
2 changes: 1 addition & 1 deletion flutter_cache_manager/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_cache_manager
description: Generic cache manager for flutter. Saves web files on the storages of the device and saves the cache info using sqflite.
version: 3.4.5
version: 3.5.0
homepage: https://github.com/Baseflow/flutter_cache_manager
topics:
- cache
Expand Down
Loading