Last active
June 24, 2022 19:00
-
-
Save buraktabn/bdb99a8e7e77dfd7c683177ca237a125 to your computer and use it in GitHub Desktop.
Hive ExpirationBox
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:typed_data'; | |
import 'package:hive/hive.dart'; | |
class Test { | |
Test() { | |
_init(); | |
} | |
_init() async { | |
final expirationBox = await ExpirationBox().openBox('myBox'); | |
expirationBox.put('name', 'paul', expire: Duration(days: 1)); | |
expirationBox.put('friends', ['Dave', 'Simon', 'Lisa'], expire: Duration(hours: 12)); | |
expirationBox.put(123, 'test', expire: Duration(minutes: 30)); | |
expirationBox.putAll({'key1': 'value1', 42: 'life'}, expire: {'key1': Duration(seconds: 30)}); | |
} | |
} | |
class ExpirationBox<E> extends BoxExpireBase<E> implements HiveExpireInterface { | |
Box _mainBox; | |
Box<int> expirationBox; | |
ExpirationBox([this._mainBox]); | |
@override | |
Future<int> add(E value, {Duration expire}) async { | |
final ik = await _mainBox.add(value); | |
if (expire != null) await expirationBox.add(_createMilisecondsFromDurationAndNow(expire)); | |
return ik; | |
} | |
@override | |
Future<Iterable<int>> addAll(Iterable<E> values, {Iterable<Duration> expire}) async { | |
final ik = await _mainBox.addAll(values); | |
if (expire != null) await expirationBox.addAll(expire.map((e) => _createMilisecondsFromDurationAndNow(e))); | |
return ik; | |
} | |
@override | |
Future<int> clear() async { | |
final ik = await _mainBox.clear(); | |
await expirationBox.clear(); | |
throw ik; | |
} | |
@override | |
Future<void> close() async { | |
await _mainBox.close(); | |
await expirationBox.close(); | |
} | |
@override | |
Future<void> compact() async { | |
await _mainBox.compact(); | |
await expirationBox.compact(); | |
} | |
@override | |
bool containsKey(key) { | |
return _mainBox.containsKey(key); | |
} | |
@override | |
Future<void> delete(key) async { | |
await _mainBox.delete(key); | |
await expirationBox.delete(key); | |
} | |
@override | |
Future<void> deleteAll(Iterable keys) async { | |
await _mainBox.deleteAll(keys); | |
await expirationBox.deleteAll(keys); | |
} | |
@override | |
Future<void> deleteAt(int index) async { | |
await _mainBox.deleteAt(index); | |
await expirationBox.deleteAt(index); | |
} | |
@override | |
Future<void> deleteFromDisk() async { | |
await _mainBox.deleteFromDisk(); | |
await expirationBox.deleteFromDisk(); | |
} | |
@override | |
bool get isEmpty => _mainBox.isEmpty; | |
@override | |
bool get isNotEmpty => _mainBox.isNotEmpty; | |
@override | |
bool get isOpen => _mainBox.isOpen; | |
@override | |
keyAt(int index) { | |
return _mainBox.keyAt(index); | |
} | |
@override | |
Iterable get keys => _mainBox.keys; | |
@override | |
bool get lazy => _mainBox.lazy; | |
@override | |
int get length => _mainBox.length; | |
@override | |
String get name => _mainBox.name; | |
@override | |
String get path => _mainBox.path; | |
@override | |
Future<void> put(key, E value, {Duration expire}) async { | |
final ik = await _mainBox.put(key, value); | |
if (expire != null) await expirationBox.put(key, _createMilisecondsFromDurationAndNow(expire)); | |
return ik; | |
} | |
@override | |
Future<void> putAll(Map<dynamic, E> entries, {Map<dynamic, Duration> expire = const {}}) async { | |
assert(expire.isEmpty && !entries.keys.every((e) => expire.keys.contains(e))); | |
final ik = await _mainBox.putAll(entries); | |
if (expire.isNotEmpty) { | |
final Map<dynamic, int> expireEntries = {}; | |
expire.forEach((k, v) => expireEntries[k] = _createMilisecondsFromDurationAndNow(v)); | |
await expirationBox.putAll(expireEntries); | |
} | |
return ik; | |
} | |
@override | |
Future<void> putAt(int index, E value, {Duration expire}) async { | |
final ik = await _mainBox.putAt(index, value); | |
await expirationBox.putAt(index, _createMilisecondsFromDurationAndNow(expire)); | |
return ik; | |
} | |
@override | |
Stream<BoxEvent> watch({key}) { | |
return _mainBox.watch(key: key); | |
} | |
@override | |
E get(key, {E defaultValue}) { | |
try { | |
if (_shouldExpire(expirationBox.get(key))) { | |
_mainBox.delete(key); | |
expirationBox.delete(key); | |
return defaultValue; | |
} | |
} catch (_) {} | |
return _mainBox.get(key, defaultValue: defaultValue); | |
} | |
@override | |
E getAt(int index) { | |
try { | |
if (_shouldExpire(expirationBox.getAt(index))) { | |
_mainBox.deleteAt(index); | |
expirationBox.deleteAt(index); | |
return null; | |
} | |
} catch (_) {} | |
return _mainBox.getAt(index); | |
} | |
@override | |
Map<dynamic, E> toMap() { | |
return _mainBox.toMap(); | |
} | |
@override | |
Iterable<E> get values => _mainBox.values; | |
@override | |
Iterable<E> valuesBetween({startKey, endKey}) { | |
return _mainBox.valuesBetween(startKey: startKey, endKey: endKey); | |
} | |
@override | |
ExpirationBox<E> box<E>(String name) { | |
final box = Hive.box<E>(name); | |
return ExpirationBox(box); | |
} | |
@override | |
Future<bool> boxExists(String name) { | |
return Hive.boxExists(name); | |
} | |
@override | |
Future<void> deleteBoxFromDisk(String name) { | |
return Hive.deleteBoxFromDisk(name); | |
} | |
@override | |
List<int> generateSecureKey() { | |
return Hive.generateSecureKey(); | |
} | |
@override | |
void ignoreTypeId<T>(int typeId) { | |
return Hive.ignoreTypeId(typeId); | |
} | |
@override | |
void init(String path) { | |
Hive.init(path); | |
} | |
@override | |
bool isAdapterRegistered(int typeId) { | |
return Hive.isAdapterRegistered(typeId); | |
} | |
@override | |
bool isBoxOpen(String name) { | |
return Hive.isBoxOpen(name); | |
} | |
@override | |
void registerAdapter<T>(TypeAdapter<T> adapter) { | |
Hive.registerAdapter(adapter); | |
} | |
@override | |
Future<ExpirationBox<E>> openBox<E>(String name, | |
{HiveCipher encryptionCipher, | |
KeyComparator keyComparator = _defaultKeyComparator, | |
CompactionStrategy compactionStrategy = _defaultCompactionStrategy, | |
bool crashRecovery = true, | |
String path, | |
Uint8List bytes, | |
List<int> encryptionKey}) async { | |
final box = await Hive.openBox( | |
name, | |
encryptionCipher: encryptionCipher, | |
keyComparator: keyComparator, | |
compactionStrategy: compactionStrategy, | |
crashRecovery: crashRecovery, | |
path: path, | |
bytes: bytes, | |
); | |
expirationBox = await Hive.openBox<int>('expirations'); | |
return ExpirationBox(box); | |
} | |
int _createMilisecondsFromDurationAndNow(Duration duration) => DateTime.now().millisecondsSinceEpoch + duration.inMilliseconds; | |
bool _shouldExpire(int expireValue) => expireValue - DateTime.now().millisecondsSinceEpoch <= 0; | |
} | |
abstract class BoxExpireBase<E> { | |
/// The name of the box. Names are always lowercase. | |
String get name; | |
/// Whether this box is currently open. | |
/// | |
/// Most of the operations on a box require it to be open. | |
bool get isOpen; | |
/// The location of the box in the file system. In the browser, this is null. | |
String get path; | |
/// Whether this box is lazy or not. | |
/// | |
/// This is equivalent to `box is LazyBox`. | |
bool get lazy; | |
/// All the keys in the box. | |
/// | |
/// The keys are sorted alphabetically in ascending order. | |
Iterable<dynamic> get keys; | |
/// The number of entries in the box. | |
int get length; | |
/// Returns `true` if there are no entries in this box. | |
bool get isEmpty; | |
/// Returns true if there is at least one entries in this box. | |
bool get isNotEmpty; | |
/// Get the n-th key in the box. | |
dynamic keyAt(int index); | |
/// Returns a broadcast stream of change events. | |
/// | |
/// If the [key] parameter is provided, only events for the specified key are | |
/// broadcasted. | |
Stream<BoxEvent> watch({dynamic key}); | |
/// Checks whether the box contains the [key]. | |
bool containsKey(dynamic key); | |
/// Saves the [key] - [value] pair. | |
Future<void> put(dynamic key, E value, {Duration expire}); | |
/// Associates the [value] with the n-th key. An exception is raised if the | |
/// key does not exist. | |
Future<void> putAt(int index, E value, {Duration expire}); | |
/// Saves all the key - value pairs in the [entries] map. | |
Future<void> putAll(Map<dynamic, E> entries, {Map<dynamic, Duration> expire}); | |
/// Saves the [value] with an auto-increment key. | |
Future<int> add(E value, {Duration expire}); | |
/// Saves all the [values] with auto-increment keys. | |
Future<Iterable<int>> addAll(Iterable<E> values, {Iterable<Duration> expire}); | |
/// Deletes the given [key] from the box. | |
/// | |
/// If it does not exist, nothing happens. | |
Future<void> delete(dynamic key); | |
/// Deletes the n-th key from the box. | |
/// | |
/// If it does not exist, nothing happens. | |
Future<void> deleteAt(int index); | |
/// Deletes all the given [keys] from the box. | |
/// | |
/// If a key does not exist, it is skipped. | |
Future<void> deleteAll(Iterable<dynamic> keys); | |
/// Induces compaction manually. This is rarely needed. You should consider | |
/// providing a custom compaction strategy instead. | |
Future<void> compact(); | |
/// Removes all entries from the box. | |
Future<int> clear(); | |
/// Closes the box. | |
/// | |
/// Be careful, this closes all instances of this box. You have to make sure | |
/// that you don't access the box anywhere else after that. | |
Future<void> close(); | |
/// Removes the file which contains the box and closes the box. | |
/// | |
/// In the browser, the IndexedDB database is being removed. | |
Future<void> deleteFromDisk(); | |
/// All the values in the box. | |
/// | |
/// The values are in the same order as their keys. | |
Iterable<E> get values; | |
/// Returns an iterable which contains all values starting with the value | |
/// associated with [startKey] (inclusive) to the value associated with | |
/// [endKey] (inclusive). | |
/// | |
/// If [startKey] does not exist, an empty iterable is returned. If [endKey] | |
/// does not exist or is before [startKey], it is ignored. | |
/// | |
/// The values are in the same order as their keys. | |
Iterable<E> valuesBetween({dynamic startKey, dynamic endKey}); | |
/// Returns the value associated with the given [key]. If the key does not | |
/// exist, `null` is returned. | |
/// | |
/// If [defaultValue] is specified, it is returned in case the key does not | |
/// exist. | |
E get(dynamic key, {E defaultValue}); | |
/// Returns the value associated with the n-th key. | |
E getAt(int index); | |
/// Returns a map which contains all key - value pairs of the box. | |
Map<dynamic, E> toMap(); | |
} | |
abstract class HiveExpireInterface implements TypeRegistry { | |
/// Initialize Hive by giving it a home directory. | |
/// | |
/// (Not necessary in the browser) | |
void init(String path); | |
/// Opens a box. | |
/// | |
/// If the box is already open, the instance is returned and all provided | |
/// parameters are being ignored. | |
Future<ExpirationBox<E>> openBox<E>( | |
String name, { | |
HiveCipher encryptionCipher, | |
KeyComparator keyComparator = _defaultKeyComparator, | |
CompactionStrategy compactionStrategy = _defaultCompactionStrategy, | |
bool crashRecovery = true, | |
String path, | |
Uint8List bytes, | |
@deprecated List<int> encryptionKey, | |
}); | |
/// Opens a lazy box. | |
/// | |
/// If the box is already open, the instance is returned and all provided | |
/// parameters are being ignored. | |
// Future<LazyBox<E>> openLazyBox<E>( | |
// String name, { | |
// HiveCipher encryptionCipher, | |
// KeyComparator keyComparator = _defaultKeyComparator, | |
// CompactionStrategy compactionStrategy = _defaultCompactionStrategy, | |
// bool crashRecovery = true, | |
// String path, | |
// @deprecated List<int> encryptionKey, | |
// }); | |
/// Returns a previously opened box. | |
ExpirationBox<E> box<E>(String name); | |
/// Returns a previously opened lazy box. | |
// LazyBox<E> lazyBox<E>(String name); | |
/// Checks if a specific box is currently open. | |
bool isBoxOpen(String name); | |
/// Closes all open boxes. | |
Future<void> close(); | |
/// Removes the file which contains the box and closes the box. | |
/// | |
/// In the browser, the IndexedDB database is being removed. | |
Future<void> deleteBoxFromDisk(String name); | |
/// Deletes all currently open boxes from disk. | |
/// | |
/// The home directory will not be deleted. | |
Future<void> deleteFromDisk(); | |
/// Generates a secure encryption key using the fortuna random algorithm. | |
List<int> generateSecureKey(); | |
/// Checks if a box exists | |
Future<bool> boxExists(String name); | |
} | |
/// | |
typedef KeyComparator = int Function(dynamic key1, dynamic key2); | |
/// A function which decides when to compact a box. | |
typedef CompactionStrategy = bool Function(int entries, int deletedEntries); | |
int _defaultKeyComparator(dynamic k1, dynamic k2) { | |
if (k1 is int) { | |
if (k2 is int) { | |
if (k1 > k2) { | |
return 1; | |
} else if (k1 < k2) { | |
return -1; | |
} else { | |
return 0; | |
} | |
} else { | |
return -1; | |
} | |
} else if (k2 is String) { | |
return (k1 as String).compareTo(k2); | |
} else { | |
return 1; | |
} | |
} | |
const _deletedRatio = 0.15; | |
const _deletedThreshold = 60; | |
/// Default compaction strategy compacts if 15% of total values and at least 60 | |
/// values have been deleted | |
bool _defaultCompactionStrategy(int entries, int deletedEntries) { | |
return deletedEntries > _deletedThreshold && deletedEntries / entries > _deletedRatio; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment