Skip to content
Merged
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.4.7] - 2026-10-28

* Keeps cache cleanup going when a cached file cannot be deleted: the cache info of the other removed files is still deleted, and only the failed file's cache info is kept so the next cleanup retries it.

## [3.4.6] - 2026-10-27

* Catches unhandled asynchronous errors when updating cache-hit metadata and during scheduled cleanup in `CacheStore` ([#535](https://github.com/Baseflow/flutter_cache_manager/issues/535)).
Expand Down
96 changes: 77 additions & 19 deletions flutter_cache_manager/lib/src/cache_store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -198,34 +198,48 @@ class CacheStore {
}

Future<void> _cleanupCache() async {
final toRemove = <int>[];
final started = <int>{};
final removed = <int>[];
final provider = await _cacheInfoRepository;
final removals = <Future<void>>[];

final overCapacity = await provider.getObjectsOverCapacity(_capacity);
final futures = <Future>[];
for (final cacheObject in overCapacity) {
futures.add(_removeCachedFile(cacheObject, toRemove));
removals.add(
_removeCachedFile(cacheObject, started: started, removed: removed),
);
}

final oldObjects = await provider.getOldObjects(_maxAge);
for (final cacheObject in oldObjects) {
futures.add(_removeCachedFile(cacheObject, toRemove));
removals.add(
_removeCachedFile(cacheObject, started: started, removed: removed),
);
}

await Future.wait(futures);
await provider.deleteAll(toRemove);
await _deleteCacheInfoOfRemoved(
provider: provider,
removals: removals,
removed: removed,
);
}

Future<void> emptyCache() async {
Comment thread
rickdijk marked this conversation as resolved.
Resolved
final provider = await _cacheInfoRepository;
final toRemove = <int>[];
final started = <int>{};
final removed = <int>[];
final allObjects = await provider.getAllObjects();
var futures = <Future>[];
final removals = <Future<void>>[];
for (final cacheObject in allObjects) {
futures.add(_removeCachedFile(cacheObject, toRemove));
removals.add(
_removeCachedFile(cacheObject, started: started, removed: removed),
);
}
await Future.wait(futures);
await provider.deleteAll(toRemove);
await _deleteCacheInfoOfRemoved(
provider: provider,
removals: removals,
removed: removed,
);
}

void emptyMemoryCache() {
Expand All @@ -234,18 +248,61 @@ class CacheStore {

Future<void> removeCachedFile(CacheObject cacheObject) async {
final provider = await _cacheInfoRepository;
final toRemove = <int>[];
await _removeCachedFile(cacheObject, toRemove);
await provider.deleteAll(toRemove);
final removed = <int>[];
final removal = _removeCachedFile(
cacheObject,
started: <int>{},
removed: removed,
);
await _deleteCacheInfoOfRemoved(
provider: provider,
removals: [removal],
removed: removed,
);
}

/// Waits for [removals] and then deletes the cache info of the files in
/// [removed], even when some removals failed.
Future<void> _deleteCacheInfoOfRemoved({
required CacheInfoRepository provider,
required List<Future<void>> removals,
required List<int> removed,
}) async {
Object? removalError;
StackTrace? removalStack;
try {
await Future.wait(removals);
} catch (e, s) {
removalError = e;
removalStack = s;
}

try {
await provider.deleteAll(removed);
} catch (e, s) {
if (removalError == null) rethrow;
cacheLogger.log(
'CacheStore: Failed to delete cache info with error:\n$e\n$s',
CacheManagerLogLevel.warning,
);
}

if (removalError != null) {
Error.throwWithStackTrace(removalError, removalStack!);
}
}

/// Adds the id to [removed] only once the file is gone, so the cache info of
/// a file that could not be deleted is kept and retried. [started] stops the
/// same object from being removed twice.
Future<void> _removeCachedFile(
CacheObject cacheObject,
List<int> toRemove,
) async {
if (toRemove.contains(cacheObject.id)) return;
CacheObject cacheObject, {
required Set<int> started,
required List<int> removed,
}) async {
final id = cacheObject.id!;
if (!started.add(id)) return;

toRemove.add(cacheObject.id!);
if (_memCache.containsKey(cacheObject.key)) {
_memCache.remove(cacheObject.key);
}
Expand All @@ -260,6 +317,7 @@ class CacheStore {
// File has already been deleted. Do nothing #184
}
}
removed.add(id);
}

bool memoryCacheContainsKey(String key) {
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.6
version: 3.4.7
homepage: https://github.com/Baseflow/flutter_cache_manager
topics:
- cache
Expand Down
Loading
Loading