Skip to content

Commit b96e0aa

Browse files
authored
Merge pull request #59042 from nextcloud/backport/59033/stable33
[stable33] fix(files_external): properly handle API errors
2 parents 5f74a4a + 14ccbda commit b96e0aa

31 files changed

Lines changed: 88 additions & 46 deletions

apps/files_external/src/components/AddExternalStorageDialog/AddExternalStorageDialog.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const open = defineModel<boolean>('open', { default: true })
3333
const {
3434
storage = { backendOptions: {}, mountOptions: {}, type: isAdmin ? 'system' : 'personal' },
3535
} = defineProps<{
36-
storage?: Partial<IStorage> & { backendOptions: IStorage['backendOptions'] }
36+
storage?: Partial<IStorage>
3737
}>()
3838
3939
defineEmits<{
@@ -88,7 +88,7 @@ watch(authMechanisms, () => {
8888
:label="t('files_external', 'Folder name')"
8989
required />
9090

91-
<MountOptions v-model="internalStorage.mountOptions" />
91+
<MountOptions v-model="internalStorage.mountOptions!" />
9292

9393
<ApplicableEntities
9494
v-if="isAdmin"
@@ -112,13 +112,13 @@ watch(authMechanisms, () => {
112112
required />
113113

114114
<BackendConfiguration
115-
v-if="backend"
115+
v-if="backend && internalStorage.backendOptions"
116116
v-model="internalStorage.backendOptions"
117117
:class="$style.externalStorageDialog__configuration"
118118
:configuration="backend.configuration" />
119119

120120
<AuthMechanismConfiguration
121-
v-if="authMechanism"
121+
v-if="authMechanism && internalStorage.backendOptions"
122122
v-model="internalStorage.backendOptions"
123123
:class="$style.externalStorageDialog__configuration"
124124
:authMechanism="authMechanism" />

apps/files_external/src/components/AddExternalStorageDialog/MountOptions.vue

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,14 @@ import NcButton from '@nextcloud/vue/components/NcButton'
1414
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
1515
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
1616
import NcSelect from '@nextcloud/vue/components/NcSelect'
17+
import { parseMountOptions } from '../../store/storages.ts'
1718
import { MountOptionsCheckFilesystem } from '../../types.ts'
1819
1920
const mountOptions = defineModel<Partial<IMountOptions>>({ required: true })
2021
watchEffect(() => {
2122
if (Object.keys(mountOptions.value).length === 0) {
22-
mountOptions.value.encrypt = true
23-
mountOptions.value.previews = true
24-
mountOptions.value.enable_sharing = false
25-
mountOptions.value.filesystem_check_changes = MountOptionsCheckFilesystem.OncePerRequest
26-
mountOptions.value.encoding_compatibility = false
27-
mountOptions.value.readonly = false
23+
// parse and initialize with defaults if needed
24+
mountOptions.value = parseMountOptions(mountOptions.value)
2825
}
2926
})
3027

apps/files_external/src/store/storages.ts

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import type { IStorage } from '../types.d.ts'
6+
import type { IStorage } from '../types.ts'
77

88
import axios from '@nextcloud/axios'
99
import { loadState } from '@nextcloud/initial-state'
1010
import { addPasswordConfirmationInterceptors, PwdConfirmationMode } from '@nextcloud/password-confirmation'
1111
import { generateUrl } from '@nextcloud/router'
1212
import { defineStore } from 'pinia'
1313
import { ref, toRaw } from 'vue'
14+
import { MountOptionsCheckFilesystem } from '../types.ts'
1415

1516
const { isAdmin } = loadState<{ isAdmin: boolean }>('files_external', 'settings')
1617

@@ -30,7 +31,7 @@ export const useStorages = defineStore('files_external--storages', () => {
3031
toRaw(storage),
3132
{ confirmPassword: PwdConfirmationMode.Strict },
3233
)
33-
globalStorages.value.push(data)
34+
globalStorages.value.push(parseStorage(data))
3435
}
3536

3637
/**
@@ -45,7 +46,7 @@ export const useStorages = defineStore('files_external--storages', () => {
4546
toRaw(storage),
4647
{ confirmPassword: PwdConfirmationMode.Strict },
4748
)
48-
userStorages.value.push(data)
49+
userStorages.value.push(parseStorage(data))
4950
}
5051

5152
/**
@@ -77,7 +78,7 @@ export const useStorages = defineStore('files_external--storages', () => {
7778
{ confirmPassword: PwdConfirmationMode.Strict },
7879
)
7980

80-
overrideStorage(data)
81+
overrideStorage(parseStorage(data))
8182
}
8283

8384
/**
@@ -87,7 +88,7 @@ export const useStorages = defineStore('files_external--storages', () => {
8788
*/
8889
async function reloadStorage(storage: IStorage) {
8990
const { data } = await axios.get(getUrl(storage))
90-
overrideStorage(data)
91+
overrideStorage(parseStorage(data))
9192
}
9293

9394
// initialize the store
@@ -111,6 +112,7 @@ export const useStorages = defineStore('files_external--storages', () => {
111112
const url = `apps/files_external/${type}`
112113
const { data } = await axios.get<Record<number, IStorage>>(generateUrl(url))
113114
return Object.values(data)
115+
.map(parseStorage)
114116
}
115117

116118
/**
@@ -150,3 +152,45 @@ export const useStorages = defineStore('files_external--storages', () => {
150152
}
151153
}
152154
})
155+
156+
/**
157+
* @param storage - The storage from API
158+
*/
159+
function parseStorage(storage: IStorage) {
160+
return {
161+
...storage,
162+
mountOptions: parseMountOptions(storage.mountOptions),
163+
}
164+
}
165+
166+
/**
167+
* Parse the mount options and convert string boolean values to
168+
* actual booleans and numeric strings to numbers
169+
*
170+
* @param options - The mount options to parse
171+
*/
172+
export function parseMountOptions(options: IStorage['mountOptions']) {
173+
const mountOptions = { ...options }
174+
mountOptions.encrypt = convertBooleanOptions(mountOptions.encrypt, true)
175+
mountOptions.previews = convertBooleanOptions(mountOptions.previews, true)
176+
mountOptions.enable_sharing = convertBooleanOptions(mountOptions.enable_sharing, false)
177+
mountOptions.filesystem_check_changes = typeof mountOptions.filesystem_check_changes === 'string'
178+
? Number.parseInt(mountOptions.filesystem_check_changes)
179+
: (mountOptions.filesystem_check_changes ?? MountOptionsCheckFilesystem.OncePerRequest)
180+
mountOptions.encoding_compatibility = convertBooleanOptions(mountOptions.encoding_compatibility, false)
181+
mountOptions.readonly = convertBooleanOptions(mountOptions.readonly, false)
182+
return mountOptions
183+
}
184+
185+
/**
186+
* Convert backend encoding of boolean options
187+
*
188+
* @param option - The option value from API
189+
* @param fallback - The fallback (default) value
190+
*/
191+
function convertBooleanOptions(option: unknown, fallback = false) {
192+
if (option === undefined) {
193+
return fallback
194+
}
195+
return option === true || option === 'true' || option === '1'
196+
}

apps/files_external/src/views/ExternalStoragesSection.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ async function addStorage(storage?: Partial<IStorage>) {
5959
}
6060
newStorage.value = undefined
6161
} catch (error) {
62-
logger.error('Failed to add external storage', { error })
62+
logger.error('Failed to add external storage', { error, storage })
63+
newStorage.value = { ...storage }
6364
showDialog.value = true
6465
}
6566
}
@@ -134,8 +135,8 @@ async function addStorage(storage?: Partial<IStorage>) {
134135
</NcButton>
135136

136137
<AddExternalStorageDialog
137-
v-model="newStorage"
138138
v-model:open="showDialog"
139+
:storage="newStorage"
139140
@close="addStorage" />
140141

141142
<UserMountSettings v-if="settings.isAdmin" />

0 commit comments

Comments
 (0)