Skip to content

Commit f5820f6

Browse files
authored
Merge pull request #5443 from nextcloud/backport/5372/stable33
[stable33] fix: Check file existence before using save as
2 parents 6a0143d + c0df7e5 commit f5820f6

2 files changed

Lines changed: 59 additions & 5 deletions

File tree

lib/Controller/DirectViewController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ public function show($token) {
123123
'urlsrc' => $urlSrc,
124124
'path' => $relativePath,
125125
'direct' => true,
126+
'userId' => $direct->getUid(),
126127
];
127128

128129
return $this->documentTemplateResponse($wopi, $params);

src/components/Modal/SaveAs.vue

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
{{ t('richdocuments', 'Cancel') }}
2020
</NcButton>
2121
<NcButton type="primary"
22+
:disabled="isChecking"
2223
@click="close">
23-
{{ t('richdocuments', 'Save') }}
24+
{{ isChecking ? t('richdocuments', 'Checking…') : t('richdocuments', 'Save') }}
2425
</NcButton>
2526
</div>
2627
</div>
@@ -29,11 +30,16 @@
2930

3031
<script>
3132
import { translate as t } from '@nextcloud/l10n'
33+
import { showError } from '@nextcloud/dialogs'
34+
import { getCurrentUser } from '@nextcloud/auth'
35+
import { getClient, getDefaultPropfind, resultToNode } from '@nextcloud/files/dav'
36+
import { emit } from '@nextcloud/event-bus'
37+
import { isPublicShare, getSharingToken } from '@nextcloud/sharing/public'
3238
3339
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
3440
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
3541
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
36-
42+
import Config from '../../services/config.tsx'
3743
export default {
3844
name: 'SaveAs',
3945
components: {
@@ -67,9 +73,16 @@ export default {
6773
data() {
6874
return {
6975
selectedPath: '',
76+
isChecking: false,
7077
}
7178
},
7279
computed: {
80+
rootPath() {
81+
if (isPublicShare()) {
82+
return `/files/${getSharingToken()}`
83+
}
84+
return `/files/${getCurrentUser()?.uid ?? Config.get('userId')}`
85+
},
7386
newFileName: {
7487
get() {
7588
if (this.selectedPath !== '') {
@@ -86,7 +99,6 @@ export default {
8699
},
87100
},
88101
mounted() {
89-
// prepare filename for having a separate picker for the path (.split('/').pop())
90102
const filename = this.path
91103
const extension = filename.split('.').pop()
92104
const filenameWithoutExtension = filename.substring(0, filename.length - extension.length - 1)
@@ -98,8 +110,49 @@ export default {
98110
},
99111
methods: {
100112
t,
101-
close() {
102-
this.$emit('close', this.newFileName)
113+
async close() {
114+
if (this.isChecking) {
115+
return
116+
}
117+
118+
this.isChecking = true
119+
120+
try {
121+
const client = getClient()
122+
const filename = this.newFileName
123+
124+
// In direct editing sessions there is no authenticated DAV session
125+
// so we skip the existence check and let the backend handle conflicts.
126+
// A future improvement could use a dedicated API endpoint that validates
127+
// the WOPI token and checks file existence on behalf of the user.
128+
if (!Config.get('direct')) {
129+
try {
130+
const result = await client.stat(`${this.rootPath}/${filename}`, {
131+
details: true,
132+
data: getDefaultPropfind(),
133+
})
134+
135+
if (result) {
136+
showError(t('richdocuments', 'A file with that name already exists.'))
137+
const node = resultToNode(result.data)
138+
emit('files:node:updated', node)
139+
this.isChecking = false
140+
return
141+
}
142+
} catch (error) {
143+
if (error.response?.status !== 404) {
144+
console.error('Error checking file existence:', error)
145+
showError(t('richdocuments', 'Error checking if file exists.'))
146+
this.isChecking = false
147+
return
148+
}
149+
}
150+
}
151+
152+
this.$emit('close', this.newFileName)
153+
} finally {
154+
this.isChecking = false
155+
}
103156
},
104157
cancel() {
105158
this.$emit('close', null)

0 commit comments

Comments
 (0)