diff --git a/flutter_cache_manager/lib/src/cache_store.dart b/flutter_cache_manager/lib/src/cache_store.dart index 44ffbfeb..4d0e267a 100644 --- a/flutter_cache_manager/lib/src/cache_store.dart +++ b/flutter_cache_manager/lib/src/cache_store.dart @@ -24,6 +24,9 @@ class CacheStore { Duration get _maxAge => _config.stalePeriod; + Duration get _maxCapacityAgeFilterDuration => + _config.maxCapacityAgeFilterDuration ?? const Duration(days: 1); + DateTime lastCleanupRun = DateTime.now(); Timer? _scheduledCleanup; @@ -146,7 +149,10 @@ class CacheStore { final toRemove = []; final provider = await _cacheInfoRepository; - final overCapacity = await provider.getObjectsOverCapacity(_capacity); + final overCapacity = await provider.getObjectsOverCapacity( + _capacity, + maxAge: _maxCapacityAgeFilterDuration, + ); for (final cacheObject in overCapacity) { _removeCachedFile(cacheObject, toRemove); } diff --git a/flutter_cache_manager/lib/src/config/_config_unsupported.dart b/flutter_cache_manager/lib/src/config/_config_unsupported.dart index b14ebeb3..68b61fcf 100644 --- a/flutter_cache_manager/lib/src/config/_config_unsupported.dart +++ b/flutter_cache_manager/lib/src/config/_config_unsupported.dart @@ -18,6 +18,8 @@ class Config implements def.Config { FileSystem? fileSystem, //ignore: avoid_unused_constructor_parameters FileService? fileService, + //ignore: avoid_unused_constructor_parameters + Duration? maxCapacityAgeFilterDuration, }) { throw UnsupportedError('Platform is not supported'); } @@ -39,4 +41,7 @@ class Config implements def.Config { @override FileService get fileService => throw UnimplementedError(); + + @override + Duration? get maxCapacityAgeFilterDuration => throw UnimplementedError(); } diff --git a/flutter_cache_manager/lib/src/config/config.dart b/flutter_cache_manager/lib/src/config/config.dart index 88dabb37..9f55fa02 100644 --- a/flutter_cache_manager/lib/src/config/config.dart +++ b/flutter_cache_manager/lib/src/config/config.dart @@ -28,6 +28,7 @@ abstract class Config { CacheInfoRepository repo, FileSystem fileSystem, FileService fileService, + Duration? maxCapacityAgeFilterDuration, }) = impl.Config; String get cacheKey; @@ -41,4 +42,6 @@ abstract class Config { FileSystem get fileSystem; FileService get fileService; + + Duration? get maxCapacityAgeFilterDuration; } diff --git a/flutter_cache_manager/lib/src/storage/cache_info_repositories/cache_info_repository.dart b/flutter_cache_manager/lib/src/storage/cache_info_repositories/cache_info_repository.dart index da204275..48ac668b 100644 --- a/flutter_cache_manager/lib/src/storage/cache_info_repositories/cache_info_repository.dart +++ b/flutter_cache_manager/lib/src/storage/cache_info_repositories/cache_info_repository.dart @@ -38,8 +38,12 @@ abstract class CacheInfoRepository { /// Gets the list of [CacheObject] that can be removed if the repository is over capacity. /// /// The exact implementation is up to the repository, but implementations should - /// return a preferred list of items. For example, the least recently accessed - Future> getObjectsOverCapacity(int capacity); + /// return a preferred list of items. For example, the least recently accessed. + /// [maxAge] filters objects older than the given duration (defaults to 1 day). + Future> getObjectsOverCapacity( + int capacity, { + Duration? maxAge, + }); /// Returns a list of [CacheObject] that are older than [maxAge] Future> getOldObjects(Duration maxAge); diff --git a/flutter_cache_manager/lib/src/storage/cache_info_repositories/cache_object_provider.dart b/flutter_cache_manager/lib/src/storage/cache_info_repositories/cache_object_provider.dart index b386ea58..91a4501e 100644 --- a/flutter_cache_manager/lib/src/storage/cache_info_repositories/cache_object_provider.dart +++ b/flutter_cache_manager/lib/src/storage/cache_info_repositories/cache_object_provider.dart @@ -160,7 +160,10 @@ class CacheObjectProvider extends CacheInfoRepository } @override - Future> getObjectsOverCapacity(int capacity) async { + Future> getObjectsOverCapacity( + int capacity, { + Duration? maxAge, + }) async { return CacheObject.fromMapList( await db!.query( _tableCacheObject, @@ -169,7 +172,7 @@ class CacheObjectProvider extends CacheInfoRepository where: '${CacheObject.columnTouched} < ?', whereArgs: [ DateTime.now() - .subtract(const Duration(days: 1)) + .subtract(maxAge ?? const Duration(days: 1)) .millisecondsSinceEpoch, ], limit: 100, diff --git a/flutter_cache_manager/lib/src/storage/cache_info_repositories/json_cache_info_repository.dart b/flutter_cache_manager/lib/src/storage/cache_info_repositories/json_cache_info_repository.dart index 5d24b24a..3d1740a1 100644 --- a/flutter_cache_manager/lib/src/storage/cache_info_repositories/json_cache_info_repository.dart +++ b/flutter_cache_manager/lib/src/storage/cache_info_repositories/json_cache_info_repository.dart @@ -90,11 +90,20 @@ class JsonCacheInfoRepository extends CacheInfoRepository } @override - Future> getObjectsOverCapacity(int capacity) async { - final allSorted = _cacheObjects.values.toList() - ..sort((c1, c2) => c1.touched!.compareTo(c2.touched!)); - if (allSorted.length <= capacity) return []; - return allSorted.getRange(0, allSorted.length - capacity).toList(); + Future> getObjectsOverCapacity( + int capacity, { + Duration? maxAge, + }) async { + final threshold = DateTime.now().subtract( + maxAge ?? const Duration(days: 1), + ); + final filtered = + _cacheObjects.values + .where((c) => c.touched!.isBefore(threshold)) + .toList() + ..sort((c1, c2) => c1.touched!.compareTo(c2.touched!)); + if (filtered.length <= capacity) return []; + return filtered.getRange(0, filtered.length - capacity).toList(); } @override diff --git a/flutter_cache_manager/lib/src/storage/cache_info_repositories/non_storing_object_provider.dart b/flutter_cache_manager/lib/src/storage/cache_info_repositories/non_storing_object_provider.dart index 01dd6c3f..513ec676 100644 --- a/flutter_cache_manager/lib/src/storage/cache_info_repositories/non_storing_object_provider.dart +++ b/flutter_cache_manager/lib/src/storage/cache_info_repositories/non_storing_object_provider.dart @@ -28,7 +28,10 @@ class NonStoringObjectProvider implements CacheInfoRepository { } @override - Future> getObjectsOverCapacity(int capacity) { + Future> getObjectsOverCapacity( + int capacity, { + Duration? maxAge, + }) { return Future.value([]); } diff --git a/flutter_cache_manager/test/mock.mocks.dart b/flutter_cache_manager/test/mock.mocks.dart index a1496764..1a3de50e 100644 --- a/flutter_cache_manager/test/mock.mocks.dart +++ b/flutter_cache_manager/test/mock.mocks.dart @@ -1,16 +1,16 @@ -// Mocks generated by Mockito 5.4.6 from annotations +// Mocks generated by Mockito from annotations // in flutter_cache_manager/test/mock.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i4; -import 'package:flutter_cache_manager/flutter_cache_manager.dart' as _i3; -import 'package:flutter_cache_manager/src/cache_store.dart' as _i5; -import 'package:flutter_cache_manager/src/storage/cache_object.dart' as _i2; -import 'package:flutter_cache_manager/src/web/web_helper.dart' as _i7; +import 'dart:async' as _i3; + +import 'package:flutter_cache_manager/flutter_cache_manager.dart' as _i2; +import 'package:flutter_cache_manager/src/cache_store.dart' as _i4; +import 'package:flutter_cache_manager/src/web/web_helper.dart' as _i6; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i6; +import 'package:mockito/src/dummies.dart' as _i5; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -18,6 +18,7 @@ import 'package:mockito/src/dummies.dart' as _i6; // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: experimental_member_use // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable @@ -37,7 +38,7 @@ class _FakeDuration_1 extends _i1.SmartFake implements Duration { : super(parent, parentInvocation); } -class _FakeFileSystem_2 extends _i1.SmartFake implements _i3.FileSystem { +class _FakeFileSystem_2 extends _i1.SmartFake implements _i2.FileSystem { _FakeFileSystem_2(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); } @@ -48,12 +49,12 @@ class _FakeDateTime_3 extends _i1.SmartFake implements DateTime { } class _FakeFileServiceResponse_4 extends _i1.SmartFake - implements _i3.FileServiceResponse { + implements _i2.FileServiceResponse { _FakeFileServiceResponse_4(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); } -class _FakeFileService_5 extends _i1.SmartFake implements _i3.FileService { +class _FakeFileService_5 extends _i1.SmartFake implements _i2.FileService { _FakeFileService_5(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); } @@ -62,37 +63,37 @@ class _FakeFileService_5 extends _i1.SmartFake implements _i3.FileService { /// /// See the documentation for Mockito's code generation for more information. class MockCacheInfoRepositoryBase extends _i1.Mock - implements _i3.CacheInfoRepository { + implements _i2.CacheInfoRepository { MockCacheInfoRepositoryBase() { _i1.throwOnMissingStub(this); } @override - _i4.Future exists() => + _i3.Future exists() => (super.noSuchMethod( Invocation.method(#exists, []), - returnValue: _i4.Future.value(false), + returnValue: _i3.Future.value(false), ) - as _i4.Future); + as _i3.Future); @override - _i4.Future open() => + _i3.Future open() => (super.noSuchMethod( Invocation.method(#open, []), - returnValue: _i4.Future.value(false), + returnValue: _i3.Future.value(false), ) - as _i4.Future); + as _i3.Future); @override - _i4.Future updateOrInsert(_i2.CacheObject? cacheObject) => + _i3.Future updateOrInsert(_i2.CacheObject? cacheObject) => (super.noSuchMethod( Invocation.method(#updateOrInsert, [cacheObject]), - returnValue: _i4.Future.value(), + returnValue: _i3.Future.value(), ) - as _i4.Future); + as _i3.Future); @override - _i4.Future<_i2.CacheObject> insert( + _i3.Future<_i2.CacheObject> insert( _i2.CacheObject? cacheObject, { bool? setTouchedToNow = true, }) => @@ -102,7 +103,7 @@ class MockCacheInfoRepositoryBase extends _i1.Mock [cacheObject], {#setTouchedToNow: setTouchedToNow}, ), - returnValue: _i4.Future<_i2.CacheObject>.value( + returnValue: _i3.Future<_i2.CacheObject>.value( _FakeCacheObject_0( this, Invocation.method( @@ -113,34 +114,34 @@ class MockCacheInfoRepositoryBase extends _i1.Mock ), ), ) - as _i4.Future<_i2.CacheObject>); + as _i3.Future<_i2.CacheObject>); @override - _i4.Future<_i2.CacheObject?> get(String? key) => + _i3.Future<_i2.CacheObject?> get(String? key) => (super.noSuchMethod( Invocation.method(#get, [key]), - returnValue: _i4.Future<_i2.CacheObject?>.value(), + returnValue: _i3.Future<_i2.CacheObject?>.value(), ) - as _i4.Future<_i2.CacheObject?>); + as _i3.Future<_i2.CacheObject?>); @override - _i4.Future delete(int? id) => + _i3.Future delete(int? id) => (super.noSuchMethod( Invocation.method(#delete, [id]), - returnValue: _i4.Future.value(0), + returnValue: _i3.Future.value(0), ) - as _i4.Future); + as _i3.Future); @override - _i4.Future deleteAll(Iterable? ids) => + _i3.Future deleteAll(Iterable? ids) => (super.noSuchMethod( Invocation.method(#deleteAll, [ids]), - returnValue: _i4.Future.value(0), + returnValue: _i3.Future.value(0), ) - as _i4.Future); + as _i3.Future); @override - _i4.Future update( + _i3.Future update( _i2.CacheObject? cacheObject, { bool? setTouchedToNow = true, }) => @@ -150,62 +151,69 @@ class MockCacheInfoRepositoryBase extends _i1.Mock [cacheObject], {#setTouchedToNow: setTouchedToNow}, ), - returnValue: _i4.Future.value(0), + returnValue: _i3.Future.value(0), ) - as _i4.Future); + as _i3.Future); @override - _i4.Future> getAllObjects() => + _i3.Future> getAllObjects() => (super.noSuchMethod( Invocation.method(#getAllObjects, []), - returnValue: _i4.Future>.value( + returnValue: _i3.Future>.value( <_i2.CacheObject>[], ), ) - as _i4.Future>); + as _i3.Future>); @override - _i4.Future> getObjectsOverCapacity(int? capacity) => + _i3.Future> getObjectsOverCapacity( + int? capacity, { + Duration? maxAge, + }) => (super.noSuchMethod( - Invocation.method(#getObjectsOverCapacity, [capacity]), - returnValue: _i4.Future>.value( + Invocation.method( + #getObjectsOverCapacity, + [capacity], + {#maxAge: maxAge}, + ), + returnValue: _i3.Future>.value( <_i2.CacheObject>[], ), ) - as _i4.Future>); + as _i3.Future>); @override - _i4.Future> getOldObjects(Duration? maxAge) => + _i3.Future> getOldObjects(Duration? maxAge) => (super.noSuchMethod( Invocation.method(#getOldObjects, [maxAge]), - returnValue: _i4.Future>.value( + returnValue: _i3.Future>.value( <_i2.CacheObject>[], ), ) - as _i4.Future>); + as _i3.Future>); @override - _i4.Future close() => + _i3.Future close() => (super.noSuchMethod( Invocation.method(#close, []), - returnValue: _i4.Future.value(false), + returnValue: _i3.Future.value(false), ) - as _i4.Future); + as _i3.Future); @override - _i4.Future deleteDataFile() => + _i3.Future deleteDataFile() => (super.noSuchMethod( Invocation.method(#deleteDataFile, []), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), ) - as _i4.Future); + as _i3.Future); } /// A class which mocks [CacheStore]. /// /// See the documentation for Mockito's code generation for more information. -class MockCacheStoreBase extends _i1.Mock implements _i5.CacheStore { +class MockCacheStoreBase extends _i1.Mock implements _i4.CacheStore { MockCacheStoreBase() { _i1.throwOnMissingStub(this); } @@ -222,7 +230,7 @@ class MockCacheStoreBase extends _i1.Mock implements _i5.CacheStore { as Duration); @override - _i3.FileSystem get fileSystem => + _i2.FileSystem get fileSystem => (super.noSuchMethod( Invocation.getter(#fileSystem), returnValue: _FakeFileSystem_2( @@ -230,13 +238,13 @@ class MockCacheStoreBase extends _i1.Mock implements _i5.CacheStore { Invocation.getter(#fileSystem), ), ) - as _i3.FileSystem); + as _i2.FileSystem); @override String get storeKey => (super.noSuchMethod( Invocation.getter(#storeKey), - returnValue: _i6.dummyValue( + returnValue: _i5.dummyValue( this, Invocation.getter(#storeKey), ), @@ -261,7 +269,7 @@ class MockCacheStoreBase extends _i1.Mock implements _i5.CacheStore { ); @override - set fileSystem(_i3.FileSystem? value) => super.noSuchMethod( + set fileSystem(_i2.FileSystem? value) => super.noSuchMethod( Invocation.setter(#fileSystem, value), returnValueForMissingStub: null, ); @@ -273,7 +281,7 @@ class MockCacheStoreBase extends _i1.Mock implements _i5.CacheStore { ); @override - _i4.Future<_i3.FileInfo?> getFile( + _i3.Future<_i2.FileInfo?> getFile( String? key, { bool? ignoreMemCache = false, }) => @@ -283,21 +291,21 @@ class MockCacheStoreBase extends _i1.Mock implements _i5.CacheStore { [key], {#ignoreMemCache: ignoreMemCache}, ), - returnValue: _i4.Future<_i3.FileInfo?>.value(), + returnValue: _i3.Future<_i2.FileInfo?>.value(), ) - as _i4.Future<_i3.FileInfo?>); + as _i3.Future<_i2.FileInfo?>); @override - _i4.Future putFile(_i2.CacheObject? cacheObject) => + _i3.Future putFile(_i2.CacheObject? cacheObject) => (super.noSuchMethod( Invocation.method(#putFile, [cacheObject]), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), ) - as _i4.Future); + as _i3.Future); @override - _i4.Future<_i2.CacheObject?> retrieveCacheData( + _i3.Future<_i2.CacheObject?> retrieveCacheData( String? key, { bool? ignoreMemCache = false, }) => @@ -307,26 +315,26 @@ class MockCacheStoreBase extends _i1.Mock implements _i5.CacheStore { [key], {#ignoreMemCache: ignoreMemCache}, ), - returnValue: _i4.Future<_i2.CacheObject?>.value(), + returnValue: _i3.Future<_i2.CacheObject?>.value(), ) - as _i4.Future<_i2.CacheObject?>); + as _i3.Future<_i2.CacheObject?>); @override - _i4.Future<_i3.FileInfo?> getFileFromMemory(String? key) => + _i3.Future<_i2.FileInfo?> getFileFromMemory(String? key) => (super.noSuchMethod( Invocation.method(#getFileFromMemory, [key]), - returnValue: _i4.Future<_i3.FileInfo?>.value(), + returnValue: _i3.Future<_i2.FileInfo?>.value(), ) - as _i4.Future<_i3.FileInfo?>); + as _i3.Future<_i2.FileInfo?>); @override - _i4.Future emptyCache() => + _i3.Future emptyCache() => (super.noSuchMethod( Invocation.method(#emptyCache, []), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), ) - as _i4.Future); + as _i3.Future); @override void emptyMemoryCache() => super.noSuchMethod( @@ -335,13 +343,13 @@ class MockCacheStoreBase extends _i1.Mock implements _i5.CacheStore { ); @override - _i4.Future removeCachedFile(_i2.CacheObject? cacheObject) => + _i3.Future removeCachedFile(_i2.CacheObject? cacheObject) => (super.noSuchMethod( Invocation.method(#removeCachedFile, [cacheObject]), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), ) - as _i4.Future); + as _i3.Future); @override bool memoryCacheContainsKey(String? key) => @@ -352,27 +360,27 @@ class MockCacheStoreBase extends _i1.Mock implements _i5.CacheStore { as bool); @override - _i4.Future dispose() => + _i3.Future dispose() => (super.noSuchMethod( Invocation.method(#dispose, []), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), ) - as _i4.Future); + as _i3.Future); @override - _i4.Future getCacheSize() => + _i3.Future getCacheSize() => (super.noSuchMethod( Invocation.method(#getCacheSize, []), - returnValue: _i4.Future.value(0), + returnValue: _i3.Future.value(0), ) - as _i4.Future); + as _i3.Future); } /// A class which mocks [FileService]. /// /// See the documentation for Mockito's code generation for more information. -class MockFileServiceBase extends _i1.Mock implements _i3.FileService { +class MockFileServiceBase extends _i1.Mock implements _i2.FileService { MockFileServiceBase() { _i1.throwOnMissingStub(this); } @@ -389,32 +397,32 @@ class MockFileServiceBase extends _i1.Mock implements _i3.FileService { ); @override - _i4.Future<_i3.FileServiceResponse> get( + _i3.Future<_i2.FileServiceResponse> get( String? url, { Map? headers, }) => (super.noSuchMethod( Invocation.method(#get, [url], {#headers: headers}), - returnValue: _i4.Future<_i3.FileServiceResponse>.value( + returnValue: _i3.Future<_i2.FileServiceResponse>.value( _FakeFileServiceResponse_4( this, Invocation.method(#get, [url], {#headers: headers}), ), ), ) - as _i4.Future<_i3.FileServiceResponse>); + as _i3.Future<_i2.FileServiceResponse>); } /// A class which mocks [WebHelper]. /// /// See the documentation for Mockito's code generation for more information. -class MockWebHelper extends _i1.Mock implements _i7.WebHelper { +class MockWebHelper extends _i1.Mock implements _i6.WebHelper { MockWebHelper() { _i1.throwOnMissingStub(this); } @override - _i3.FileService get fileFetcher => + _i2.FileService get fileFetcher => (super.noSuchMethod( Invocation.getter(#fileFetcher), returnValue: _FakeFileService_5( @@ -422,7 +430,7 @@ class MockWebHelper extends _i1.Mock implements _i7.WebHelper { Invocation.getter(#fileFetcher), ), ) - as _i3.FileService); + as _i2.FileService); @override int get concurrentCalls => @@ -436,7 +444,7 @@ class MockWebHelper extends _i1.Mock implements _i7.WebHelper { ); @override - _i4.Stream<_i3.FileResponse> downloadFile( + _i3.Stream<_i2.FileResponse> downloadFile( String? url, { String? key, Map? authHeaders, @@ -452,7 +460,7 @@ class MockWebHelper extends _i1.Mock implements _i7.WebHelper { #ignoreMemCache: ignoreMemCache, }, ), - returnValue: _i4.Stream<_i3.FileResponse>.empty(), + returnValue: _i3.Stream<_i2.FileResponse>.empty(), ) - as _i4.Stream<_i3.FileResponse>); + as _i3.Stream<_i2.FileResponse>); } diff --git a/flutter_cache_manager/test/repositories/json_file_repository_test.dart b/flutter_cache_manager/test/repositories/json_file_repository_test.dart index 1daf59b6..364ab061 100644 --- a/flutter_cache_manager/test/repositories/json_file_repository_test.dart +++ b/flutter_cache_manager/test/repositories/json_file_repository_test.dart @@ -101,7 +101,7 @@ void main() { test('getObjectsOverCapacity should return oldest objects', () async { var repo = await JsonRepoHelpers.createRepository(); - var result = await repo.getObjectsOverCapacity(1); + var result = await repo.getObjectsOverCapacity(1, maxAge: Duration.zero); expect(result.length, 2); expectIdInList(result, 1); expectIdInList(result, 3);