LCOV - code coverage report
Current view: top level - src - storage.dart (source / functions) Coverage Total Hit
Test: filtered_lcov.info Lines: 83.9 % 87 73
Test Date: 2024-10-02 13:56:45 Functions: - 0 0

            Line data    Source code
       1              : import 'dart:async';
       2              : 
       3              : import 'package:collection/collection.dart';
       4              : import 'package:flutter/widgets.dart';
       5              : import 'package:logging/logging.dart';
       6              : import 'package:path/path.dart';
       7              : import 'package:sqflite/sqflite.dart';
       8              : import 'package:the_storage/src/abstract_storage.dart';
       9              : 
      10              : const _sqLiteSliceSize = 512;
      11              : 
      12              : /// Storage (db backend)
      13              : class Storage implements AbstractStorage<StorageValue> {
      14              :   /// Create a new storage
      15            3 :   Storage();
      16              : 
      17              :   late final Database _database;
      18              :   final _log = Logger('TheStorage: Storage');
      19              :   late final String _dbName;
      20              : 
      21              :   /// Init storage
      22            3 :   Future<void> init([String dbName = AbstractStorage.storageFileName]) async {
      23            3 :     WidgetsFlutterBinding.ensureInitialized();
      24              : 
      25            3 :     _dbName = dbName;
      26              : 
      27            6 :     _database = await openDatabase(
      28            3 :       join(
      29            3 :         await getDatabasesPath(),
      30              :         dbName,
      31              :       ),
      32            3 :       onCreate: _onCreate,
      33            3 :       onUpgrade: _onUpgrade,
      34            3 :       onDowngrade: _onDowngrade,
      35              :       version: 1,
      36              :     );
      37              : 
      38            6 :     _log.finest('initialized');
      39              :   }
      40              : 
      41              :   /// Dispose storage
      42            2 :   Future<void> dispose() async {
      43            4 :     await _database.close();
      44              :   }
      45              : 
      46            3 :   @override
      47              :   Future<void> reset() async {
      48            3 :     WidgetsFlutterBinding.ensureInitialized();
      49              : 
      50            6 :     await _database.close();
      51            3 :     await deleteDatabase(
      52            3 :       join(
      53            3 :         await getDatabasesPath(),
      54            3 :         _dbName,
      55              :       ),
      56              :     );
      57              : 
      58            6 :     _log.finest('reset');
      59              :   }
      60              : 
      61            3 :   Future<void> _onCreate(Database db, int _) async {
      62            3 :     await db.execute(
      63              :       '''
      64              :         CREATE TABLE storage (
      65              :           domain TEXT NOT NULL,
      66              :           key TEXT NOT NULL,
      67              :           value TEXT NOT NULL,
      68              :           iv TEXT NOT NULL,
      69              :           PRIMARY KEY (domain, key)
      70              :         );
      71              :       ''',
      72              :     );
      73            3 :     await db.execute(
      74              :       '''
      75              :         CREATE INDEX storage_domain_index ON storage(domain);
      76              :       ''',
      77              :     );
      78              : 
      79            6 :     _log.finest('database created');
      80              :   }
      81              : 
      82            0 :   FutureOr<void> _onUpgrade(Database _, int oldVersion, int newVersion) {
      83            0 :     _log
      84            0 :       ..finest('database upgraded from $oldVersion to $newVersion')
      85            0 :       ..warning('no upgrade migrations found');
      86              :   }
      87              : 
      88            0 :   FutureOr<void> _onDowngrade(Database _, int oldVersion, int newVersion) {
      89            0 :     _log
      90            0 :       ..finest('database downgraded from $oldVersion to $newVersion')
      91            0 :       ..warning('no downgrade migrations found');
      92              :   }
      93              : 
      94            3 :   @override
      95              :   Future<void> clearAll() async {
      96              :     const query = '''
      97              :       DELETE FROM storage;
      98              :     ''';
      99              : 
     100            6 :     await _database.execute(query);
     101              : 
     102            6 :     _log.finest('storage cleared');
     103              :   }
     104              : 
     105            3 :   @override
     106              :   Future<void> clearDomain([
     107              :     String? domain = AbstractStorage.defaultDomain,
     108              :   ]) async {
     109              :     final query = '''
     110              :       DELETE FROM storage WHERE domain = '$domain';
     111            3 :     ''';
     112              : 
     113            6 :     await _database.execute(query);
     114              : 
     115            9 :     _log.finest('domain $domain cleared');
     116              :   }
     117              : 
     118            3 :   @override
     119              :   Future<void> set(
     120              :     String key,
     121              :     StorageValue value, {
     122              :     String domain = AbstractStorage.defaultDomain,
     123              :     bool overwrite = true,
     124              :   }) async {
     125            3 :     return setDomain(
     126            3 :       {
     127              :         key: value,
     128              :       },
     129              :       domain: domain,
     130              :       overwrite: overwrite,
     131              :     );
     132              :   }
     133              : 
     134            3 :   @override
     135              :   Future<void> setDomain(
     136              :     Map<String, StorageValue> pairs, {
     137              :     String domain = AbstractStorage.defaultDomain,
     138              :     bool overwrite = true,
     139              :   }) async {
     140            3 :     if (pairs.isEmpty) {
     141            0 :       _log.info('setAll called with empty pair map');
     142              : 
     143              :       return;
     144              :     }
     145              : 
     146              :     var isFirst = true;
     147            9 :     final values = pairs.entries.fold('', (previousValue, pair) {
     148              :       final prefix = isFirst ? '' : ', ';
     149              :       final result =
     150              :           // ignore: lines_longer_than_80_chars
     151           18 :           "$previousValue$prefix('$domain', '${pair.key}', '${pair.value.value}', '${pair.value.iv}' )";
     152              :       isFirst = false;
     153              : 
     154              :       return result;
     155              :     });
     156              : 
     157              :     final conflictClause = overwrite ? 'REPLACE' : 'IGNORE';
     158              :     final query = '''
     159              :       INSERT OR $conflictClause INTO storage (domain, key, value, iv) VALUES $values;
     160            3 :     ''';
     161              : 
     162            6 :     await _database.execute(query);
     163              :   }
     164              : 
     165            3 :   @override
     166              :   Future<void> delete(
     167              :     String key, {
     168              :     String domain = AbstractStorage.defaultDomain,
     169              :   }) async {
     170            6 :     return deleteDomain([key], domain: domain);
     171              :   }
     172              : 
     173            3 :   @override
     174              :   Future<void> deleteDomain(
     175              :     List<String> keys, {
     176              :     String domain = AbstractStorage.defaultDomain,
     177              :   }) async {
     178            3 :     if (keys.isEmpty) {
     179            0 :       _log.info('deleteDomain called with empty key list');
     180              : 
     181              :       return;
     182              :     }
     183              : 
     184              :     // SQLite has a limit of 999 variables per query
     185            9 :     keys.slices(_sqLiteSliceSize).forEach((keys) async {
     186              :       var isFirst = true;
     187            6 :       final andClause = keys.fold('', (previousValue, key) {
     188              :         final prefix = isFirst ? '' : ' OR ';
     189            3 :         final result = "$previousValue$prefix(key = '$key')";
     190              :         isFirst = false;
     191              : 
     192              :         return result;
     193              :       });
     194              : 
     195              :       final query = '''
     196              :         DELETE FROM storage WHERE domain = '$domain' AND ($andClause)
     197            3 :       ''';
     198              : 
     199            6 :       await _database.execute(query);
     200              :     });
     201              :   }
     202              : 
     203            3 :   @override
     204              :   Future<StorageValue?> get(
     205              :     String key, {
     206              :     StorageValue? defaultValue,
     207              :     String domain = AbstractStorage.defaultDomain,
     208              :   }) async {
     209            6 :     final list = await _database.rawQuery(
     210              :       '''
     211              :         SELECT value, iv FROM storage WHERE domain = '$domain' and key = '$key' LIMIT 1;
     212            3 :       ''',
     213              :     );
     214              : 
     215            3 :     return list.isNotEmpty
     216            3 :         ? StorageValue(
     217            6 :             list.first['value']! as String,
     218            6 :             list.first['iv']! as String,
     219              :           )
     220              :         : defaultValue;
     221              :   }
     222              : 
     223            3 :   @override
     224              :   Future<Map<String, StorageValue>> getDomain({
     225              :     String domain = AbstractStorage.defaultDomain,
     226              :   }) async {
     227            6 :     final list = await _database.rawQuery(
     228              :       '''
     229              :         SELECT key, value, iv FROM storage WHERE domain = '$domain';
     230            3 :       ''',
     231              :     );
     232              : 
     233            3 :     return {
     234              :       // There is no way to write null in these fields
     235              :       // ignore: cast_nullable_to_non_nullable
     236            3 :       for (final pair in list)
     237            9 :         pair['key']! as String: StorageValue(
     238            3 :           pair['value']! as String,
     239            3 :           pair['iv']! as String,
     240              :         ),
     241              :     };
     242              :   }
     243              : 
     244            3 :   @override
     245              :   Future<List<String>> getDomainKeys({
     246              :     String domain = AbstractStorage.defaultDomain,
     247              :   }) async {
     248            6 :     final list = await _database.rawQuery(
     249              :       '''
     250              :       SELECT key FROM storage WHERE domain = '$domain';
     251            3 :     ''',
     252              :     );
     253              : 
     254            3 :     return [
     255              :       // There is no way to write null in these fields
     256              :       // ignore: cast_nullable_to_non_nullable
     257            9 :       for (final pair in list) pair['key']! as String,
     258              :     ];
     259              :   }
     260              : }
     261              : 
     262              : /// Storage value unit
     263              : @immutable
     264              : class StorageValue implements Comparable<StorageValue> {
     265              :   /// Create a new storage value unit
     266            4 :   const StorageValue(this.value, this.iv);
     267              : 
     268              :   /// Value
     269              :   final String value;
     270              : 
     271              :   /// Initialization vector
     272              :   final String iv;
     273              : 
     274            0 :   @override
     275              :   int compareTo(StorageValue other) {
     276            0 :     return (value.compareTo(other.value) == 0 && iv.compareTo(other.iv) == 0)
     277              :         ? 0
     278              :         : 1;
     279              :   }
     280              : 
     281            2 :   @override
     282              :   bool operator ==(Object other) {
     283           14 :     return other is StorageValue && other.value == value && other.iv == iv;
     284              :   }
     285              : 
     286            0 :   @override
     287              :   int get hashCode {
     288            0 :     return value.hashCode + iv.hashCode;
     289              :   }
     290              : }
        

Generated by: LCOV version 2.1-1