From 94e84a6335cf984f8acfac73bd7424a8fe99b986 Mon Sep 17 00:00:00 2001 From: David Minor Date: Fri, 23 Aug 2019 10:58:28 -0700 Subject: [PATCH 1/2] Add an optional cache key Add an option to use a cache key that is different from the image URI. This is useful for example if the image URI contains a parameter that changes, but the image doesn't vary as a result, such as an authentication token. If the cache key is not present, the image URI will be used as the cache key. --- src/CacheManager.ts | 29 ++++++++++++++++++----------- src/Image.tsx | 5 +++-- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/CacheManager.ts b/src/CacheManager.ts index 8ca7cb4..e83037e 100644 --- a/src/CacheManager.ts +++ b/src/CacheManager.ts @@ -15,14 +15,17 @@ export class CacheEntry { options: DownloadOptions; - constructor(uri: string, options: DownloadOptions) { + cacheKey: string; + + constructor(uri: string, options: DownloadOptions, cacheKey: string) { this.uri = uri; this.options = options; + this.cacheKey = cacheKey; } async getPath(): Promise { - const { uri, options } = this; - const { path, exists, tmpPath } = await getCacheEntry(uri); + const { uri, options, cacheKey } = this; + const { path, exists, tmpPath } = await getCacheEntry(cacheKey); if (exists) { return path; } @@ -39,11 +42,11 @@ export class CacheEntry { export default class CacheManager { static entries: { [uri: string]: CacheEntry } = {}; - static get(uri: string, options: DownloadOptions): CacheEntry { - if (!CacheManager.entries[uri]) { - CacheManager.entries[uri] = new CacheEntry(uri, options); + static get(uri: string, options: DownloadOptions, cacheKey: string): CacheEntry { + if (!CacheManager.entries[cacheKey]) { + CacheManager.entries[cacheKey] = new CacheEntry(uri, options, cacheKey); } - return CacheManager.entries[uri]; + return CacheManager.entries[cacheKey]; } static async clearCache(): Promise { @@ -60,11 +63,15 @@ export default class CacheManager { } } -const getCacheEntry = async (uri: string): Promise<{ exists: boolean; path: string; tmpPath: string }> => { - const filename = uri.substring(uri.lastIndexOf("/"), uri.indexOf("?") === -1 ? uri.length : uri.indexOf("?")); +const getCacheEntry = async (cacheKey: string): Promise<{ exists: boolean; path: string; tmpPath: string }> => { + const filename = cacheKey.substring( + cacheKey.lastIndexOf("/"), + cacheKey.indexOf("?") === -1 ? cacheKey.length : cacheKey.indexOf("?") + ); const ext = filename.indexOf(".") === -1 ? ".jpg" : filename.substring(filename.lastIndexOf(".")); - const path = `${BASE_DIR}${SHA1(uri)}${ext}`; - const tmpPath = `${BASE_DIR}${SHA1(uri)}-${_.uniqueId()}${ext}`; + const sha = SHA1(cacheKey); + const path = `${BASE_DIR}${sha}${ext}`; + const tmpPath = `${BASE_DIR}${sha}-${_.uniqueId()}${ext}`; // TODO: maybe we don't have to do this every time try { await FileSystem.makeDirectoryAsync(BASE_DIR); diff --git a/src/Image.tsx b/src/Image.tsx index 3c7afe0..cd5189d 100644 --- a/src/Image.tsx +++ b/src/Image.tsx @@ -22,6 +22,7 @@ interface ImageProps { preview?: ImageSourcePropType; options?: DownloadOptions; uri: string; + cacheKey?: string; transitionDuration?: number; tint?: "dark" | "light"; } @@ -66,9 +67,9 @@ export default class Image extends React.Component { this.mounted = false; } - async load({ uri, options = {} }: ImageProps): Promise { + async load({ uri, options = {}, cacheKey }: ImageProps): Promise { if (uri) { - const path = await CacheManager.get(uri, options).getPath(); + const path = await CacheManager.get(uri, options, cacheKey || uri).getPath(); if (this.mounted) { this.setState({ uri: path }); } From 969ac0662975f98ad8e55f65b7dd75b3da455bf8 Mon Sep 17 00:00:00 2001 From: David Minor Date: Fri, 23 Aug 2019 13:31:03 -0700 Subject: [PATCH 2/2] Avoid repeat downloads Create a single promise to handle the downloading of a cache entry. Otherwise, multiple calls to get the path of a new cache entry will result in multiple downloads, until one of the downloads is successful. If the download is unsuccesful, unset the promise so that the next request will result in a new attempt to download the image. --- src/CacheManager.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/CacheManager.ts b/src/CacheManager.ts index e83037e..5d1e08c 100644 --- a/src/CacheManager.ts +++ b/src/CacheManager.ts @@ -17,6 +17,8 @@ export class CacheEntry { cacheKey: string; + downloadPromise: Promise | undefined; + constructor(uri: string, options: DownloadOptions, cacheKey: string) { this.uri = uri; this.options = options; @@ -24,14 +26,23 @@ export class CacheEntry { } async getPath(): Promise { - const { uri, options, cacheKey } = this; + const { cacheKey } = this; const { path, exists, tmpPath } = await getCacheEntry(cacheKey); if (exists) { return path; } + if (!this.downloadPromise) { + this.downloadPromise = this.download(path, tmpPath); + } + return this.downloadPromise; + } + + private async download(path: string, tmpPath: string): Promise { + const { uri, options } = this; const result = await FileSystem.createDownloadResumable(uri, tmpPath, options).downloadAsync(); // If the image download failed, we don't cache anything if (result && result.status !== 200) { + this.downloadPromise = undefined; return undefined; } await FileSystem.moveAsync({ from: tmpPath, to: path });