From 30dce2ecc8ed86d2607d5fc5aded973a60e67a4d Mon Sep 17 00:00:00 2001 From: Juergen Kellerer Date: Mon, 24 Oct 2022 19:22:20 +0200 Subject: [PATCH] Fix 'source' URL in viewer Also ensure 'filename' is consistent and includes app and user Signed-off-by: Juergen Kellerer --- .../Collection/CollectionContent.vue | 5 +- src/components/FileLegacy.vue | 5 +- src/mixins/FetchFacesMixin.js | 7 +- src/services/AlbumContent.js | 5 +- src/services/FileInfo.js | 8 +- src/services/FolderInfo.js | 8 +- src/services/PhotoSearch.js | 3 +- src/services/TaggedImages.js | 2 - src/store/files.js | 7 +- src/utils/fileUtils.js | 90 ++++++++++++++++++- src/views/FaceContent.vue | 8 +- src/views/Folders.vue | 7 +- src/views/TagContent.vue | 5 +- src/views/Timeline.vue | 5 +- 14 files changed, 129 insertions(+), 36 deletions(-) diff --git a/src/components/Collection/CollectionContent.vue b/src/components/Collection/CollectionContent.vue index 681255d3b..ed6d615ed 100644 --- a/src/components/Collection/CollectionContent.vue +++ b/src/components/Collection/CollectionContent.vue @@ -68,6 +68,7 @@ import FilesListViewer from '.././FilesListViewer.vue' import File from '.././File.vue' import FolderIllustration from '../../assets/Illustrations/folder.svg' import SemaphoreWithPriority from '../../utils/semaphoreWithPriority.js' +import { toViewerFileInfo } from '../../utils/fileUtils.js' export default { name: 'CollectionContent', @@ -129,8 +130,8 @@ export default { openViewer(fileId) { const file = this.files[fileId] OCA.Viewer.open({ - fileInfo: file, - list: this.collectionFileIds.map(fileId => this.files[fileId]).filter(file => !file.sectionHeader), + fileInfo: toViewerFileInfo(file), + list: this.collectionFileIds.map(fileId => toViewerFileInfo(this.files[fileId])).filter(file => !file.sectionHeader), loadMore: file.loadMore ? async () => await file.loadMore(true) : () => [], canLoop: file.canLoop, }) diff --git a/src/components/FileLegacy.vue b/src/components/FileLegacy.vue index 16ea8aa2d..42b15a0da 100644 --- a/src/components/FileLegacy.vue +++ b/src/components/FileLegacy.vue @@ -60,6 +60,7 @@ import { generateUrl } from '@nextcloud/router' import UserConfig from '../mixins/UserConfig.js' +import { toViewerFileInfo } from '../utils/fileUtils.js' export default { name: 'FileLegacy', @@ -105,8 +106,8 @@ export default { methods: { openViewer() { OCA.Viewer.open({ - path: this.item.injected.filename, - list: this.item.injected.list, + fileInfo: toViewerFileInfo(this.item.injected), + list: this.item.injected.list.map(toViewerFileInfo), loadMore: this.item.injected.loadMore ? async () => await this.item.injected.loadMore(true) : () => [], canLoop: this.item.injected.canLoop, }) diff --git a/src/mixins/FetchFacesMixin.js b/src/mixins/FetchFacesMixin.js index c87c5b329..13167d82b 100644 --- a/src/mixins/FetchFacesMixin.js +++ b/src/mixins/FetchFacesMixin.js @@ -28,7 +28,7 @@ import { getCurrentUser } from '@nextcloud/auth' import client from '../services/DavClient.js' import logger from '../services/logger.js' import DavRequest from '../services/DavRequest' -import { genFileInfo } from '../utils/fileUtils' +import { genFileInfo, toAbsoluteFilePath } from '../utils/fileUtils' import AbortControllerMixin from './AbortControllerMixin' export default { @@ -117,9 +117,12 @@ export default { } ) + const fixRealpath = file => file.realpath.replace(`/${getCurrentUser().uid}/files`, '') + fetchedFiles = fetchedFiles .map(file => genFileInfo(file)) - .map(file => ({ ...file, filename: file.realpath.replace(`/${getCurrentUser().uid}/files`, '') })) + .map(file => ({ ...file, filename: toAbsoluteFilePath(fixRealpath(file)) })) + .map(file => genFileInfo(file)) // ensure we have a correct source URL const fileIds = fetchedFiles.map(file => '' + file.fileid) diff --git a/src/services/AlbumContent.js b/src/services/AlbumContent.js index f6fea719f..e442059a3 100644 --- a/src/services/AlbumContent.js +++ b/src/services/AlbumContent.js @@ -22,7 +22,7 @@ import axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' -import { genFileInfo, encodeFilePath } from '../utils/fileUtils.js' +import { genFileInfo, encodeFilePath, toRelativeFilePath } from '../utils/fileUtils.js' import allowedMimes from './AllowedMimes.js' /** @@ -37,6 +37,7 @@ export default async function(path = '/', options = {}) { const prefixPath = generateUrl(`/apps/photos/api/v1/${options.shared ? 'shared' : 'albums'}`) // fetch listing + path = toRelativeFilePath(path) const response = await axios.get(prefixPath + encodeFilePath(path), options) const list = response.data.map(data => genFileInfo(data)) @@ -47,7 +48,7 @@ export default async function(path = '/', options = {}) { for (const entry of list) { // is this the current provided path ? - if (entry.filename === path) { + if (toRelativeFilePath(entry.filename) === path) { folder = entry } else if (entry.type !== 'file') { folders.push(entry) diff --git a/src/services/FileInfo.js b/src/services/FileInfo.js index 069590388..59f5b49bc 100644 --- a/src/services/FileInfo.js +++ b/src/services/FileInfo.js @@ -20,9 +20,9 @@ * */ -import client, { prefixPath } from './DavClient.js' +import client from './DavClient.js' import request from './DavRequest.js' -import { genFileInfo } from '../utils/fileUtils.js' +import { genFileInfo, toAbsoluteFilePath } from '../utils/fileUtils.js' /** * Get a file info @@ -32,10 +32,10 @@ import { genFileInfo } from '../utils/fileUtils.js' */ export default async function(path) { // getDirectoryContents doesn't accept / for root - const fixedPath = path === '/' ? '' : path + const fixedPath = toAbsoluteFilePath(path.endsWith('/') ? path.substring(0, -1) : path) // fetch listing - const response = await client.stat(prefixPath + fixedPath, { + const response = await client.stat(fixedPath, { data: request, details: true, }) diff --git a/src/services/FolderInfo.js b/src/services/FolderInfo.js index 9cf94c76a..fe28065b2 100644 --- a/src/services/FolderInfo.js +++ b/src/services/FolderInfo.js @@ -20,9 +20,9 @@ * */ -import client, { prefixPath } from './DavClient.js' +import client from './DavClient.js' import request from './DavRequest.js' -import { genFileInfo } from '../utils/fileUtils.js' +import { genFileInfo, toAbsoluteFilePath } from '../utils/fileUtils.js' /** * List files from a folder and filter out unwanted mimes @@ -32,10 +32,10 @@ import { genFileInfo } from '../utils/fileUtils.js' */ export default async function(path) { // getDirectoryContents doesn't accept / for root - const fixedPath = path === '/' ? '' : path + const fixedPath = toAbsoluteFilePath(path.endsWith('/') ? path.substring(0, -1) : path) // fetch listing - const response = await client.stat(prefixPath + fixedPath, { + const response = await client.stat(fixedPath, { data: request, details: true, }) diff --git a/src/services/PhotoSearch.js b/src/services/PhotoSearch.js index 62e997f4a..fadc5ebc0 100644 --- a/src/services/PhotoSearch.js +++ b/src/services/PhotoSearch.js @@ -20,7 +20,7 @@ * */ -import { genFileInfo } from '../utils/fileUtils.js' +import { genFileInfo, toRelativeFilePath } from '../utils/fileUtils.js' import { getCurrentUser } from '@nextcloud/auth' import { allMimes } from './AllowedMimes.js' import client from './DavClient.js' @@ -52,6 +52,7 @@ export default async function(path = '', options = {}) { } const prefixPath = `/files/${getCurrentUser().uid}` + path = toRelativeFilePath(path) // generating the search or condition // based on the allowed mimetypes diff --git a/src/services/TaggedImages.js b/src/services/TaggedImages.js index a0611e4f4..98baac804 100644 --- a/src/services/TaggedImages.js +++ b/src/services/TaggedImages.js @@ -60,6 +60,4 @@ export default async function(id, options = {}) { // hardcoded props and mime is not one of them // https://github.com/nextcloud/server/blob/5bf3d1bb384da56adbf205752be8f840aac3b0c5/apps/dav/lib/Connector/Sabre/FilesReportPlugin.php#L274 .filter(file => file.mime && allowedMimes.indexOf(file.mime) !== -1) - // remove prefix path from full file path - .map(data => Object.assign({}, data, { filename: data.filename.replace(prefixPath, '') })) } diff --git a/src/store/files.js b/src/store/files.js index cbbd7aafd..57c9bb7bd 100644 --- a/src/store/files.js +++ b/src/store/files.js @@ -25,7 +25,8 @@ import moment from '@nextcloud/moment' import { showError } from '@nextcloud/dialogs' import logger from '../services/logger.js' -import client, { prefixPath } from '../services/DavClient.js' +import client from '../services/DavClient.js' +import { toRelativeFilePath } from '../utils/fileUtils.js' import Semaphore from '../utils/semaphoreWithPriority.js' const state = { @@ -44,8 +45,8 @@ const mutations = { const files = {} newFiles.forEach(file => { // Ignore the file if the path is excluded - if (state.nomediaPaths.some(nomediaPath => file.filename.startsWith(nomediaPath) - || file.filename.startsWith(prefixPath + nomediaPath))) { + if (state.nomediaPaths.some(nomediaPath => toRelativeFilePath(file.filename).startsWith(nomediaPath))) { + logger.debug('Excluded file: ', file.filename) return } diff --git a/src/utils/fileUtils.js b/src/utils/fileUtils.js index 2e9fd1d62..3f590203f 100644 --- a/src/utils/fileUtils.js +++ b/src/utils/fileUtils.js @@ -20,8 +20,9 @@ * */ import { generateRemoteUrl } from '@nextcloud/router' +import { getCurrentUser } from '@nextcloud/auth' import camelcase from 'camelcase' -import { rootPath } from '../services/DavClient.js' +import { rootPath, prefixPath } from '../services/DavClient.js' import { isNumber } from './numberUtils.js' /** @@ -99,6 +100,84 @@ const sortCompare = function(fileInfo1, fileInfo2, key, asc = true) { : -fileInfo1[key]?.toString()?.localeCompare(fileInfo2[key].toString(), OC.getLanguage()) || -1 } +const knownApps = ['files', 'photos', 'recognize'] + +/** + * Checks if the specified path starts with app and user + * + * @param {string} path the path to check + * @return {object} the check result + */ +function analyzePath(path) { + const parts = typeof path === 'string' + ? path.split('/').filter(p => p.length > 0) + : [] + + const [app, user] = parts + const hasCurrentUser = user && user === `${getCurrentUser()?.uid}` + const hasKnownApp = app && knownApps.includes(app) + + return { hasCurrentUser, hasKnownApp, parts } +} + +/** + * Strips '/app/user/' from path or fileInfo.filename + * + * @param {string|object.filename} path the path or fileInfo to convert + * @return {string|object.filename} the converted input or the original if no conversion was required + */ +function toRelativeFilePath(path) { + if (typeof path === 'object' && 'filename' in path) { + return { ...path, filename: toRelativeFilePath(path.filename) } + } + + const { hasKnownApp, hasCurrentUser, parts } = analyzePath(path) + return hasKnownApp || hasCurrentUser + ? '/' + parts.slice(2).join('/') + : path +} + +/** + * Changes path (or fileInfo.filename) so that it starts with '/app/user/...' + * + * @param {string|object.filename} path the path or fileInfo to convert + * @param {string} contextPath the prefix to add to relative paths + * @return {string|object.filename} the converted input or the original if no conversion was required + */ +function toAbsoluteFilePath(path, contextPath = prefixPath) { + if (typeof path === 'object' && 'filename' in path) { + return { ...path, filename: toAbsoluteFilePath(path.filename) } + } + + const { hasKnownApp, hasCurrentUser } = analyzePath(path) + return !hasKnownApp && !hasCurrentUser + ? contextPath + path + : path +} + +/** + * Converts the given fileInfo to an obj suitable for OCA.Viewer + * + * @param {object} obj the fileInfo to prepare for sending it to OCA.Viewer + * @returns a clone of obj with properties adjusted when needed + */ +function toViewerFileInfo(obj) { + if (typeof obj !== 'object' || typeof obj.filename !== 'string') { + throw new Error('fileInfo obj must be given, found: ' + obj) + } + + // according to Viewer docs, filename must be URL when route is not /files/user/ + obj = obj.filename.startsWith('/files/') + ? { ...obj, filename: toRelativeFilePath(obj.filename) } + : { ...obj, filename: obj.source } + + // ensure basename is correct + const [, basename] = extractFilePaths(obj.filename) + obj.basename = basename + + return obj +} + /** * @param {object} obj - object to flatten and format. */ @@ -120,12 +199,15 @@ function genFileInfo(obj) { } }, {}) + // format source and filename (ensure app & user is always included) if (fileInfo.filename) { - // Adding context - fileInfo.source = generateRemoteUrl(rootPath) + '/' + fileInfo.filename + fileInfo.filename = toAbsoluteFilePath(fileInfo.filename) + + const path = encodeFilePath(fileInfo.filename) + fileInfo.source = generateRemoteUrl(rootPath) + (path.startsWith('/') ? '' : '/') + path } return fileInfo } -export { encodeFilePath, extractFilePaths, sortCompare, genFileInfo } +export { encodeFilePath, extractFilePaths, sortCompare, genFileInfo, toRelativeFilePath, toAbsoluteFilePath, toViewerFileInfo } diff --git a/src/views/FaceContent.vue b/src/views/FaceContent.vue index bd554a56f..f479c3166 100644 --- a/src/views/FaceContent.vue +++ b/src/views/FaceContent.vue @@ -183,6 +183,7 @@ import FilesListViewer from '../components/FilesListViewer.vue' import File from '../components/File.vue' import logger from '../services/logger.js' import FetchFacesMixin from '../mixins/FetchFacesMixin.js' +import { toViewerFileInfo } from '../utils/fileUtils.js' import Vue from 'vue' import FaceMergeForm from '../components/FaceMergeForm.vue' @@ -286,11 +287,8 @@ export default { openViewer(fileId) { const file = this.files[fileId] OCA.Viewer.open({ - path: file.filename, - list: this.faceFileIds.map(fileId => ({ - ...this.files[fileId], - basename: this.files[fileId].basename.split('-').slice(1).join('-'), - })).filter(file => !file.sectionHeader), + path: toViewerFileInfo(file).filename, + list: this.faceFileIds.map(fileId => toViewerFileInfo(this.files[fileId])).filter(file => !file.sectionHeader), loadMore: file.loadMore ? async () => await file.loadMore(true) : () => [], canLoop: file.canLoop, }) diff --git a/src/views/Folders.vue b/src/views/Folders.vue index a5f263742..37ae5d3e1 100644 --- a/src/views/Folders.vue +++ b/src/views/Folders.vue @@ -42,7 +42,7 @@ :root-title="rootTitle" @refresh="onRefresh"> @@ -78,6 +78,7 @@ import getAlbumContent from '../services/AlbumContent.js' import AbortControllerMixin from '../mixins/AbortControllerMixin.js' import GridConfigMixin from '../mixins/GridConfig.js' import getFileInfo from '../services/FileInfo.js' +import { toRelativeFilePath } from '../utils/fileUtils.js' export default { name: 'Folders', @@ -218,6 +219,10 @@ export default { this.fetchFolderContent() }, + toRelative(path) { + return toRelativeFilePath(path) + }, + async fetchFolderContent() { this.error = null this.loading = true diff --git a/src/views/TagContent.vue b/src/views/TagContent.vue index 77f037d29..f8398acb1 100644 --- a/src/views/TagContent.vue +++ b/src/views/TagContent.vue @@ -75,6 +75,7 @@ import File from '../components/File.vue' import FilesListViewer from '../components/FilesListViewer.vue' import SemaphoreWithPriority from '../utils/semaphoreWithPriority.js' +import { toViewerFileInfo } from '../utils/fileUtils.js' import FilesSelectionMixin from '../mixins/FilesSelectionMixin.js' import AbortControllerMixin from '../mixins/AbortControllerMixin.js' @@ -175,8 +176,8 @@ export default { openViewer(fileId) { const file = this.files[fileId] OCA.Viewer.open({ - path: file.filename, - list: this.fileIds.map(fileId => this.files[fileId]), + fileInfo: toViewerFileInfo(file), + list: this.fileIds.map(fileId => toViewerFileInfo(this.files[fileId])), loadMore: file.loadMore ? async () => await file.loadMore(true) : () => [], canLoop: file.canLoop, }) diff --git a/src/views/Timeline.vue b/src/views/Timeline.vue index 85fc96166..801e9644b 100644 --- a/src/views/Timeline.vue +++ b/src/views/Timeline.vue @@ -136,6 +136,7 @@ import { NcModal, NcActions, NcActionButton, NcButton, NcEmptyContent, isMobile import moment from '@nextcloud/moment' import { allMimes } from '../services/AllowedMimes.js' +import { toViewerFileInfo } from '../utils/fileUtils.js' import FetchFilesMixin from '../mixins/FetchFilesMixin.js' import FilesByMonthMixin from '../mixins/FilesByMonthMixin.js' import FilesSelectionMixin from '../mixins/FilesSelectionMixin.js' @@ -244,8 +245,8 @@ export default { openViewer(fileId) { const file = this.files[fileId] OCA.Viewer.open({ - fileInfo: file, - list: Object.values(this.fileIdsByMonth).flat().map(fileId => this.files[fileId]), + fileInfo: toViewerFileInfo(file), + list: Object.values(this.fileIdsByMonth).flat().map(fileId => toViewerFileInfo(this.files[fileId])), loadMore: file.loadMore ? async () => await file.loadMore(true) : () => [], canLoop: file.canLoop, })