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