strata-storage 2.6.1 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI-INTEGRATION-GUIDE.md +88 -10
- package/README.md +42 -64
- package/android/AGENTS.md +18 -3
- package/android/CLAUDE.md +36 -3
- package/android/build.gradle +5 -1
- package/android/src/main/java/com/strata/storage/EncryptedStorage.java +31 -18
- package/android/src/main/java/com/strata/storage/FilesystemStorage.java +62 -10
- package/android/src/main/java/com/strata/storage/SQLiteStorage.java +65 -13
- package/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +63 -51
- package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +80 -11
- package/dist/README.md +42 -64
- package/dist/adapters/capacitor/FilesystemAdapter.d.ts +2 -2
- package/dist/adapters/capacitor/FilesystemAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/FilesystemAdapter.js +6 -2
- package/dist/adapters/capacitor/PreferencesAdapter.d.ts +2 -2
- package/dist/adapters/capacitor/PreferencesAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/PreferencesAdapter.js +6 -2
- package/dist/adapters/capacitor/SecureAdapter.d.ts +3 -3
- package/dist/adapters/capacitor/SecureAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/SecureAdapter.js +6 -2
- package/dist/adapters/capacitor/SqliteAdapter.d.ts +24 -3
- package/dist/adapters/capacitor/SqliteAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/SqliteAdapter.js +105 -23
- package/dist/adapters/capacitor/index.d.ts +4 -4
- package/dist/adapters/web/CacheAdapter.d.ts +2 -2
- package/dist/adapters/web/CookieAdapter.d.ts +2 -2
- package/dist/adapters/web/IndexedDBAdapter.d.ts +10 -3
- package/dist/adapters/web/IndexedDBAdapter.d.ts.map +1 -1
- package/dist/adapters/web/IndexedDBAdapter.js +14 -3
- package/dist/adapters/web/LocalStorageAdapter.d.ts +2 -2
- package/dist/adapters/web/MemoryAdapter.d.ts +2 -2
- package/dist/adapters/web/SessionStorageAdapter.d.ts +10 -10
- package/dist/adapters/web/SessionStorageAdapter.d.ts.map +1 -1
- package/dist/adapters/web/SessionStorageAdapter.js +3 -2
- package/dist/adapters/web/URLAdapter.d.ts +2 -2
- package/dist/adapters/web/index.d.ts +7 -7
- package/dist/capacitor.d.ts +6 -6
- package/dist/capacitor.d.ts.map +1 -1
- package/dist/capacitor.js +5 -4
- package/dist/core/AdapterRegistry.d.ts +1 -1
- package/dist/core/BaseAdapter.d.ts +21 -5
- package/dist/core/BaseAdapter.d.ts.map +1 -1
- package/dist/core/BaseAdapter.js +29 -1
- package/dist/core/Strata.d.ts +41 -7
- package/dist/core/Strata.d.ts.map +1 -1
- package/dist/core/Strata.js +476 -118
- package/dist/features/compression/index.d.ts +1 -1
- package/dist/features/compression.d.ts +5 -1
- package/dist/features/compression.d.ts.map +1 -1
- package/dist/features/encryption/index.d.ts +1 -1
- package/dist/features/encryption.d.ts +55 -2
- package/dist/features/encryption.d.ts.map +1 -1
- package/dist/features/encryption.js +139 -26
- package/dist/features/migration.d.ts +1 -1
- package/dist/features/observer/index.d.ts +1 -1
- package/dist/features/observer.d.ts +1 -1
- package/dist/features/query/index.d.ts +1 -1
- package/dist/features/query.d.ts +1 -1
- package/dist/features/query.d.ts.map +1 -1
- package/dist/features/query.js +3 -1
- package/dist/features/sync/index.d.ts +1 -1
- package/dist/features/sync.d.ts +19 -3
- package/dist/features/sync.d.ts.map +1 -1
- package/dist/features/sync.js +57 -9
- package/dist/features/ttl/index.d.ts +1 -1
- package/dist/features/ttl.d.ts +2 -2
- package/dist/firebase.d.ts +1 -1
- package/dist/firebase.d.ts.map +1 -1
- package/dist/firebase.js +34 -9
- package/dist/index.d.ts +24 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/integrations/angular/index.d.ts +2 -2
- package/dist/integrations/angular/index.d.ts.map +1 -1
- package/dist/integrations/angular/index.js +7 -8
- package/dist/integrations/react/index.d.ts +3 -3
- package/dist/integrations/react/index.d.ts.map +1 -1
- package/dist/integrations/react/index.js +16 -4
- package/dist/integrations/vue/index.d.ts +2 -2
- package/dist/integrations/vue/index.d.ts.map +1 -1
- package/dist/integrations/vue/index.js +5 -2
- package/dist/package.json +11 -2
- package/dist/plugin/definitions.d.ts +29 -3
- package/dist/plugin/definitions.d.ts.map +1 -1
- package/dist/plugin/index.d.ts +2 -2
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +3 -0
- package/dist/plugin/web.d.ts +11 -2
- package/dist/plugin/web.d.ts.map +1 -1
- package/dist/plugin/web.js +23 -4
- package/dist/types/index.d.ts +50 -20
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -10
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +7 -0
- package/ios/AGENTS.md +14 -3
- package/ios/CLAUDE.md +25 -3
- package/ios/Plugin/FilesystemStorage.swift +19 -0
- package/ios/Plugin/SQLiteStorage.swift +59 -9
- package/ios/Plugin/StrataStoragePlugin.m +1 -0
- package/ios/Plugin/StrataStoragePlugin.swift +87 -34
- package/ios/Plugin/UserDefaultsStorage.swift +47 -46
- package/package.json +27 -24
- package/scripts/build.js +16 -14
- package/scripts/cli.js +1 -1
- package/scripts/configure.js +2 -2
- package/scripts/postinstall.js +3 -2
- package/dist/android/AGENTS.md +0 -51
- package/dist/android/CLAUDE.md +0 -89
- package/dist/android/build.gradle +0 -58
- package/dist/android/proguard-rules.pro +0 -11
- package/dist/android/settings.gradle +0 -2
- package/dist/android/src/main/AndroidManifest.xml +0 -2
- package/dist/android/src/main/java/com/strata/storage/EncryptedStorage.java +0 -218
- package/dist/android/src/main/java/com/strata/storage/FilesystemStorage.java +0 -287
- package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +0 -371
- package/dist/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +0 -209
- package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +0 -616
- package/dist/core/StorageStrategy.d.ts +0 -55
- package/dist/core/StorageStrategy.d.ts.map +0 -1
- package/dist/core/StorageStrategy.js +0 -200
- package/dist/ios/AGENTS.md +0 -48
- package/dist/ios/CLAUDE.md +0 -84
- package/dist/ios/Plugin/FilesystemStorage.swift +0 -218
- package/dist/ios/Plugin/KeychainStorage.swift +0 -233
- package/dist/ios/Plugin/SQLiteStorage.swift +0 -426
- package/dist/ios/Plugin/StrataStoragePlugin.m +0 -23
- package/dist/ios/Plugin/StrataStoragePlugin.swift +0 -411
- package/dist/ios/Plugin/UserDefaultsStorage.swift +0 -87
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
package com.strata.storage;
|
|
2
|
-
|
|
3
|
-
import android.content.Context;
|
|
4
|
-
import android.content.ContentValues;
|
|
5
|
-
import android.database.Cursor;
|
|
6
|
-
import android.database.sqlite.SQLiteDatabase;
|
|
7
|
-
import android.database.sqlite.SQLiteOpenHelper;
|
|
8
|
-
import android.util.Log;
|
|
9
|
-
import com.getcapacitor.JSObject;
|
|
10
|
-
import java.nio.charset.StandardCharsets;
|
|
11
|
-
import java.util.ArrayList;
|
|
12
|
-
import java.util.HashMap;
|
|
13
|
-
import java.util.HashSet;
|
|
14
|
-
import java.util.List;
|
|
15
|
-
import java.util.Map;
|
|
16
|
-
import java.util.Set;
|
|
17
|
-
import org.json.JSONException;
|
|
18
|
-
import org.json.JSONObject;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* SQLite-backed storage. One {@link SQLiteStorage} instance maps to a single
|
|
22
|
-
* database file (one {@link SQLiteOpenHelper}). A single instance can serve
|
|
23
|
-
* multiple logical tables: every public method takes a sanitized {@code table}
|
|
24
|
-
* name and the table is created on first use via {@code CREATE TABLE IF NOT
|
|
25
|
-
* EXISTS}.
|
|
26
|
-
*
|
|
27
|
-
* <p>Value-shape contract (matches the TS {@code SqliteAdapter}): {@code set}
|
|
28
|
-
* receives the FULL wrapper object ({@code value, created, updated, expires?,
|
|
29
|
-
* tags?, metadata?}). The entire wrapper is JSON-serialized into the
|
|
30
|
-
* {@code value} column for round-trip fidelity, while {@code created},
|
|
31
|
-
* {@code updated}, {@code expires}, {@code tags} and {@code metadata} are also
|
|
32
|
-
* extracted into their own columns for TTL + query support. {@code get} parses
|
|
33
|
-
* the {@code value} column back into a {@link JSObject} wrapper.
|
|
34
|
-
*
|
|
35
|
-
* <p>All database access is synchronized on the instance so overlapping bridge
|
|
36
|
-
* calls cannot corrupt the connection.
|
|
37
|
-
*/
|
|
38
|
-
public class SQLiteStorage extends SQLiteOpenHelper {
|
|
39
|
-
private static final int DATABASE_VERSION = 1;
|
|
40
|
-
private static final String DEFAULT_TABLE = "storage";
|
|
41
|
-
|
|
42
|
-
private static final String KEY_ID = "key";
|
|
43
|
-
private static final String KEY_VALUE = "value";
|
|
44
|
-
private static final String KEY_CREATED = "created";
|
|
45
|
-
private static final String KEY_UPDATED = "updated";
|
|
46
|
-
private static final String KEY_EXPIRES = "expires";
|
|
47
|
-
private static final String KEY_TAGS = "tags";
|
|
48
|
-
private static final String KEY_METADATA = "metadata";
|
|
49
|
-
|
|
50
|
-
/** Tables already created in this DB during the process lifetime. */
|
|
51
|
-
private final Set<String> ensuredTables = new HashSet<>();
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* @param context Android context
|
|
55
|
-
* @param dbName fully-resolved database file name (already sanitized +
|
|
56
|
-
* suffixed with ".db" by the plugin)
|
|
57
|
-
*/
|
|
58
|
-
public SQLiteStorage(Context context, String dbName) {
|
|
59
|
-
super(context, dbName, null, DATABASE_VERSION);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Sanitize a table name to a safe SQL identifier ({@code ^[A-Za-z0-9_]+$}).
|
|
64
|
-
* Table names cannot be passed as bound parameters in SQLite, so they MUST
|
|
65
|
-
* be validated before being interpolated into DDL/DML.
|
|
66
|
-
*/
|
|
67
|
-
public static String sanitizeTable(String table) {
|
|
68
|
-
if (table == null || table.isEmpty()) {
|
|
69
|
-
return DEFAULT_TABLE;
|
|
70
|
-
}
|
|
71
|
-
String cleaned = table.replaceAll("[^A-Za-z0-9_]", "");
|
|
72
|
-
return cleaned.isEmpty() ? DEFAULT_TABLE : cleaned;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
@Override
|
|
76
|
-
public void onCreate(SQLiteDatabase db) {
|
|
77
|
-
// Tables are created lazily per (database, table) via ensureTable().
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
@Override
|
|
81
|
-
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
82
|
-
// No destructive migrations: tables are created on demand and persist.
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Create the table for {@code safeTable} if it does not yet exist. Caller
|
|
87
|
-
* MUST pass an already-sanitized identifier.
|
|
88
|
-
*/
|
|
89
|
-
private void ensureTable(SQLiteDatabase db, String safeTable) {
|
|
90
|
-
if (ensuredTables.contains(safeTable)) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
String createTable = "CREATE TABLE IF NOT EXISTS " + safeTable + "("
|
|
94
|
-
+ KEY_ID + " TEXT PRIMARY KEY,"
|
|
95
|
-
+ KEY_VALUE + " TEXT NOT NULL,"
|
|
96
|
-
+ KEY_CREATED + " INTEGER NOT NULL,"
|
|
97
|
-
+ KEY_UPDATED + " INTEGER NOT NULL,"
|
|
98
|
-
+ KEY_EXPIRES + " INTEGER,"
|
|
99
|
-
+ KEY_TAGS + " TEXT,"
|
|
100
|
-
+ KEY_METADATA + " TEXT" + ")";
|
|
101
|
-
db.execSQL(createTable);
|
|
102
|
-
ensuredTables.add(safeTable);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Persist the full wrapper for {@code key}. The wrapper is stored verbatim
|
|
107
|
-
* as JSON in the {@code value} column; {@code created}/{@code updated}/
|
|
108
|
-
* {@code expires}/{@code tags}/{@code metadata} are mirrored into columns
|
|
109
|
-
* for TTL + query.
|
|
110
|
-
*
|
|
111
|
-
* @param wrapper the full {@link StorageValue} wrapper as a {@link JSObject}
|
|
112
|
-
*/
|
|
113
|
-
public synchronized boolean set(String table, String key, JSObject wrapper) {
|
|
114
|
-
if (wrapper == null) {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
String safeTable = sanitizeTable(table);
|
|
118
|
-
long now = System.currentTimeMillis();
|
|
119
|
-
|
|
120
|
-
// Column extraction for TTL + query. Defaults keep legacy fidelity.
|
|
121
|
-
long created = wrapper.optLong(KEY_CREATED, now);
|
|
122
|
-
long updated = wrapper.optLong(KEY_UPDATED, now);
|
|
123
|
-
|
|
124
|
-
ContentValues values = new ContentValues();
|
|
125
|
-
values.put(KEY_ID, key);
|
|
126
|
-
values.put(KEY_VALUE, wrapper.toString());
|
|
127
|
-
values.put(KEY_CREATED, created);
|
|
128
|
-
values.put(KEY_UPDATED, updated);
|
|
129
|
-
|
|
130
|
-
if (wrapper.has(KEY_EXPIRES) && !wrapper.isNull(KEY_EXPIRES)) {
|
|
131
|
-
values.put(KEY_EXPIRES, wrapper.optLong(KEY_EXPIRES));
|
|
132
|
-
}
|
|
133
|
-
// tags / metadata are stored as their JSON text for query use.
|
|
134
|
-
Object tags = wrapper.opt(KEY_TAGS);
|
|
135
|
-
if (tags != null && tags != JSONObject.NULL) {
|
|
136
|
-
values.put(KEY_TAGS, tags.toString());
|
|
137
|
-
}
|
|
138
|
-
Object metadata = wrapper.opt(KEY_METADATA);
|
|
139
|
-
if (metadata != null && metadata != JSONObject.NULL) {
|
|
140
|
-
values.put(KEY_METADATA, metadata.toString());
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
SQLiteDatabase db = getWritableDatabase();
|
|
145
|
-
ensureTable(db, safeTable);
|
|
146
|
-
long result = db.insertWithOnConflict(
|
|
147
|
-
safeTable, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
|
148
|
-
return result != -1;
|
|
149
|
-
} catch (Exception e) {
|
|
150
|
-
Log.e("StrataStorage", "Failed to set value in SQLite", e);
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Read the full wrapper for {@code key}, parsed back from the JSON stored in
|
|
157
|
-
* the {@code value} column. Missing row → {@code null}. Unparseable legacy
|
|
158
|
-
* bytes → {@code null} (treated as a miss; never throws).
|
|
159
|
-
*/
|
|
160
|
-
public synchronized JSObject get(String table, String key) {
|
|
161
|
-
String safeTable = sanitizeTable(table);
|
|
162
|
-
SQLiteDatabase db = getReadableDatabase();
|
|
163
|
-
ensureTable(db, safeTable);
|
|
164
|
-
Cursor cursor = null;
|
|
165
|
-
try {
|
|
166
|
-
cursor = db.query(safeTable, new String[]{KEY_VALUE}, KEY_ID + "=?",
|
|
167
|
-
new String[]{key}, null, null, null, null);
|
|
168
|
-
if (cursor != null && cursor.moveToFirst()) {
|
|
169
|
-
String stored = cursor.getString(0);
|
|
170
|
-
if (stored == null) {
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
try {
|
|
174
|
-
return new JSObject(stored);
|
|
175
|
-
} catch (JSONException parseError) {
|
|
176
|
-
// Legacy / corrupted payload: treat as a miss rather than throw.
|
|
177
|
-
Log.w("StrataStorage", "Unparseable SQLite value for key " + key);
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return null;
|
|
182
|
-
} finally {
|
|
183
|
-
if (cursor != null) {
|
|
184
|
-
cursor.close();
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
public synchronized boolean remove(String table, String key) {
|
|
190
|
-
String safeTable = sanitizeTable(table);
|
|
191
|
-
SQLiteDatabase db = getWritableDatabase();
|
|
192
|
-
ensureTable(db, safeTable);
|
|
193
|
-
int result = db.delete(safeTable, KEY_ID + " = ?", new String[]{key});
|
|
194
|
-
return result > 0;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
public synchronized boolean clear(String table, String prefix) {
|
|
198
|
-
String safeTable = sanitizeTable(table);
|
|
199
|
-
SQLiteDatabase db = getWritableDatabase();
|
|
200
|
-
ensureTable(db, safeTable);
|
|
201
|
-
int result;
|
|
202
|
-
if (prefix != null) {
|
|
203
|
-
result = db.delete(safeTable, KEY_ID + " LIKE ?", new String[]{prefix + "%"});
|
|
204
|
-
} else {
|
|
205
|
-
result = db.delete(safeTable, null, null);
|
|
206
|
-
}
|
|
207
|
-
return result >= 0;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
public synchronized List<String> keys(String table, String pattern) {
|
|
211
|
-
String safeTable = sanitizeTable(table);
|
|
212
|
-
List<String> keys = new ArrayList<>();
|
|
213
|
-
SQLiteDatabase db = getReadableDatabase();
|
|
214
|
-
ensureTable(db, safeTable);
|
|
215
|
-
Cursor cursor = null;
|
|
216
|
-
try {
|
|
217
|
-
if (pattern != null) {
|
|
218
|
-
cursor = db.query(safeTable, new String[]{KEY_ID}, KEY_ID + " LIKE ?",
|
|
219
|
-
new String[]{"%" + pattern + "%"}, null, null, null, null);
|
|
220
|
-
} else {
|
|
221
|
-
cursor = db.query(safeTable, new String[]{KEY_ID},
|
|
222
|
-
null, null, null, null, null);
|
|
223
|
-
}
|
|
224
|
-
if (cursor != null && cursor.moveToFirst()) {
|
|
225
|
-
do {
|
|
226
|
-
keys.add(cursor.getString(0));
|
|
227
|
-
} while (cursor.moveToNext());
|
|
228
|
-
}
|
|
229
|
-
return keys;
|
|
230
|
-
} finally {
|
|
231
|
-
if (cursor != null) {
|
|
232
|
-
cursor.close();
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
public synchronized boolean has(String table, String key) {
|
|
238
|
-
String safeTable = sanitizeTable(table);
|
|
239
|
-
SQLiteDatabase db = getReadableDatabase();
|
|
240
|
-
ensureTable(db, safeTable);
|
|
241
|
-
Cursor cursor = null;
|
|
242
|
-
try {
|
|
243
|
-
cursor = db.query(safeTable, new String[]{KEY_ID}, KEY_ID + "=?",
|
|
244
|
-
new String[]{key}, null, null, null, null);
|
|
245
|
-
return cursor != null && cursor.getCount() > 0;
|
|
246
|
-
} finally {
|
|
247
|
-
if (cursor != null) {
|
|
248
|
-
cursor.close();
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Enumerate every key in the table. The TS {@code SqliteAdapter} applies the
|
|
255
|
-
* real query condition by re-fetching/filtering each key, so this only needs
|
|
256
|
-
* to surface candidate keys.
|
|
257
|
-
*/
|
|
258
|
-
public synchronized List<Map<String, Object>> query(String table) {
|
|
259
|
-
String safeTable = sanitizeTable(table);
|
|
260
|
-
List<Map<String, Object>> rows = new ArrayList<>();
|
|
261
|
-
SQLiteDatabase db = getReadableDatabase();
|
|
262
|
-
ensureTable(db, safeTable);
|
|
263
|
-
Cursor cursor = null;
|
|
264
|
-
try {
|
|
265
|
-
cursor = db.query(safeTable, new String[]{KEY_ID},
|
|
266
|
-
null, null, null, null, null);
|
|
267
|
-
if (cursor != null && cursor.moveToFirst()) {
|
|
268
|
-
do {
|
|
269
|
-
Map<String, Object> row = new HashMap<>();
|
|
270
|
-
row.put("key", cursor.getString(0));
|
|
271
|
-
rows.add(row);
|
|
272
|
-
} while (cursor.moveToNext());
|
|
273
|
-
}
|
|
274
|
-
return rows;
|
|
275
|
-
} finally {
|
|
276
|
-
if (cursor != null) {
|
|
277
|
-
cursor.close();
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Size information. {@code total} = Σ(length(key) + length(value)), matching
|
|
284
|
-
* the web adapters' convention (key bytes are included). When {@code
|
|
285
|
-
* detailed} is true the byte breakdown is also populated: {@code keys} = Σ
|
|
286
|
-
* length(key), {@code values} = Σ length(value), {@code metadata} = Σ
|
|
287
|
-
* length(metadata) (0 for null); {@code count} is the row count.
|
|
288
|
-
*/
|
|
289
|
-
public synchronized SizeInfo size(String table, boolean detailed) {
|
|
290
|
-
String safeTable = sanitizeTable(table);
|
|
291
|
-
SQLiteDatabase db = getReadableDatabase();
|
|
292
|
-
ensureTable(db, safeTable);
|
|
293
|
-
Cursor cursor = null;
|
|
294
|
-
try {
|
|
295
|
-
if (!detailed) {
|
|
296
|
-
cursor = db.rawQuery("SELECT COUNT(*), SUM(LENGTH(" + KEY_ID + ") + LENGTH("
|
|
297
|
-
+ KEY_VALUE + ")) FROM " + safeTable, null);
|
|
298
|
-
long totalSize = 0;
|
|
299
|
-
int count = 0;
|
|
300
|
-
if (cursor != null && cursor.moveToFirst()) {
|
|
301
|
-
count = cursor.getInt(0);
|
|
302
|
-
if (!cursor.isNull(1)) {
|
|
303
|
-
totalSize = cursor.getLong(1);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
return new SizeInfo(totalSize, count);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
cursor = db.query(safeTable,
|
|
310
|
-
new String[]{KEY_ID, KEY_VALUE, KEY_METADATA},
|
|
311
|
-
null, null, null, null, null);
|
|
312
|
-
long keysBytes = 0;
|
|
313
|
-
long valuesBytes = 0;
|
|
314
|
-
long metadataBytes = 0;
|
|
315
|
-
int count = 0;
|
|
316
|
-
if (cursor != null && cursor.moveToFirst()) {
|
|
317
|
-
do {
|
|
318
|
-
count++;
|
|
319
|
-
String key = cursor.getString(0);
|
|
320
|
-
String value = cursor.getString(1);
|
|
321
|
-
String metadata = cursor.getString(2);
|
|
322
|
-
if (key != null) {
|
|
323
|
-
keysBytes += key.getBytes(StandardCharsets.UTF_8).length;
|
|
324
|
-
}
|
|
325
|
-
if (value != null) {
|
|
326
|
-
valuesBytes += value.getBytes(StandardCharsets.UTF_8).length;
|
|
327
|
-
}
|
|
328
|
-
if (metadata != null) {
|
|
329
|
-
metadataBytes += metadata.getBytes(StandardCharsets.UTF_8).length;
|
|
330
|
-
}
|
|
331
|
-
} while (cursor.moveToNext());
|
|
332
|
-
}
|
|
333
|
-
return new SizeInfo(keysBytes + valuesBytes, count, keysBytes, valuesBytes, metadataBytes);
|
|
334
|
-
} finally {
|
|
335
|
-
if (cursor != null) {
|
|
336
|
-
cursor.close();
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Size information. The detailed byte breakdown is only meaningful when
|
|
343
|
-
* {@link #detailed} is true.
|
|
344
|
-
*/
|
|
345
|
-
public static class SizeInfo {
|
|
346
|
-
public final long total;
|
|
347
|
-
public final int count;
|
|
348
|
-
public final boolean detailed;
|
|
349
|
-
public final long keysBytes;
|
|
350
|
-
public final long valuesBytes;
|
|
351
|
-
public final long metadataBytes;
|
|
352
|
-
|
|
353
|
-
public SizeInfo(long total, int count) {
|
|
354
|
-
this.total = total;
|
|
355
|
-
this.count = count;
|
|
356
|
-
this.detailed = false;
|
|
357
|
-
this.keysBytes = 0;
|
|
358
|
-
this.valuesBytes = 0;
|
|
359
|
-
this.metadataBytes = 0;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
public SizeInfo(long total, int count, long keysBytes, long valuesBytes, long metadataBytes) {
|
|
363
|
-
this.total = total;
|
|
364
|
-
this.count = count;
|
|
365
|
-
this.detailed = true;
|
|
366
|
-
this.keysBytes = keysBytes;
|
|
367
|
-
this.valuesBytes = valuesBytes;
|
|
368
|
-
this.metadataBytes = metadataBytes;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
package com.strata.storage;
|
|
2
|
-
|
|
3
|
-
import android.content.Context;
|
|
4
|
-
import android.content.SharedPreferences;
|
|
5
|
-
import android.util.Log;
|
|
6
|
-
import java.util.Map;
|
|
7
|
-
import java.util.Set;
|
|
8
|
-
import java.util.HashSet;
|
|
9
|
-
import java.util.List;
|
|
10
|
-
import java.util.ArrayList;
|
|
11
|
-
import org.json.JSONObject;
|
|
12
|
-
import org.json.JSONArray;
|
|
13
|
-
|
|
14
|
-
public class SharedPreferencesStorage {
|
|
15
|
-
private final SharedPreferences prefs;
|
|
16
|
-
private final SharedPreferences.Editor editor;
|
|
17
|
-
private static final String DEFAULT_NAME = "StrataStorage";
|
|
18
|
-
|
|
19
|
-
public SharedPreferencesStorage(Context context) {
|
|
20
|
-
this(context, DEFAULT_NAME);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
public SharedPreferencesStorage(Context context, String name) {
|
|
24
|
-
this.prefs = context.getSharedPreferences(name != null ? name : DEFAULT_NAME, Context.MODE_PRIVATE);
|
|
25
|
-
this.editor = prefs.edit();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
public boolean set(String key, Object value) {
|
|
29
|
-
try {
|
|
30
|
-
if (value instanceof String) {
|
|
31
|
-
editor.putString(key, (String) value);
|
|
32
|
-
} else if (value instanceof Integer) {
|
|
33
|
-
editor.putInt(key, (Integer) value);
|
|
34
|
-
} else if (value instanceof Long) {
|
|
35
|
-
editor.putLong(key, (Long) value);
|
|
36
|
-
} else if (value instanceof Float) {
|
|
37
|
-
editor.putFloat(key, (Float) value);
|
|
38
|
-
} else if (value instanceof Boolean) {
|
|
39
|
-
editor.putBoolean(key, (Boolean) value);
|
|
40
|
-
} else if (value instanceof Set) {
|
|
41
|
-
// Safely convert to Set<String>
|
|
42
|
-
Set<?> rawSet = (Set<?>) value;
|
|
43
|
-
Set<String> stringSet = new HashSet<>();
|
|
44
|
-
for (Object item : rawSet) {
|
|
45
|
-
stringSet.add(item != null ? item.toString() : "null");
|
|
46
|
-
}
|
|
47
|
-
editor.putStringSet(key, stringSet);
|
|
48
|
-
} else {
|
|
49
|
-
// Convert complex objects to JSON
|
|
50
|
-
String json;
|
|
51
|
-
if (value instanceof JSONObject) {
|
|
52
|
-
json = ((JSONObject) value).toString();
|
|
53
|
-
} else {
|
|
54
|
-
// Use reflection to convert object to JSON string
|
|
55
|
-
try {
|
|
56
|
-
json = objectToJsonString(value);
|
|
57
|
-
} catch (Exception e) {
|
|
58
|
-
// Fallback to string representation
|
|
59
|
-
json = value.toString();
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
editor.putString(key, json);
|
|
63
|
-
}
|
|
64
|
-
return editor.commit();
|
|
65
|
-
} catch (Exception e) {
|
|
66
|
-
Log.e("StrataStorage", "Failed to set value in SharedPreferences", e);
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
public Object get(String key) {
|
|
72
|
-
Map<String, ?> all = prefs.getAll();
|
|
73
|
-
return all.get(key);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
public boolean remove(String key) {
|
|
77
|
-
editor.remove(key);
|
|
78
|
-
return editor.commit();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
public boolean clear() {
|
|
82
|
-
return clear(null);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
public boolean clear(String prefix) {
|
|
86
|
-
if (prefix != null) {
|
|
87
|
-
// Clear only keys with the given prefix
|
|
88
|
-
Set<String> keysToRemove = new HashSet<>();
|
|
89
|
-
for (String key : prefs.getAll().keySet()) {
|
|
90
|
-
if (key.startsWith(prefix) || key.contains(prefix)) {
|
|
91
|
-
keysToRemove.add(key);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
for (String key : keysToRemove) {
|
|
95
|
-
editor.remove(key);
|
|
96
|
-
}
|
|
97
|
-
} else {
|
|
98
|
-
// Clear all keys
|
|
99
|
-
editor.clear();
|
|
100
|
-
}
|
|
101
|
-
return editor.commit();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
public List<String> keys() {
|
|
105
|
-
return keys(null);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
public List<String> keys(String pattern) {
|
|
109
|
-
Set<String> allKeys = prefs.getAll().keySet();
|
|
110
|
-
|
|
111
|
-
if (pattern == null) {
|
|
112
|
-
return new ArrayList<>(allKeys);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Filter keys by pattern
|
|
116
|
-
List<String> filteredKeys = new ArrayList<>();
|
|
117
|
-
for (String key : allKeys) {
|
|
118
|
-
if (key.startsWith(pattern) || key.contains(pattern)) {
|
|
119
|
-
filteredKeys.add(key);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return filteredKeys;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
public boolean has(String key) {
|
|
126
|
-
return prefs.contains(key);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
public Map<String, ?> getAll() {
|
|
130
|
-
return prefs.getAll();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
public SizeInfo size() {
|
|
134
|
-
Map<String, ?> all = prefs.getAll();
|
|
135
|
-
long totalSize = 0;
|
|
136
|
-
int count = all.size();
|
|
137
|
-
|
|
138
|
-
for (Map.Entry<String, ?> entry : all.entrySet()) {
|
|
139
|
-
String key = entry.getKey();
|
|
140
|
-
Object value = entry.getValue();
|
|
141
|
-
|
|
142
|
-
// Estimate size (key + value in bytes)
|
|
143
|
-
totalSize += key.getBytes().length;
|
|
144
|
-
if (value != null) {
|
|
145
|
-
totalSize += value.toString().getBytes().length;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return new SizeInfo(totalSize, count);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Convert an object to JSON string using reflection
|
|
154
|
-
*/
|
|
155
|
-
private String objectToJsonString(Object obj) throws Exception {
|
|
156
|
-
if (obj == null) {
|
|
157
|
-
return "null";
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) {
|
|
161
|
-
return obj.toString();
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (obj instanceof Map) {
|
|
165
|
-
JSONObject jsonObj = new JSONObject();
|
|
166
|
-
Map<?, ?> map = (Map<?, ?>) obj;
|
|
167
|
-
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
|
168
|
-
String key = entry.getKey().toString();
|
|
169
|
-
jsonObj.put(key, entry.getValue());
|
|
170
|
-
}
|
|
171
|
-
return jsonObj.toString();
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (obj instanceof List || obj.getClass().isArray()) {
|
|
175
|
-
JSONArray jsonArray = new JSONArray();
|
|
176
|
-
if (obj instanceof List) {
|
|
177
|
-
List<?> list = (List<?>) obj;
|
|
178
|
-
for (Object item : list) {
|
|
179
|
-
jsonArray.put(item);
|
|
180
|
-
}
|
|
181
|
-
} else {
|
|
182
|
-
Object[] array = (Object[]) obj;
|
|
183
|
-
for (Object item : array) {
|
|
184
|
-
jsonArray.put(item);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return jsonArray.toString();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// For other objects, create a simple JSON object with their string representation
|
|
191
|
-
JSONObject jsonObj = new JSONObject();
|
|
192
|
-
jsonObj.put("value", obj.toString());
|
|
193
|
-
jsonObj.put("type", obj.getClass().getSimpleName());
|
|
194
|
-
return jsonObj.toString();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Size information class
|
|
199
|
-
*/
|
|
200
|
-
public static class SizeInfo {
|
|
201
|
-
public final long total;
|
|
202
|
-
public final int count;
|
|
203
|
-
|
|
204
|
-
public SizeInfo(long total, int count) {
|
|
205
|
-
this.total = total;
|
|
206
|
-
this.count = count;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|