Skip to content
Merged
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__')
})
16 changes: 16 additions & 0 deletions lib/upload/errors/UploadCancelledError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

export class UploadCancelledError extends Error {
__UPLOAD_CANCELLED__ = true

public constructor(cause?: unknown) {
super('Upload has been cancelled', { cause })
}

public static isCancelledError(error: unknown): error is UploadCancelledError {
return typeof error === 'object' && error !== null && (error as UploadCancelledError).__UPLOAD_CANCELLED__ === true
}
}
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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always wonder if we should not auto detect the public boolean. What if an app creates a normal instance and the other a public without forceCreate?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes we should!

but lets do changes in separate PRs this PR is mainly for copy it over and adjust code to work in this repo

if (forceRecreate || scopedGlobals.uploader === undefined) {
scopedGlobals.uploader = new Uploader(isPublic)
}

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

export type { Eta, EtaEventsMap } from './uploader/index.ts'
export type { Directory, IDirectory } from './utils/fileTree.ts'

export { getUploader } from './getUploader.ts'
export { Upload, UploadStatus } from './uploader/Upload.ts'
export { EtaStatus, Uploader, UploaderStatus } from './uploader/index.ts'
export { getConflicts, hasConflict } from './utils/conflicts.ts'
Loading