@nativescript-community/ui-image 5.0.12 → 5.0.13

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.
@@ -8,8 +8,10 @@ package com.nativescript.image;
8
8
  public class GlideConfiguration {
9
9
  private static GlideConfiguration instance;
10
10
 
11
+ private long diskCacheSize = 250 * 1024 * 1024;
11
12
  private long memoryCacheSize = -1; // -1 means use default
12
13
  private float memoryCacheScreens = 2.0f;
14
+ private String diskCacheName = null;
13
15
 
14
16
  private GlideConfiguration() {
15
17
  }
@@ -21,6 +23,19 @@ public class GlideConfiguration {
21
23
  return instance;
22
24
  }
23
25
 
26
+ /**
27
+ * Set custom disk cache name.
28
+ * Must be called before Glide is initialized (before first image load).
29
+ * @param name String
30
+ */
31
+ public void setDiskCacheName(String diskCacheName) {
32
+ this.diskCacheName = diskCacheName;
33
+ }
34
+
35
+ public String getDiskCacheName() {
36
+ return diskCacheName;
37
+ }
38
+
24
39
  /**
25
40
  * Set custom memory cache size in bytes.
26
41
  * Must be called before Glide is initialized (before first image load).
@@ -34,6 +49,19 @@ public class GlideConfiguration {
34
49
  return memoryCacheSize;
35
50
  }
36
51
 
52
+ /**
53
+ * Set custom disk cache size in bytes.
54
+ * Must be called before Glide is initialized (before first image load).
55
+ * @param bytes Size in bytes
56
+ */
57
+ public void setDiskCacheSize(long bytes) {
58
+ this.diskCacheSize = bytes;
59
+ }
60
+
61
+ public long getDiskCacheSize() {
62
+ return diskCacheSize;
63
+ }
64
+
37
65
  /**
38
66
  * Set memory cache screens multiplier (default 2.0).
39
67
  * Only used if memoryCacheSize is -1.
@@ -0,0 +1,321 @@
1
+ package com.bumptech.glide.load.engine.cache;
2
+
3
+ import android.util.Log;
4
+ import androidx.annotation.NonNull;
5
+ import androidx.annotation.Nullable;
6
+ import com.bumptech.glide.disklrucache.DiskLruCache;
7
+ import com.bumptech.glide.disklrucache.DiskLruCache.Value;
8
+ import com.bumptech.glide.load.Key;
9
+ import java.io.File;
10
+ import java.io.IOException;
11
+ import java.util.ArrayList;
12
+ import java.util.List;
13
+ import java.util.Map;
14
+ import java.util.concurrent.ConcurrentHashMap;
15
+
16
+ /**
17
+ * A disk cache wrapper that tracks cache keys for efficient querying and eviction based on
18
+ * model/signature pairs. Similar to ModelSignatureMemoryCache but for disk cache.
19
+ *
20
+ * Features:
21
+ * - Tracks all cached keys with their string representations
22
+ * - Supports querying existence by model and signature
23
+ * - Supports bulk eviction by model/signature or signature version
24
+ * - Thread-safe operations with minimal overhead
25
+ */
26
+ public class ModelSignatureDiskLruCacheWrapper implements DiskCache {
27
+ private static final String TAG = "JS";
28
+
29
+ private static final int APP_VERSION = 1;
30
+ private static final int VALUE_COUNT = 1;
31
+ private static ModelSignatureDiskLruCacheWrapper wrapper;
32
+
33
+ private final SafeKeyGenerator safeKeyGenerator;
34
+ private final File directory;
35
+ private final long maxSize;
36
+ private final DiskCacheWriteLocker writeLocker = new DiskCacheWriteLocker();
37
+ private DiskLruCache diskLruCache;
38
+
39
+ // Index of safeKey -> original key string for efficient lookup
40
+ // This allows us to query and evict based on model/signature without iterating the entire cache
41
+ private final Map<String, String> keyIndex = new ConcurrentHashMap<>();
42
+
43
+ /**
44
+ * Get a DiskCache in the given directory and size. If a disk cache has already been created with
45
+ * a different directory and/or size, it will be returned instead and the new arguments will be
46
+ * ignored.
47
+ *
48
+ * @param directory The directory for the disk cache
49
+ * @param maxSize The max size for the disk cache
50
+ * @return The new disk cache with the given arguments, or the current cache if one already exists
51
+ * @deprecated Use {@link #create(File, long)} to create a new cache with the specified arguments.
52
+ */
53
+ @SuppressWarnings("deprecation")
54
+ @Deprecated
55
+ public static synchronized DiskCache get(File directory, long maxSize) {
56
+ if (wrapper == null) {
57
+ wrapper = new ModelSignatureDiskLruCacheWrapper(directory, maxSize);
58
+ }
59
+ return wrapper;
60
+ }
61
+
62
+ /**
63
+ * Create a new DiskCache in the given directory with a specified max size.
64
+ *
65
+ * @param directory The directory for the disk cache
66
+ * @param maxSize The max size for the disk cache
67
+ * @return The new disk cache with the given arguments
68
+ */
69
+ @SuppressWarnings("deprecation")
70
+ public static ModelSignatureDiskLruCacheWrapper create(File directory, long maxSize) {
71
+ return new ModelSignatureDiskLruCacheWrapper(directory, maxSize);
72
+ }
73
+
74
+ /**
75
+ * @deprecated Do not extend this class.
76
+ */
77
+ @Deprecated
78
+ // Deprecated public API.
79
+ @SuppressWarnings({"WeakerAccess", "DeprecatedIsStillUsed"})
80
+ protected ModelSignatureDiskLruCacheWrapper(File directory, long maxSize) {
81
+ this.directory = directory;
82
+ this.maxSize = maxSize;
83
+ this.safeKeyGenerator = new SafeKeyGenerator();
84
+ }
85
+
86
+ private synchronized DiskLruCache getDiskCache() throws IOException {
87
+ if (diskLruCache == null) {
88
+ diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);
89
+ }
90
+ return diskLruCache;
91
+ }
92
+
93
+ @Override
94
+ public File get(Key key) {
95
+ String safeKey = safeKeyGenerator.getSafeKey(key);
96
+ File result = null;
97
+ try {
98
+ // It is possible that the there will be a put in between these two gets. If so that shouldn't
99
+ // be a problem because we will always put the same value at the same key so our input streams
100
+ // will still represent the same data.
101
+ final DiskLruCache.Value value = getDiskCache().get(safeKey);
102
+ // Log.v(TAG, "DiskCache Get: " + safeKey + " for Key: " + key + " found: " + value);
103
+ if (value != null) {
104
+ result = value.getFile(0);
105
+ }
106
+ } catch (IOException e) {
107
+ if (Log.isLoggable(TAG, Log.WARN)) {
108
+ Log.w(TAG, "Unable to get from disk cache", e);
109
+ }
110
+ }
111
+ return result;
112
+ }
113
+
114
+ @Override
115
+ public void put(Key key, Writer writer) {
116
+ // We want to make sure that puts block so that data is available when put completes. We may
117
+ // actually not write any data if we find that data is written by the time we acquire the lock.
118
+ String safeKey = safeKeyGenerator.getSafeKey(key);
119
+ writeLocker.acquire(safeKey);
120
+ try {
121
+ try {
122
+ // We assume we only need to put once, so if data was written while we were trying to get
123
+ // the lock, we can simply abort.
124
+ DiskLruCache diskCache = getDiskCache();
125
+ Value current = diskCache.get(safeKey);
126
+ if (current != null) {
127
+ return;
128
+ }
129
+
130
+ DiskLruCache.Editor editor = diskCache.edit(safeKey);
131
+ if (editor == null) {
132
+ throw new IllegalStateException("Had two simultaneous puts for: " + safeKey);
133
+ }
134
+ try {
135
+ File file = editor.getFile(0);
136
+ if (writer.write(file)) {
137
+ editor.commit();
138
+ // Track the key in our index after successful write
139
+ keyIndex.put(safeKey, key.toString());
140
+ // Log.w(TAG, "DiskCache Put: " + safeKey + " for Key: " + key + " size: " + keyIndex.size());
141
+ }
142
+ } finally {
143
+ editor.abortUnlessCommitted();
144
+ }
145
+ } catch (IOException e) {
146
+ if (Log.isLoggable(TAG, Log.WARN)) {
147
+ Log.w(TAG, "Unable to put to disk cache", e);
148
+ }
149
+ }
150
+ } finally {
151
+ writeLocker.release(safeKey);
152
+ }
153
+ }
154
+
155
+ @Override
156
+ public void delete(Key key) {
157
+ String safeKey = safeKeyGenerator.getSafeKey(key);
158
+ try {
159
+ getDiskCache().remove(safeKey);
160
+ // Remove from index
161
+ keyIndex.remove(safeKey);
162
+ // Log.w(TAG, "DiskCache deleted: " + safeKey + " size: " + keyIndex.size());
163
+ } catch (IOException e) {
164
+ if (Log.isLoggable(TAG, Log.WARN)) {
165
+ Log.w(TAG, "Unable to delete from disk cache", e);
166
+ }
167
+ }
168
+ }
169
+
170
+ @Override
171
+ public synchronized void clear() {
172
+ try {
173
+ getDiskCache().delete();
174
+ // Clear the index
175
+ keyIndex.clear();
176
+ } catch (IOException e) {
177
+ Log.w(TAG, "Unable to clear disk cache or disk cache cleared externally", e);
178
+ } finally {
179
+ // Delete can close the cache but still throw. If we don't null out the disk cache here, every
180
+ // subsequent request will try to act on a closed disk cache and fail. By nulling out the disk
181
+ // cache we at least allow for attempts to open the cache in the future. See #2465.
182
+ resetDiskCache();
183
+ }
184
+ }
185
+
186
+ private synchronized void resetDiskCache() {
187
+ diskLruCache = null;
188
+ }
189
+
190
+ /**
191
+ * Check if an entry exists in the cache by model and signature.
192
+ * This is more efficient than trying to get the actual file.
193
+ *
194
+ * @param model The model (e.g., URI)
195
+ * @param signature The signature Key
196
+ * @return true if at least one matching entry exists
197
+ */
198
+ public boolean containsByModelAndSignature(@NonNull Object model, @NonNull Key signature) {
199
+ final String modelStr = model.toString();
200
+ final String signatureStr = signature.toString();
201
+
202
+ for (String keyStr : keyIndex.values()) {
203
+ if (matchesModelAndSignature(keyStr, modelStr, signatureStr)) {
204
+ return true;
205
+ }
206
+ }
207
+ return false;
208
+ }
209
+
210
+ /**
211
+ * Remove all entries matching the given model and signature.
212
+ * This removes both raw (source) and transformed (resource) cache entries.
213
+ *
214
+ * @param model The model to match
215
+ * @param signature The signature to match
216
+ * @return Number of entries removed
217
+ */
218
+ public int removeByModelAndSignature(@NonNull Object model, @NonNull Key signature) {
219
+ final String modelStr = model.toString();
220
+ final String signatureStr = signature.toString();
221
+
222
+ // Log.w(TAG, "DiskCache removeByModelAndSignature: model=" + modelStr + " signature=" + signatureStr + " cacheCount=" + keyIndex.size());
223
+
224
+ List<String> safeKeysToRemove = new ArrayList<>();
225
+
226
+ // Find all matching keys
227
+ for (Map.Entry<String, String> entry : keyIndex.entrySet()) {
228
+ String keyStr = entry.getValue();
229
+ if (matchesModelAndSignature(keyStr, modelStr, signatureStr)) {
230
+ safeKeysToRemove.add(entry.getKey());
231
+ }
232
+ }
233
+
234
+ // Remove them from disk cache
235
+ int removed = 0;
236
+ for (String safeKey : safeKeysToRemove) {
237
+ try {
238
+ getDiskCache().remove(safeKey);
239
+ keyIndex.remove(safeKey);
240
+ removed++;
241
+ // Log.v(TAG, "DiskCache removed: " + safeKey);
242
+ } catch (IOException e) {
243
+ Log.w(TAG, "Failed to remove disk cache entry: " + safeKey, e);
244
+ }
245
+ }
246
+
247
+ // Log.w(TAG, "DiskCache removed " + removed + " entries for model/signature");
248
+ return removed;
249
+ }
250
+
251
+ /**
252
+ * Remove all entries that do NOT match the given signature.
253
+ * Useful for cache invalidation when changing signature versions (e.g., v1 -> v2).
254
+ *
255
+ * @param currentSignature The current/valid signature to keep
256
+ * @return Number of entries removed
257
+ */
258
+ public int removeAllExceptSignature(@NonNull Key currentSignature) {
259
+ final String signatureStr = currentSignature.toString();
260
+
261
+ List<String> safeKeysToRemove = new ArrayList<>();
262
+
263
+ // Find all keys that don't match the current signature
264
+ for (Map.Entry<String, String> entry : keyIndex.entrySet()) {
265
+ String keyStr = entry.getValue();
266
+ if (!keyStr.contains(signatureStr) && !keyStr.contains("signature=" + signatureStr)) {
267
+ safeKeysToRemove.add(entry.getKey());
268
+ }
269
+ }
270
+
271
+ // Remove them
272
+ int removed = 0;
273
+ for (String safeKey : safeKeysToRemove) {
274
+ try {
275
+ getDiskCache().remove(safeKey);
276
+ keyIndex.remove(safeKey);
277
+ removed++;
278
+ } catch (IOException e) {
279
+ Log.w(TAG, "Failed to remove disk cache entry: " + safeKey, e);
280
+ }
281
+ }
282
+
283
+ // Log.i(TAG, "DiskCache removed " + removed + " entries with different signatures");
284
+ return removed;
285
+ }
286
+
287
+ /**
288
+ * Get statistics about the cache index.
289
+ *
290
+ * @return Array: [indexSize, diskCacheSize]
291
+ */
292
+ public long[] getStats() {
293
+ long indexSize = keyIndex.size();
294
+ long diskSize = 0;
295
+ try {
296
+ diskSize = getDiskCache().size();
297
+ } catch (IOException e) {
298
+ Log.w(TAG, "Failed to get disk cache size", e);
299
+ }
300
+ return new long[] { indexSize, diskSize };
301
+ }
302
+
303
+ /**
304
+ * Helper method to check if a key string matches a model and signature.
305
+ * Uses multiple matching strategies for robustness.
306
+ */
307
+ private boolean matchesModelAndSignature(String keyStr, String modelStr, String signatureStr) {
308
+ // Strategy 1: Exact pattern matching for known key formats (e.g., DataCacheKey, ResourceCacheKey)
309
+ if (keyStr.contains("model=" + modelStr) && keyStr.contains("signature=" + signatureStr)) {
310
+ return true;
311
+ }
312
+
313
+ // Strategy 2: Substring matching (fallback for other key formats)
314
+ // Only match if both model and signature are present
315
+ if (keyStr.contains(modelStr) && keyStr.contains(signatureStr)) {
316
+ return true;
317
+ }
318
+
319
+ return false;
320
+ }
321
+ }
@@ -0,0 +1,155 @@
1
+ package com.nativescript.image;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import androidx.annotation.Nullable;
5
+
6
+ import com.bumptech.glide.load.Key;
7
+ import com.bumptech.glide.load.engine.Resource;
8
+ import com.bumptech.glide.load.engine.cache.LruResourceCache;
9
+
10
+ import java.util.ArrayList;
11
+ import java.util.List;
12
+ import java.util.Map;
13
+ import java.util.concurrent.ConcurrentHashMap;
14
+ import java.util.Arrays;
15
+
16
+ import android.util.Log;
17
+
18
+ /**
19
+ * An LRU memory cache that keeps a small index of the string form of each Key so callers can
20
+ * evict all entries matching a model + signature pair.
21
+ */
22
+ public final class ModelSignatureMemoryCache extends LruResourceCache {
23
+ private static final String TAG = "JS";
24
+
25
+ // Map of the Key -> cached key string (avoid calling toString() repeatedly and to have a quick
26
+ // snapshot of what's in cache). Entries are added on put and removed on remove / eviction.
27
+ private final Map<Key, String> keyStrings = new ConcurrentHashMap<>();
28
+
29
+ public ModelSignatureMemoryCache(long maxSizeBytes) {
30
+ super(maxSizeBytes);
31
+ }
32
+ @Override
33
+ public void trimMemory(int level) {
34
+ super.trimMemory(level);
35
+ }
36
+
37
+ @Nullable
38
+ @Override
39
+ public Resource<?> put(@NonNull Key key, @Nullable Resource<?> resource) {
40
+ // Save string representation for indexing before delegating.
41
+ keyStrings.put(key, key.toString());
42
+ // Log.w(TAG, "MemoryCache put " + key.toString() + " " + keyStrings.size());
43
+ return super.put(key, resource);
44
+ }
45
+
46
+ @Nullable
47
+ @Override
48
+ public Resource<?> remove(@NonNull Key key) {
49
+ // Log.w(TAG, "MemoryCache remove " + key + " " + keyStrings.size());
50
+ keyStrings.remove(key);
51
+ return super.remove(key);
52
+ }
53
+
54
+ @Override
55
+ protected void onItemEvicted(@NonNull Key key, @Nullable Resource<?> item) {
56
+ // Called by the LRU when an item is evicted; keep our index in sync.
57
+ keyStrings.remove(key);
58
+ super.onItemEvicted(key, item);
59
+ }
60
+
61
+ /**
62
+ * Remove all entries whose underlying Key looks like it was created for the given model and
63
+ * signature. Both checks are substring based against Key.toString().
64
+ *
65
+ * @param model The model (for example the URI or whatever you passed to .load(...)).
66
+ * The code uses model.toString() to match keys.
67
+ * @param signature The request signature (a Key, e.g. new ObjectKey(...)) as used in the
68
+ * original request. Uses signature.toString() to match keys.
69
+ * @return number of removed entries
70
+ */
71
+ public int removeByModelAndSignature(@NonNull Object model, @NonNull Key signature) {
72
+ final String modelStr = model.toString();
73
+ final String modelCleanStr = (modelStr.startsWith("ObjectKey{object=") && modelStr.endsWith("}")) ? modelStr.substring(17, modelStr.length() - 1) : null;
74
+ final String signatureStr = signature.toString();
75
+
76
+ List<Key> toRemove = new ArrayList<>();
77
+ for (Map.Entry<Key, String> e : keyStrings.entrySet()) {
78
+ String s = e.getValue();
79
+ // There are two checks: a direct "model=" / "signature=" pattern check (matches EngineKey.toString
80
+ // format currently used), and a fallback that simply checks both substrings exist. The first
81
+ // reduces false positives.
82
+ if (((s.contains("model=" + modelStr) || (modelCleanStr != null && s.contains("model=" + modelCleanStr)))&& s.contains("signature=" + signatureStr))) {
83
+ toRemove.add(e.getKey());
84
+ }
85
+ }
86
+ // Log.w(TAG, "ModelSignatureMemoryCache removeByModelAndSignature will remove " + toRemove.size() + " items from " + keyStrings.size() );
87
+
88
+ for (Key k : toRemove) {
89
+ remove(k);
90
+ }
91
+ return toRemove.size();
92
+ }
93
+
94
+ /**
95
+ * Convenience method if you only have a model string (e.g. a URI) and a signature string.
96
+ *
97
+ * This is less type-safe (signatures should be Key), but may be helpful in some cases.
98
+ */
99
+ public int removeByModelAndSignatureStrings(@NonNull String modelStr, @NonNull String signatureStr) {
100
+ List<Key> toRemove = new ArrayList<>();
101
+ final String modelCleanStr = (modelStr.startsWith("ObjectKey{object=") && modelStr.endsWith("}")) ? modelStr.substring(17, modelStr.length() - 1) : null;
102
+ for (Map.Entry<Key, String> e : keyStrings.entrySet()) {
103
+ String s = e.getValue();
104
+ if (((s.contains("model=" + modelStr) || (modelCleanStr != null && s.contains("model=" + modelCleanStr)))&& s.contains("signature=" + signatureStr))) {
105
+ toRemove.add(e.getKey());
106
+ }
107
+ }
108
+ for (Key k : toRemove) {
109
+ remove(k);
110
+ }
111
+ return toRemove.size();
112
+ }
113
+
114
+ /**
115
+ * Test whether there is at least one entry in the memory cache that matches the given
116
+ * model and signature.
117
+ *
118
+ * Matching is performed by checking the cached Key.toString() for substrings derived from the
119
+ * provided model and signature (same heuristic used by removeByModelAndSignature).
120
+ *
121
+ * @param model The model object used in the original request (e.g. the URI or model instance).
122
+ * @param signature The signature Key used in the original request.
123
+ * @return true if at least one matching entry exists in memory cache.
124
+ */
125
+ public boolean containsByModelAndSignature(@NonNull Object model, @NonNull Key signature) {
126
+ final String modelStr = model.toString();
127
+ final String modelCleanStr = (modelStr.startsWith("ObjectKey{object=") && modelStr.endsWith("}")) ? modelStr.substring(17, modelStr.length() - 1) : null;
128
+ final String signatureStr = signature.toString();
129
+ // Log.i(TAG, "ModelSignatureMemoryCache containsByModelAndSignature " + modelStr + " " + signatureStr + " " + keyStrings.size());
130
+
131
+ for (String s : keyStrings.values()) {
132
+ if (((s.contains("model=" + modelStr) || (modelCleanStr != null && s.contains("model=" + modelCleanStr)))&& s.contains("signature=" + signatureStr))) {
133
+ return true;
134
+ }
135
+ }
136
+ return false;
137
+ }
138
+
139
+ /**
140
+ * Convenience version that accepts precomputed strings for model and signature.
141
+ *
142
+ * @param modelString model.toString() or similar representation.
143
+ * @param signatureString signature.toString() or similar representation.
144
+ * @return true if at least one matching entry exists in memory cache.
145
+ */
146
+ public boolean containsByModelAndSignatureStrings(@NonNull String modelStr, @NonNull String signatureStr) {
147
+ final String modelCleanStr = (modelStr.startsWith("ObjectKey{object=") && modelStr.endsWith("}")) ? modelStr.substring(17, modelStr.length() - 1) : null;
148
+ for (String s : keyStrings.values()) {
149
+ if (((s.contains("model=" + modelStr) || (modelCleanStr != null && s.contains("model=" + modelCleanStr)))&& s.contains("signature=" + signatureStr))) {
150
+ return true;
151
+ }
152
+ }
153
+ return false;
154
+ }
155
+ }