Skip to content

Commit 5cb2623

Browse files
Merge pull request #57201 from nextcloud/dependabot/npm_and_yarn/nextcloud/files-4.0.0-beta.5
build(deps): Bump @nextcloud/files from 4.0.0-beta.4 to 4.0.0-beta.7
2 parents 75e5776 + 0934814 commit 5cb2623

File tree

273 files changed

+1078
-776
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

273 files changed

+1078
-776
lines changed

apps/files/src/composables/useNavigation.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ describe('Composables: useNavigation', () => {
4141
it('should return already active navigation', async () => {
4242
const view = new nextcloudFiles.View({ getContents: () => Promise.reject(new Error()), icon: '<svg></svg>', id: 'view-1', name: 'My View 1', order: 0 })
4343
navigation.register(view)
44-
navigation.setActive(view)
44+
navigation.setActive(view.id)
4545
// Now the navigation is already set it should take the active navigation
4646
const wrapper = mount(TestComponent)
4747
expect((wrapper.vm as unknown as { currentView: View | null }).currentView).toBe(view)
@@ -55,7 +55,7 @@ describe('Composables: useNavigation', () => {
5555
// no active navigation
5656
expect((wrapper.vm as unknown as { currentView: View | null }).currentView).toBe(null)
5757

58-
navigation.setActive(view)
58+
navigation.setActive(view.id)
5959
// Now the navigation is set it should take the active navigation
6060
expect((wrapper.vm as unknown as { currentView: View | null }).currentView).toBe(view)
6161
})
Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,49 @@
1-
/**
1+
/*!
22
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
5+
56
import type { ContentsWithRoot } from '@nextcloud/files'
67

78
import { getCurrentUser } from '@nextcloud/auth'
89
import { Folder, Permission } from '@nextcloud/files'
910
import { getFavoriteNodes, getRemoteURL, getRootPath } from '@nextcloud/files/dav'
10-
import { CancelablePromise } from 'cancelable-promise'
11+
import logger from '../logger.ts'
1112
import { getContents as filesContents } from './Files.ts'
1213
import { client } from './WebdavClient.ts'
1314

1415
/**
16+
* Get the contents for the favorites view
1517
*
16-
* @param path
18+
* @param path - The path to get the contents for
19+
* @param options - Additional options
20+
* @param options.signal - Optional AbortSignal to cancel the request
21+
* @return A promise resolving to the contents with root folder
1722
*/
18-
export function getContents(path = '/'): CancelablePromise<ContentsWithRoot> {
23+
export async function getContents(path = '/', options: { signal: AbortSignal }): Promise<ContentsWithRoot> {
1924
// We only filter root files for favorites, for subfolders we can simply reuse the files contents
20-
if (path !== '/') {
21-
return filesContents(path)
25+
if (path && path !== '/') {
26+
return filesContents(path, options)
2227
}
2328

24-
return new CancelablePromise((resolve, reject, cancel) => {
25-
const promise = getFavoriteNodes(client)
26-
.catch(reject)
27-
.then((contents) => {
28-
if (!contents) {
29-
reject()
30-
return
31-
}
32-
resolve({
33-
contents,
34-
folder: new Folder({
35-
id: 0,
36-
source: `${getRemoteURL()}${getRootPath()}`,
37-
root: getRootPath(),
38-
owner: getCurrentUser()?.uid || null,
39-
permissions: Permission.READ,
40-
}),
41-
})
42-
})
43-
cancel(() => promise.cancel())
44-
})
29+
try {
30+
const contents = await getFavoriteNodes({ client, signal: options.signal })
31+
return {
32+
contents,
33+
folder: new Folder({
34+
id: 0,
35+
source: `${getRemoteURL()}${getRootPath()}`,
36+
root: getRootPath(),
37+
owner: getCurrentUser()?.uid || null,
38+
permissions: Permission.READ,
39+
}),
40+
}
41+
} catch (error) {
42+
if (options.signal.aborted) {
43+
logger.debug('Favorite nodes request was aborted')
44+
throw new DOMException('Aborted', 'AbortError')
45+
}
46+
logger.error('Failed to load favorite nodes via WebDAV', { error })
47+
throw error
48+
}
4549
}

apps/files/src/services/Files.ts

Lines changed: 33 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import type { ContentsWithRoot, File, Folder } from '@nextcloud/files'
66
import type { FileStat, ResponseDataDetailed } from 'webdav'
77

88
import { getDefaultPropfind, getRootPath, resultToNode } from '@nextcloud/files/dav'
9-
import { CancelablePromise } from 'cancelable-promise'
109
import { join } from 'path'
1110
import logger from '../logger.ts'
1211
import { useFilesStore } from '../store/files.ts'
@@ -20,66 +19,55 @@ import { searchNodes } from './WebDavSearch.ts'
2019
* This also allows to fetch local search results when the user is currently filtering.
2120
*
2221
* @param path - The path to query
22+
* @param options - Options
23+
* @param options.signal - Abort signal to cancel the request
2324
*/
24-
export function getContents(path = '/'): CancelablePromise<ContentsWithRoot> {
25-
const controller = new AbortController()
25+
export async function getContents(path = '/', options?: { signal: AbortSignal }): Promise<ContentsWithRoot> {
2626
const searchStore = useSearchStore(getPinia())
2727

28-
if (searchStore.query.length >= 3) {
29-
return new CancelablePromise((resolve, reject, cancel) => {
30-
cancel(() => controller.abort())
31-
getLocalSearch(path, searchStore.query, controller.signal)
32-
.then(resolve)
33-
.catch(reject)
34-
})
35-
} else {
36-
return defaultGetContents(path)
28+
if (searchStore.query.length < 3) {
29+
return await defaultGetContents(path, options)
3730
}
31+
32+
return await getLocalSearch(path, searchStore.query, options?.signal)
3833
}
3934

4035
/**
4136
* Generic `getContents` implementation for the users files.
4237
*
4338
* @param path - The path to get the contents
39+
* @param options - Options
40+
* @param options.signal - Abort signal to cancel the request
4441
*/
45-
export function defaultGetContents(path: string): CancelablePromise<ContentsWithRoot> {
42+
export async function defaultGetContents(path: string, options?: { signal: AbortSignal }): Promise<ContentsWithRoot> {
4643
path = join(getRootPath(), path)
47-
const controller = new AbortController()
4844
const propfindPayload = getDefaultPropfind()
4945

50-
return new CancelablePromise(async (resolve, reject, onCancel) => {
51-
onCancel(() => controller.abort())
46+
const contentsResponse = await client.getDirectoryContents(path, {
47+
details: true,
48+
data: propfindPayload,
49+
includeSelf: true,
50+
signal: options?.signal,
51+
}) as ResponseDataDetailed<FileStat[]>
5252

53-
try {
54-
const contentsResponse = await client.getDirectoryContents(path, {
55-
details: true,
56-
data: propfindPayload,
57-
includeSelf: true,
58-
signal: controller.signal,
59-
}) as ResponseDataDetailed<FileStat[]>
53+
const root = contentsResponse.data[0]!
54+
const contents = contentsResponse.data.slice(1)
55+
if (root?.filename !== path && `${root?.filename}/` !== path) {
56+
logger.debug(`Exepected "${path}" but got filename "${root.filename}" instead.`)
57+
throw new Error('Root node does not match requested path')
58+
}
6059

61-
const root = contentsResponse.data[0]
62-
const contents = contentsResponse.data.slice(1)
63-
if (root?.filename !== path && `${root?.filename}/` !== path) {
64-
logger.debug(`Exepected "${path}" but got filename "${root.filename}" instead.`)
65-
throw new Error('Root node does not match requested path')
60+
return {
61+
folder: resultToNode(root) as Folder,
62+
contents: contents.map((result) => {
63+
try {
64+
return resultToNode(result)
65+
} catch (error) {
66+
logger.error(`Invalid node detected '${result.basename}'`, { error })
67+
return null
6668
}
67-
68-
resolve({
69-
folder: resultToNode(root) as Folder,
70-
contents: contents.map((result) => {
71-
try {
72-
return resultToNode(result)
73-
} catch (error) {
74-
logger.error(`Invalid node detected '${result.basename}'`, { error })
75-
return null
76-
}
77-
}).filter(Boolean) as File[],
78-
})
79-
} catch (error) {
80-
reject(error)
81-
}
82-
})
69+
}).filter(Boolean) as File[],
70+
}
8371
}
8472

8573
/**
@@ -89,7 +77,7 @@ export function defaultGetContents(path: string): CancelablePromise<ContentsWith
8977
* @param query - The current search query
9078
* @param signal - The aboort signal
9179
*/
92-
async function getLocalSearch(path: string, query: string, signal: AbortSignal): Promise<ContentsWithRoot> {
80+
async function getLocalSearch(path: string, query: string, signal?: AbortSignal): Promise<ContentsWithRoot> {
9381
const filesStore = useFilesStore(getPinia())
9482
let folder = filesStore.getDirectoryByPath('files', path)
9583
if (!folder) {

apps/files/src/services/FolderTree.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55

66
import type { ContentsWithRoot } from '@nextcloud/files'
7-
import type { CancelablePromise } from 'cancelable-promise'
87

98
import { getCurrentUser } from '@nextcloud/auth'
109
import axios from '@nextcloud/axios'
@@ -47,10 +46,11 @@ const collator = Intl.Collator(
4746
const compareNodes = (a: TreeNodeData, b: TreeNodeData) => collator.compare(a.displayName ?? a.basename, b.displayName ?? b.basename)
4847

4948
/**
49+
* Get all tree nodes recursively
5050
*
51-
* @param tree
52-
* @param currentPath
53-
* @param nodes
51+
* @param tree - The tree to process
52+
* @param currentPath - The current path
53+
* @param nodes - The nodes collected so far
5454
*/
5555
function getTreeNodes(tree: Tree, currentPath: string = '/', nodes: TreeNode[] = []): TreeNode[] {
5656
const sortedTree = tree.toSorted(compareNodes)
@@ -76,9 +76,10 @@ function getTreeNodes(tree: Tree, currentPath: string = '/', nodes: TreeNode[] =
7676
}
7777

7878
/**
79+
* Get folder tree nodes
7980
*
80-
* @param path
81-
* @param depth
81+
* @param path - The path to get the tree from
82+
* @param depth - The depth to fetch
8283
*/
8384
export async function getFolderTreeNodes(path: string = '/', depth: number = 1): Promise<TreeNode[]> {
8485
const { data: tree } = await axios.get<Tree>(generateOcsUrl('/apps/files/api/v1/folder-tree'), {
@@ -88,20 +89,22 @@ export async function getFolderTreeNodes(path: string = '/', depth: number = 1):
8889
return nodes
8990
}
9091

91-
export const getContents = (path: string): CancelablePromise<ContentsWithRoot> => getFiles(path)
92+
export const getContents = (path: string, options: { signal: AbortSignal }): Promise<ContentsWithRoot> => getFiles(path, options)
9293

9394
/**
95+
* Encode source URL
9496
*
95-
* @param source
97+
* @param source - The source URL
9698
*/
9799
export function encodeSource(source: string): string {
98100
const { origin } = new URL(source)
99101
return origin + encodePath(source.slice(origin.length))
100102
}
101103

102104
/**
105+
* Get parent source URL
103106
*
104-
* @param source
107+
* @param source - The source URL
105108
*/
106109
export function getSourceParent(source: string): string {
107110
const parent = dirname(source)

apps/files/src/services/PersonalFiles.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55

66
import type { ContentsWithRoot, Node } from '@nextcloud/files'
7-
import type { CancelablePromise } from 'cancelable-promise'
87

98
import { getCurrentUser } from '@nextcloud/auth'
109
import { getContents as getFiles } from './Files.ts'
@@ -31,13 +30,17 @@ export function isPersonalFile(node: Node): boolean {
3130
}
3231

3332
/**
33+
* Get personal files from a given path
3434
*
35-
* @param path
35+
* @param path - The path to get the personal files from
36+
* @param options - Options
37+
* @param options.signal - Abort signal to cancel the request
38+
* @return A promise that resolves to the personal files
3639
*/
37-
export function getContents(path: string = '/'): CancelablePromise<ContentsWithRoot> {
40+
export function getContents(path: string = '/', options: { signal: AbortSignal }): Promise<ContentsWithRoot> {
3841
// get all the files from the current path as a cancellable promise
3942
// then filter the files that the user does not own, or has shared / is a group folder
40-
return getFiles(path)
43+
return getFiles(path, options)
4144
.then((content) => {
4245
content.contents = content.contents.filter(isPersonalFile)
4346
return content

apps/files/src/services/Recent.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { ResponseDataDetailed, SearchResult } from 'webdav'
88
import { getCurrentUser } from '@nextcloud/auth'
99
import { Folder, Permission } from '@nextcloud/files'
1010
import { getRecentSearch, getRemoteURL, getRootPath, resultToNode } from '@nextcloud/files/dav'
11-
import { CancelablePromise } from 'cancelable-promise'
11+
import logger from '../logger.ts'
1212
import { getPinia } from '../store/index.ts'
1313
import { useUserConfigStore } from '../store/userconfig.ts'
1414
import { client } from './WebdavClient.ts'
@@ -22,8 +22,10 @@ const lastTwoWeeksTimestamp = Math.round((Date.now() / 1000) - (60 * 60 * 24 * 1
2222
* If hidden files are not shown, then also recently changed files *in* hidden directories are filtered.
2323
*
2424
* @param path Path to search for recent changes
25+
* @param options Options including abort signal
26+
* @param options.signal Abort signal to cancel the request
2527
*/
26-
export function getContents(path = '/'): CancelablePromise<ContentsWithRoot> {
28+
export async function getContents(path = '/', options: { signal: AbortSignal }): Promise<ContentsWithRoot> {
2729
const store = useUserConfigStore(getPinia())
2830

2931
/**
@@ -35,10 +37,9 @@ export function getContents(path = '/'): CancelablePromise<ContentsWithRoot> {
3537
|| store.userConfig.show_hidden // If configured to show hidden files we can early return
3638
|| !node.dirname.split('/').some((dir) => dir.startsWith('.')) // otherwise only include the file if non of the parent directories is hidden
3739

38-
const controller = new AbortController()
39-
const handler = async () => {
40+
try {
4041
const contentsResponse = await client.search('/', {
41-
signal: controller.signal,
42+
signal: options.signal,
4243
details: true,
4344
data: getRecentSearch(lastTwoWeeksTimestamp),
4445
}) as ResponseDataDetailed<SearchResult>
@@ -61,10 +62,12 @@ export function getContents(path = '/'): CancelablePromise<ContentsWithRoot> {
6162
}),
6263
contents,
6364
}
65+
} catch (error) {
66+
if (options.signal.aborted) {
67+
logger.info('Fetching recent files aborted')
68+
throw new DOMException('Aborted', 'AbortError')
69+
}
70+
logger.error('Failed to fetch recent files', { error })
71+
throw error
6472
}
65-
66-
return new CancelablePromise(async (resolve, reject, cancel) => {
67-
cancel(() => controller.abort())
68-
resolve(handler())
69-
})
7073
}

apps/files/src/services/Search.spec.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ describe('Search service', () => {
3535
searchNodes.mockImplementationOnce(() => {
3636
throw new Error('expected error')
3737
})
38-
expect(getContents).rejects.toThrow('expected error')
38+
expect(() => getContents('', { signal: new AbortController().signal })).rejects.toThrow('expected error')
3939
})
4040

4141
it('returns the search results and a fake root', async () => {
4242
searchNodes.mockImplementationOnce(() => [fakeFolder])
43-
const { contents, folder } = await getContents()
43+
const { contents, folder } = await getContents('', { signal: new AbortController().signal })
4444

4545
expect(searchNodes).toHaveBeenCalledOnce()
4646
expect(contents).toHaveLength(1)
@@ -57,8 +57,9 @@ describe('Search service', () => {
5757
return []
5858
})
5959

60-
const content = getContents()
61-
content.cancel()
60+
const controller = new AbortController()
61+
getContents('', { signal: controller.signal })
62+
controller.abort()
6263

6364
// its cancelled thus the promise returns the event
6465
const event = await promise

0 commit comments

Comments
 (0)