LCOV - code coverage report
Current view: top level - src - the_storage.dart (source / functions) Coverage Total Hit
Test: filtered_lcov.info Lines: 98.7 % 150 148
Test Date: 2024-09-16 09:20:02 Functions: - 0 0

            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              : }
        

Generated by: LCOV version 2.1-1