Line data Source code
1 : import 'dart:async';
2 :
3 : import 'package:flutter/foundation.dart';
4 : import 'package:logging/logging.dart';
5 : import 'package:rxdart/rxdart.dart';
6 : import 'package:the_storage/src/abstract_storage.dart';
7 : import 'package:the_storage/src/cipher_storage.dart';
8 : import 'package:the_storage/src/db/storage_database.dart';
9 : import 'package:the_storage/src/encrypt_helper.dart';
10 : import 'package:the_storage/src/reactive_storage.dart';
11 : import 'package:the_storage/src/storage.dart';
12 :
13 : /// The storage interface
14 : typedef TheStorageInterface = AbstractStorage<String>;
15 :
16 : /// The reactive interface
17 : typedef ReactiveInterface = ReactiveStorage<String>;
18 :
19 : /// The storage: fast and secure
20 : class TheStorage implements TheStorageInterface, ReactiveInterface {
21 : /// Get storage instance
22 2 : factory TheStorage.i() {
23 2 : _instance ??= TheStorage._();
24 : return _instance!;
25 : }
26 2 : TheStorage._();
27 : static TheStorage? _instance;
28 :
29 : final _log = Logger('TheLogger');
30 :
31 : final _cipherStorage = CipherStorage();
32 : late EncryptHelper _encryptHelper;
33 : final _storage = Storage();
34 :
35 : bool _initialized = false;
36 :
37 : /// (key, domain, defaultValue, keepAlive) -> BehaviorSubject of value
38 : final Map<(String, String, String?, bool), BehaviorSubject<String?>>
39 : _subjects = {};
40 :
41 : /// (domain, keepAlive) -> BehaviorSubject of domain
42 : final Map<(String, bool), BehaviorSubject<Map<String, String>>>
43 : _domainSubjects = {};
44 :
45 : /// (domain, keepAlive) -> BehaviorSubject of domain keys
46 : final Map<(String, bool), BehaviorSubject<List<String>>> _domainKeysSubjects =
47 : {};
48 :
49 : /// Init encrypted storage
50 2 : Future<void> init([
51 : String dbName = AbstractStorage.storageFileName,
52 : @visibleForTesting StorageDatabase? database,
53 : ]) async {
54 2 : if (_initialized) {
55 0 : _log.warning('TheStorage is already initialized!');
56 : return;
57 : }
58 :
59 2 : _initialized = true;
60 :
61 4 : await Future.wait([
62 4 : _cipherStorage.init(),
63 4 : _storage.init(dbName, database),
64 : ]);
65 6 : _encryptHelper = EncryptHelper(_cipherStorage);
66 : }
67 :
68 : /// Dispose storage
69 2 : Future<void> dispose() async {
70 2 : _assureInitialized();
71 :
72 2 : _clearSubjects();
73 :
74 4 : await _storage.dispose();
75 :
76 : _instance = null;
77 : }
78 :
79 2 : @override
80 : Future<void> reset() async {
81 2 : _assureInitialized();
82 :
83 4 : await _storage.reset();
84 :
85 2 : _notifyClearAll();
86 :
87 2 : await dispose();
88 : // _initialized = false;
89 : }
90 :
91 2 : @override
92 : Future<void> clearAll() async {
93 2 : _assureInitialized();
94 :
95 4 : await _storage.clearAll();
96 :
97 2 : _notifyClearAll();
98 : }
99 :
100 2 : @override
101 : Future<void> clearDomain([
102 : String domain = AbstractStorage.defaultDomain,
103 : ]) async {
104 2 : _assureInitialized();
105 :
106 4 : await _storage.clearDomain(domain);
107 :
108 2 : await _notifyAllSubjects(null, domain: domain);
109 : }
110 :
111 2 : @override
112 : Future<void> delete(
113 : String key, {
114 : String domain = AbstractStorage.defaultDomain,
115 : }) async {
116 2 : _assureInitialized();
117 :
118 8 : await _storage.delete(_encryptHelper.encrypt(key), domain: domain);
119 :
120 2 : await _notifyAllSubjects(key, domain: domain);
121 : }
122 :
123 2 : @override
124 : Future<void> deleteDomain(
125 : List<String> keys, {
126 : String domain = AbstractStorage.defaultDomain,
127 : }) async {
128 2 : _assureInitialized();
129 :
130 4 : await _storage.deleteDomain(
131 10 : keys.map((key) => _encryptHelper.encrypt(key)).toList(),
132 : domain: domain,
133 : );
134 :
135 2 : await _notifyAllSubjects(null, domain: domain);
136 : }
137 :
138 2 : @override
139 : Future<String?> get(
140 : String key, {
141 : String? defaultValue,
142 : String domain = AbstractStorage.defaultDomain,
143 : }) async {
144 2 : _assureInitialized();
145 :
146 4 : final storageValue = await _storage.get(
147 4 : _encryptHelper.encrypt(key),
148 : defaultValue: defaultValue != null
149 3 : ? StorageValue(_encryptHelper.encrypt(defaultValue), '')
150 : : null,
151 : domain: domain,
152 : );
153 :
154 4 : return _encryptHelper.decryptNullable(
155 2 : storageValue?.value,
156 2 : storageValue?.iv,
157 : );
158 : }
159 :
160 2 : @override
161 : Future<Map<String, String>> getDomain({
162 : String domain = AbstractStorage.defaultDomain,
163 : }) async {
164 2 : _assureInitialized();
165 :
166 4 : final pairs = await _storage.getDomain(
167 : domain: domain,
168 : );
169 :
170 2 : return pairs.map(
171 4 : (key, value) => MapEntry(
172 4 : _encryptHelper.decrypt(key),
173 8 : _encryptHelper.decrypt(value.value, value.iv),
174 : ),
175 : );
176 : }
177 :
178 2 : @override
179 : Future<List<String>> getDomainKeys({
180 : String domain = AbstractStorage.defaultDomain,
181 : }) async {
182 2 : _assureInitialized();
183 :
184 4 : final keys = await _storage.getDomainKeys(
185 : domain: domain,
186 : );
187 :
188 10 : return keys.map((key) => _encryptHelper.decrypt(key)).toList();
189 : }
190 :
191 2 : @override
192 : Future<void> set(
193 : String key,
194 : String value, {
195 : String domain = AbstractStorage.defaultDomain,
196 : bool overwrite = true,
197 : }) async {
198 2 : _assureInitialized();
199 :
200 4 : final iv = CipherStorage.ivFromSecureRandom().base64;
201 :
202 4 : await _storage.set(
203 4 : _encryptHelper.encrypt(key),
204 6 : StorageValue(_encryptHelper.encrypt(value, iv), iv),
205 : domain: domain,
206 : overwrite: overwrite,
207 : );
208 :
209 2 : await _notifyAllSubjects(key, domain: domain);
210 : }
211 :
212 2 : @override
213 : Future<void> setDomain(
214 : Map<String, String> pairs, {
215 : String domain = AbstractStorage.defaultDomain,
216 : bool overwrite = true,
217 : }) async {
218 2 : _assureInitialized();
219 :
220 4 : await _storage.setDomain(
221 4 : pairs.map((key, value) {
222 4 : final iv = CipherStorage.ivFromSecureRandom().base64;
223 :
224 2 : return MapEntry(
225 4 : _encryptHelper.encrypt(key),
226 6 : StorageValue(_encryptHelper.encrypt(value, iv), iv),
227 : );
228 : }),
229 : domain: domain,
230 : overwrite: overwrite,
231 : );
232 :
233 2 : await _notifyAllSubjects(null, domain: domain);
234 : }
235 :
236 2 : void _assureInitialized() {
237 2 : if (!_initialized) {
238 0 : throw Exception('TheStorage is not initialized!');
239 : }
240 : }
241 :
242 1 : @override
243 : Future<ValueStream<String?>> subscribe(
244 : String key, {
245 : String? defaultValue,
246 : String domain = AbstractStorage.defaultDomain,
247 : bool keepAlive = true,
248 : }) async {
249 1 : _assureInitialized();
250 :
251 2 : final subject = _subjects[(key, domain, defaultValue, keepAlive)];
252 : if (subject != null) {
253 1 : return subject.stream;
254 : }
255 :
256 1 : final data = await get(key, defaultValue: defaultValue, domain: domain);
257 :
258 1 : final newSubject = BehaviorSubject<String?>.seeded(
259 : data,
260 : onCancel: keepAlive
261 : ? null
262 1 : : () {
263 1 : unawaited(
264 3 : _subjects[(key, domain, defaultValue, keepAlive)]?.close(),
265 : );
266 2 : _subjects.remove((key, domain, defaultValue, keepAlive));
267 : },
268 : );
269 2 : _subjects[(key, domain, defaultValue, keepAlive)] = newSubject;
270 :
271 1 : return newSubject.stream;
272 : }
273 :
274 1 : @override
275 : Future<ValueStream<Map<String, String>>> subscribeDomain({
276 : String domain = AbstractStorage.defaultDomain,
277 : bool keepAlive = true,
278 : }) async {
279 1 : _assureInitialized();
280 :
281 2 : final subject = _domainSubjects[(domain, keepAlive)];
282 : if (subject != null) {
283 1 : return subject.stream;
284 : }
285 :
286 1 : final data = await getDomain(domain: domain);
287 :
288 1 : final newSubject = BehaviorSubject<Map<String, String>>.seeded(
289 : data,
290 : onCancel: keepAlive
291 : ? null
292 1 : : () {
293 1 : unawaited(
294 3 : _domainSubjects[(domain, keepAlive)]?.close(),
295 : );
296 2 : _domainSubjects.remove((domain, keepAlive));
297 : },
298 : );
299 2 : _domainSubjects[(domain, keepAlive)] = newSubject;
300 :
301 1 : return newSubject.stream;
302 : }
303 :
304 1 : @override
305 : Future<ValueStream<List<String>>> subscribeDomainKeys({
306 : String domain = AbstractStorage.defaultDomain,
307 : bool keepAlive = true,
308 : }) async {
309 1 : _assureInitialized();
310 :
311 2 : final subject = _domainKeysSubjects[(domain, keepAlive)];
312 : if (subject != null) {
313 1 : return subject.stream;
314 : }
315 :
316 1 : final data = await getDomainKeys(domain: domain);
317 :
318 1 : final newSubject = BehaviorSubject<List<String>>.seeded(
319 : data,
320 : onCancel: keepAlive
321 : ? null
322 1 : : () {
323 1 : unawaited(
324 3 : _domainKeysSubjects[(domain, keepAlive)]?.close(),
325 : );
326 2 : _domainKeysSubjects.remove((domain, keepAlive));
327 : },
328 : );
329 2 : _domainKeysSubjects[(domain, keepAlive)] = newSubject;
330 :
331 1 : return newSubject.stream;
332 : }
333 :
334 2 : void _clearSubjects() {
335 5 : for (final subject in _subjects.values) {
336 2 : unawaited(subject.close());
337 : }
338 4 : _subjects.clear();
339 :
340 5 : for (final subject in _domainSubjects.values) {
341 2 : unawaited(subject.close());
342 : }
343 4 : _domainSubjects.clear();
344 :
345 5 : for (final subject in _domainKeysSubjects.values) {
346 2 : unawaited(subject.close());
347 : }
348 4 : _domainKeysSubjects.clear();
349 : }
350 :
351 2 : void _notifyClearAll() {
352 5 : for (final subject in _subjects.values) {
353 1 : subject.add(null);
354 : }
355 :
356 5 : for (final subject in _domainSubjects.values) {
357 2 : subject.add({});
358 : }
359 :
360 5 : for (final subject in _domainKeysSubjects.values) {
361 2 : subject.add([]);
362 : }
363 : }
364 :
365 2 : Future<void> _notifyAllSubjects(
366 : String? key, {
367 : required String domain,
368 : }) async {
369 4 : await Future.wait([
370 2 : _notifySubjects(key, domain: domain),
371 2 : _notifyDomainSubjects(domain),
372 2 : _notifyDomainKeysSubjects(domain),
373 : ]);
374 : }
375 :
376 : /// Notify all [_subjects] filtered by [key] and [domain]. If [key] is null
377 : /// then all [_subjects] with [domain] will be notified.
378 2 : Future<void> _notifySubjects(
379 : String? key, {
380 : required String domain,
381 : }) async {
382 4 : if (_subjects.isEmpty) {
383 : return;
384 : }
385 :
386 : if (key == null) {
387 1 : final domainPairs = await getDomain(domain: domain);
388 3 : for (final entry in _subjects.entries.where(
389 3 : (entry) => entry.key.$2 == domain,
390 1 : )) {
391 4 : entry.value.add(domainPairs[entry.key.$1]);
392 : }
393 : return;
394 : }
395 :
396 1 : final value = await get(key, domain: domain);
397 3 : for (final entry in _subjects.entries.where(
398 5 : (entry) => entry.key.$1 == key && entry.key.$2 == domain,
399 1 : )) {
400 2 : entry.value.add(value);
401 : }
402 : }
403 :
404 : /// Notify all [_domainSubjects] filtered by [domain].
405 2 : Future<void> _notifyDomainSubjects(String domain) async {
406 4 : if (_domainSubjects.isEmpty) {
407 : return;
408 : }
409 :
410 1 : final domainPairs = await getDomain(domain: domain);
411 3 : for (final entry in _domainSubjects.entries.where(
412 3 : (entry) => entry.key.$1 == domain,
413 1 : )) {
414 2 : entry.value.add(domainPairs);
415 : }
416 : }
417 :
418 : /// Notify all [_domainKeysSubjects] filtered by [domain].
419 2 : Future<void> _notifyDomainKeysSubjects(String domain) async {
420 4 : if (_domainKeysSubjects.isEmpty) {
421 : return;
422 : }
423 :
424 1 : final domainKeys = await getDomainKeys(domain: domain);
425 3 : for (final entry in _domainKeysSubjects.entries.where(
426 3 : (entry) => entry.key.$1 == domain,
427 1 : )) {
428 2 : entry.value.add(domainKeys);
429 : }
430 : }
431 : }
|