chore(files): adjust getContents to use AbortController

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2025-12-21 20:08:36 +01:00
parent 77f9897060
commit 9919c2bc91
No known key found for this signature in database
GPG key ID: 45FAE7268762B400
11 changed files with 199 additions and 194 deletions

View file

@ -1,45 +1,49 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { ContentsWithRoot } from '@nextcloud/files'
import { getCurrentUser } from '@nextcloud/auth'
import { Folder, Permission } from '@nextcloud/files'
import { getFavoriteNodes, getRemoteURL, getRootPath } from '@nextcloud/files/dav'
import { CancelablePromise } from 'cancelable-promise'
import logger from '../logger.ts'
import { getContents as filesContents } from './Files.ts'
import { client } from './WebdavClient.ts'
/**
* Get the contents for the favorites view
*
* @param path
* @param path - The path to get the contents for
* @param options - Additional options
* @param options.signal - Optional AbortSignal to cancel the request
* @return A promise resolving to the contents with root folder
*/
export function getContents(path = '/'): CancelablePromise<ContentsWithRoot> {
export async function getContents(path = '/', options: { signal: AbortSignal }): Promise<ContentsWithRoot> {
// We only filter root files for favorites, for subfolders we can simply reuse the files contents
if (path !== '/') {
return filesContents(path)
if (path && path !== '/') {
return filesContents(path, options)
}
return new CancelablePromise((resolve, reject, cancel) => {
const promise = getFavoriteNodes(client)
.catch(reject)
.then((contents) => {
if (!contents) {
reject()
return
}
resolve({
contents,
folder: new Folder({
id: 0,
source: `${getRemoteURL()}${getRootPath()}`,
root: getRootPath(),
owner: getCurrentUser()?.uid || null,
permissions: Permission.READ,
}),
})
})
cancel(() => promise.cancel())
})
try {
const contents = await getFavoriteNodes({ client, signal: options.signal })
return {
contents,
folder: new Folder({
id: 0,
source: `${getRemoteURL()}${getRootPath()}`,
root: getRootPath(),
owner: getCurrentUser()?.uid || null,
permissions: Permission.READ,
}),
}
} catch (error) {
if (options.signal.aborted) {
logger.debug('Favorite nodes request was aborted')
throw new DOMException('Aborted', 'AbortError')
}
logger.error('Failed to load favorite nodes via WebDAV', { error })
throw error
}
}

View file

@ -6,7 +6,6 @@ import type { ContentsWithRoot, File, Folder } from '@nextcloud/files'
import type { FileStat, ResponseDataDetailed } from 'webdav'
import { getDefaultPropfind, getRootPath, resultToNode } from '@nextcloud/files/dav'
import { CancelablePromise } from 'cancelable-promise'
import { join } from 'path'
import logger from '../logger.ts'
import { useFilesStore } from '../store/files.ts'
@ -20,66 +19,55 @@ import { searchNodes } from './WebDavSearch.ts'
* This also allows to fetch local search results when the user is currently filtering.
*
* @param path - The path to query
* @param options - Options
* @param options.signal - Abort signal to cancel the request
*/
export function getContents(path = '/'): CancelablePromise<ContentsWithRoot> {
const controller = new AbortController()
export async function getContents(path = '/', options?: { signal: AbortSignal }): Promise<ContentsWithRoot> {
const searchStore = useSearchStore(getPinia())
if (searchStore.query.length >= 3) {
return new CancelablePromise((resolve, reject, cancel) => {
cancel(() => controller.abort())
getLocalSearch(path, searchStore.query, controller.signal)
.then(resolve)
.catch(reject)
})
} else {
return defaultGetContents(path)
if (searchStore.query.length < 3) {
return await defaultGetContents(path, options)
}
return await getLocalSearch(path, searchStore.query, options?.signal)
}
/**
* Generic `getContents` implementation for the users files.
*
* @param path - The path to get the contents
* @param options - Options
* @param options.signal - Abort signal to cancel the request
*/
export function defaultGetContents(path: string): CancelablePromise<ContentsWithRoot> {
export async function defaultGetContents(path: string, options?: { signal: AbortSignal }): Promise<ContentsWithRoot> {
path = join(getRootPath(), path)
const controller = new AbortController()
const propfindPayload = getDefaultPropfind()
return new CancelablePromise(async (resolve, reject, onCancel) => {
onCancel(() => controller.abort())
const contentsResponse = await client.getDirectoryContents(path, {
details: true,
data: propfindPayload,
includeSelf: true,
signal: options?.signal,
}) as ResponseDataDetailed<FileStat[]>
try {
const contentsResponse = await client.getDirectoryContents(path, {
details: true,
data: propfindPayload,
includeSelf: true,
signal: controller.signal,
}) as ResponseDataDetailed<FileStat[]>
const root = contentsResponse.data[0]!
const contents = contentsResponse.data.slice(1)
if (root?.filename !== path && `${root?.filename}/` !== path) {
logger.debug(`Exepected "${path}" but got filename "${root.filename}" instead.`)
throw new Error('Root node does not match requested path')
}
const root = contentsResponse.data[0]
const contents = contentsResponse.data.slice(1)
if (root?.filename !== path && `${root?.filename}/` !== path) {
logger.debug(`Exepected "${path}" but got filename "${root.filename}" instead.`)
throw new Error('Root node does not match requested path')
return {
folder: resultToNode(root) as Folder,
contents: contents.map((result) => {
try {
return resultToNode(result)
} catch (error) {
logger.error(`Invalid node detected '${result.basename}'`, { error })
return null
}
resolve({
folder: resultToNode(root) as Folder,
contents: contents.map((result) => {
try {
return resultToNode(result)
} catch (error) {
logger.error(`Invalid node detected '${result.basename}'`, { error })
return null
}
}).filter(Boolean) as File[],
})
} catch (error) {
reject(error)
}
})
}).filter(Boolean) as File[],
}
}
/**
@ -89,7 +77,7 @@ export function defaultGetContents(path: string): CancelablePromise<ContentsWith
* @param query - The current search query
* @param signal - The aboort signal
*/
async function getLocalSearch(path: string, query: string, signal: AbortSignal): Promise<ContentsWithRoot> {
async function getLocalSearch(path: string, query: string, signal?: AbortSignal): Promise<ContentsWithRoot> {
const filesStore = useFilesStore(getPinia())
let folder = filesStore.getDirectoryByPath('files', path)
if (!folder) {

View file

@ -4,7 +4,6 @@
*/
import type { ContentsWithRoot } from '@nextcloud/files'
import type { CancelablePromise } from 'cancelable-promise'
import { getCurrentUser } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
@ -47,10 +46,11 @@ const collator = Intl.Collator(
const compareNodes = (a: TreeNodeData, b: TreeNodeData) => collator.compare(a.displayName ?? a.basename, b.displayName ?? b.basename)
/**
* Get all tree nodes recursively
*
* @param tree
* @param currentPath
* @param nodes
* @param tree - The tree to process
* @param currentPath - The current path
* @param nodes - The nodes collected so far
*/
function getTreeNodes(tree: Tree, currentPath: string = '/', nodes: TreeNode[] = []): TreeNode[] {
const sortedTree = tree.toSorted(compareNodes)
@ -76,9 +76,10 @@ function getTreeNodes(tree: Tree, currentPath: string = '/', nodes: TreeNode[] =
}
/**
* Get folder tree nodes
*
* @param path
* @param depth
* @param path - The path to get the tree from
* @param depth - The depth to fetch
*/
export async function getFolderTreeNodes(path: string = '/', depth: number = 1): Promise<TreeNode[]> {
const { data: tree } = await axios.get<Tree>(generateOcsUrl('/apps/files/api/v1/folder-tree'), {
@ -88,11 +89,12 @@ export async function getFolderTreeNodes(path: string = '/', depth: number = 1):
return nodes
}
export const getContents = (path: string): CancelablePromise<ContentsWithRoot> => getFiles(path)
export const getContents = (path: string, options: { signal: AbortSignal }): Promise<ContentsWithRoot> => getFiles(path, options)
/**
* Encode source URL
*
* @param source
* @param source - The source URL
*/
export function encodeSource(source: string): string {
const { origin } = new URL(source)
@ -100,8 +102,9 @@ export function encodeSource(source: string): string {
}
/**
* Get parent source URL
*
* @param source
* @param source - The source URL
*/
export function getSourceParent(source: string): string {
const parent = dirname(source)

View file

@ -4,7 +4,6 @@
*/
import type { ContentsWithRoot, Node } from '@nextcloud/files'
import type { CancelablePromise } from 'cancelable-promise'
import { getCurrentUser } from '@nextcloud/auth'
import { getContents as getFiles } from './Files.ts'
@ -31,13 +30,17 @@ export function isPersonalFile(node: Node): boolean {
}
/**
* Get personal files from a given path
*
* @param path
* @param path - The path to get the personal files from
* @param options - Options
* @param options.signal - Abort signal to cancel the request
* @return A promise that resolves to the personal files
*/
export function getContents(path: string = '/'): CancelablePromise<ContentsWithRoot> {
export function getContents(path: string = '/', options: { signal: AbortSignal }): Promise<ContentsWithRoot> {
// get all the files from the current path as a cancellable promise
// then filter the files that the user does not own, or has shared / is a group folder
return getFiles(path)
return getFiles(path, options)
.then((content) => {
content.contents = content.contents.filter(isPersonalFile)
return content

View file

@ -8,7 +8,7 @@ import type { ResponseDataDetailed, SearchResult } from 'webdav'
import { getCurrentUser } from '@nextcloud/auth'
import { Folder, Permission } from '@nextcloud/files'
import { getRecentSearch, getRemoteURL, getRootPath, resultToNode } from '@nextcloud/files/dav'
import { CancelablePromise } from 'cancelable-promise'
import logger from '../logger.ts'
import { getPinia } from '../store/index.ts'
import { useUserConfigStore } from '../store/userconfig.ts'
import { client } from './WebdavClient.ts'
@ -22,8 +22,10 @@ const lastTwoWeeksTimestamp = Math.round((Date.now() / 1000) - (60 * 60 * 24 * 1
* If hidden files are not shown, then also recently changed files *in* hidden directories are filtered.
*
* @param path Path to search for recent changes
* @param options Options including abort signal
* @param options.signal Abort signal to cancel the request
*/
export function getContents(path = '/'): CancelablePromise<ContentsWithRoot> {
export async function getContents(path = '/', options: { signal: AbortSignal }): Promise<ContentsWithRoot> {
const store = useUserConfigStore(getPinia())
/**
@ -35,10 +37,9 @@ export function getContents(path = '/'): CancelablePromise<ContentsWithRoot> {
|| store.userConfig.show_hidden // If configured to show hidden files we can early return
|| !node.dirname.split('/').some((dir) => dir.startsWith('.')) // otherwise only include the file if non of the parent directories is hidden
const controller = new AbortController()
const handler = async () => {
try {
const contentsResponse = await client.search('/', {
signal: controller.signal,
signal: options.signal,
details: true,
data: getRecentSearch(lastTwoWeeksTimestamp),
}) as ResponseDataDetailed<SearchResult>
@ -61,10 +62,12 @@ export function getContents(path = '/'): CancelablePromise<ContentsWithRoot> {
}),
contents,
}
} catch (error) {
if (options.signal.aborted) {
logger.info('Fetching recent files aborted')
throw new DOMException('Aborted', 'AbortError')
}
logger.error('Failed to fetch recent files', { error })
throw error
}
return new CancelablePromise(async (resolve, reject, cancel) => {
cancel(() => controller.abort())
resolve(handler())
})
}

View file

@ -35,12 +35,12 @@ describe('Search service', () => {
searchNodes.mockImplementationOnce(() => {
throw new Error('expected error')
})
expect(getContents).rejects.toThrow('expected error')
expect(() => getContents('', { signal: new AbortController().signal })).rejects.toThrow('expected error')
})
it('returns the search results and a fake root', async () => {
searchNodes.mockImplementationOnce(() => [fakeFolder])
const { contents, folder } = await getContents()
const { contents, folder } = await getContents('', { signal: new AbortController().signal })
expect(searchNodes).toHaveBeenCalledOnce()
expect(contents).toHaveLength(1)
@ -57,8 +57,9 @@ describe('Search service', () => {
return []
})
const content = getContents()
content.cancel()
const controller = new AbortController()
getContents('', { signal: controller.signal })
controller.abort()
// its cancelled thus the promise returns the event
const event = await promise

View file

@ -8,7 +8,6 @@ import type { ContentsWithRoot } from '@nextcloud/files'
import { getCurrentUser } from '@nextcloud/auth'
import { Folder, Permission } from '@nextcloud/files'
import { defaultRemoteURL, getRootPath } from '@nextcloud/files/dav'
import { CancelablePromise } from 'cancelable-promise'
import logger from '../logger.ts'
import { getPinia } from '../store/index.ts'
import { useSearchStore } from '../store/search.ts'
@ -16,29 +15,32 @@ import { searchNodes } from './WebDavSearch.ts'
/**
* Get the contents for a search view
*
* @param path - (not used)
* @param options - Options including abort signal
* @param options.signal - Abort signal to cancel the request
*/
export function getContents(): CancelablePromise<ContentsWithRoot> {
const controller = new AbortController()
export async function getContents(path, options: { signal: AbortSignal }): Promise<ContentsWithRoot> {
const searchStore = useSearchStore(getPinia())
return new CancelablePromise<ContentsWithRoot>(async (resolve, reject, cancel) => {
cancel(() => controller.abort())
try {
const contents = await searchNodes(searchStore.query, { signal: controller.signal })
resolve({
contents,
folder: new Folder({
id: 0,
source: `${defaultRemoteURL}${getRootPath()}}#search`,
owner: getCurrentUser()!.uid,
permissions: Permission.READ,
root: getRootPath(),
}),
})
} catch (error) {
logger.error('Failed to fetch search results', { error })
reject(error)
try {
const contents = await searchNodes(searchStore.query, { signal: options.signal })
return {
contents,
folder: new Folder({
id: 0,
source: `${defaultRemoteURL}${getRootPath()}}#search`,
owner: getCurrentUser()!.uid,
permissions: Permission.READ,
root: getRootPath(),
}),
}
})
} catch (error) {
if (options.signal.aborted) {
logger.info('Fetching search results aborted')
throw new DOMException('Aborted', 'AbortError')
}
logger.error('Failed to fetch search results', { error })
throw error
}
}

View file

@ -161,7 +161,6 @@
<script lang="ts">
import type { ContentsWithRoot, FileListAction, INode, Node } from '@nextcloud/files'
import type { Upload } from '@nextcloud/upload'
import type { CancelablePromise } from 'cancelable-promise'
import type { ComponentPublicInstance } from 'vue'
import type { Route } from 'vue-router'
import type { UserConfig } from '../types.ts'
@ -295,7 +294,8 @@ export default defineComponent({
loading: true,
loadingAction: null as string | null,
error: null as string | null,
promise: null as CancelablePromise<ContentsWithRoot> | Promise<ContentsWithRoot> | null,
controller: new AbortController(),
promise: null as Promise<ContentsWithRoot> | null,
dirContentsFiltered: [] as INode[],
}
@ -640,13 +640,14 @@ export default defineComponent({
logger.debug('Fetching contents for directory', { dir, currentView })
// If we have a cancellable promise ongoing, cancel it
if (this.promise && 'cancel' in this.promise) {
this.promise.cancel()
if (this.promise) {
this.controller.abort()
logger.debug('Cancelled previous ongoing fetch')
}
// Fetch the current dir contents
this.promise = currentView.getContents(dir) as Promise<ContentsWithRoot>
this.controller = new AbortController()
this.promise = currentView.getContents(dir, { signal: this.controller.signal })
try {
const { folder, contents } = await this.promise
logger.debug('Fetched contents', { dir, folder, contents })

View file

@ -3,12 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Folder as CFolder, Navigation } from '@nextcloud/files'
import type { Navigation, Folder as NcFolder } from '@nextcloud/files'
import * as eventBus from '@nextcloud/event-bus'
import * as filesUtils from '@nextcloud/files'
import * as filesDavUtils from '@nextcloud/files/dav'
import { CancelablePromise } from 'cancelable-promise'
import { basename } from 'path'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import { action } from '../actions/favoriteAction.ts'
@ -42,8 +41,8 @@ describe('Favorites view definition', () => {
test('Default empty favorite view', async () => {
vi.spyOn(eventBus, 'subscribe')
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([]))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(Promise.resolve([]))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as NcFolder, contents: [] }))
await registerFavoritesView()
const favoritesView = Navigation.views.find((view) => view.id === 'favorites')
@ -95,8 +94,8 @@ describe('Favorites view definition', () => {
owner: 'admin',
}),
]
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve(favoriteFolders))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(Promise.resolve(favoriteFolders))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as NcFolder, contents: favoriteFolders }))
await registerFavoritesView()
const favoritesView = Navigation.views.find((view) => view.id === 'favorites')
@ -140,8 +139,8 @@ describe('Dynamic update of favorite folders', () => {
test('Add a favorite folder creates a new entry in the navigation', async () => {
vi.spyOn(eventBus, 'emit')
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([]))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(Promise.resolve([]))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as NcFolder, contents: [] }))
await registerFavoritesView()
const favoritesView = Navigation.views.find((view) => view.id === 'favorites')
@ -164,7 +163,7 @@ describe('Dynamic update of favorite folders', () => {
await action.exec({
nodes: [folder],
view: favoritesView,
folder: {} as CFolder,
folder: {} as NcFolder,
contents: [],
})
@ -173,16 +172,15 @@ describe('Dynamic update of favorite folders', () => {
})
test('Remove a favorite folder remove the entry from the navigation column', async () => {
const favoriteFolders = [new Folder({
id: 42,
root: '/files/admin',
source: 'http://nextcloud.local/remote.php/dav/files/admin/Foo/Bar',
owner: 'admin',
})]
vi.spyOn(eventBus, 'emit')
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([
new Folder({
id: 42,
root: '/files/admin',
source: 'http://nextcloud.local/remote.php/dav/files/admin/Foo/Bar',
owner: 'admin',
}),
]))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(Promise.resolve(favoriteFolders))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as NcFolder, contents: favoriteFolders }))
await registerFavoritesView()
let favoritesView = Navigation.views.find((view) => view.id === 'favorites')
@ -211,7 +209,7 @@ describe('Dynamic update of favorite folders', () => {
await action.exec({
nodes: [folder],
view: favoritesView,
folder: {} as CFolder,
folder: {} as NcFolder,
contents: [],
})
@ -230,8 +228,8 @@ describe('Dynamic update of favorite folders', () => {
test('Renaming a favorite folder updates the navigation', async () => {
vi.spyOn(eventBus, 'emit')
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([]))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(Promise.resolve([]))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as NcFolder, contents: [] }))
await registerFavoritesView()
const favoritesView = Navigation.views.find((view) => view.id === 'favorites')
@ -256,7 +254,7 @@ describe('Dynamic update of favorite folders', () => {
await action.exec({
nodes: [folder],
view: favoritesView,
folder: {} as CFolder,
folder: {} as NcFolder,
contents: [],
})
expect(eventBus.emit).toHaveBeenNthCalledWith(1, 'files:favorites:added', folder)

View file

@ -1,4 +1,4 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@ -9,17 +9,16 @@ import FolderSvg from '@mdi/svg/svg/folder-outline.svg?raw'
import StarSvg from '@mdi/svg/svg/star-outline.svg?raw'
import { subscribe } from '@nextcloud/event-bus'
import { FileType, getNavigation, View } from '@nextcloud/files'
import { getFavoriteNodes } from '@nextcloud/files/dav'
import { getCanonicalLocale, getLanguage, t } from '@nextcloud/l10n'
import logger from '../logger.ts'
import { getContents } from '../services/Favorites.ts'
import { client } from '../services/WebdavClient.ts'
import { hashCode } from '../utils/hashUtils.ts'
/**
* Generate a favorite folder view
*
* @param folder
* @param index
* @param folder - The folder to generate the view for
* @param index - The order index
*/
function generateFavoriteFolderView(folder: Folder, index = 0): View {
return new View({
@ -44,15 +43,16 @@ function generateFavoriteFolderView(folder: Folder, index = 0): View {
}
/**
* Generate a unique id from the folder path
*
* @param path
* @param path - The folder path
*/
function generateIdFromPath(path: string): string {
return `favorite-${hashCode(path)}`
}
/**
*
* Register the favorites view and setup event listeners to update it
*/
export async function registerFavoritesView() {
const Navigation = getNavigation()
@ -72,8 +72,11 @@ export async function registerFavoritesView() {
getContents,
}))
const favoriteFolders = (await getFavoriteNodes(client)).filter((node) => node.type === FileType.Folder) as Folder[]
const favoriteFoldersViews = favoriteFolders.map((folder, index) => generateFavoriteFolderView(folder, index)) as View[]
const controller = new AbortController()
const favoriteFolders = (await getContents('', { signal: controller.signal })).contents
.filter((node) => node.type === FileType.Folder) as Folder[]
const favoriteFoldersViews = favoriteFolders
.map((folder, index) => generateFavoriteFolderView(folder, index)) as View[]
logger.debug('Generating favorites view', { favoriteFolders })
favoriteFoldersViews.forEach((view) => Navigation.register(view))
@ -143,7 +146,7 @@ export async function registerFavoritesView() {
/**
* Add a folder to the favorites paths array and update the views
*
* @param node
* @param node - The folder node
*/
function addToFavorites(node: Folder) {
const view = generateFavoriteFolderView(node)
@ -165,7 +168,7 @@ export async function registerFavoritesView() {
/**
* Remove a folder from the favorites paths array and update the views
*
* @param path
* @param path - The folder path
*/
function removePathFromFavorites(path: string) {
const id = generateIdFromPath(path)
@ -188,7 +191,7 @@ export async function registerFavoritesView() {
/**
* Update a folder from the favorites paths array and update the views
*
* @param node
* @param node - The updated folder node
*/
function updateNodeFromFavorites(node: Folder) {
const favoriteFolder = favoriteFolders.find((folder) => folder.fileid === node.fileid)

View file

@ -9,7 +9,6 @@ import LinkSvg from '@mdi/svg/svg/link.svg?raw'
import { Folder, getNavigation, Permission, View } from '@nextcloud/files'
import { getDefaultPropfind, getRemoteURL, getRootPath, resultToNode } from '@nextcloud/files/dav'
import { translate as t } from '@nextcloud/l10n'
import { CancelablePromise } from 'cancelable-promise'
import { client } from '../../../files/src/services/WebdavClient.ts'
import logger from '../services/logger.ts'
@ -25,41 +24,41 @@ export default () => {
icon: LinkSvg,
order: 1,
getContents: () => {
return new CancelablePromise(async (resolve, reject, onCancel) => {
const abort = new AbortController()
onCancel(() => abort.abort())
try {
const node = await client.stat(
getRootPath(),
{
data: getDefaultPropfind(),
details: true,
signal: abort.signal,
},
) as ResponseDataDetailed<FileStat>
getContents: async (path, { signal }) => {
try {
const node = await client.stat(
getRootPath(),
{
data: getDefaultPropfind(),
details: true,
signal,
},
) as ResponseDataDetailed<FileStat>
resolve({
// We only have one file as the content
contents: [resultToNode(node.data)],
// Fake a readonly folder as root
folder: new Folder({
id: 0,
source: `${getRemoteURL()}${getRootPath()}`,
root: getRootPath(),
owner: null,
permissions: Permission.READ,
attributes: {
// Ensure the share note is set on the root
note: node.data.props?.note,
},
}),
})
} catch (e) {
logger.error(e as Error)
reject(e as Error)
return {
// We only have one file as the content
contents: [resultToNode(node.data)],
// Fake a readonly folder as root
folder: new Folder({
id: 0,
source: `${getRemoteURL()}${getRootPath()}`,
root: getRootPath(),
owner: null,
permissions: Permission.READ,
attributes: {
// Ensure the share note is set on the root
note: node.data.props?.note,
},
}),
}
})
} catch (error) {
if (signal.aborted) {
logger.info('Fetching contents for public file share was aborted', { error })
throw new DOMException('Aborted', 'AbortError')
}
logger.error('Failed to get contents for public file share', { error })
throw error
}
},
})