Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/globalScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
import type { IFileAction, IFileListAction } from './ui/actions/index.ts'
import type { FilesRegistry } from './ui/registry.ts'
import type { ISidebarAction, ISidebarTab } from './ui/sidebar/index.ts'
import type { Uploader } from './upload/index.ts'

interface InternalGlobalScope {
davNamespaces?: DavProperty
Expand All @@ -22,6 +23,8 @@ interface InternalGlobalScope {
navigation?: Navigation
registry?: FilesRegistry

uploader?: Uploader

fileActions?: Map<string, IFileAction>
fileListActions?: Map<string, IFileListAction>
fileListFilters?: Map<string, IFileListFilter>
Expand Down
17 changes: 17 additions & 0 deletions lib/upload/errors/UploadCancelledError.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { expect, test } from 'vitest'
import { UploadCancelledError } from './UploadCancelledError.ts'

test('UploadCancelledError', () => {
const cause = new Error('Network error')
const error = new UploadCancelledError(cause)
expect(error).toBeInstanceOf(Error)
expect(error).toBeInstanceOf(UploadCancelledError)
expect(error.message).toBe('Upload has been cancelled')
expect(error.cause).toBe(cause)
expect(error).toHaveProperty('__UPLOAD_CANCELLED__')
})
12 changes: 12 additions & 0 deletions lib/upload/errors/UploadCancelledError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

export class UploadCancelledError extends Error {
private __UPLOAD_CANCELLED__ = true

public constructor(cause?: unknown) {
super('Upload has been cancelled', { cause })
}
}
44 changes: 44 additions & 0 deletions lib/upload/errors/UploadFailedError.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*!
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { AxiosRequestHeaders, AxiosResponse } from 'axios'

import { AxiosError } from 'axios'
import { expect, test } from 'vitest'
import { UploadFailedError } from './UploadFailedError.ts'

test('UploadFailedError - axios error but no response', () => {
const cause = new AxiosError('Network error')
const error = new UploadFailedError(cause)
expect(error).toBeInstanceOf(Error)
expect(error).toBeInstanceOf(UploadFailedError)
expect(error.message).toBe('Upload has failed')
expect(error.cause).toBe(cause)
expect(error).toHaveProperty('__UPLOAD_FAILED__')
expect(error.response).toBeUndefined()
})

test('UploadFailedError - axios error', () => {
const response = {} as AxiosResponse
const cause = new AxiosError('Network error', '200', { headers: {} as AxiosRequestHeaders }, {}, response)
const error = new UploadFailedError(cause)
expect(error).toBeInstanceOf(Error)
expect(error).toBeInstanceOf(UploadFailedError)
expect(error.message).toBe('Upload has failed')
expect(error.cause).toBe(cause)
expect(error).toHaveProperty('__UPLOAD_FAILED__')
expect(error.response).toBe(response)
})

test('UploadFailedError - generic error', () => {
const cause = new Error('Generic error')
const error = new UploadFailedError(cause)
expect(error).toBeInstanceOf(Error)
expect(error).toBeInstanceOf(UploadFailedError)
expect(error.message).toBe('Upload has failed')
expect(error.cause).toBe(cause)
expect(error).toHaveProperty('__UPLOAD_FAILED__')
expect(error.response).toBeUndefined()
})
21 changes: 21 additions & 0 deletions lib/upload/errors/UploadFailedError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*!
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { AxiosResponse } from '@nextcloud/axios'

import { isAxiosError } from '@nextcloud/axios'

export class UploadFailedError extends Error {
private __UPLOAD_FAILED__ = true

readonly response?: AxiosResponse

public constructor(cause?: unknown) {
super('Upload has failed', { cause })
if (isAxiosError(cause) && cause.response) {
this.response = cause.response
}
}
}
31 changes: 31 additions & 0 deletions lib/upload/getUploader.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*!
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { join } from '@nextcloud/paths'
import { expect, test } from 'vitest'
import { defaultRemoteURL, defaultRootPath } from '../dav/dav.ts'
import { scopedGlobals } from '../globalScope.ts'
import { Folder } from '../node/folder.ts'
import { getUploader } from './getUploader.ts'
import { Uploader } from './uploader/Uploader.ts'

test('getUploader - should return the uploader instance from the global scope', async () => {
const uploader = new Uploader(false, new Folder({ owner: 'test', root: defaultRootPath, source: join(defaultRemoteURL, defaultRootPath) }))
scopedGlobals.uploader = uploader
const returnedUploader = getUploader()
expect(returnedUploader).toBe(uploader)
})

test('getUploader - should return the same instance on multiple calls', async () => {
const uploader1 = getUploader()
const uploader2 = getUploader()
expect(uploader1).toBe(uploader2)
})

test('getUploader - should not return the same instance on multiple calls with forceRecreate', async () => {
const uploader1 = getUploader(true)
const uploader2 = getUploader(true, true)
expect(uploader1).not.toBe(uploader2)
})
26 changes: 26 additions & 0 deletions lib/upload/getUploader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*!
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { isPublicShare } from '@nextcloud/sharing/public'
import { scopedGlobals } from '../globalScope.ts'
import { Uploader } from './uploader/Uploader.ts'

/**
* Get the global Uploader instance.
*
* Note: If you need a local uploader you can just create a new instance,
* this global instance will be shared with other apps and is mostly useful
* for the Files app web UI to keep track of all uploads and their progress.
*
* @param isPublic Set to true to use public upload endpoint (by default it is auto detected)
* @param forceRecreate Force a new uploader instance - main purpose is for testing
*/
export function getUploader(isPublic: boolean = isPublicShare(), forceRecreate = false): Uploader {
if (forceRecreate || scopedGlobals.uploader === undefined) {
scopedGlobals.uploader = new Uploader(isPublic)
}

return scopedGlobals.uploader
}
9 changes: 9 additions & 0 deletions lib/upload/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*!
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

export { UploadCancelledError } from './errors/UploadCancelledError.ts'
export { UploadFailedError } from './errors/UploadFailedError.ts'
export * from './uploader/index.ts'
export { getUploader } from './getUploader.ts'
Loading
Loading