Line data Source code
1 : import 'dart:async';
2 :
3 : import 'package:collection/collection.dart';
4 : import 'package:drift/drift.dart';
5 : import 'package:flutter/widgets.dart';
6 : import 'package:logging/logging.dart';
7 : import 'package:the_storage/src/abstract_storage.dart';
8 : import 'package:the_storage/src/db/storage_database.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 StorageDatabase _database;
18 : final _log = Logger('TheStorage: Storage');
19 :
20 : /// Init storage
21 3 : Future<void> init([
22 : String dbName = AbstractStorage.storageFileName,
23 : @visibleForTesting StorageDatabase? database,
24 : ]) async {
25 3 : WidgetsFlutterBinding.ensureInitialized();
26 :
27 3 : _database = database ?? StorageDatabase.withName(dbName);
28 :
29 6 : _log.finest('initialized');
30 : }
31 :
32 : /// Dispose storage
33 3 : Future<void> dispose() async {
34 6 : await _database.close();
35 : }
36 :
37 2 : @override
38 : Future<void> reset() async {
39 2 : WidgetsFlutterBinding.ensureInitialized();
40 :
41 4 : await _database.close();
42 :
43 4 : _log.finest('reset');
44 : }
45 :
46 3 : @override
47 : Future<void> clearAll() async {
48 15 : await _database.delete(_database.storageEntries).go();
49 :
50 6 : _log.finest('storage cleared');
51 : }
52 :
53 3 : @override
54 : Future<void> clearDomain([
55 : String? domain = AbstractStorage.defaultDomain,
56 : ]) async {
57 6 : await (_database.delete(
58 6 : _database.storageEntries,
59 15 : )..where((t) => t.domain.equals(domain!))).go();
60 :
61 9 : _log.finest('domain $domain cleared');
62 : }
63 :
64 3 : @override
65 : Future<void> set(
66 : String key,
67 : StorageValue value, {
68 : String domain = AbstractStorage.defaultDomain,
69 : bool overwrite = true,
70 : }) async {
71 3 : return setDomain(
72 3 : {
73 : key: value,
74 : },
75 : domain: domain,
76 : overwrite: overwrite,
77 : );
78 : }
79 :
80 3 : @override
81 : Future<void> setDomain(
82 : Map<String, StorageValue> pairs, {
83 : String domain = AbstractStorage.defaultDomain,
84 : bool overwrite = true,
85 : }) async {
86 3 : if (pairs.isEmpty) {
87 0 : _log.info('setAll called with empty pair map');
88 :
89 : return;
90 : }
91 :
92 : final mode = overwrite ? InsertMode.replace : InsertMode.insertOrIgnore;
93 :
94 9 : await _database.batch((batch) {
95 6 : for (final entry in pairs.entries) {
96 3 : batch.insert(
97 6 : _database.storageEntries,
98 3 : StorageEntriesCompanion.insert(
99 : domain: domain,
100 3 : key: entry.key,
101 6 : value: entry.value.value,
102 6 : iv: entry.value.iv,
103 : ),
104 : mode: mode,
105 : );
106 : }
107 : });
108 : }
109 :
110 3 : @override
111 : Future<void> delete(
112 : String key, {
113 : String domain = AbstractStorage.defaultDomain,
114 : }) async {
115 6 : return deleteDomain([key], domain: domain);
116 : }
117 :
118 3 : @override
119 : Future<void> deleteDomain(
120 : List<String> keys, {
121 : String domain = AbstractStorage.defaultDomain,
122 : }) async {
123 3 : if (keys.isEmpty) {
124 0 : _log.info('deleteDomain called with empty key list');
125 :
126 : return;
127 : }
128 :
129 : // SQLite has a limit of 999 variables per query
130 6 : for (final slice in keys.slices(_sqLiteSliceSize)) {
131 15 : await (_database.delete(_database.storageEntries)..where(
132 18 : (t) => t.domain.equals(domain) & t.key.isIn(slice),
133 : ))
134 3 : .go();
135 : }
136 : }
137 :
138 3 : @override
139 : Future<StorageValue?> get(
140 : String key, {
141 : StorageValue? defaultValue,
142 : String domain = AbstractStorage.defaultDomain,
143 : }) async {
144 : final row =
145 15 : await (_database.select(_database.storageEntries)..where(
146 18 : (t) => t.domain.equals(domain) & t.key.equals(key),
147 : ))
148 3 : .getSingleOrNull();
149 :
150 9 : return row != null ? StorageValue(row.value, row.iv) : defaultValue;
151 : }
152 :
153 3 : @override
154 : Future<Map<String, StorageValue>> getDomain({
155 : String domain = AbstractStorage.defaultDomain,
156 : }) async {
157 6 : final rows = await (_database.select(
158 6 : _database.storageEntries,
159 15 : )..where((t) => t.domain.equals(domain))).get();
160 :
161 3 : return {
162 18 : for (final row in rows) row.key: StorageValue(row.value, row.iv),
163 : };
164 : }
165 :
166 3 : @override
167 : Future<List<String>> getDomainKeys({
168 : String domain = AbstractStorage.defaultDomain,
169 : }) async {
170 6 : final rows = await (_database.select(
171 6 : _database.storageEntries,
172 15 : )..where((t) => t.domain.equals(domain))).get();
173 :
174 3 : return [
175 6 : for (final row in rows) row.key,
176 : ];
177 : }
178 : }
179 :
180 : /// Storage value unit
181 : @immutable
182 : class StorageValue implements Comparable<StorageValue> {
183 : /// Create a new storage value unit
184 3 : const StorageValue(this.value, this.iv);
185 :
186 : /// Value
187 : final String value;
188 :
189 : /// Initialization vector
190 : final String iv;
191 :
192 0 : @override
193 : int compareTo(StorageValue other) {
194 0 : return (value.compareTo(other.value) == 0 && iv.compareTo(other.iv) == 0)
195 : ? 0
196 : : 1;
197 : }
198 :
199 2 : @override
200 : bool operator ==(Object other) {
201 14 : return other is StorageValue && other.value == value && other.iv == iv;
202 : }
203 :
204 0 : @override
205 : int get hashCode {
206 0 : return value.hashCode + iv.hashCode;
207 : }
208 : }
|