Merge pull request #41346 from nextcloud/fix/files/quota-warning

This commit is contained in:
John Molakvoæ 2023-12-05 14:18:25 +01:00 committed by GitHub
commit 788fc781be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 132 additions and 31 deletions

View file

@ -193,6 +193,8 @@ class QuotaPlugin extends \Sabre\DAV\ServerPlugin {
$parentPath = '';
}
$req = $this->server->httpRequest;
// If chunked upload
if ($req->getHeader('OC-Chunked')) {
$info = \OC_FileChunking::decodeName($newName);
$chunkHandler = $this->getFileChunking($info);
@ -202,6 +204,10 @@ class QuotaPlugin extends \Sabre\DAV\ServerPlugin {
// use target file name for free space check in case of shared files
$path = rtrim($parentPath, '/') . '/' . $info['name'];
}
// Strip any duplicate slashes
$path = str_replace('//', '/', $path);
$freeSpace = $this->getFreeSpace($path);
if ($freeSpace >= 0 && $length > $freeSpace) {
if (isset($chunkHandler)) {

View file

@ -27,6 +27,7 @@ import { showError } from '@nextcloud/dialogs'
import { subscribe } from '@nextcloud/event-bus'
import { translate } from '@nextcloud/l10n'
import axios from '@nextcloud/axios'
import ChartPie from 'vue-material-design-icons/ChartPie.vue'
import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
import NcProgressBar from '@nextcloud/vue/dist/Components/NcProgressBar.js'
@ -86,6 +87,13 @@ export default {
subscribe('files:node:updated', this.throttleUpdateStorageStats)
},
mounted() {
// Warn the user if the available storage is 0 on page load
if (this.storageStats?.free <= 0) {
this.showStorageFullWarning()
}
},
methods: {
// From user input
debounceUpdateStorageStats: debounce(200, function(event) {
@ -113,6 +121,12 @@ export default {
if (!response?.data?.data) {
throw new Error('Invalid storage stats')
}
// Warn the user if the available storage changed from > 0 to 0
if (this.storageStats?.free > 0 && response.data.data?.free <= 0) {
this.showStorageFullWarning()
}
this.storageStats = response.data.data
} catch (error) {
logger.error('Could not refresh storage stats', { error })
@ -125,6 +139,10 @@ export default {
}
},
showStorageFullWarning() {
showError(this.t('files', 'Your storage is full, files can not be updated or synced anymore!'))
},
t: translate,
},
}

View file

@ -25,6 +25,7 @@
<!-- Current folder breadcrumbs -->
<BreadCrumbs :path="dir" @reload="fetchContent">
<template #actions>
<!-- Sharing button -->
<NcButton v-if="canShare && filesListWidth >= 512"
:aria-label="shareButtonLabel"
:class="{ 'files-list__header-share-button--shared': shareButtonType }"
@ -37,11 +38,27 @@
<ShareVariantIcon v-else :size="20" />
</template>
</NcButton>
<!-- Disabled upload button -->
<NcButton v-if="!canUpload || isQuotaExceeded"
:aria-label="cantUploadLabel"
:title="cantUploadLabel"
class="files-list__header-upload-button--disabled"
:disabled="true"
type="secondary">
<template #icon>
<PlusIcon :size="20" />
</template>
{{ t('files', 'Add') }}
</NcButton>
<!-- Uploader -->
<UploadPicker v-if="currentFolder && canUpload"
<UploadPicker v-else-if="currentFolder"
:content="dirContents"
:destination="currentFolder"
:multiple="true"
class="files-list__header-upload-button"
@failed="onUploadFail"
@uploaded="onUpload" />
</template>
</BreadCrumbs>
@ -110,6 +127,8 @@ import { Folder, Node, Permission } from '@nextcloud/files'
import { getCapabilities } from '@nextcloud/capabilities'
import { join, dirname } from 'path'
import { orderBy } from 'natural-orderby'
import { Parser } from 'xml2js'
import { showError } from '@nextcloud/dialogs'
import { translate, translatePlural } from '@nextcloud/l10n'
import { Type } from '@nextcloud/sharing'
import { UploadPicker } from '@nextcloud/upload'
@ -122,6 +141,7 @@ import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import ShareVariantIcon from 'vue-material-design-icons/ShareVariant.vue'
import ViewGridIcon from 'vue-material-design-icons/ViewGrid.vue'
@ -155,6 +175,7 @@ export default defineComponent({
NcEmptyContent,
NcIconSvgWrapper,
NcLoadingIcon,
PlusIcon,
ShareVariantIcon,
UploadPicker,
ViewGridIcon,
@ -360,6 +381,15 @@ export default defineComponent({
canUpload() {
return this.currentFolder && (this.currentFolder.permissions & Permission.CREATE) !== 0
},
isQuotaExceeded() {
return this.currentFolder?.attributes?.['quota-available-bytes'] === 0
},
cantUploadLabel() {
if (this.isQuotaExceeded) {
return this.t('files', 'Your have used your space quota and cannot upload files anymore')
}
return this.t('files', 'You dont have permission to upload or create files here')
},
/**
* Check if current folder has share permissions
@ -488,6 +518,39 @@ export default defineComponent({
}
},
async onUploadFail(upload: Upload) {
const status = upload.response?.status || 0
// Check known status codes
if (status === 507) {
showError(this.t('files', 'Not enough free space'))
return
} else if (status === 404 || status === 409) {
showError(this.t('files', 'Target folder does not exist any more'))
return
} else if (status === 403) {
showError(this.t('files', 'Operation is blocked by access control'))
return
} else if (status !== 0) {
showError(this.t('files', 'Error when assembling chunks, status code {status}', { status }))
return
}
// Else we try to parse the response error message
try {
const parser = new Parser({ trim: true, explicitRoot: false })
const response = await parser.parseStringPromise(upload.response?.data)
const message = response['s:message'][0] as string
if (typeof message === 'string' && message.trim() !== '') {
// Unfortunatly, the server message is not translated
showError(this.t('files', 'Error during upload: {message}', { message }))
return
}
} catch (error) {}
showError(this.t('files', 'Unknown error during upload'))
},
openSharingSidebar() {
if (window?.OCA?.Files?.Sidebar?.setActiveTab) {
window.OCA.Files.Sidebar.setActiveTab('sharing')

4
dist/core-common.js vendored

File diff suppressed because one or more lines are too long

View file

@ -74,6 +74,8 @@
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/**
* @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
*

File diff suppressed because one or more lines are too long

4
dist/files-init.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,3 @@
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*

File diff suppressed because one or more lines are too long

4
dist/files-main.js vendored

File diff suppressed because one or more lines are too long

View file

@ -16,6 +16,8 @@
* @license MIT
*/
/*! http://mths.be/fromcodepoint v0.1.0 by @mathias */
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,3 @@
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*

File diff suppressed because one or more lines are too long

21
package-lock.json generated
View file

@ -87,7 +87,8 @@
"vuedraggable": "^2.24.3",
"vuex": "^3.6.2",
"vuex-router-sync": "^5.0.0",
"webdav": "^5.3.1"
"webdav": "^5.3.1",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@babel/node": "^7.22.10",
@ -21152,6 +21153,18 @@
"xml2js": "^0.4.5"
}
},
"node_modules/parse-bmfont-xml/node_modules/xml2js": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/parse-headers": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz",
@ -28026,9 +28039,9 @@
"integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="
},
"node_modules/xml2js": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"

View file

@ -114,7 +114,8 @@
"vuedraggable": "^2.24.3",
"vuex": "^3.6.2",
"vuex-router-sync": "^5.0.0",
"webdav": "^5.3.1"
"webdav": "^5.3.1",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@babel/node": "^7.22.10",