diff --git a/packages/web-client/src/sse.ts b/packages/web-client/src/sse.ts index f64a0f3b786..8142dd23f8d 100644 --- a/packages/web-client/src/sse.ts +++ b/packages/web-client/src/sse.ts @@ -2,7 +2,8 @@ import { fetchEventSource, FetchEventSourceInit } from '@microsoft/fetch-event-s export enum MESSAGE_TYPE { NOTIFICATION = 'userlog-notification', - POSTPROCESSING_FINISHED = 'postprocessing-finished' + POSTPROCESSING_FINISHED = 'postprocessing-finished', + ITEM_RENAMED = 'item-renamed' } export class RetriableError extends Error { diff --git a/packages/web-runtime/src/container/bootstrap.ts b/packages/web-runtime/src/container/bootstrap.ts index 9f0490ff938..857c2b34205 100644 --- a/packages/web-runtime/src/container/bootstrap.ts +++ b/packages/web-runtime/src/container/bootstrap.ts @@ -27,8 +27,7 @@ import { useSharesStore, useResourcesStore, ResourcesStore, - SpacesStore, - ImageDimension + SpacesStore } from '@ownclouders/web-pkg' import { authService } from '../services/auth' import { @@ -49,15 +48,14 @@ import { } from '@ownclouders/web-pkg' import { MESSAGE_TYPE } from '@ownclouders/web-client/src/sse' import { getQueryParam } from '../helpers/url' -import { z } from 'zod' import PQueue from 'p-queue' -import { extractNodeId, extractStorageId } from '@ownclouders/web-client/src/helpers' import { storeToRefs } from 'pinia' import { getExtensionNavItems } from '../helpers/navItems' import { RawConfigSchema, SentryConfig } from '@ownclouders/web-pkg/src/composables/piniaStores/config/types' +import { onSSEItemRenamedEvent, onSSEProcessingFinishedEvent } from './sse' const getEmbedConfigFromQuery = ( doesEmbedEnabledOptionExists: boolean @@ -641,100 +639,20 @@ export const announceCustomStyles = ({ configStore }: { configStore?: ConfigStor }) } -const fileReadyEventSchema = z.object({ - itemid: z.string(), - parentitemid: z.string() -}) - -const onSSEProcessingFinishedEvent = async ({ - resourcesStore, - spacesStore, - msg, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - clientService, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - resourceQueue, - previewService -}: { - resourcesStore: ResourcesStore - spacesStore: SpacesStore - msg: MessageEvent - clientService: ClientService - resourceQueue: PQueue - previewService: PreviewService -}) => { - try { - const postProcessingData = fileReadyEventSchema.parse(JSON.parse(msg.data)) - const currentFolder = resourcesStore.currentFolder - if (!currentFolder) { - return - } - - // UPDATE_RESOURCE_FIELD only handles files in the currentFolder, so we can shortcut here for now - if (!extractNodeId(currentFolder.id)) { - // if we don't have a nodeId here, we have a space (root) as current folder and can only check against the storageId - if (currentFolder.id !== extractStorageId(postProcessingData.parentitemid)) { - return - } - } else { - if (currentFolder.id !== postProcessingData.parentitemid) { - return - } - } - - const resource = resourcesStore.resources.find((f) => f.id === postProcessingData.itemid) - const matchingSpace = spacesStore.spaces.find((s) => s.id === resource.storageId) - const isFileLoaded = !!resource - if (isFileLoaded) { - resourcesStore.updateResourceField({ - id: postProcessingData.itemid, - field: 'processing', - value: false - }) - - if (matchingSpace) { - const preview = await previewService.loadPreview({ - resource, - space: matchingSpace, - dimensions: ImageDimension.Thumbnail - }) - - if (preview) { - resourcesStore.updateResourceField({ - id: postProcessingData.itemid, - field: 'thumbnail', - value: preview - }) - } - } - } else { - // FIXME: we currently cannot do this, we need to block this for ongoing uploads and copy operations - // when fixing revert the changelog removal - // resourceQueue.add(async () => { - // const { resource } = await clientService.webdav.listFilesById({ - // fileId: postProcessingData.itemid - // }) - // resource.path = urlJoin(currentFolder.path, resource.name) - // resourcesStore.upsertResource(resource) - // }) - } - } catch (e) { - console.error('Unable to parse sse postprocessing data', e) - } -} - export const registerSSEEventListeners = ({ resourcesStore, spacesStore, clientService, previewService, - configStore + configStore, + router }: { resourcesStore: ResourcesStore spacesStore: SpacesStore clientService: ClientService previewService: PreviewService configStore: ConfigStore + router: Router }): void => { const resourceQueue = new PQueue({ concurrency: configStore.options.concurrentRequests.sse @@ -747,6 +665,16 @@ export const registerSSEEventListeners = ({ } ) + clientService.sseAuthenticated.addEventListener(MESSAGE_TYPE.ITEM_RENAMED, (msg) => + onSSEItemRenamedEvent({ + resourcesStore, + spacesStore, + msg, + clientService, + router + }) + ) + clientService.sseAuthenticated.addEventListener(MESSAGE_TYPE.POSTPROCESSING_FINISHED, (msg) => onSSEProcessingFinishedEvent({ resourcesStore, diff --git a/packages/web-runtime/src/container/sse.ts b/packages/web-runtime/src/container/sse.ts new file mode 100644 index 00000000000..cd324d4389c --- /dev/null +++ b/packages/web-runtime/src/container/sse.ts @@ -0,0 +1,176 @@ +import { + ClientService, + createFileRouteOptions, + ImageDimension, + PreviewService, + ResourcesStore, + SpacesStore +} from '@ownclouders/web-pkg' +import PQueue from 'p-queue' +import { extractNodeId, extractStorageId } from '@ownclouders/web-client/src/helpers' +import { z } from 'zod' +import { Router } from 'vue-router' + +const fileReadyEventSchema = z.object({ + itemid: z.string(), + parentitemid: z.string() +}) + +type SSEMessageData = { + itemid?: string + parentitemid?: string +} + +const itemInCurrentFolder = ({ + resourcesStore, + sseData +}: { + resourcesStore: ResourcesStore + sseData: SSEMessageData +}) => { + const currentFolder = resourcesStore.currentFolder + if (!currentFolder) { + return false + } + + if (!extractNodeId(currentFolder.id)) { + // if we don't have a nodeId here, we have a space (root) as current folder and can only check against the storageId + if (currentFolder.id !== extractStorageId(sseData.parentitemid)) { + return false + } + } else { + if (currentFolder.id !== sseData.parentitemid) { + return false + } + } + + return true +} + +export const onSSEItemRenamedEvent = async ({ + resourcesStore, + spacesStore, + msg, + clientService, + router +}: { + resourcesStore: ResourcesStore + spacesStore: SpacesStore + msg: MessageEvent + clientService: ClientService + router: Router +}) => { + try { + const sseData = fileReadyEventSchema.parse(JSON.parse(msg.data)) + + const currentFolder = resourcesStore.currentFolder + const resourceIsCurrentFolder = currentFolder.id === sseData.itemid + + if (!resourceIsCurrentFolder && !itemInCurrentFolder({ resourcesStore, sseData })) { + return false + } + + const resource = resourceIsCurrentFolder + ? currentFolder + : resourcesStore.resources.find((f) => f.id === sseData.itemid) + + const space = spacesStore.spaces.find((s) => s.id === resource.storageId) + + if (!resource || !space) { + return + } + + const updatedResource = await clientService.webdav.getFileInfo(space, { + fileId: sseData.itemid + }) + + if (resourceIsCurrentFolder) { + resourcesStore.setCurrentFolder(updatedResource) + return router.push( + createFileRouteOptions(space, { + path: updatedResource.path, + fileId: updatedResource.fileId + }) + ) + } + + resourcesStore.updateResourceField({ + id: sseData.itemid, + field: 'name', + value: updatedResource.name + }) + + resourcesStore.updateResourceField({ + id: sseData.itemid, + field: 'path', + value: updatedResource.path + }) + } catch (e) { + console.error('Unable to parse sse event item renamed data', e) + } +} +export const onSSEProcessingFinishedEvent = async ({ + resourcesStore, + spacesStore, + msg, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + clientService, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + resourceQueue, + previewService +}: { + resourcesStore: ResourcesStore + spacesStore: SpacesStore + msg: MessageEvent + clientService: ClientService + resourceQueue: PQueue + previewService: PreviewService +}) => { + try { + const sseData = fileReadyEventSchema.parse(JSON.parse(msg.data)) + + if (!itemInCurrentFolder({ resourcesStore, sseData })) { + return false + } + + const resource = resourcesStore.resources.find((f) => f.id === sseData.itemid) + const space = spacesStore.spaces.find((s) => s.id === resource.storageId) + const isFileLoaded = !!resource + + if (isFileLoaded) { + resourcesStore.updateResourceField({ + id: sseData.itemid, + field: 'processing', + value: false + }) + + if (space) { + const preview = await previewService.loadPreview({ + resource, + space, + dimensions: ImageDimension.Thumbnail + }) + + if (preview) { + resourcesStore.updateResourceField({ + id: sseData.itemid, + field: 'thumbnail', + value: preview + }) + } + } + } else { + // FIXME: we currently cannot do this, we need to block this for ongoing uploads and copy operations + // when fixing revert the changelog removal + // resourceQueue.add(async () => { + // const { resource } = await clientService.webdav.listFilesById({ + // fileId: sseData.itemid + // }) + // resource.path = urlJoin(currentFolder.path, resource.name) + // resourcesStore.upsertResource(resource) + // }) + } + } catch (e) { + console.error('Unable to parse sse event postprocessing-finished data', e) + } +} diff --git a/packages/web-runtime/src/index.ts b/packages/web-runtime/src/index.ts index 040fbe34e37..68b3bfa2858 100644 --- a/packages/web-runtime/src/index.ts +++ b/packages/web-runtime/src/index.ts @@ -196,7 +196,8 @@ export const bootstrapApp = async (configurationPath: string): Promise => spacesStore, clientService, previewService, - configStore + configStore, + router }) }