Skip to content

Commit

Permalink
feat: Add Hive as cache store.
Browse files Browse the repository at this point in the history
Closes #10.
  • Loading branch information
llfbandit committed Apr 15, 2021
1 parent 2745bd9 commit 2a4f3b6
Show file tree
Hide file tree
Showing 6 changed files with 315 additions and 5 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 2.1.0
feat: Add Hive as cache store.

## 2.0.0
- core: Update dio to 4.0.0.
- Renamed `cacheStoreForce` to `forceCache`.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Dio HTTP cache interceptor with multiple stores respecting HTTP directives (or n
- BackupCacheStore: Combined store with primary and secondary.
- DbCacheStore: Cache with database (Moor).
- FileCacheStore: Cache with file system (no web support obviously).
- HiveCacheStore: Cache using Hive package (available on all platforms).
- MemCacheStore: Volatile cache with LRU strategy.

### DbCacheStore:
Expand Down
7 changes: 4 additions & 3 deletions lib/dio_cache_interceptor.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
library dio_cache_interceptor;

export 'io_unsupported.dart'
if (dart.library.io) 'src/store/file_cache_store.dart';
export 'src/dio_cache_interceptor.dart';
export 'src/model/cache_control.dart';
export 'src/model/cache_options.dart';
export 'src/model/cache_priority.dart';
export 'src/model/cache_response.dart';
export 'src/store/backup_cache_store.dart';
export 'src/store/cache_store.dart';
export 'src/store/db_cache_store.dart';
export 'io_unsupported.dart'
if (dart.library.io) 'src/store/file_cache_store.dart';
export 'src/store/hive_cache_store.dart';
export 'src/store/mem_cache_store.dart';
export 'src/store/backup_cache_store.dart';
275 changes: 275 additions & 0 deletions lib/src/store/hive_cache_store.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
import 'package:hive/hive.dart';

import '../../dio_cache_interceptor.dart';

/// A store saving responses using hive.
///
class HiveCacheStore implements CacheStore {
static const _hiveBoxName = 'dio_cache';

Box<CacheResponse>? _box;

/// Initialize cache store by giving Hive a home directory.
/// [directory] can be null only on web platform or if you already use Hive
/// in your app.
HiveCacheStore(String? directory) {
if (directory != null) {
Hive.init(directory);
}

if (!Hive.isAdapterRegistered(_CacheResponseAdapter._typeId)) {
Hive.registerAdapter(_CacheResponseAdapter());
}
if (!Hive.isAdapterRegistered(_CacheControlAdapter._typeId)) {
Hive.registerAdapter(_CacheControlAdapter());
}
if (!Hive.isAdapterRegistered(_CachePriorityAdapter._typeId)) {
Hive.registerAdapter(_CachePriorityAdapter());
}

clean(staleOnly: true);
}

@override
Future<void> clean({
CachePriority priorityOrBelow = CachePriority.high,
bool staleOnly = false,
}) async {
final box = await _openBox();

final keys = <String>[];

for (var i = 0; i < box.values.length; i++) {
final resp = box.getAt(i);

if (resp != null) {
var shouldRemove = resp.priority.index <= priorityOrBelow.index;
shouldRemove &= (staleOnly && resp.isStaled()) || !staleOnly;

if (shouldRemove) {
keys.add(resp.key);
}
}
}

return box.deleteAll(keys);
}

@override
Future<void> close() {
final checkedBox = _box;
if (checkedBox != null && checkedBox.isOpen) {
_box = null;
return checkedBox.close();
}

return Future.value();
}

@override
Future<void> delete(String key, {bool staleOnly = false}) async {
final box = await _openBox();
final resp = box.get(key);
if (resp == null) return Future.value();

if (staleOnly && !resp.isStaled()) {
return Future.value();
}

await box.delete(key);
}

@override
Future<bool> exists(String key) async {
final box = await _openBox();
return box.containsKey(key);
}

@override
Future<CacheResponse?> get(String key) async {
final box = await _openBox();
final resp = box.get(key);

// Purge entry if staled
if (resp?.isStaled() ?? false) {
await delete(key);
return null;
}

return resp;
}

@override
Future<void> set(CacheResponse response) async {
final box = await _openBox();
return box.put(response.key, response);
}

Future<Box<CacheResponse>> _openBox() async {
_box ??= await Hive.openBox<CacheResponse>(_hiveBoxName);
return Future.value(_box);
}
}

class _CacheResponseAdapter extends TypeAdapter<CacheResponse> {
static const int _typeId = 93;

@override
final int typeId = _typeId;

@override
CacheResponse read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return CacheResponse(
cacheControl: fields[0] as CacheControl?,
content: (fields[1] as List?)?.cast<int>(),
date: fields[2] as DateTime?,
eTag: fields[3] as String?,
expires: fields[4] as DateTime?,
headers: (fields[5] as List?)?.cast<int>(),
key: fields[6] as String,
lastModified: fields[7] as String?,
maxStale: fields[8] as DateTime?,
priority: fields[9] as CachePriority,
responseDate: fields[10] as DateTime,
url: fields[11] as String,
);
}

@override
void write(BinaryWriter writer, CacheResponse obj) {
writer
..writeByte(12)
..writeByte(0)
..write(obj.cacheControl)
..writeByte(1)
..write(obj.content)
..writeByte(2)
..write(obj.date)
..writeByte(3)
..write(obj.eTag)
..writeByte(4)
..write(obj.expires)
..writeByte(5)
..write(obj.headers)
..writeByte(6)
..write(obj.key)
..writeByte(7)
..write(obj.lastModified)
..writeByte(8)
..write(obj.maxStale)
..writeByte(9)
..write(obj.priority)
..writeByte(10)
..write(obj.responseDate)
..writeByte(11)
..write(obj.url);
}

@override
int get hashCode => typeId.hashCode;

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is _CacheResponseAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

class _CacheControlAdapter extends TypeAdapter<CacheControl> {
static const int _typeId = 94;

@override
final int typeId = _typeId;

@override
CacheControl read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return CacheControl(
maxAge: fields[0] as int?,
privacy: fields[1] as String?,
noCache: fields[2] as bool?,
noStore: fields[3] as bool?,
other: (fields[4] as List).cast<String>(),
);
}

@override
void write(BinaryWriter writer, CacheControl obj) {
writer
..writeByte(5)
..writeByte(0)
..write(obj.maxAge)
..writeByte(1)
..write(obj.privacy)
..writeByte(2)
..write(obj.noCache)
..writeByte(3)
..write(obj.noStore)
..writeByte(4)
..write(obj.other);
}

@override
int get hashCode => typeId.hashCode;

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is _CacheControlAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

class _CachePriorityAdapter extends TypeAdapter<CachePriority> {
static const int _typeId = 95;

@override
final int typeId = _typeId;

@override
CachePriority read(BinaryReader reader) {
switch (reader.readByte()) {
case 0:
return CachePriority.low;
case 1:
return CachePriority.normal;
case 2:
return CachePriority.high;
default:
return CachePriority.low;
}
}

@override
void write(BinaryWriter writer, CachePriority obj) {
switch (obj) {
case CachePriority.low:
writer.writeByte(0);
break;
case CachePriority.normal:
writer.writeByte(1);
break;
case CachePriority.high:
writer.writeByte(2);
break;
}
}

@override
int get hashCode => typeId.hashCode;

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is _CachePriorityAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
6 changes: 4 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ description: Dio HTTP cache interceptor with multiple stores respecting HTTP dir
repository: https://github.com/llfbandit/dio_cache_interceptor
issue_tracker: https://github.com/llfbandit/dio_cache_interceptor/issues

version: 2.0.0
version: 2.1.0

environment:
sdk: ">=2.12.0 <3.0.0"

dependencies:
# https://pub.dev/packages/dio
dio: ^4.0.0
# https://pub.dev/packages/hive
hive: ^2.0.3
# https://pub.dev/packages/moor
moor: ^4.1.0
# https://pub.dev/packages/path
Expand All @@ -19,7 +21,7 @@ dependencies:
uuid: ^3.0.1

dev_dependencies:
# https://pub.dev/packages/moor_generator
# https://pub.dev/packages/moor_generator
moor_generator: ^4.2.1
# https://pub.dev/packages/build_runner
build_runner: ^1.12.2
Expand Down
28 changes: 28 additions & 0 deletions test/hive_store_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'dart:io';

import 'package:dio_cache_interceptor/src/store/hive_cache_store.dart';
import 'package:test/test.dart';

import 'common_store_testing.dart';

void main() {
late HiveCacheStore store;

setUp(() async {
store = HiveCacheStore('${Directory.current.path}/test/data/file_store');
await store.clean();
});

tearDown(() async {
await store.close();
});

test('Empty by default', () async => await emptyByDefault(store));
test('Add item', () async => await addItem(store));
test('Get item', () async => await getItem(store));
test('Delete item', () async => await deleteItem(store));
test('Clean', () async => await clean(store));
test('Expires', () async => await expires(store));
test('LastModified', () async => await lastModified(store));
test('Staled', () async => await staled(store));
}

0 comments on commit 2a4f3b6

Please sign in to comment.