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 3 : _subjects[(key, domain, defaultValue, keepAlive)]?.close();
259 2 : _subjects.remove((key, domain, defaultValue, keepAlive));
260 : },
261 : );
262 2 : _subjects[(key, domain, defaultValue, keepAlive)] = newSubject;
263 :
264 1 : return newSubject.stream;
265 : }
266 :
267 1 : @override
268 : Future<ValueStream<Map<String, String>>> subscribeDomain({
269 : String domain = AbstractStorage.defaultDomain,
270 : bool keepAlive = true,
271 : }) async {
272 1 : _assureInitialized();
273 :
274 2 : final subject = _domainSubjects[(domain, keepAlive)];
275 : if (subject != null) {
276 1 : return subject.stream;
277 : }
278 :
279 1 : final data = await getDomain(domain: domain);
280 :
281 1 : final newSubject = BehaviorSubject<Map<String, String>>.seeded(
282 : data,
283 : onCancel: keepAlive
284 : ? null
285 1 : : () {
286 3 : _domainSubjects[(domain, keepAlive)]?.close();
287 2 : _domainSubjects.remove((domain, keepAlive));
288 : },
289 : );
290 2 : _domainSubjects[(domain, keepAlive)] = newSubject;
291 :
292 1 : return newSubject.stream;
293 : }
294 :
295 1 : @override
296 : Future<ValueStream<List<String>>> subscribeDomainKeys({
297 : String domain = AbstractStorage.defaultDomain,
298 : bool keepAlive = true,
299 : }) async {
300 1 : _assureInitialized();
301 :
302 2 : final subject = _domainKeysSubjects[(domain, keepAlive)];
303 : if (subject != null) {
304 1 : return subject.stream;
305 : }
306 :
307 1 : final data = await getDomainKeys(domain: domain);
308 :
309 1 : final newSubject = BehaviorSubject<List<String>>.seeded(
310 : data,
311 : onCancel: keepAlive
312 : ? null
313 1 : : () {
314 3 : _domainKeysSubjects[(domain, keepAlive)]?.close();
315 2 : _domainKeysSubjects.remove((domain, keepAlive));
316 : },
317 : );
318 2 : _domainKeysSubjects[(domain, keepAlive)] = newSubject;
319 :
320 1 : return newSubject.stream;
321 : }
322 :
323 2 : void _clearSubjects() {
324 5 : for (final subject in _subjects.values) {
325 1 : subject.close();
326 : }
327 4 : _subjects.clear();
328 :
329 5 : for (final subject in _domainSubjects.values) {
330 1 : subject.close();
331 : }
332 4 : _domainSubjects.clear();
333 :
334 5 : for (final subject in _domainKeysSubjects.values) {
335 1 : subject.close();
336 : }
337 4 : _domainKeysSubjects.clear();
338 : }
339 :
340 2 : void _notifyClearAll() {
341 5 : for (final subject in _subjects.values) {
342 1 : subject.add(null);
343 : }
344 :
345 5 : for (final subject in _domainSubjects.values) {
346 2 : subject.add({});
347 : }
348 :
349 5 : for (final subject in _domainKeysSubjects.values) {
350 2 : subject.add([]);
351 : }
352 : }
353 :
354 2 : Future<void> _notifyAllSubjects(
355 : String? key, {
356 : required String domain,
357 : }) async {
358 4 : await Future.wait([
359 2 : _notifySubjects(key, domain: domain),
360 2 : _notifyDomainSubjects(domain),
361 2 : _notifyDomainKeysSubjects(domain),
362 : ]);
363 : }
364 :
365 : /// Notify all [_subjects] filtered by [key] and [domain]. If [key] is null
366 : /// then all [_subjects] with [domain] will be notified.
367 2 : Future<void> _notifySubjects(
368 : String? key, {
369 : required String domain,
370 : }) async {
371 4 : if (_subjects.isEmpty) {
372 : return;
373 : }
374 :
375 : if (key == null) {
376 1 : final domainPairs = await getDomain(domain: domain);
377 : for (final entry
378 7 : in _subjects.entries.where((entry) => entry.key.$2 == domain)) {
379 4 : entry.value.add(domainPairs[entry.key.$1]);
380 : }
381 : return;
382 : }
383 :
384 1 : final value = await get(key, domain: domain);
385 2 : for (final entry in _subjects.entries
386 7 : .where((entry) => entry.key.$1 == key && entry.key.$2 == domain)) {
387 2 : entry.value.add(value);
388 : }
389 : }
390 :
391 : /// Notify all [_domainSubjects] filtered by [domain].
392 2 : Future<void> _notifyDomainSubjects(String domain) async {
393 4 : if (_domainSubjects.isEmpty) {
394 : return;
395 : }
396 :
397 1 : final domainPairs = await getDomain(domain: domain);
398 : for (final entry
399 7 : in _domainSubjects.entries.where((entry) => entry.key.$1 == domain)) {
400 2 : entry.value.add(domainPairs);
401 : }
402 : }
403 :
404 : /// Notify all [_domainKeysSubjects] filtered by [domain].
405 2 : Future<void> _notifyDomainKeysSubjects(String domain) async {
406 4 : if (_domainKeysSubjects.isEmpty) {
407 : return;
408 : }
409 :
410 1 : final domainKeys = await getDomainKeys(domain: domain);
411 2 : for (final entry in _domainKeysSubjects.entries
412 5 : .where((entry) => entry.key.$1 == domain)) {
413 2 : entry.value.add(domainKeys);
414 : }
415 : }
416 : }
|