From 2ef2a16e84d13397f9c2ccfe073138a72613fb99 Mon Sep 17 00:00:00 2001 From: gokberkbar Date: Wed, 10 Aug 2022 13:57:05 +0300 Subject: [PATCH] bug fix and isEmpty & isNotEmpty feature added --- CHANGELOG.md | 7 +++ README.md | 2 +- lib/src/cache.dart | 51 +++++++++++++------- pubspec.yaml | 5 +- test/memory_cache_test.dart | 95 +++++++++++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 19 deletions(-) create mode 100644 test/memory_cache_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index effe43c..3b77e4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.1.0 + +- `isEmpty` & `isNotEmpty` features added. +- Some bug fixes. + +--- + ## 1.0.0 - Initial version. diff --git a/README.md b/README.md index 0a3ee81..875fa8d 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ $ flutter pub add memory_cache This will add a line like this to your package's pubspec.yaml (and run an implicit `dart pub get` or `flutter pub get`): ``` dependencies: - memory_cache: ^1.0.0 + memory_cache: ^1.1.0 ``` ## Usage diff --git a/lib/src/cache.dart b/lib/src/cache.dart index 203dea8..d2ad8ac 100644 --- a/lib/src/cache.dart +++ b/lib/src/cache.dart @@ -8,17 +8,21 @@ class MemoryCache { /// internal cache final Map _cache = {}; + /// Checks if cache is empty. + bool get isEmpty { + _cache.removeWhere((key, value) => _isExpired(value)); + return _cache.isEmpty; + } + + /// Checks if cache is not empty. + bool get isNotEmpty => !isEmpty; + /// If cache contains a value for the [key], returns the value. /// If cache does not contains a value for the [key], returns null. T? read(String key) { - if (_cache.containsKey(key)) { + if (_expiryAwareContains(key)) { final item = _cache[key]!; - if (item.expiry != null && item.expiry!.isBefore(DateTime.now())) { - delete(key); - return null; - } else { - return item.value; - } + return item.value; } return null; } @@ -32,15 +36,9 @@ class MemoryCache { /// If cache does not contains a value for the [key], sets the [value] to the cache and returns true. /// If cache contains a value for the [key], returns false. bool createIfAbsent(String key, T value, {Duration? expiry}) { - if (!_cache.containsKey(key)) { + if (!_expiryAwareContains(key)) { create(key, value, expiry: expiry); return true; - } else { - final item = _cache[key]!; - if (item.expiry != null && item.expiry!.isBefore(DateTime.now())) { - create(key, value, expiry: expiry); - return true; - } } return false; } @@ -48,7 +46,7 @@ class MemoryCache { /// If cache contains a value for the [key], updates the value on the cache and returns true. /// If cache does not contains a value for the [key], returns false. bool update(String key, T value, {Duration? expiry}) { - if (_cache.containsKey(key)) { + if (_expiryAwareContains(key)) { _cache[key] = _cache[key]!.copyWith( value: value, expiry: _setExpiry(expiry), @@ -70,10 +68,31 @@ class MemoryCache { /// Returns true if cached value exists. bool contains(String key) { - return _cache.containsKey(key); + return _expiryAwareContains(key); } /// Adds [expiry] to DateTime.now() and returns it. DateTime? _setExpiry(Duration? expiry) => expiry != null ? DateTime.now().add(expiry) : null; + + /// Checks if [item] is expired. + bool _isExpired(CacheItem item) { + if (item.expiry != null && item.expiry!.isBefore(DateTime.now())) { + return true; + } + return false; + } + + /// Returns true if cached value exists but not expired. + /// If cached value expired removes from cache. + bool _expiryAwareContains(String key) { + final item = _cache[key]; + if (item == null) { + return false; + } else if (_isExpired(item)) { + delete(key); + return false; + } + return true; + } } diff --git a/pubspec.yaml b/pubspec.yaml index d522804..b314b86 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: memory_cache -description: Memory Cache is simple, fast and global in-memory cache. -version: 1.0.0 +description: Memory Cache is simple, fast and global in-memory cache with CRUD features. +version: 1.1.0 repository: https://github.com/gokberkbar/memory_cache environment: @@ -9,3 +9,4 @@ environment: dev_dependencies: lints: ^2.0.0 test: ^1.16.0 + analyzer: ^4.5.0 diff --git a/test/memory_cache_test.dart b/test/memory_cache_test.dart new file mode 100644 index 0000000..68d3089 --- /dev/null +++ b/test/memory_cache_test.dart @@ -0,0 +1,95 @@ +import 'package:memory_cache/memory_cache.dart'; +import 'package:test/test.dart'; + +void main() { + group('CRUD Operations', () { + final key = 'key0'; + final value = 'value0'; + + setUpAll(() { + MemoryCache.instance.invalidate(); + MemoryCache.instance.create(key, value); + }); + + test('is item created', () { + expect(MemoryCache.instance.contains(key), true); + }); + + test('read item', () { + final cachedValue = MemoryCache.instance.read(key); + expect(cachedValue, value); + }); + + test('create if item is absent', () { + final newValue = 'key1'; + final newKey = 'value1'; + final isCreated = MemoryCache.instance.createIfAbsent(key, newValue); + expect(isCreated, false); + final isNewValueCreated = + MemoryCache.instance.createIfAbsent(newKey, newValue); + expect(isNewValueCreated, true); + }); + + test('update item', () { + final newValue = 'updatedValue'; + final isUpdated = MemoryCache.instance.update(key, newValue); + expect(isUpdated, true); + final cachedValue = MemoryCache.instance.read(key); + expect(cachedValue, newValue); + }); + + test('delete item', () { + MemoryCache.instance.delete(key); + expect(MemoryCache.instance.contains(key), false); + }); + + test('expire item', () async { + MemoryCache.instance + .create(key, value, expiry: Duration(seconds: 1)); + await Future.delayed(Duration(seconds: 2)); + final isValueExist = MemoryCache.instance.contains(key); + expect(isValueExist, false); + }); + + test('createIfAbsent test on expired item', () async { + MemoryCache.instance + .create(key, value, expiry: Duration(seconds: 1)); + await Future.delayed(Duration(seconds: 2)); + final isValueCreated = + MemoryCache.instance.createIfAbsent('newKey', 'newItem'); + expect(isValueCreated, true); + }); + + test('re-create item with expiry', () async { + final newKey = 'newKey'; + final item = 'item'; + final newItem = 'newItem'; + + MemoryCache.instance + .create(newKey, item, expiry: Duration(seconds: 1)); + MemoryCache.instance + .create(newKey, newItem, expiry: Duration(seconds: 5)); + await Future.delayed(Duration(seconds: 2)); + final value = MemoryCache.instance.read(newKey); + expect(value, newItem); + }); + + test('update expired item', () async { + final newKey = 'newKey'; + final item = 'item'; + final newItem = 'newItem'; + + MemoryCache.instance + .create(newKey, item, expiry: Duration(seconds: 1)); + await Future.delayed(Duration(seconds: 2)); + final isUpdated = MemoryCache.instance.update(newKey, newItem); + expect(isUpdated, false); + }); + + test('check if cache is empty', () async { + MemoryCache.instance.invalidate(); + final isEmpty = MemoryCache.instance.isEmpty; + expect(isEmpty, true); + }); + }); +}