Skip to content

Commit b72553c

Browse files
committed
fix!: Revert breaking changes in DAV endpoint handling
5.3.3 introduced breaking changes as it did no longer support Nextcloud 28 and older. Revert those changes. Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent 1c61723 commit b72553c

File tree

7 files changed

+104
-20
lines changed

7 files changed

+104
-20
lines changed

lib/composables/dav.spec.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const waitRefLoaded = (isLoading: Ref<boolean>) => new Promise((resolve) => {
3737
})
3838

3939
const TestComponent = defineComponent({
40-
props: ['currentView', 'currentPath', 'isPublic'],
40+
props: ['currentView', 'currentPath'],
4141
setup(props) {
4242
const dav = useDAVFiles(toRef(props, 'currentView'), toRef(props, 'currentPath'))
4343
return {
@@ -60,7 +60,6 @@ describe('dav composable', () => {
6060
propsData: {
6161
currentView: 'files',
6262
currentPath: '/',
63-
isPublic: false,
6463
},
6564
})
6665
// Loading is set to true
@@ -85,7 +84,6 @@ describe('dav composable', () => {
8584
propsData: {
8685
currentView: 'files',
8786
currentPath: '/',
88-
isPublic: false,
8987
},
9088
})
9189

@@ -105,7 +103,6 @@ describe('dav composable', () => {
105103
propsData: {
106104
currentView: 'files',
107105
currentPath: '/',
108-
isPublic: false,
109106
},
110107
})
111108

@@ -133,7 +130,6 @@ describe('dav composable', () => {
133130
propsData: {
134131
currentView: 'files',
135132
currentPath: '/',
136-
isPublic: false,
137133
},
138134
})
139135

lib/composables/dav.ts

+95-11
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
*/
55
import type { ContentsWithRoot, Folder, Node } from '@nextcloud/files'
66
import type { ComputedRef, Ref } from 'vue'
7+
import type { FileStat, ResponseDataDetailed, SearchResult } from 'webdav'
78

8-
import { davGetClient, davRootPath, getFavoriteNodes } from '@nextcloud/files'
9-
import { CancelablePromise } from 'cancelable-promise'
9+
import { davGetClient, davGetDefaultPropfind, davGetRecentSearch, davRemoteURL, davResultToNode, davRootPath, getFavoriteNodes } from '@nextcloud/files'
10+
import { generateRemoteUrl } from '@nextcloud/router'
11+
import { isPublicShare } from '@nextcloud/sharing/public'
1012
import { join } from 'node:path'
11-
import { onMounted, ref, shallowRef, watch } from 'vue'
12-
import { getFile, getNodes, getRecentNodes } from '../utils/dav'
13+
import { computed, onMounted, ref, shallowRef, watch } from 'vue'
14+
import { CancelablePromise } from 'cancelable-promise'
1315

1416
/**
1517
* Handle file loading using WebDAV
@@ -22,10 +24,76 @@ export const useDAVFiles = function(
2224
currentPath: Ref<string> | ComputedRef<string>,
2325
) {
2426

27+
const isPublicEndpoint = isPublicShare()
28+
29+
const defaultRootPath = isPublicEndpoint ? '/' : davRootPath
30+
31+
const defaultRemoteUrl = computed(() => {
32+
if (isPublicEndpoint) {
33+
return generateRemoteUrl('webdav').replace('/remote.php', '/public.php')
34+
}
35+
return davRemoteURL
36+
})
37+
2538
/**
2639
* The WebDAV client
2740
*/
28-
const client = davGetClient()
41+
const client = computed(() => {
42+
if (isPublicEndpoint) {
43+
const token = (document.getElementById('sharingToken')! as HTMLInputElement).value
44+
const authorization = btoa(`${token}:null`)
45+
46+
return davGetClient(defaultRemoteUrl.value, {
47+
Authorization: `Basic ${authorization}`,
48+
})
49+
}
50+
51+
return davGetClient()
52+
})
53+
54+
const resultToNode = (result: FileStat) => davResultToNode(result, defaultRootPath, defaultRemoteUrl.value)
55+
56+
const getRecentNodes = (): CancelablePromise<Node[]> => {
57+
const controller = new AbortController()
58+
// unix timestamp in seconds, two weeks ago
59+
const lastTwoWeek = Math.round(Date.now() / 1000) - (60 * 60 * 24 * 14)
60+
return new CancelablePromise(async (resolve, reject, onCancel) => {
61+
onCancel(() => controller.abort())
62+
try {
63+
const { data } = await client.value.search('/', {
64+
signal: controller.signal,
65+
details: true,
66+
data: davGetRecentSearch(lastTwoWeek),
67+
}) as ResponseDataDetailed<SearchResult>
68+
const nodes = data.results.map(resultToNode)
69+
resolve(nodes)
70+
} catch (error) {
71+
reject(error)
72+
}
73+
})
74+
}
75+
76+
const getNodes = (): CancelablePromise<Node[]> => {
77+
const controller = new AbortController()
78+
return new CancelablePromise(async (resolve, reject, onCancel) => {
79+
onCancel(() => controller.abort())
80+
try {
81+
const results = await client.value.getDirectoryContents(`${defaultRootPath}${currentPath.value}`, {
82+
signal: controller.signal,
83+
details: true,
84+
data: davGetDefaultPropfind(),
85+
}) as ResponseDataDetailed<FileStat[]>
86+
let nodes = results.data.map(resultToNode)
87+
// Hack for the public endpoint which always returns folder itself
88+
if (isPublicEndpoint) {
89+
nodes = nodes.filter((file) => file.path !== currentPath.value)
90+
}
91+
resolve(nodes)
92+
} catch (error) {
93+
reject(error)
94+
}
95+
})
96+
}
2997

3098
/**
3199
* All files in current view and path
@@ -51,17 +119,33 @@ export const useDAVFiles = function(
51119
* Create a new directory in the current path
52120
* The directory will be added to the current file list
53121
* @param name Name of the new directory
54-
* @return {Promise<Folder>} The created directory
122+
* @return The created directory
55123
*/
56124
async function createDirectory(name: string): Promise<Folder> {
57125
const path = join(currentPath.value, name)
58126

59-
await client.createDirectory(join(davRootPath, path))
60-
const directory = await getFile(client, path) as Folder
127+
await client.value.createDirectory(join(defaultRootPath, path))
128+
const directory = await getFile(path) as Folder
61129
files.value = [...files.value, directory]
62130
return directory
63131
}
64132

133+
/**
134+
* Get information for one file
135+
*
136+
* @param path The path of the file or folder
137+
* @param rootPath DAV root path, defaults to '/files/USERID'
138+
*/
139+
async function getFile(path: string, rootPath: string|undefined = undefined) {
140+
rootPath = rootPath ?? defaultRootPath
141+
142+
const { data } = await client.value.stat(`${rootPath}${path}`, {
143+
details: true,
144+
data: davGetDefaultPropfind(),
145+
}) as ResponseDataDetailed<FileStat>
146+
return resultToNode(data)
147+
}
148+
65149
/**
66150
* Force reload files using the DAV client
67151
*/
@@ -72,11 +156,11 @@ export const useDAVFiles = function(
72156
isLoading.value = true
73157

74158
if (currentView.value === 'favorites') {
75-
promise.value = getFavoriteNodes(client, currentPath.value)
159+
promise.value = getFavoriteNodes(client.value, currentPath.value, defaultRootPath)
76160
} else if (currentView.value === 'recent') {
77-
promise.value = getRecentNodes(client)
161+
promise.value = getRecentNodes()
78162
} else {
79-
promise.value = getNodes(client, currentPath.value)
163+
promise.value = getNodes()
80164
}
81165
const content = await promise.value
82166
if ('folder' in content) {

lib/composables/filesSettings.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import { useFilesSettings } from './filesSettings'
1111
const axios = vi.hoisted(() => ({
1212
get: vi.fn(),
1313
}))
14-
const isPublic = vi.hoisted(() => ({ value: false }))
14+
const nextcloudSharing = vi.hoisted(() => ({ isPublicShare: vi.fn(() => false) }))
1515

1616
vi.mock('@nextcloud/axios', () => ({ default: axios }))
17-
vi.mock('./isPublic', () => ({ useIsPublic: () => ({ isPublic }) }))
17+
vi.mock('@nextcloud/sharing/public', () => nextcloudSharing)
1818

1919
const TestComponent = defineComponent({
2020
setup() {

lib/toast.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ export function showUndo(text: string, onUndo: (e: MouseEvent) => void, options?
214214
// force 10 seconds of timeout
215215
timeout: TOAST_UNDO_TIMEOUT,
216216
// remove close button
217-
close: false
217+
close: false,
218218
})
219219

220220
// Generate undo layout

lib/utils/dav.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ describe('DAV utils', () => {
3131
expect(client.stat).toBeCalledWith(`${nextcloudFiles.davRootPath}/some/path/file.ext`, { details: true, data: 'propfind content' })
3232
expect(nextcloudFiles.davResultToNode).toBeCalledWith({ path: `${nextcloudFiles.davRootPath}/some/path/file.ext` })
3333
})
34-
})
34+
})

lib/utils/dialogs.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Vue, { toRaw } from 'vue'
1313
* @param props Properties to pass to the dialog
1414
* @param onClose Callback when the dialog is closed
1515
*/
16+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1617
export const spawnDialog = (dialog: Component | AsyncComponent, props: any, onClose: (...rest: unknown[]) => void = () => {}): Vue => {
1718
const el = document.createElement('div')
1819

tsconfig.json

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
"lib": ["DOM", "ESNext"],
88
"outDir": "./dist",
99
"rootDir": "lib/",
10+
"module": "ESNext",
11+
"moduleResolution": "Bundler",
12+
"target": "ESNext",
1013
"sourceMap": true,
1114
"plugins": [
1215
{ "name": "typescript-plugin-css-modules" }

0 commit comments

Comments
 (0)