Skip to content

Commit 2a4f3b6

Browse files
committed
feat: Add Hive as cache store.
Closes #10.
1 parent 2745bd9 commit 2a4f3b6

6 files changed

Lines changed: 315 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 2.1.0
2+
feat: Add Hive as cache store.
3+
14
## 2.0.0
25
- core: Update dio to 4.0.0.
36
- Renamed `cacheStoreForce` to `forceCache`.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Dio HTTP cache interceptor with multiple stores respecting HTTP directives (or n
1313
- BackupCacheStore: Combined store with primary and secondary.
1414
- DbCacheStore: Cache with database (Moor).
1515
- FileCacheStore: Cache with file system (no web support obviously).
16+
- HiveCacheStore: Cache using Hive package (available on all platforms).
1617
- MemCacheStore: Volatile cache with LRU strategy.
1718

1819
### DbCacheStore:

lib/dio_cache_interceptor.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
library dio_cache_interceptor;
22

3+
export 'io_unsupported.dart'
4+
if (dart.library.io) 'src/store/file_cache_store.dart';
35
export 'src/dio_cache_interceptor.dart';
46
export 'src/model/cache_control.dart';
57
export 'src/model/cache_options.dart';
68
export 'src/model/cache_priority.dart';
79
export 'src/model/cache_response.dart';
10+
export 'src/store/backup_cache_store.dart';
811
export 'src/store/cache_store.dart';
912
export 'src/store/db_cache_store.dart';
10-
export 'io_unsupported.dart'
11-
if (dart.library.io) 'src/store/file_cache_store.dart';
13+
export 'src/store/hive_cache_store.dart';
1214
export 'src/store/mem_cache_store.dart';
13-
export 'src/store/backup_cache_store.dart';
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
import 'package:hive/hive.dart';
2+
3+
import '../../dio_cache_interceptor.dart';
4+
5+
/// A store saving responses using hive.
6+
///
7+
class HiveCacheStore implements CacheStore {
8+
static const _hiveBoxName = 'dio_cache';
9+
10+
Box<CacheResponse>? _box;
11+
12+
/// Initialize cache store by giving Hive a home directory.
13+
/// [directory] can be null only on web platform or if you already use Hive
14+
/// in your app.
15+
HiveCacheStore(String? directory) {
16+
if (directory != null) {
17+
Hive.init(directory);
18+
}
19+
20+
if (!Hive.isAdapterRegistered(_CacheResponseAdapter._typeId)) {
21+
Hive.registerAdapter(_CacheResponseAdapter());
22+
}
23+
if (!Hive.isAdapterRegistered(_CacheControlAdapter._typeId)) {
24+
Hive.registerAdapter(_CacheControlAdapter());
25+
}
26+
if (!Hive.isAdapterRegistered(_CachePriorityAdapter._typeId)) {
27+
Hive.registerAdapter(_CachePriorityAdapter());
28+
}
29+
30+
clean(staleOnly: true);
31+
}
32+
33+
@override
34+
Future<void> clean({
35+
CachePriority priorityOrBelow = CachePriority.high,
36+
bool staleOnly = false,
37+
}) async {
38+
final box = await _openBox();
39+
40+
final keys = <String>[];
41+
42+
for (var i = 0; i < box.values.length; i++) {
43+
final resp = box.getAt(i);
44+
45+
if (resp != null) {
46+
var shouldRemove = resp.priority.index <= priorityOrBelow.index;
47+
shouldRemove &= (staleOnly && resp.isStaled()) || !staleOnly;
48+
49+
if (shouldRemove) {
50+
keys.add(resp.key);
51+
}
52+
}
53+
}
54+
55+
return box.deleteAll(keys);
56+
}
57+
58+
@override
59+
Future<void> close() {
60+
final checkedBox = _box;
61+
if (checkedBox != null && checkedBox.isOpen) {
62+
_box = null;
63+
return checkedBox.close();
64+
}
65+
66+
return Future.value();
67+
}
68+
69+
@override
70+
Future<void> delete(String key, {bool staleOnly = false}) async {
71+
final box = await _openBox();
72+
final resp = box.get(key);
73+
if (resp == null) return Future.value();
74+
75+
if (staleOnly && !resp.isStaled()) {
76+
return Future.value();
77+
}
78+
79+
await box.delete(key);
80+
}
81+
82+
@override
83+
Future<bool> exists(String key) async {
84+
final box = await _openBox();
85+
return box.containsKey(key);
86+
}
87+
88+
@override
89+
Future<CacheResponse?> get(String key) async {
90+
final box = await _openBox();
91+
final resp = box.get(key);
92+
93+
// Purge entry if staled
94+
if (resp?.isStaled() ?? false) {
95+
await delete(key);
96+
return null;
97+
}
98+
99+
return resp;
100+
}
101+
102+
@override
103+
Future<void> set(CacheResponse response) async {
104+
final box = await _openBox();
105+
return box.put(response.key, response);
106+
}
107+
108+
Future<Box<CacheResponse>> _openBox() async {
109+
_box ??= await Hive.openBox<CacheResponse>(_hiveBoxName);
110+
return Future.value(_box);
111+
}
112+
}
113+
114+
class _CacheResponseAdapter extends TypeAdapter<CacheResponse> {
115+
static const int _typeId = 93;
116+
117+
@override
118+
final int typeId = _typeId;
119+
120+
@override
121+
CacheResponse read(BinaryReader reader) {
122+
final numOfFields = reader.readByte();
123+
final fields = <int, dynamic>{
124+
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
125+
};
126+
return CacheResponse(
127+
cacheControl: fields[0] as CacheControl?,
128+
content: (fields[1] as List?)?.cast<int>(),
129+
date: fields[2] as DateTime?,
130+
eTag: fields[3] as String?,
131+
expires: fields[4] as DateTime?,
132+
headers: (fields[5] as List?)?.cast<int>(),
133+
key: fields[6] as String,
134+
lastModified: fields[7] as String?,
135+
maxStale: fields[8] as DateTime?,
136+
priority: fields[9] as CachePriority,
137+
responseDate: fields[10] as DateTime,
138+
url: fields[11] as String,
139+
);
140+
}
141+
142+
@override
143+
void write(BinaryWriter writer, CacheResponse obj) {
144+
writer
145+
..writeByte(12)
146+
..writeByte(0)
147+
..write(obj.cacheControl)
148+
..writeByte(1)
149+
..write(obj.content)
150+
..writeByte(2)
151+
..write(obj.date)
152+
..writeByte(3)
153+
..write(obj.eTag)
154+
..writeByte(4)
155+
..write(obj.expires)
156+
..writeByte(5)
157+
..write(obj.headers)
158+
..writeByte(6)
159+
..write(obj.key)
160+
..writeByte(7)
161+
..write(obj.lastModified)
162+
..writeByte(8)
163+
..write(obj.maxStale)
164+
..writeByte(9)
165+
..write(obj.priority)
166+
..writeByte(10)
167+
..write(obj.responseDate)
168+
..writeByte(11)
169+
..write(obj.url);
170+
}
171+
172+
@override
173+
int get hashCode => typeId.hashCode;
174+
175+
@override
176+
bool operator ==(Object other) =>
177+
identical(this, other) ||
178+
other is _CacheResponseAdapter &&
179+
runtimeType == other.runtimeType &&
180+
typeId == other.typeId;
181+
}
182+
183+
class _CacheControlAdapter extends TypeAdapter<CacheControl> {
184+
static const int _typeId = 94;
185+
186+
@override
187+
final int typeId = _typeId;
188+
189+
@override
190+
CacheControl read(BinaryReader reader) {
191+
final numOfFields = reader.readByte();
192+
final fields = <int, dynamic>{
193+
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
194+
};
195+
return CacheControl(
196+
maxAge: fields[0] as int?,
197+
privacy: fields[1] as String?,
198+
noCache: fields[2] as bool?,
199+
noStore: fields[3] as bool?,
200+
other: (fields[4] as List).cast<String>(),
201+
);
202+
}
203+
204+
@override
205+
void write(BinaryWriter writer, CacheControl obj) {
206+
writer
207+
..writeByte(5)
208+
..writeByte(0)
209+
..write(obj.maxAge)
210+
..writeByte(1)
211+
..write(obj.privacy)
212+
..writeByte(2)
213+
..write(obj.noCache)
214+
..writeByte(3)
215+
..write(obj.noStore)
216+
..writeByte(4)
217+
..write(obj.other);
218+
}
219+
220+
@override
221+
int get hashCode => typeId.hashCode;
222+
223+
@override
224+
bool operator ==(Object other) =>
225+
identical(this, other) ||
226+
other is _CacheControlAdapter &&
227+
runtimeType == other.runtimeType &&
228+
typeId == other.typeId;
229+
}
230+
231+
class _CachePriorityAdapter extends TypeAdapter<CachePriority> {
232+
static const int _typeId = 95;
233+
234+
@override
235+
final int typeId = _typeId;
236+
237+
@override
238+
CachePriority read(BinaryReader reader) {
239+
switch (reader.readByte()) {
240+
case 0:
241+
return CachePriority.low;
242+
case 1:
243+
return CachePriority.normal;
244+
case 2:
245+
return CachePriority.high;
246+
default:
247+
return CachePriority.low;
248+
}
249+
}
250+
251+
@override
252+
void write(BinaryWriter writer, CachePriority obj) {
253+
switch (obj) {
254+
case CachePriority.low:
255+
writer.writeByte(0);
256+
break;
257+
case CachePriority.normal:
258+
writer.writeByte(1);
259+
break;
260+
case CachePriority.high:
261+
writer.writeByte(2);
262+
break;
263+
}
264+
}
265+
266+
@override
267+
int get hashCode => typeId.hashCode;
268+
269+
@override
270+
bool operator ==(Object other) =>
271+
identical(this, other) ||
272+
other is _CachePriorityAdapter &&
273+
runtimeType == other.runtimeType &&
274+
typeId == other.typeId;
275+
}

pubspec.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ description: Dio HTTP cache interceptor with multiple stores respecting HTTP dir
33
repository: https://github.com/llfbandit/dio_cache_interceptor
44
issue_tracker: https://github.com/llfbandit/dio_cache_interceptor/issues
55

6-
version: 2.0.0
6+
version: 2.1.0
77

88
environment:
99
sdk: ">=2.12.0 <3.0.0"
1010

1111
dependencies:
1212
# https://pub.dev/packages/dio
1313
dio: ^4.0.0
14+
# https://pub.dev/packages/hive
15+
hive: ^2.0.3
1416
# https://pub.dev/packages/moor
1517
moor: ^4.1.0
1618
# https://pub.dev/packages/path
@@ -19,7 +21,7 @@ dependencies:
1921
uuid: ^3.0.1
2022

2123
dev_dependencies:
22-
# https://pub.dev/packages/moor_generator
24+
# https://pub.dev/packages/moor_generator
2325
moor_generator: ^4.2.1
2426
# https://pub.dev/packages/build_runner
2527
build_runner: ^1.12.2

test/hive_store_test.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import 'dart:io';
2+
3+
import 'package:dio_cache_interceptor/src/store/hive_cache_store.dart';
4+
import 'package:test/test.dart';
5+
6+
import 'common_store_testing.dart';
7+
8+
void main() {
9+
late HiveCacheStore store;
10+
11+
setUp(() async {
12+
store = HiveCacheStore('${Directory.current.path}/test/data/file_store');
13+
await store.clean();
14+
});
15+
16+
tearDown(() async {
17+
await store.close();
18+
});
19+
20+
test('Empty by default', () async => await emptyByDefault(store));
21+
test('Add item', () async => await addItem(store));
22+
test('Get item', () async => await getItem(store));
23+
test('Delete item', () async => await deleteItem(store));
24+
test('Clean', () async => await clean(store));
25+
test('Expires', () async => await expires(store));
26+
test('LastModified', () async => await lastModified(store));
27+
test('Staled', () async => await staled(store));
28+
}

0 commit comments

Comments
 (0)