From 6f078acb8d65374eef18f6d18d46c40c8a2465d3 Mon Sep 17 00:00:00 2001 From: Vitalii Date: Thu, 22 Jun 2023 16:31:30 +0300 Subject: [PATCH] web/satellite: add gallery view caching Add caching for gallery view previews and map. Issue: https://github.com/storj/storj/issues/5969 Change-Id: I6c9755aec6e1d4143005835adad212cafd46f649 --- .../browser/galleryView/GalleryView.vue | 55 ++++++++++++++++++- .../src/store/modules/objectBrowserStore.ts | 27 +++++++-- 2 files changed, 74 insertions(+), 8 deletions(-) diff --git a/web/satellite/src/components/browser/galleryView/GalleryView.vue b/web/satellite/src/components/browser/galleryView/GalleryView.vue index 5181febe3..e6291ecfe 100644 --- a/web/satellite/src/components/browser/galleryView/GalleryView.vue +++ b/web/satellite/src/components/browser/galleryView/GalleryView.vue @@ -116,10 +116,11 @@ import { Component, computed, onBeforeMount, onMounted, ref, Teleport, watch } f import { useRoute } from 'vue-router'; import prettyBytes from 'pretty-bytes'; -import { BrowserObject, useObjectBrowserStore } from '@/store/modules/objectBrowserStore'; +import { BrowserObject, PreviewCache, useObjectBrowserStore } from '@/store/modules/objectBrowserStore'; import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames'; import { useAppStore } from '@/store/modules/appStore'; import { useNotify } from '@/utils/hooks'; +import { useBucketsStore } from '@/store/modules/bucketsStore'; import { RouteConfig } from '@/types/router'; import ButtonIcon from '@/components/browser/galleryView/ButtonIcon.vue'; @@ -145,6 +146,7 @@ import ArrowIcon from '@/../static/images/browser/galleryView/arrow.svg'; const appStore = useAppStore(); const obStore = useObjectBrowserStore(); +const bucketsStore = useBucketsStore(); const notify = useNotify(); const route = useRoute(); @@ -159,6 +161,13 @@ const objectPreviewUrl = ref(''); const folderType = 'folder'; +/** + * Returns object preview URLs cache from store. + */ +const cachedObjectPreviewURLs = computed((): Map => { + return obStore.state.cachedObjectPreviewURLs; +}); + /** * Retrieve the file object that the modal is set to from the store. */ @@ -194,6 +203,13 @@ const extension = computed((): string | undefined => { return filePath.value.split('.').pop(); }); +/** + * Returns bucket name from store. + */ +const bucket = computed((): string => { + return bucketsStore.state.fileComponentBucketName; +}); + /** * Check to see if the current file is an image file. */ @@ -263,6 +279,9 @@ async function fetchPreviewAndMapUrl(): Promise { return; } + const encodedPath = encodeURIComponent(`${bucket.value}/${filePath.value.trim()}`); + obStore.cacheObjectPreviewURL(encodedPath, { url, lastModified: file.value.LastModified.getTime() }); + objectMapUrl.value = `${url}?map=1`; objectPreviewUrl.value = `${url}?view=1`; isLoading.value = false; @@ -386,11 +405,41 @@ function setNewObjectPath(objectKey: string): void { obStore.setObjectPathForModal(`${currentPath.value}${objectKey}`); } +/** + * Loads object URL from cache or generates new URL. + */ +function processFilePath(): void { + const url = findCachedURL(); + if (!url) { + fetchPreviewAndMapUrl(); + return; + } + + objectMapUrl.value = `${url}?map=1`; + objectPreviewUrl.value = `${url}?view=1`; +} + +/** + * Try to find current object path in cache. + */ +function findCachedURL(): string | undefined { + const encodedPath = encodeURIComponent(`${bucket.value}/${filePath.value.trim()}`); + const cache = cachedObjectPreviewURLs.value.get(encodedPath); + + if (!cache) return undefined; + if (cache.lastModified !== file.value.LastModified.getTime()) { + obStore.removeFromObjectPreviewCache(encodedPath); + return undefined; + } + + return cache.url; +} + /** * Call `fetchPreviewAndMapUrl` on before mount lifecycle method. */ onBeforeMount((): void => { - fetchPreviewAndMapUrl(); + processFilePath(); }); onMounted((): void => { @@ -403,7 +452,7 @@ onMounted((): void => { watch(filePath, () => { if (!filePath.value) return; - fetchPreviewAndMapUrl(); + processFilePath(); }); diff --git a/web/satellite/src/store/modules/objectBrowserStore.ts b/web/satellite/src/store/modules/objectBrowserStore.ts index 20bd700b1..2300552be 100644 --- a/web/satellite/src/store/modules/objectBrowserStore.ts +++ b/web/satellite/src/store/modules/objectBrowserStore.ts @@ -31,7 +31,7 @@ type Promisable = T | PromiseLike; export type BrowserObject = { Key: string; Size: number; - LastModified: number; + LastModified: Date; type?: 'file' | 'folder'; progress?: number; upload?: { @@ -58,6 +58,11 @@ export type UploadingBrowserObject = BrowserObject & { failedMessage?: FailedUploadMessage; } +export type PreviewCache = { + url: string, + lastModified: number, +} + export class FilesState { s3: S3Client | null = null; accessKey: null | string = null; @@ -80,6 +85,7 @@ export class FilesState { openModalOnFirstUpload = false; objectPathForModal = ''; objectsCount = 0; + cachedObjectPreviewURLs: Map = new Map(); } type InitializedFilesState = FilesState & { @@ -273,7 +279,7 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => { Prefix: string; }): BrowserObject => ({ Key: Prefix.slice(path.length, -1), - LastModified: 0, + LastModified: new Date(), Size: 0, type: 'folder', }); @@ -455,7 +461,7 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => { upload, progress: 0, Size: 0, - LastModified: 0, + LastModified: new Date(), Body: file, status: UploadingStatus.Failed, failedMessage: FailedUploadMessage.TooBig, @@ -471,7 +477,7 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => { upload, progress: 0, Size: 0, - LastModified: 0, + LastModified: new Date(), status: UploadingStatus.InProgress, }); @@ -552,7 +558,7 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => { upload, progress: 0, Size: 0, - LastModified: 0, + LastModified: new Date(), status: UploadingStatus.InProgress, }; @@ -770,6 +776,14 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => { state.objectPathForModal = path; } + function cacheObjectPreviewURL(path: string, cacheValue: PreviewCache): void { + state.cachedObjectPreviewURLs.set(path, cacheValue); + } + + function removeFromObjectPreviewCache(path: string): void { + state.cachedObjectPreviewURLs.delete(path); + } + function setSelectedAnchorFile(file: BrowserObject | null): void { state.selectedAnchorFile = file; } @@ -803,6 +817,7 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => { state.orderBy = 'asc'; state.openModalOnFirstUpload = false; state.objectPathForModal = ''; + state.cachedObjectPreviewURLs = new Map(); } return { @@ -835,6 +850,8 @@ export const useObjectBrowserStore = defineStore('objectBrowser', () => { setSelectedAnchorFile, setUnselectedAnchorFile, cancelUpload, + cacheObjectPreviewURL, + removeFromObjectPreviewCache, clearUploading, clear, };