Skip to content

Instantly share code, notes, and snippets.

@buraktabn
Last active June 24, 2022 19:00
Show Gist options
  • Save buraktabn/bdb99a8e7e77dfd7c683177ca237a125 to your computer and use it in GitHub Desktop.
Save buraktabn/bdb99a8e7e77dfd7c683177ca237a125 to your computer and use it in GitHub Desktop.
Hive ExpirationBox
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