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,5 +1,9 @@
## [Unreleased]

## [3.4.4] - 2026-09-16

* Awaits cache-info persist in `putFile`, `putFileStream`, and downloads so the stored object has an id before those calls return ([#492](https://github.com/Baseflow/flutter_cache_manager/issues/492)). A failed repository write now throws from `putFile`/`putFileStream` and errors the download stream instead of returning a usable file with no cache-info row.

## [3.4.3] - 2026-09-15

* Fixes `JsonCacheInfoRepository` losing metadata when the app exits within 3 seconds of a cache change by writing through promptly with serialized, atomic file writes ([#491](https://github.com/Baseflow/flutter_cache_manager/issues/491))
Expand Down
4 changes: 2 additions & 2 deletions flutter_cache_manager/lib/src/cache_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ class CacheManager implements BaseCacheManager {

final file = await _config.fileSystem.createFile(cacheObject.relativePath);
await file.writeAsBytes(fileBytes);
_store.putFile(cacheObject);
await _store.putFile(cacheObject);
return file;
}

Expand Down Expand Up @@ -287,7 +287,7 @@ class CacheManager implements BaseCacheManager {
.map((event) => event)
.pipe(sink);

_store.putFile(cacheObject);
await _store.putFile(cacheObject);
return file;
}

Expand Down
18 changes: 11 additions & 7 deletions flutter_cache_manager/lib/src/web/web_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,10 @@ class WebHelper {
newCacheObject = newCacheObject.copyWith(length: savedBytes);
}

_store.putFile(newCacheObject).then((_) {
if (newCacheObject.relativePath != oldCacheObject.relativePath) {
_removeOldFile(oldCacheObject.relativePath);
}
});
await _store.putFile(newCacheObject);
Comment thread
rickdijk marked this conversation as resolved.
if (newCacheObject.relativePath != oldCacheObject.relativePath) {
await _removeOldFile(oldCacheObject.relativePath);
}

final file = await _store.fileSystem.createFile(
newCacheObject.relativePath,
Expand Down Expand Up @@ -232,8 +231,13 @@ class WebHelper {
Future<void> _removeOldFile(String? relativePath) async {
if (relativePath == null) return;
final file = await _store.fileSystem.createFile(relativePath);
if (await file.exists()) {
await file.delete();
try {
if (await file.exists()) {
await file.delete();
}
} on FileSystemException {
// Already deleted (see #184) or not deletable. The cache info no longer
// points at this path, so there is nothing to recover here.
}
}
}
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.3
version: 3.4.4
homepage: https://github.com/Baseflow/flutter_cache_manager
topics:
- cache
Expand Down
63 changes: 63 additions & 0 deletions flutter_cache_manager/test/cache_manager_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';

import 'helpers/config_extensions.dart';
import 'helpers/json_repo_helpers.dart';
import 'helpers/mock_cache_store.dart';
import 'helpers/mock_file_fetcher_response.dart';
import 'helpers/mock_file_service.dart';
import 'helpers/test_configuration.dart';
import 'mock.mocks.dart';

Expand Down Expand Up @@ -423,6 +425,44 @@ void main() {
expect(arg.key, fileKey);
expect(arg.url, fileUrl);
});

test('putFile waits for store persist before returning', () async {
final persisted = Completer<void>();
final store = MockCacheStore();
when(store.putFile(any)).thenAnswer((_) => persisted.future);
final cacheManager = TestCacheManager(createTestConfig(), store: store);
var returned = false;
final put = cacheManager.putFile('baseflow.com/test', Uint8List(8))
..whenComplete(() => returned = true);
await pumpEventQueue();
expect(
returned,
isFalse,
reason: 'putFile returned before the store persisted',
);
persisted.complete();
await put;
});

test('putFileStream waits for store persist before returning', () async {
final persisted = Completer<void>();
final store = MockCacheStore();
when(store.putFile(any)).thenAnswer((_) => persisted.future);
final cacheManager = TestCacheManager(createTestConfig(), store: store);
var returned = false;
final put = cacheManager.putFileStream(
'baseflow.com/test',
Stream<List<int>>.value([1, 2, 3]),
)..whenComplete(() => returned = true);
await pumpEventQueue();
expect(
returned,
isFalse,
reason: 'putFileStream returned before the store persisted',
);
persisted.complete();
await put;
});
});

group('Testing remove files from cache', () {
Expand Down Expand Up @@ -461,6 +501,29 @@ void main() {
verifyNever(store.removeCachedFile(any));
});

test('removeFile deletes the entry right after putFile', () async {
final repo = JsonCacheInfoRepository.withFile(
await JsonRepoHelpers.createDatabaseFile(),
);
final config = Config(
'test',
fileSystem: TestFileSystem(),
repo: repo,
fileService: MockFileService(),
);
final cacheManager = TestCacheManager(config);
const url = 'baseflow.com/test';
final file = await cacheManager.putFile(
url,
Uint8List(8),
fileExtension: 'jpg',
);
await cacheManager.removeFile(url);
await pumpEventQueue();
expect(await repo.get(url), isNull);
expect(await file.exists(), isFalse);
});

test("Don't crash if the cached object doesn't have an id", () async {
var fileUrl = 'baseflow.com/test';

Expand Down
41 changes: 41 additions & 0 deletions flutter_cache_manager/test/web_helper_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,47 @@ void main() {
verify(store.putFile(any)).called(1);
});

test('downloadFile waits for persist before yielding FileInfo', () async {
const imageUrl = 'baseflow.com/testimage';

final persisted = Completer<void>();
var config = createTestConfig();
var store = _createStore(config);
when(store.putFile(any)).thenAnswer((_) => persisted.future);

final fileService = MockFileService();
when(fileService.get(imageUrl, headers: anyNamed('headers'))).thenAnswer((
_,
) {
return Future.value(
MockFileFetcherResponse(
Stream.value([0, 1, 2, 3, 4, 5]),
6,
'testv1',
'.jpg',
200,
DateTime.now(),
),
);
});

final webHelper = WebHelper(store, fileService);
var yielded = false;
final download =
webHelper
.downloadFile(imageUrl)
.firstWhere((r) => r is FileInfo, orElse: null)
..whenComplete(() => yielded = true);
await pumpEventQueue();
expect(
yielded,
isFalse,
reason: 'downloadFile yielded FileInfo before the store persisted',
);
persisted.complete();
await download;
});

test('File should be removed if extension changed', () async {
const imageUrl = 'baseflow.com/testimage';
var imageName = 'image.png';
Expand Down