strata-storage 2.2.0 → 2.3.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/android/src/main/java/com/strata/storage/EncryptedStorage.java +56 -7
- package/android/src/main/java/com/strata/storage/SQLiteStorage.java +57 -2
- package/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +38 -4
- package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +10 -20
- package/dist/android/src/main/java/com/strata/storage/EncryptedStorage.java +56 -7
- package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +57 -2
- package/dist/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +38 -4
- package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +10 -20
- package/dist/ios/Plugin/KeychainStorage.swift +44 -9
- package/dist/ios/Plugin/SQLiteStorage.swift +36 -2
- package/dist/ios/Plugin/UserDefaultsStorage.swift +14 -0
- package/dist/package.json +1 -1
- package/ios/Plugin/KeychainStorage.swift +44 -9
- package/ios/Plugin/SQLiteStorage.swift +36 -2
- package/ios/Plugin/UserDefaultsStorage.swift +14 -0
- package/package.json +8 -8
|
@@ -8,6 +8,9 @@ import androidx.security.crypto.MasterKey;
|
|
|
8
8
|
import java.util.Set;
|
|
9
9
|
import java.util.HashSet;
|
|
10
10
|
import java.util.Map;
|
|
11
|
+
import java.util.List;
|
|
12
|
+
import java.util.ArrayList;
|
|
13
|
+
import org.json.JSONObject;
|
|
11
14
|
|
|
12
15
|
public class EncryptedStorage {
|
|
13
16
|
private SharedPreferences encryptedPrefs;
|
|
@@ -42,9 +45,23 @@ public class EncryptedStorage {
|
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
public boolean set(String key,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
public boolean set(String key, Object value) {
|
|
49
|
+
try {
|
|
50
|
+
String stringValue;
|
|
51
|
+
if (value instanceof String) {
|
|
52
|
+
stringValue = (String) value;
|
|
53
|
+
} else {
|
|
54
|
+
// Convert complex objects to JSON
|
|
55
|
+
stringValue = value instanceof JSONObject ?
|
|
56
|
+
((JSONObject) value).toString() :
|
|
57
|
+
new JSONObject(value).toString();
|
|
58
|
+
}
|
|
59
|
+
editor.putString(key, stringValue);
|
|
60
|
+
return editor.commit();
|
|
61
|
+
} catch (Exception e) {
|
|
62
|
+
e.printStackTrace();
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
public String get(String key) {
|
|
@@ -79,19 +96,19 @@ public class EncryptedStorage {
|
|
|
79
96
|
return editor.commit();
|
|
80
97
|
}
|
|
81
98
|
|
|
82
|
-
public
|
|
99
|
+
public List<String> keys() {
|
|
83
100
|
return keys(null);
|
|
84
101
|
}
|
|
85
102
|
|
|
86
|
-
public
|
|
103
|
+
public List<String> keys(String pattern) {
|
|
87
104
|
Set<String> allKeys = encryptedPrefs.getAll().keySet();
|
|
88
105
|
|
|
89
106
|
if (pattern == null) {
|
|
90
|
-
return allKeys;
|
|
107
|
+
return new ArrayList<>(allKeys);
|
|
91
108
|
}
|
|
92
109
|
|
|
93
110
|
// Filter keys by pattern
|
|
94
|
-
|
|
111
|
+
List<String> filteredKeys = new ArrayList<>();
|
|
95
112
|
for (String key : allKeys) {
|
|
96
113
|
if (key.startsWith(pattern) || key.contains(pattern)) {
|
|
97
114
|
filteredKeys.add(key);
|
|
@@ -103,4 +120,36 @@ public class EncryptedStorage {
|
|
|
103
120
|
public boolean has(String key) {
|
|
104
121
|
return encryptedPrefs.contains(key);
|
|
105
122
|
}
|
|
123
|
+
|
|
124
|
+
public SizeInfo size() {
|
|
125
|
+
Map<String, ?> all = encryptedPrefs.getAll();
|
|
126
|
+
long totalSize = 0;
|
|
127
|
+
int count = all.size();
|
|
128
|
+
|
|
129
|
+
for (Map.Entry<String, ?> entry : all.entrySet()) {
|
|
130
|
+
String key = entry.getKey();
|
|
131
|
+
Object value = entry.getValue();
|
|
132
|
+
|
|
133
|
+
// Estimate size (key + value in bytes)
|
|
134
|
+
totalSize += key.getBytes().length;
|
|
135
|
+
if (value != null) {
|
|
136
|
+
totalSize += value.toString().getBytes().length;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return new SizeInfo(totalSize, count);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Size information class
|
|
145
|
+
*/
|
|
146
|
+
public static class SizeInfo {
|
|
147
|
+
public final long total;
|
|
148
|
+
public final int count;
|
|
149
|
+
|
|
150
|
+
public SizeInfo(long total, int count) {
|
|
151
|
+
this.total = total;
|
|
152
|
+
this.count = count;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
106
155
|
}
|
|
@@ -9,6 +9,8 @@ import java.util.ArrayList;
|
|
|
9
9
|
import java.util.List;
|
|
10
10
|
import java.util.HashMap;
|
|
11
11
|
import java.util.Map;
|
|
12
|
+
import org.json.JSONObject;
|
|
13
|
+
import java.nio.charset.StandardCharsets;
|
|
12
14
|
|
|
13
15
|
public class SQLiteStorage extends SQLiteOpenHelper {
|
|
14
16
|
private static final int DATABASE_VERSION = 1;
|
|
@@ -50,13 +52,31 @@ public class SQLiteStorage extends SQLiteOpenHelper {
|
|
|
50
52
|
onCreate(db);
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
public boolean set(String key,
|
|
55
|
+
public boolean set(String key, Object value, Long expires, String tags, String metadata) {
|
|
56
|
+
byte[] valueBytes;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
if (value instanceof byte[]) {
|
|
60
|
+
valueBytes = (byte[]) value;
|
|
61
|
+
} else if (value instanceof String) {
|
|
62
|
+
valueBytes = ((String) value).getBytes(StandardCharsets.UTF_8);
|
|
63
|
+
} else {
|
|
64
|
+
// Convert complex objects to JSON then to bytes
|
|
65
|
+
String json = value instanceof JSONObject ?
|
|
66
|
+
((JSONObject) value).toString() :
|
|
67
|
+
new JSONObject(value).toString();
|
|
68
|
+
valueBytes = json.getBytes(StandardCharsets.UTF_8);
|
|
69
|
+
}
|
|
70
|
+
} catch (Exception e) {
|
|
71
|
+
e.printStackTrace();
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
54
74
|
SQLiteDatabase db = this.getWritableDatabase();
|
|
55
75
|
ContentValues values = new ContentValues();
|
|
56
76
|
|
|
57
77
|
long now = System.currentTimeMillis();
|
|
58
78
|
values.put(KEY_ID, key);
|
|
59
|
-
values.put(KEY_VALUE,
|
|
79
|
+
values.put(KEY_VALUE, valueBytes);
|
|
60
80
|
values.put(KEY_CREATED, now);
|
|
61
81
|
values.put(KEY_UPDATED, now);
|
|
62
82
|
|
|
@@ -75,6 +95,11 @@ public class SQLiteStorage extends SQLiteOpenHelper {
|
|
|
75
95
|
return result != -1;
|
|
76
96
|
}
|
|
77
97
|
|
|
98
|
+
// Convenience method for simple Object values
|
|
99
|
+
public boolean set(String key, Object value) {
|
|
100
|
+
return set(key, value, null, null, null);
|
|
101
|
+
}
|
|
102
|
+
|
|
78
103
|
public Map<String, Object> get(String key) {
|
|
79
104
|
SQLiteDatabase db = this.getReadableDatabase();
|
|
80
105
|
Cursor cursor = db.query(TABLE_NAME, null, KEY_ID + "=?",
|
|
@@ -174,4 +199,34 @@ public class SQLiteStorage extends SQLiteOpenHelper {
|
|
|
174
199
|
db.close();
|
|
175
200
|
return exists;
|
|
176
201
|
}
|
|
202
|
+
|
|
203
|
+
public SizeInfo size() {
|
|
204
|
+
SQLiteDatabase db = this.getReadableDatabase();
|
|
205
|
+
Cursor cursor = db.rawQuery("SELECT COUNT(*), SUM(LENGTH(" + KEY_VALUE + ")) FROM " + TABLE_NAME, null);
|
|
206
|
+
|
|
207
|
+
long totalSize = 0;
|
|
208
|
+
int count = 0;
|
|
209
|
+
|
|
210
|
+
if (cursor != null && cursor.moveToFirst()) {
|
|
211
|
+
count = cursor.getInt(0);
|
|
212
|
+
totalSize = cursor.getLong(1);
|
|
213
|
+
cursor.close();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
db.close();
|
|
217
|
+
return new SizeInfo(totalSize, count);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Size information class
|
|
222
|
+
*/
|
|
223
|
+
public static class SizeInfo {
|
|
224
|
+
public final long total;
|
|
225
|
+
public final int count;
|
|
226
|
+
|
|
227
|
+
public SizeInfo(long total, int count) {
|
|
228
|
+
this.total = total;
|
|
229
|
+
this.count = count;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
177
232
|
}
|
|
@@ -5,6 +5,8 @@ import android.content.SharedPreferences;
|
|
|
5
5
|
import java.util.Map;
|
|
6
6
|
import java.util.Set;
|
|
7
7
|
import java.util.HashSet;
|
|
8
|
+
import java.util.List;
|
|
9
|
+
import java.util.ArrayList;
|
|
8
10
|
import org.json.JSONObject;
|
|
9
11
|
import org.json.JSONArray;
|
|
10
12
|
|
|
@@ -83,19 +85,19 @@ public class SharedPreferencesStorage {
|
|
|
83
85
|
return editor.commit();
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
public
|
|
88
|
+
public List<String> keys() {
|
|
87
89
|
return keys(null);
|
|
88
90
|
}
|
|
89
91
|
|
|
90
|
-
public
|
|
92
|
+
public List<String> keys(String pattern) {
|
|
91
93
|
Set<String> allKeys = prefs.getAll().keySet();
|
|
92
94
|
|
|
93
95
|
if (pattern == null) {
|
|
94
|
-
return allKeys;
|
|
96
|
+
return new ArrayList<>(allKeys);
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
// Filter keys by pattern
|
|
98
|
-
|
|
100
|
+
List<String> filteredKeys = new ArrayList<>();
|
|
99
101
|
for (String key : allKeys) {
|
|
100
102
|
if (key.startsWith(pattern) || key.contains(pattern)) {
|
|
101
103
|
filteredKeys.add(key);
|
|
@@ -111,4 +113,36 @@ public class SharedPreferencesStorage {
|
|
|
111
113
|
public Map<String, ?> getAll() {
|
|
112
114
|
return prefs.getAll();
|
|
113
115
|
}
|
|
116
|
+
|
|
117
|
+
public SizeInfo size() {
|
|
118
|
+
Map<String, ?> all = prefs.getAll();
|
|
119
|
+
long totalSize = 0;
|
|
120
|
+
int count = all.size();
|
|
121
|
+
|
|
122
|
+
for (Map.Entry<String, ?> entry : all.entrySet()) {
|
|
123
|
+
String key = entry.getKey();
|
|
124
|
+
Object value = entry.getValue();
|
|
125
|
+
|
|
126
|
+
// Estimate size (key + value in bytes)
|
|
127
|
+
totalSize += key.getBytes().length;
|
|
128
|
+
if (value != null) {
|
|
129
|
+
totalSize += value.toString().getBytes().length;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return new SizeInfo(totalSize, count);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Size information class
|
|
138
|
+
*/
|
|
139
|
+
public static class SizeInfo {
|
|
140
|
+
public final long total;
|
|
141
|
+
public final int count;
|
|
142
|
+
|
|
143
|
+
public SizeInfo(long total, int count) {
|
|
144
|
+
this.total = total;
|
|
145
|
+
this.count = count;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
114
148
|
}
|
|
@@ -11,7 +11,6 @@ import com.strata.storage.SQLiteStorage;
|
|
|
11
11
|
import org.json.JSONArray;
|
|
12
12
|
import org.json.JSONException;
|
|
13
13
|
import java.util.List;
|
|
14
|
-
import java.util.Set;
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* Main Capacitor plugin for Strata Storage
|
|
@@ -226,40 +225,31 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
226
225
|
String storage = call.getString("storage", "preferences");
|
|
227
226
|
|
|
228
227
|
try {
|
|
229
|
-
|
|
228
|
+
JSObject result = new JSObject();
|
|
230
229
|
|
|
231
230
|
switch (storage) {
|
|
232
231
|
case "secure":
|
|
233
|
-
|
|
232
|
+
EncryptedStorage.SizeInfo encryptedSizeInfo = encryptedStorage.size();
|
|
233
|
+
result.put("total", encryptedSizeInfo.total);
|
|
234
|
+
result.put("count", encryptedSizeInfo.count);
|
|
234
235
|
break;
|
|
235
236
|
case "sqlite":
|
|
236
|
-
|
|
237
|
+
SQLiteStorage.SizeInfo sqliteSizeInfo = sqliteStorage.size();
|
|
238
|
+
result.put("total", sqliteSizeInfo.total);
|
|
239
|
+
result.put("count", sqliteSizeInfo.count);
|
|
237
240
|
break;
|
|
238
241
|
case "preferences":
|
|
239
242
|
default:
|
|
240
|
-
|
|
243
|
+
SharedPreferencesStorage.SizeInfo prefsSizeInfo = sharedPrefsStorage.size();
|
|
244
|
+
result.put("total", prefsSizeInfo.total);
|
|
245
|
+
result.put("count", prefsSizeInfo.count);
|
|
241
246
|
break;
|
|
242
247
|
}
|
|
243
248
|
|
|
244
|
-
JSObject result = new JSObject();
|
|
245
|
-
result.put("total", sizeInfo.total);
|
|
246
|
-
result.put("count", sizeInfo.count);
|
|
247
249
|
call.resolve(result);
|
|
248
250
|
} catch (Exception e) {
|
|
249
251
|
call.reject("Failed to get size", e);
|
|
250
252
|
}
|
|
251
253
|
}
|
|
252
254
|
|
|
253
|
-
/**
|
|
254
|
-
* Size information class
|
|
255
|
-
*/
|
|
256
|
-
static class SizeInfo {
|
|
257
|
-
public final long total;
|
|
258
|
-
public final int count;
|
|
259
|
-
|
|
260
|
-
public SizeInfo(long total, int count) {
|
|
261
|
-
this.total = total;
|
|
262
|
-
this.count = count;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
255
|
}
|
|
@@ -8,6 +8,9 @@ import androidx.security.crypto.MasterKey;
|
|
|
8
8
|
import java.util.Set;
|
|
9
9
|
import java.util.HashSet;
|
|
10
10
|
import java.util.Map;
|
|
11
|
+
import java.util.List;
|
|
12
|
+
import java.util.ArrayList;
|
|
13
|
+
import org.json.JSONObject;
|
|
11
14
|
|
|
12
15
|
public class EncryptedStorage {
|
|
13
16
|
private SharedPreferences encryptedPrefs;
|
|
@@ -42,9 +45,23 @@ public class EncryptedStorage {
|
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
public boolean set(String key,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
public boolean set(String key, Object value) {
|
|
49
|
+
try {
|
|
50
|
+
String stringValue;
|
|
51
|
+
if (value instanceof String) {
|
|
52
|
+
stringValue = (String) value;
|
|
53
|
+
} else {
|
|
54
|
+
// Convert complex objects to JSON
|
|
55
|
+
stringValue = value instanceof JSONObject ?
|
|
56
|
+
((JSONObject) value).toString() :
|
|
57
|
+
new JSONObject(value).toString();
|
|
58
|
+
}
|
|
59
|
+
editor.putString(key, stringValue);
|
|
60
|
+
return editor.commit();
|
|
61
|
+
} catch (Exception e) {
|
|
62
|
+
e.printStackTrace();
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
public String get(String key) {
|
|
@@ -79,19 +96,19 @@ public class EncryptedStorage {
|
|
|
79
96
|
return editor.commit();
|
|
80
97
|
}
|
|
81
98
|
|
|
82
|
-
public
|
|
99
|
+
public List<String> keys() {
|
|
83
100
|
return keys(null);
|
|
84
101
|
}
|
|
85
102
|
|
|
86
|
-
public
|
|
103
|
+
public List<String> keys(String pattern) {
|
|
87
104
|
Set<String> allKeys = encryptedPrefs.getAll().keySet();
|
|
88
105
|
|
|
89
106
|
if (pattern == null) {
|
|
90
|
-
return allKeys;
|
|
107
|
+
return new ArrayList<>(allKeys);
|
|
91
108
|
}
|
|
92
109
|
|
|
93
110
|
// Filter keys by pattern
|
|
94
|
-
|
|
111
|
+
List<String> filteredKeys = new ArrayList<>();
|
|
95
112
|
for (String key : allKeys) {
|
|
96
113
|
if (key.startsWith(pattern) || key.contains(pattern)) {
|
|
97
114
|
filteredKeys.add(key);
|
|
@@ -103,4 +120,36 @@ public class EncryptedStorage {
|
|
|
103
120
|
public boolean has(String key) {
|
|
104
121
|
return encryptedPrefs.contains(key);
|
|
105
122
|
}
|
|
123
|
+
|
|
124
|
+
public SizeInfo size() {
|
|
125
|
+
Map<String, ?> all = encryptedPrefs.getAll();
|
|
126
|
+
long totalSize = 0;
|
|
127
|
+
int count = all.size();
|
|
128
|
+
|
|
129
|
+
for (Map.Entry<String, ?> entry : all.entrySet()) {
|
|
130
|
+
String key = entry.getKey();
|
|
131
|
+
Object value = entry.getValue();
|
|
132
|
+
|
|
133
|
+
// Estimate size (key + value in bytes)
|
|
134
|
+
totalSize += key.getBytes().length;
|
|
135
|
+
if (value != null) {
|
|
136
|
+
totalSize += value.toString().getBytes().length;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return new SizeInfo(totalSize, count);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Size information class
|
|
145
|
+
*/
|
|
146
|
+
public static class SizeInfo {
|
|
147
|
+
public final long total;
|
|
148
|
+
public final int count;
|
|
149
|
+
|
|
150
|
+
public SizeInfo(long total, int count) {
|
|
151
|
+
this.total = total;
|
|
152
|
+
this.count = count;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
106
155
|
}
|
|
@@ -9,6 +9,8 @@ import java.util.ArrayList;
|
|
|
9
9
|
import java.util.List;
|
|
10
10
|
import java.util.HashMap;
|
|
11
11
|
import java.util.Map;
|
|
12
|
+
import org.json.JSONObject;
|
|
13
|
+
import java.nio.charset.StandardCharsets;
|
|
12
14
|
|
|
13
15
|
public class SQLiteStorage extends SQLiteOpenHelper {
|
|
14
16
|
private static final int DATABASE_VERSION = 1;
|
|
@@ -50,13 +52,31 @@ public class SQLiteStorage extends SQLiteOpenHelper {
|
|
|
50
52
|
onCreate(db);
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
public boolean set(String key,
|
|
55
|
+
public boolean set(String key, Object value, Long expires, String tags, String metadata) {
|
|
56
|
+
byte[] valueBytes;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
if (value instanceof byte[]) {
|
|
60
|
+
valueBytes = (byte[]) value;
|
|
61
|
+
} else if (value instanceof String) {
|
|
62
|
+
valueBytes = ((String) value).getBytes(StandardCharsets.UTF_8);
|
|
63
|
+
} else {
|
|
64
|
+
// Convert complex objects to JSON then to bytes
|
|
65
|
+
String json = value instanceof JSONObject ?
|
|
66
|
+
((JSONObject) value).toString() :
|
|
67
|
+
new JSONObject(value).toString();
|
|
68
|
+
valueBytes = json.getBytes(StandardCharsets.UTF_8);
|
|
69
|
+
}
|
|
70
|
+
} catch (Exception e) {
|
|
71
|
+
e.printStackTrace();
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
54
74
|
SQLiteDatabase db = this.getWritableDatabase();
|
|
55
75
|
ContentValues values = new ContentValues();
|
|
56
76
|
|
|
57
77
|
long now = System.currentTimeMillis();
|
|
58
78
|
values.put(KEY_ID, key);
|
|
59
|
-
values.put(KEY_VALUE,
|
|
79
|
+
values.put(KEY_VALUE, valueBytes);
|
|
60
80
|
values.put(KEY_CREATED, now);
|
|
61
81
|
values.put(KEY_UPDATED, now);
|
|
62
82
|
|
|
@@ -75,6 +95,11 @@ public class SQLiteStorage extends SQLiteOpenHelper {
|
|
|
75
95
|
return result != -1;
|
|
76
96
|
}
|
|
77
97
|
|
|
98
|
+
// Convenience method for simple Object values
|
|
99
|
+
public boolean set(String key, Object value) {
|
|
100
|
+
return set(key, value, null, null, null);
|
|
101
|
+
}
|
|
102
|
+
|
|
78
103
|
public Map<String, Object> get(String key) {
|
|
79
104
|
SQLiteDatabase db = this.getReadableDatabase();
|
|
80
105
|
Cursor cursor = db.query(TABLE_NAME, null, KEY_ID + "=?",
|
|
@@ -174,4 +199,34 @@ public class SQLiteStorage extends SQLiteOpenHelper {
|
|
|
174
199
|
db.close();
|
|
175
200
|
return exists;
|
|
176
201
|
}
|
|
202
|
+
|
|
203
|
+
public SizeInfo size() {
|
|
204
|
+
SQLiteDatabase db = this.getReadableDatabase();
|
|
205
|
+
Cursor cursor = db.rawQuery("SELECT COUNT(*), SUM(LENGTH(" + KEY_VALUE + ")) FROM " + TABLE_NAME, null);
|
|
206
|
+
|
|
207
|
+
long totalSize = 0;
|
|
208
|
+
int count = 0;
|
|
209
|
+
|
|
210
|
+
if (cursor != null && cursor.moveToFirst()) {
|
|
211
|
+
count = cursor.getInt(0);
|
|
212
|
+
totalSize = cursor.getLong(1);
|
|
213
|
+
cursor.close();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
db.close();
|
|
217
|
+
return new SizeInfo(totalSize, count);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Size information class
|
|
222
|
+
*/
|
|
223
|
+
public static class SizeInfo {
|
|
224
|
+
public final long total;
|
|
225
|
+
public final int count;
|
|
226
|
+
|
|
227
|
+
public SizeInfo(long total, int count) {
|
|
228
|
+
this.total = total;
|
|
229
|
+
this.count = count;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
177
232
|
}
|
|
@@ -5,6 +5,8 @@ import android.content.SharedPreferences;
|
|
|
5
5
|
import java.util.Map;
|
|
6
6
|
import java.util.Set;
|
|
7
7
|
import java.util.HashSet;
|
|
8
|
+
import java.util.List;
|
|
9
|
+
import java.util.ArrayList;
|
|
8
10
|
import org.json.JSONObject;
|
|
9
11
|
import org.json.JSONArray;
|
|
10
12
|
|
|
@@ -83,19 +85,19 @@ public class SharedPreferencesStorage {
|
|
|
83
85
|
return editor.commit();
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
public
|
|
88
|
+
public List<String> keys() {
|
|
87
89
|
return keys(null);
|
|
88
90
|
}
|
|
89
91
|
|
|
90
|
-
public
|
|
92
|
+
public List<String> keys(String pattern) {
|
|
91
93
|
Set<String> allKeys = prefs.getAll().keySet();
|
|
92
94
|
|
|
93
95
|
if (pattern == null) {
|
|
94
|
-
return allKeys;
|
|
96
|
+
return new ArrayList<>(allKeys);
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
// Filter keys by pattern
|
|
98
|
-
|
|
100
|
+
List<String> filteredKeys = new ArrayList<>();
|
|
99
101
|
for (String key : allKeys) {
|
|
100
102
|
if (key.startsWith(pattern) || key.contains(pattern)) {
|
|
101
103
|
filteredKeys.add(key);
|
|
@@ -111,4 +113,36 @@ public class SharedPreferencesStorage {
|
|
|
111
113
|
public Map<String, ?> getAll() {
|
|
112
114
|
return prefs.getAll();
|
|
113
115
|
}
|
|
116
|
+
|
|
117
|
+
public SizeInfo size() {
|
|
118
|
+
Map<String, ?> all = prefs.getAll();
|
|
119
|
+
long totalSize = 0;
|
|
120
|
+
int count = all.size();
|
|
121
|
+
|
|
122
|
+
for (Map.Entry<String, ?> entry : all.entrySet()) {
|
|
123
|
+
String key = entry.getKey();
|
|
124
|
+
Object value = entry.getValue();
|
|
125
|
+
|
|
126
|
+
// Estimate size (key + value in bytes)
|
|
127
|
+
totalSize += key.getBytes().length;
|
|
128
|
+
if (value != null) {
|
|
129
|
+
totalSize += value.toString().getBytes().length;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return new SizeInfo(totalSize, count);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Size information class
|
|
138
|
+
*/
|
|
139
|
+
public static class SizeInfo {
|
|
140
|
+
public final long total;
|
|
141
|
+
public final int count;
|
|
142
|
+
|
|
143
|
+
public SizeInfo(long total, int count) {
|
|
144
|
+
this.total = total;
|
|
145
|
+
this.count = count;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
114
148
|
}
|
|
@@ -11,7 +11,6 @@ import com.strata.storage.SQLiteStorage;
|
|
|
11
11
|
import org.json.JSONArray;
|
|
12
12
|
import org.json.JSONException;
|
|
13
13
|
import java.util.List;
|
|
14
|
-
import java.util.Set;
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* Main Capacitor plugin for Strata Storage
|
|
@@ -226,40 +225,31 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
226
225
|
String storage = call.getString("storage", "preferences");
|
|
227
226
|
|
|
228
227
|
try {
|
|
229
|
-
|
|
228
|
+
JSObject result = new JSObject();
|
|
230
229
|
|
|
231
230
|
switch (storage) {
|
|
232
231
|
case "secure":
|
|
233
|
-
|
|
232
|
+
EncryptedStorage.SizeInfo encryptedSizeInfo = encryptedStorage.size();
|
|
233
|
+
result.put("total", encryptedSizeInfo.total);
|
|
234
|
+
result.put("count", encryptedSizeInfo.count);
|
|
234
235
|
break;
|
|
235
236
|
case "sqlite":
|
|
236
|
-
|
|
237
|
+
SQLiteStorage.SizeInfo sqliteSizeInfo = sqliteStorage.size();
|
|
238
|
+
result.put("total", sqliteSizeInfo.total);
|
|
239
|
+
result.put("count", sqliteSizeInfo.count);
|
|
237
240
|
break;
|
|
238
241
|
case "preferences":
|
|
239
242
|
default:
|
|
240
|
-
|
|
243
|
+
SharedPreferencesStorage.SizeInfo prefsSizeInfo = sharedPrefsStorage.size();
|
|
244
|
+
result.put("total", prefsSizeInfo.total);
|
|
245
|
+
result.put("count", prefsSizeInfo.count);
|
|
241
246
|
break;
|
|
242
247
|
}
|
|
243
248
|
|
|
244
|
-
JSObject result = new JSObject();
|
|
245
|
-
result.put("total", sizeInfo.total);
|
|
246
|
-
result.put("count", sizeInfo.count);
|
|
247
249
|
call.resolve(result);
|
|
248
250
|
} catch (Exception e) {
|
|
249
251
|
call.reject("Failed to get size", e);
|
|
250
252
|
}
|
|
251
253
|
}
|
|
252
254
|
|
|
253
|
-
/**
|
|
254
|
-
* Size information class
|
|
255
|
-
*/
|
|
256
|
-
static class SizeInfo {
|
|
257
|
-
public final long total;
|
|
258
|
-
public final int count;
|
|
259
|
-
|
|
260
|
-
public SizeInfo(long total, int count) {
|
|
261
|
-
this.total = total;
|
|
262
|
-
this.count = count;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
255
|
}
|
|
@@ -11,18 +11,30 @@ import Security
|
|
|
11
11
|
super.init()
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
@objc public func set(key: String, value:
|
|
14
|
+
@objc public func set(key: String, value: Any) throws -> Bool {
|
|
15
|
+
let data: Data
|
|
16
|
+
|
|
17
|
+
if let dataValue = value as? Data {
|
|
18
|
+
data = dataValue
|
|
19
|
+
} else if let stringValue = value as? String {
|
|
20
|
+
data = stringValue.data(using: .utf8) ?? Data()
|
|
21
|
+
} else {
|
|
22
|
+
// Convert to JSON for complex objects
|
|
23
|
+
let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])
|
|
24
|
+
data = jsonData
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
let query = createQuery(key: key)
|
|
16
28
|
SecItemDelete(query as CFDictionary)
|
|
17
29
|
|
|
18
30
|
var newItem = query
|
|
19
|
-
newItem[kSecValueData as String] =
|
|
31
|
+
newItem[kSecValueData as String] = data
|
|
20
32
|
|
|
21
33
|
let status = SecItemAdd(newItem as CFDictionary, nil)
|
|
22
34
|
return status == errSecSuccess
|
|
23
35
|
}
|
|
24
36
|
|
|
25
|
-
@objc public func get(key: String) ->
|
|
37
|
+
@objc public func get(key: String) throws -> Any? {
|
|
26
38
|
var query = createQuery(key: key)
|
|
27
39
|
query[kSecReturnData as String] = true
|
|
28
40
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
@@ -31,22 +43,29 @@ import Security
|
|
|
31
43
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
32
44
|
|
|
33
45
|
guard status == errSecSuccess else { return nil }
|
|
34
|
-
|
|
46
|
+
guard let data = result as? Data else { return nil }
|
|
47
|
+
|
|
48
|
+
// Try to parse as JSON first, fallback to string
|
|
49
|
+
if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []) {
|
|
50
|
+
return jsonObject
|
|
51
|
+
} else {
|
|
52
|
+
return String(data: data, encoding: .utf8)
|
|
53
|
+
}
|
|
35
54
|
}
|
|
36
55
|
|
|
37
|
-
@objc public func remove(key: String) -> Bool {
|
|
56
|
+
@objc public func remove(key: String) throws -> Bool {
|
|
38
57
|
let query = createQuery(key: key)
|
|
39
58
|
let status = SecItemDelete(query as CFDictionary)
|
|
40
59
|
return status == errSecSuccess
|
|
41
60
|
}
|
|
42
61
|
|
|
43
|
-
@objc public func clear(prefix: String? = nil) -> Bool {
|
|
62
|
+
@objc public func clear(prefix: String? = nil) throws -> Bool {
|
|
44
63
|
if let prefix = prefix {
|
|
45
64
|
// Clear only keys with the given prefix
|
|
46
|
-
let keysToRemove = keys(pattern: prefix)
|
|
65
|
+
let keysToRemove = try keys(pattern: prefix)
|
|
47
66
|
var allSuccess = true
|
|
48
67
|
for key in keysToRemove {
|
|
49
|
-
if !remove(key: key) {
|
|
68
|
+
if !(try remove(key: key)) {
|
|
50
69
|
allSuccess = false
|
|
51
70
|
}
|
|
52
71
|
}
|
|
@@ -62,7 +81,7 @@ import Security
|
|
|
62
81
|
}
|
|
63
82
|
}
|
|
64
83
|
|
|
65
|
-
@objc public func keys(pattern: String? = nil) -> [String] {
|
|
84
|
+
@objc public func keys(pattern: String? = nil) throws -> [String] {
|
|
66
85
|
var query: [String: Any] = [
|
|
67
86
|
kSecClass as String: kSecClassGenericPassword,
|
|
68
87
|
kSecAttrService as String: service,
|
|
@@ -106,4 +125,20 @@ import Security
|
|
|
106
125
|
|
|
107
126
|
return query
|
|
108
127
|
}
|
|
128
|
+
|
|
129
|
+
@objc public func size() throws -> (total: Int, count: Int) {
|
|
130
|
+
let allKeys = try keys()
|
|
131
|
+
var totalSize = 0
|
|
132
|
+
|
|
133
|
+
for key in allKeys {
|
|
134
|
+
if let data = try get(key: key) as? Data {
|
|
135
|
+
totalSize += data.count
|
|
136
|
+
} else if let string = try get(key: key) as? String {
|
|
137
|
+
totalSize += string.data(using: .utf8)?.count ?? 0
|
|
138
|
+
}
|
|
139
|
+
totalSize += key.data(using: .utf8)?.count ?? 0
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return (total: totalSize, count: allKeys.count)
|
|
143
|
+
}
|
|
109
144
|
}
|
|
@@ -53,7 +53,18 @@ import SQLite3
|
|
|
53
53
|
sqlite3_finalize(createTableStatement)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
@objc public func set(key: String, value:
|
|
56
|
+
@objc public func set(key: String, value: Any, expires: Int64? = nil, tags: [String]? = nil, metadata: [String: Any]? = nil) throws -> Bool {
|
|
57
|
+
let data: Data
|
|
58
|
+
|
|
59
|
+
if let dataValue = value as? Data {
|
|
60
|
+
data = dataValue
|
|
61
|
+
} else if let stringValue = value as? String {
|
|
62
|
+
data = stringValue.data(using: .utf8) ?? Data()
|
|
63
|
+
} else {
|
|
64
|
+
// Convert to JSON for complex objects
|
|
65
|
+
data = try JSONSerialization.data(withJSONObject: value, options: [])
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
let now = Int64(Date().timeIntervalSince1970 * 1000)
|
|
58
69
|
let tagsJson = tags != nil ? try? JSONSerialization.data(withJSONObject: tags!, options: []) : nil
|
|
59
70
|
let metadataJson = metadata != nil ? try? JSONSerialization.data(withJSONObject: metadata!, options: []) : nil
|
|
@@ -67,7 +78,7 @@ import SQLite3
|
|
|
67
78
|
var statement: OpaquePointer?
|
|
68
79
|
let result = sqlite3_prepare_v2(db, insertSQL, -1, &statement, nil) == SQLITE_OK &&
|
|
69
80
|
sqlite3_bind_text(statement, 1, key, -1, nil) == SQLITE_OK &&
|
|
70
|
-
sqlite3_bind_blob(statement, 2, (
|
|
81
|
+
sqlite3_bind_blob(statement, 2, (data as NSData).bytes, Int32(data.count), nil) == SQLITE_OK &&
|
|
71
82
|
sqlite3_bind_int64(statement, 3, now) == SQLITE_OK &&
|
|
72
83
|
sqlite3_bind_int64(statement, 4, now) == SQLITE_OK &&
|
|
73
84
|
(expires != nil ? sqlite3_bind_int64(statement, 5, expires!) : sqlite3_bind_null(statement, 5)) == SQLITE_OK &&
|
|
@@ -79,6 +90,11 @@ import SQLite3
|
|
|
79
90
|
return result
|
|
80
91
|
}
|
|
81
92
|
|
|
93
|
+
// Convenience method for simple values
|
|
94
|
+
@objc public func set(key: String, value: Any) throws -> Bool {
|
|
95
|
+
return try set(key: key, value: value, expires: nil, tags: nil, metadata: nil)
|
|
96
|
+
}
|
|
97
|
+
|
|
82
98
|
@objc public func get(key: String) -> [String: Any]? {
|
|
83
99
|
let querySQL = "SELECT * FROM \(tableName) WHERE key = ? LIMIT 1"
|
|
84
100
|
var statement: OpaquePointer?
|
|
@@ -187,4 +203,22 @@ import SQLite3
|
|
|
187
203
|
sqlite3_finalize(statement)
|
|
188
204
|
return keys
|
|
189
205
|
}
|
|
206
|
+
|
|
207
|
+
@objc public func size() throws -> (total: Int, count: Int) {
|
|
208
|
+
let querySQL = "SELECT COUNT(*), SUM(LENGTH(value)) FROM \(tableName)"
|
|
209
|
+
var statement: OpaquePointer?
|
|
210
|
+
|
|
211
|
+
var totalSize = 0
|
|
212
|
+
var count = 0
|
|
213
|
+
|
|
214
|
+
if sqlite3_prepare_v2(db, querySQL, -1, &statement, nil) == SQLITE_OK {
|
|
215
|
+
if sqlite3_step(statement) == SQLITE_ROW {
|
|
216
|
+
count = Int(sqlite3_column_int(statement, 0))
|
|
217
|
+
totalSize = Int(sqlite3_column_int64(statement, 1))
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
sqlite3_finalize(statement)
|
|
222
|
+
return (total: totalSize, count: count)
|
|
223
|
+
}
|
|
190
224
|
}
|
|
@@ -59,4 +59,18 @@ import Foundation
|
|
|
59
59
|
@objc public func has(key: String) -> Bool {
|
|
60
60
|
return userDefaults.object(forKey: key) != nil
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
@objc public func size() -> (total: Int, count: Int) {
|
|
64
|
+
let all = userDefaults.dictionaryRepresentation()
|
|
65
|
+
var totalSize = 0
|
|
66
|
+
let count = all.count
|
|
67
|
+
|
|
68
|
+
for (key, value) in all {
|
|
69
|
+
// Estimate size (key + value in bytes)
|
|
70
|
+
totalSize += key.data(using: .utf8)?.count ?? 0
|
|
71
|
+
totalSize += "\(value)".data(using: .utf8)?.count ?? 0
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (total: totalSize, count: count)
|
|
75
|
+
}
|
|
62
76
|
}
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strata-storage",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Zero-dependency universal storage plugin providing a unified API for all storage operations across web, Android, and iOS platforms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -11,18 +11,30 @@ import Security
|
|
|
11
11
|
super.init()
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
@objc public func set(key: String, value:
|
|
14
|
+
@objc public func set(key: String, value: Any) throws -> Bool {
|
|
15
|
+
let data: Data
|
|
16
|
+
|
|
17
|
+
if let dataValue = value as? Data {
|
|
18
|
+
data = dataValue
|
|
19
|
+
} else if let stringValue = value as? String {
|
|
20
|
+
data = stringValue.data(using: .utf8) ?? Data()
|
|
21
|
+
} else {
|
|
22
|
+
// Convert to JSON for complex objects
|
|
23
|
+
let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])
|
|
24
|
+
data = jsonData
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
let query = createQuery(key: key)
|
|
16
28
|
SecItemDelete(query as CFDictionary)
|
|
17
29
|
|
|
18
30
|
var newItem = query
|
|
19
|
-
newItem[kSecValueData as String] =
|
|
31
|
+
newItem[kSecValueData as String] = data
|
|
20
32
|
|
|
21
33
|
let status = SecItemAdd(newItem as CFDictionary, nil)
|
|
22
34
|
return status == errSecSuccess
|
|
23
35
|
}
|
|
24
36
|
|
|
25
|
-
@objc public func get(key: String) ->
|
|
37
|
+
@objc public func get(key: String) throws -> Any? {
|
|
26
38
|
var query = createQuery(key: key)
|
|
27
39
|
query[kSecReturnData as String] = true
|
|
28
40
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
@@ -31,22 +43,29 @@ import Security
|
|
|
31
43
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
32
44
|
|
|
33
45
|
guard status == errSecSuccess else { return nil }
|
|
34
|
-
|
|
46
|
+
guard let data = result as? Data else { return nil }
|
|
47
|
+
|
|
48
|
+
// Try to parse as JSON first, fallback to string
|
|
49
|
+
if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []) {
|
|
50
|
+
return jsonObject
|
|
51
|
+
} else {
|
|
52
|
+
return String(data: data, encoding: .utf8)
|
|
53
|
+
}
|
|
35
54
|
}
|
|
36
55
|
|
|
37
|
-
@objc public func remove(key: String) -> Bool {
|
|
56
|
+
@objc public func remove(key: String) throws -> Bool {
|
|
38
57
|
let query = createQuery(key: key)
|
|
39
58
|
let status = SecItemDelete(query as CFDictionary)
|
|
40
59
|
return status == errSecSuccess
|
|
41
60
|
}
|
|
42
61
|
|
|
43
|
-
@objc public func clear(prefix: String? = nil) -> Bool {
|
|
62
|
+
@objc public func clear(prefix: String? = nil) throws -> Bool {
|
|
44
63
|
if let prefix = prefix {
|
|
45
64
|
// Clear only keys with the given prefix
|
|
46
|
-
let keysToRemove = keys(pattern: prefix)
|
|
65
|
+
let keysToRemove = try keys(pattern: prefix)
|
|
47
66
|
var allSuccess = true
|
|
48
67
|
for key in keysToRemove {
|
|
49
|
-
if !remove(key: key) {
|
|
68
|
+
if !(try remove(key: key)) {
|
|
50
69
|
allSuccess = false
|
|
51
70
|
}
|
|
52
71
|
}
|
|
@@ -62,7 +81,7 @@ import Security
|
|
|
62
81
|
}
|
|
63
82
|
}
|
|
64
83
|
|
|
65
|
-
@objc public func keys(pattern: String? = nil) -> [String] {
|
|
84
|
+
@objc public func keys(pattern: String? = nil) throws -> [String] {
|
|
66
85
|
var query: [String: Any] = [
|
|
67
86
|
kSecClass as String: kSecClassGenericPassword,
|
|
68
87
|
kSecAttrService as String: service,
|
|
@@ -106,4 +125,20 @@ import Security
|
|
|
106
125
|
|
|
107
126
|
return query
|
|
108
127
|
}
|
|
128
|
+
|
|
129
|
+
@objc public func size() throws -> (total: Int, count: Int) {
|
|
130
|
+
let allKeys = try keys()
|
|
131
|
+
var totalSize = 0
|
|
132
|
+
|
|
133
|
+
for key in allKeys {
|
|
134
|
+
if let data = try get(key: key) as? Data {
|
|
135
|
+
totalSize += data.count
|
|
136
|
+
} else if let string = try get(key: key) as? String {
|
|
137
|
+
totalSize += string.data(using: .utf8)?.count ?? 0
|
|
138
|
+
}
|
|
139
|
+
totalSize += key.data(using: .utf8)?.count ?? 0
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return (total: totalSize, count: allKeys.count)
|
|
143
|
+
}
|
|
109
144
|
}
|
|
@@ -53,7 +53,18 @@ import SQLite3
|
|
|
53
53
|
sqlite3_finalize(createTableStatement)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
@objc public func set(key: String, value:
|
|
56
|
+
@objc public func set(key: String, value: Any, expires: Int64? = nil, tags: [String]? = nil, metadata: [String: Any]? = nil) throws -> Bool {
|
|
57
|
+
let data: Data
|
|
58
|
+
|
|
59
|
+
if let dataValue = value as? Data {
|
|
60
|
+
data = dataValue
|
|
61
|
+
} else if let stringValue = value as? String {
|
|
62
|
+
data = stringValue.data(using: .utf8) ?? Data()
|
|
63
|
+
} else {
|
|
64
|
+
// Convert to JSON for complex objects
|
|
65
|
+
data = try JSONSerialization.data(withJSONObject: value, options: [])
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
let now = Int64(Date().timeIntervalSince1970 * 1000)
|
|
58
69
|
let tagsJson = tags != nil ? try? JSONSerialization.data(withJSONObject: tags!, options: []) : nil
|
|
59
70
|
let metadataJson = metadata != nil ? try? JSONSerialization.data(withJSONObject: metadata!, options: []) : nil
|
|
@@ -67,7 +78,7 @@ import SQLite3
|
|
|
67
78
|
var statement: OpaquePointer?
|
|
68
79
|
let result = sqlite3_prepare_v2(db, insertSQL, -1, &statement, nil) == SQLITE_OK &&
|
|
69
80
|
sqlite3_bind_text(statement, 1, key, -1, nil) == SQLITE_OK &&
|
|
70
|
-
sqlite3_bind_blob(statement, 2, (
|
|
81
|
+
sqlite3_bind_blob(statement, 2, (data as NSData).bytes, Int32(data.count), nil) == SQLITE_OK &&
|
|
71
82
|
sqlite3_bind_int64(statement, 3, now) == SQLITE_OK &&
|
|
72
83
|
sqlite3_bind_int64(statement, 4, now) == SQLITE_OK &&
|
|
73
84
|
(expires != nil ? sqlite3_bind_int64(statement, 5, expires!) : sqlite3_bind_null(statement, 5)) == SQLITE_OK &&
|
|
@@ -79,6 +90,11 @@ import SQLite3
|
|
|
79
90
|
return result
|
|
80
91
|
}
|
|
81
92
|
|
|
93
|
+
// Convenience method for simple values
|
|
94
|
+
@objc public func set(key: String, value: Any) throws -> Bool {
|
|
95
|
+
return try set(key: key, value: value, expires: nil, tags: nil, metadata: nil)
|
|
96
|
+
}
|
|
97
|
+
|
|
82
98
|
@objc public func get(key: String) -> [String: Any]? {
|
|
83
99
|
let querySQL = "SELECT * FROM \(tableName) WHERE key = ? LIMIT 1"
|
|
84
100
|
var statement: OpaquePointer?
|
|
@@ -187,4 +203,22 @@ import SQLite3
|
|
|
187
203
|
sqlite3_finalize(statement)
|
|
188
204
|
return keys
|
|
189
205
|
}
|
|
206
|
+
|
|
207
|
+
@objc public func size() throws -> (total: Int, count: Int) {
|
|
208
|
+
let querySQL = "SELECT COUNT(*), SUM(LENGTH(value)) FROM \(tableName)"
|
|
209
|
+
var statement: OpaquePointer?
|
|
210
|
+
|
|
211
|
+
var totalSize = 0
|
|
212
|
+
var count = 0
|
|
213
|
+
|
|
214
|
+
if sqlite3_prepare_v2(db, querySQL, -1, &statement, nil) == SQLITE_OK {
|
|
215
|
+
if sqlite3_step(statement) == SQLITE_ROW {
|
|
216
|
+
count = Int(sqlite3_column_int(statement, 0))
|
|
217
|
+
totalSize = Int(sqlite3_column_int64(statement, 1))
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
sqlite3_finalize(statement)
|
|
222
|
+
return (total: totalSize, count: count)
|
|
223
|
+
}
|
|
190
224
|
}
|
|
@@ -59,4 +59,18 @@ import Foundation
|
|
|
59
59
|
@objc public func has(key: String) -> Bool {
|
|
60
60
|
return userDefaults.object(forKey: key) != nil
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
@objc public func size() -> (total: Int, count: Int) {
|
|
64
|
+
let all = userDefaults.dictionaryRepresentation()
|
|
65
|
+
var totalSize = 0
|
|
66
|
+
let count = all.count
|
|
67
|
+
|
|
68
|
+
for (key, value) in all {
|
|
69
|
+
// Estimate size (key + value in bytes)
|
|
70
|
+
totalSize += key.data(using: .utf8)?.count ?? 0
|
|
71
|
+
totalSize += "\(value)".data(using: .utf8)?.count ?? 0
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (total: totalSize, count: count)
|
|
75
|
+
}
|
|
62
76
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strata-storage",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Zero-dependency universal storage plugin providing a unified API for all storage operations across web, Android, and iOS platforms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -90,18 +90,18 @@
|
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
92
|
"@eslint/js": "^10.0.0",
|
|
93
|
-
"@types/node": "^24.
|
|
94
|
-
"@types/react": "^19.
|
|
95
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
96
|
-
"@typescript-eslint/parser": "^8.
|
|
93
|
+
"@types/node": "^24.7.0",
|
|
94
|
+
"@types/react": "^19.2.2",
|
|
95
|
+
"@typescript-eslint/eslint-plugin": "^8.46.0",
|
|
96
|
+
"@typescript-eslint/parser": "^8.46.0",
|
|
97
97
|
"@vitest/coverage-v8": "^3.2.4",
|
|
98
|
-
"eslint": "^9.
|
|
98
|
+
"eslint": "^9.37.0",
|
|
99
99
|
"eslint-config-prettier": "^10.1.8",
|
|
100
100
|
"eslint-plugin-prettier": "^5.5.4",
|
|
101
101
|
"jsdom": "^27.0.0",
|
|
102
102
|
"prettier": "^3.6.2",
|
|
103
|
-
"typescript": "^5.9.
|
|
104
|
-
"typescript-eslint": "^8.
|
|
103
|
+
"typescript": "^5.9.3",
|
|
104
|
+
"typescript-eslint": "^8.46.0",
|
|
105
105
|
"vitest": "^3.2.4"
|
|
106
106
|
},
|
|
107
107
|
"engines": {
|