fix(files): protect filelist actions with try...catch

Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
This commit is contained in:
skjnldsv 2024-12-12 10:50:39 +01:00 committed by Christopher Ng
parent 9c08d3b0b8
commit 34299e6a5d
2 changed files with 50 additions and 10 deletions

View file

@ -44,13 +44,15 @@
force-name>
<NcActionButton v-for="action in enabledFileListActions"
:key="action.id"
:disabled="!!loadingAction"
close-after-click
@click="action.exec(currentView, dirContents, currentFolder)">
@click="execFileListAction(action)">
<template #icon>
<NcIconSvgWrapper v-if="action.iconSvgInline !== undefined"
<NcLoadingIcon v-if="loadingAction === action.id" :size="18" />
<NcIconSvgWrapper v-else-if="action.iconSvgInline !== undefined && currentView"
:svg="action.iconSvgInline(currentView)" />
</template>
{{ action.displayName(currentView) }}
{{ actionDisplayName(action) }}
</NcActionButton>
</NcActions>
@ -132,7 +134,7 @@
</template>
<script lang="ts">
import type { ContentsWithRoot, Folder, INode } from '@nextcloud/files'
import type { ContentsWithRoot, FileListAction, Folder, INode } from '@nextcloud/files'
import type { Upload } from '@nextcloud/upload'
import type { CancelablePromise } from 'cancelable-promise'
import type { ComponentPublicInstance } from 'vue'
@ -144,7 +146,7 @@ import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
import { Node, Permission, sortNodes, getFileListActions } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { join, dirname, normalize } from 'path'
import { showError, showWarning } from '@nextcloud/dialogs'
import { showError, showSuccess, showWarning } from '@nextcloud/dialogs'
import { ShareType } from '@nextcloud/sharing'
import { UploadPicker, UploadStatus } from '@nextcloud/upload'
import { loadState } from '@nextcloud/initial-state'
@ -258,6 +260,7 @@ export default defineComponent({
data() {
return {
loading: true,
loadingAction: null as string | null,
error: null as string | null,
promise: null as CancelablePromise<ContentsWithRoot> | Promise<ContentsWithRoot> | null,
@ -437,6 +440,10 @@ export default defineComponent({
},
enabledFileListActions() {
if (!this.currentView || !this.currentFolder) {
return []
}
const actions = getFileListActions()
const enabledActions = actions
.filter(action => {
@ -446,7 +453,7 @@ export default defineComponent({
return action.enabled(
this.currentView!,
this.dirContents,
{ folder: this.currentFolder! },
this.currentFolder as Folder,
)
})
.toSorted((a, b) => a.order - b.order)
@ -714,6 +721,40 @@ export default defineComponent({
}
this.dirContentsFiltered = nodes
},
actionDisplayName(action: FileListAction): string {
let displayName = action.id
try {
displayName = action.displayName(this.currentView!)
} catch (error) {
logger.error('Error while getting action display name', { action, error })
}
return displayName
},
async execFileListAction(action: FileListAction) {
this.loadingAction = action.id
const displayName = this.actionDisplayName(action)
try {
const success = await action.exec(this.source, this.dirContents, this.currentDir)
// If the action returns null, we stay silent
if (success === null || success === undefined) {
return
}
if (success) {
showSuccess(t('files', '"{displayName}" action executed successfully', { displayName }))
return
}
showError(t('files', '"{displayName}" action failed', { displayName }))
} catch (error) {
logger.error('Error while executing action', { action, error })
showError(t('files', '"{displayName}" action failed', { displayName }))
} finally {
this.loadingAction = null
}
},
},
})
</script>

View file

@ -2,8 +2,7 @@
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Node } from '@nextcloud/files'
import type { Node, View, Folder } from '@nextcloud/files'
import PQueue from 'p-queue'
import { FileListAction } from '@nextcloud/files'
@ -73,14 +72,14 @@ export const emptyTrashAction = new FileListAction({
displayName: () => t('files_trashbin', 'Empty deleted files'),
order: 0,
enabled: (view, nodes, folder) => {
enabled(view: View, nodes: Node[], folder: Folder) {
if (view.id !== 'trashbin') {
return false
}
return nodes.length > 0 && folder.path === '/'
},
exec: async (view, nodes) => {
async exec(view: View, nodes: Node[]) {
const dialog = getDialogBuilder(t('files_trashbin', 'Confirm permanent deletion'))
.setSeverity(DialogSeverity.Warning)
// TODO Add note for groupfolders