diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-09-16 16:35:01 +0200 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-09-28 13:18:29 +0200 |
commit | 0f6760c810e370023728d93a31f69c79dc5c3e3d (patch) | |
tree | ec8ac8201ef131b1f1727b33060c731b915414b0 /apps/files/src | |
parent | 2f66bd5b754f072a3cfeda759d71a479e4538350 (diff) | |
download | nextcloud-server-0f6760c810e370023728d93a31f69c79dc5c3e3d.tar.gz nextcloud-server-0f6760c810e370023728d93a31f69c79dc5c3e3d.zip |
feat(files): Make the files download action use WebDAV zip download
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Diffstat (limited to 'apps/files/src')
-rw-r--r-- | apps/files/src/actions/downloadAction.spec.ts | 4 | ||||
-rw-r--r-- | apps/files/src/actions/downloadAction.ts | 96 |
2 files changed, 58 insertions, 42 deletions
diff --git a/apps/files/src/actions/downloadAction.spec.ts b/apps/files/src/actions/downloadAction.spec.ts index 2c24625e90e..2d42de5b8b1 100644 --- a/apps/files/src/actions/downloadAction.spec.ts +++ b/apps/files/src/actions/downloadAction.spec.ts @@ -141,7 +141,7 @@ describe('Download action execute tests', () => { // Silent action expect(exec).toBe(null) expect(link.download).toEqual('') - expect(link.href.startsWith('/index.php/apps/files/ajax/download.php?dir=%2F&files=%5B%22FooBar%22%5D&downloadStartSecret=')).toBe(true) + expect(link.href).toMatch('https://cloud.domain.com/remote.php/dav/files/admin/FooBar/?accept=zip') expect(link.click).toHaveBeenCalledTimes(1) }) @@ -166,7 +166,7 @@ describe('Download action execute tests', () => { // Silent action expect(exec).toStrictEqual([null, null]) expect(link.download).toEqual('') - expect(link.href.startsWith('/index.php/apps/files/ajax/download.php?dir=%2FDir&files=%5B%22foo.txt%22%2C%22bar.txt%22%5D&downloadStartSecret=')).toBe(true) + expect(link.href).toMatch('https://cloud.domain.com/remote.php/dav/files/admin/Dir/?accept=zip&files=%5B%22foo.txt%22%2C%22bar.txt%22%5D') expect(link.click).toHaveBeenCalledTimes(1) }) }) diff --git a/apps/files/src/actions/downloadAction.ts b/apps/files/src/actions/downloadAction.ts index 97d1cc773d4..19e0b3502fa 100644 --- a/apps/files/src/actions/downloadAction.ts +++ b/apps/files/src/actions/downloadAction.ts @@ -2,11 +2,8 @@ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { FileAction, Node, FileType, View, DefaultType } from '@nextcloud/files' +import { FileAction, Node, FileType, DefaultType } from '@nextcloud/files' import { t } from '@nextcloud/l10n' -import { generateUrl } from '@nextcloud/router' -import { getSharingToken, isPublicShare } from '@nextcloud/sharing/public' -import { basename } from 'path' import { isDownloadable } from '../utils/permissions' import ArrowDownSvg from '@mdi/svg/svg/arrow-down.svg?raw' @@ -18,25 +15,57 @@ const triggerDownload = function(url: string) { hiddenElement.click() } -const downloadNodes = function(dir: string, nodes: Node[]) { - const secret = Math.random().toString(36).substring(2) - let url: string - if (isPublicShare()) { - url = generateUrl('/s/{token}/download/{filename}?path={dir}&files={files}&downloadStartSecret={secret}', { - dir, - secret, - files: JSON.stringify(nodes.map(node => node.basename)), - token: getSharingToken(), - filename: `${basename(dir)}.zip}`, - }) +/** + * Find the longest common path prefix of both input paths + * @param first The first path + * @param second The second path + */ +function longestCommonPath(first: string, second: string): string { + const firstSegments = first.split('/').filter(Boolean) + const secondSegments = second.split('/').filter(Boolean) + let base = '' + for (const [index, segment] of firstSegments.entries()) { + if (index >= second.length) { + break + } + if (segment !== secondSegments[index]) { + break + } + const sep = base === '' ? '' : '/' + base = `${base}${sep}${segment}` + } + return base +} + +const downloadNodes = function(nodes: Node[]) { + let url: URL + + if (nodes.length === 1) { + if (nodes[0].type === FileType.File) { + return triggerDownload(nodes[0].encodedSource) + } else { + url = new URL(nodes[0].encodedSource) + url.searchParams.append('accept', 'zip') + } } else { - url = generateUrl('/apps/files/ajax/download.php?dir={dir}&files={files}&downloadStartSecret={secret}', { - dir, - secret, - files: JSON.stringify(nodes.map(node => node.basename)), - }) + url = new URL(nodes[0].source) + let base = url.pathname + for (const node of nodes.slice(1)) { + base = longestCommonPath(base, (new URL(node.source).pathname)) + } + url.pathname = base + + // The URL contains the path encoded so we need to decode as the query.append will re-encode it + const filenames = nodes.map((node) => decodeURI(node.encodedSource.slice(url.href.length + 1))) + url.searchParams.append('accept', 'zip') + url.searchParams.append('files', JSON.stringify(filenames)) } - triggerDownload(url) + + if (url.pathname.at(-1) !== '/') { + url.pathname = `${url.pathname}/` + } + + return triggerDownload(url.href) } export const action = new FileAction({ @@ -51,34 +80,21 @@ export const action = new FileAction({ return false } - // We can download direct dav files. But if we have - // some folders, we need to use the /apps/files/ajax/download.php - // endpoint, which only supports user root folder. - if (nodes.some(node => node.type === FileType.Folder) - && nodes.some(node => !node.root?.startsWith('/files'))) { + // We can only download dav files and folders. + if (nodes.some(node => !node.isDavRessource)) { return false } return nodes.every(isDownloadable) }, - async exec(node: Node, view: View, dir: string) { - if (node.type === FileType.Folder) { - downloadNodes(dir, [node]) - return null - } - - triggerDownload(node.encodedSource) + async exec(node: Node) { + downloadNodes([node]) return null }, - async execBatch(nodes: Node[], view: View, dir: string) { - if (nodes.length === 1) { - this.exec(nodes[0], view, dir) - return [null] - } - - downloadNodes(dir, nodes) + async execBatch(nodes: Node[]) { + downloadNodes(nodes) return new Array(nodes.length).fill(null) }, |