diff options
author | Louis Chemineau <louis@chmn.me> | 2022-11-10 09:48:22 +0100 |
---|---|---|
committer | Louis Chemineau <louis@chmn.me> | 2022-11-28 17:31:27 +0100 |
commit | a32c25e1c8870bf751ed5b3fb7efe94a2d0302d6 (patch) | |
tree | 9fe68fae86d9daf5c6f20719cc0773b6c4be77e3 /apps | |
parent | a28838b8667645fda47df8a0d3911614fd601c35 (diff) | |
download | nextcloud-server-a32c25e1c8870bf751ed5b3fb7efe94a2d0302d6.tar.gz nextcloud-server-a32c25e1c8870bf751ed5b3fb7efe94a2d0302d6.zip |
Extract logic into separate files
Signed-off-by: Louis Chemineau <louis@chmn.me>
Diffstat (limited to 'apps')
-rw-r--r-- | apps/files_versions/src/utils/davClient.js | 34 | ||||
-rw-r--r-- | apps/files_versions/src/utils/davRequest.js | 33 | ||||
-rw-r--r-- | apps/files_versions/src/utils/logger.js | 27 | ||||
-rw-r--r-- | apps/files_versions/src/utils/versions.js | 124 | ||||
-rw-r--r-- | apps/files_versions/src/views/VersionTab.vue | 141 |
5 files changed, 253 insertions, 106 deletions
diff --git a/apps/files_versions/src/utils/davClient.js b/apps/files_versions/src/utils/davClient.js new file mode 100644 index 00000000000..e4bfeb10411 --- /dev/null +++ b/apps/files_versions/src/utils/davClient.js @@ -0,0 +1,34 @@ +/** + * @copyright 2022 Louis Chemineau <mlouis@chmn.me> + * + * @author Louis Chemineau <mlouis@chmn.me> + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import { createClient, getPatcher } from 'webdav' +import { generateRemoteUrl } from '@nextcloud/router' +import axios from '@nextcloud/axios' + +const rootPath = 'dav' + +// force our axios +const patcher = getPatcher() +patcher.patch('request', axios) + +// init webdav client on default dav endpoint +const remote = generateRemoteUrl(rootPath) +export default createClient(remote) diff --git a/apps/files_versions/src/utils/davRequest.js b/apps/files_versions/src/utils/davRequest.js new file mode 100644 index 00000000000..b77cd150643 --- /dev/null +++ b/apps/files_versions/src/utils/davRequest.js @@ -0,0 +1,33 @@ +/** + * @copyright Copyright (c) 2019 Louis Chmn <louis@chmn.me> + * + * @author Louis Chmn <louis@chmn.me> + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +export default `<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:" + xmlns:oc="http://owncloud.org/ns" + xmlns:nc="http://nextcloud.org/ns" + xmlns:ocs="http://open-collaboration-services.org/ns"> + <d:prop> + <d:getcontentlength /> + <d:getcontenttype /> + <d:getlastmodified /> + </d:prop> +</d:propfind>` diff --git a/apps/files_versions/src/utils/logger.js b/apps/files_versions/src/utils/logger.js new file mode 100644 index 00000000000..4f0356764d9 --- /dev/null +++ b/apps/files_versions/src/utils/logger.js @@ -0,0 +1,27 @@ +/** + * @copyright 2022 Louis Chemineau <mlouis@chmn.me> + * + * @author Louis Chemineau <mlouis@chmn.me> + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import { getLoggerBuilder } from '@nextcloud/logger' + +export default getLoggerBuilder() + .setApp('files_version') + .detectUser() + .build() diff --git a/apps/files_versions/src/utils/versions.js b/apps/files_versions/src/utils/versions.js new file mode 100644 index 00000000000..477e7ef65fe --- /dev/null +++ b/apps/files_versions/src/utils/versions.js @@ -0,0 +1,124 @@ +/** + * @copyright 2022 Louis Chemineau <mlouis@chmn.me> + * + * @author Louis Chemineau <mlouis@chmn.me> + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import { getCurrentUser } from '@nextcloud/auth' +import client from '../utils/davClient.js' +import davRequest from '../utils/davRequest.js' +import logger from '../utils/logger.js' +import { basename, joinPaths } from '@nextcloud/paths' +import { generateUrl } from '@nextcloud/router' +import { translate } from '@nextcloud/l10n' +import moment from '@nextcloud/moment' + +/** + * @typedef {Object} Version + * @property {string} title - 'Current version' or '' + * @property {string} fileName - File name relative to the version DAV endpoint + * @property {string} mimeType - Empty for the current version, else the actual mime type of the version + * @property {string} size - Human readable size + * @property {string} type - 'file' + * @property {number} mtime - Version creation date as a timestamp + * @property {string} preview - Preview URL of the version + * @property {string} url - Download URL of the version + * @property {string|null} fileVersion - The version id, null for the current version + * @property {boolean} isCurrent - Whether this is the current version of the file + */ + +/** + * @param fileInfo + * @return {Promise<Version[]>} + */ +export async function fetchVersions(fileInfo) { + const path = `/versions/${getCurrentUser()?.uid}/versions/${fileInfo.id}` + + try { + /** @type {import('webdav').FileStat[]} */ + const response = await client.getDirectoryContents(path, { + data: davRequest, + }) + return response.map(version => formatVersion(version, fileInfo)) + } catch (exception) { + logger.error('Could not fetch version', { exception }) + throw exception + } +} + +/** + * Restore the given version + * + * @param {Version} version + * @param {object} fileInfo + */ +export async function restoreVersion(version, fileInfo) { + try { + logger.debug('Restoring version', { url: version.url }) + await client.moveFile( + `/versions/${getCurrentUser()?.uid}/versions/${fileInfo.id}/${version.fileVersion}`, + `/versions/${getCurrentUser()?.uid}/restore/target` + ) + } catch (exception) { + logger.error('Could not restore version', { exception }) + throw exception + } +} + +/** + * Format version + * + * @param {object} version - raw version received from the versions DAV endpoint + * @param {object} fileInfo - file properties received from the files DAV endpoint + * @return {Version} + */ +function formatVersion(version, fileInfo) { + const isCurrent = version.mime === '' + const fileVersion = isCurrent ? null : basename(version.filename) + + let url = null + let preview = null + + if (isCurrent) { + // https://nextcloud_server2.test/remote.php/webdav/welcome.txt?downloadStartSecret=hl5awd7tbzg + url = joinPaths('/remote.php/webdav', fileInfo.path, fileInfo.name) + preview = generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0', { + fileId: fileInfo.id, + fileEtag: fileInfo.etag, + }) + } else { + url = joinPaths('/remote.php/dav', version.filename) + preview = generateUrl('/apps/files_versions/preview?file={file}&version={fileVersion}', { + file: joinPaths(fileInfo.path, fileInfo.name), + fileVersion, + }) + } + + return { + title: isCurrent ? translate('files_versions', 'Current version') : '', + fileName: version.filename, + mimeType: version.mime, + size: isCurrent ? fileInfo.size : version.size, + type: version.type, + mtime: moment(isCurrent ? fileInfo.mtime : version.lastmod).unix(), + preview, + url, + fileVersion, + isCurrent, + } +} diff --git a/apps/files_versions/src/views/VersionTab.vue b/apps/files_versions/src/views/VersionTab.vue index 90664491941..8159415dfc7 100644 --- a/apps/files_versions/src/views/VersionTab.vue +++ b/apps/files_versions/src/views/VersionTab.vue @@ -19,7 +19,7 @@ <div> <ul> <NcListItem v-for="version in versions" - :key="version.dateTime.unix()" + :key="version.mtime" class="version" :title="version.title" :href="version.url"> @@ -29,25 +29,25 @@ alt="" height="256" width="256" - class="version-image"> + class="version__image"> </template> <template #subtitle> - <div class="version-info"> - <a v-tooltip="version.dateTime" :href="version.url">{{ version.relativeTime }}</a> - <span class="version-info-size">•</span> - <span class="version-info-size"> - {{ version.size }} - </span> + <div class="version__info"> + <span>{{ version.mtime | humanDateFromNow }}</span> + <!-- Separate dot to improve alignement --> + <span class="version__info__size">•</span> + <span class="version__info__size">{{ version.size | humanReadableSize }}</span> </div> </template> - <template #actions> - <NcActionLink :href="version.url"> + <template v-if="!version.isCurrent" #actions> + <NcActionLink :href="version.url" + :download="version.url"> <template #icon> <Download :size="22" /> </template> {{ t('files_versions', 'Download version') }} </NcActionLink> - <NcActionButton v-if="!version.isCurrent" @click="restoreVersion(version)"> + <NcActionButton @click="restoreVersion(version)"> <template #icon> <BackupRestore :size="22" /> </template> @@ -67,10 +67,6 @@ </template> <script> -import { createClient, getPatcher } from 'webdav' -import axios from '@nextcloud/axios' -import { generateRemoteUrl, generateUrl } from '@nextcloud/router' -import { getCurrentUser } from '@nextcloud/auth' import BackupRestore from 'vue-material-design-icons/BackupRestore.vue' import Download from 'vue-material-design-icons/Download.vue' import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' @@ -78,78 +74,8 @@ import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js' import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js' import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js' import { showError, showSuccess } from '@nextcloud/dialogs' +import { fetchVersions, restoreVersion } from '../utils/versions.js' import moment from '@nextcloud/moment' -import { basename, joinPaths } from '@nextcloud/paths' -import { getLoggerBuilder } from '@nextcloud/logger' -import { translate } from '@nextcloud/l10n' - -const logger = getLoggerBuilder() - .setApp('files_version') - .detectUser() - .build() - -/** - * Get WebDAV request body for version list - */ -function getDavRequest() { - return `<?xml version="1.0"?> - <d:propfind xmlns:d="DAV:" - xmlns:oc="http://owncloud.org/ns" - xmlns:nc="http://nextcloud.org/ns" - xmlns:ocs="http://open-collaboration-services.org/ns"> - <d:prop> - <d:getcontentlength /> - <d:getcontenttype /> - <d:getlastmodified /> - </d:prop> - </d:propfind>` -} - -/** - * Format version - * - * @param version - * @param fileInfo - */ -function formatVersion(version, fileInfo) { - const fileVersion = basename(version.filename) - const isCurrent = version.mime === '' - - const preview = isCurrent - ? generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0', { - fileId: fileInfo.id, - fileEtag: fileInfo.etag, - }) - : generateUrl('/apps/files_versions/preview?file={file}&version={fileVersion}', { - file: joinPaths(fileInfo.path, fileInfo.name), - fileVersion, - }) - - return { - displayVersionName: fileVersion, - title: isCurrent ? translate('files_versions', 'Current version') : '', - fileName: version.filename, - mimeType: version.mime, - size: OC.Util.humanFileSize(isCurrent ? fileInfo.size : version.size), - type: version.type, - dateTime: moment(isCurrent ? fileInfo.mtime : version.lastmod), - relativeTime: moment(isCurrent ? fileInfo.mtime : version.lastmod).fromNow(), - preview, - url: isCurrent ? joinPaths('/remote.php/dav', version.filename) : joinPaths('/remote.php/dav', fileInfo.path, fileInfo.name), - fileVersion, - isCurrent, - } -} - -const rootPath = 'dav' - -// force our axios -const patcher = getPatcher() -patcher.patch('request', axios) - -// init webdav client on default dav endpoint -const remote = generateRemoteUrl(rootPath) -const client = createClient(remote) export default { name: 'VersionTab', @@ -161,12 +87,20 @@ export default { BackupRestore, Download, }, + filters: { + humanReadableSize(bytes) { + return OC.Util.humanFileSize(bytes) + }, + humanDateFromNow(timestamp) { + return moment(timestamp * 1000).fromNow() + }, + }, data() { - return { fileInfo: null, + /** @type {import('../utils/versions.js').Version[]} */ versions: [], - loading: true, + loading: false, } }, methods: { @@ -185,16 +119,10 @@ export default { * Get the existing versions infos */ async fetchVersions() { - const path = `/versions/${getCurrentUser().uid}/versions/${this.fileInfo.id}` - try { - const response = await client.getDirectoryContents(path, { - data: getDavRequest(), - }) - this.versions = response.map(version => formatVersion(version, this.fileInfo)) - this.loading = false - } catch (exception) { - logger.error('Could not fetch version', { exception }) + this.loading = true + this.versions = await fetchVersions(this.fileInfo) + } finally { this.loading = false } }, @@ -206,15 +134,13 @@ export default { */ async restoreVersion(version) { try { - logger.debug('restoring version', version.url) - await client.moveFile( - `/versions/${getCurrentUser().uid}/versions/${this.fileInfo.id}/${version.fileVersion}`, - `/versions/${getCurrentUser().uid}/restore/target` - ) + await restoreVersion(version, this.fileInfo) + // File info is not updated so we manually update its size and mtime if the restoration went fine. + this.fileInfo.size = version.size + this.fileInfo.mtime = version.lastmod showSuccess(t('files_versions', 'Version restored')) await this.fetchVersions() } catch (exception) { - logger.error('Could not restore version', { exception }) showError(t('files_versions', 'Could not restore version')) } }, @@ -233,16 +159,19 @@ export default { .version { display: flex; flex-direction: row; - &-info { + + &__info { display: flex; flex-direction: row; align-items: center; gap: 0.5rem; - &-size { + + &__size { color: var(--color-text-lighter); } } - &-image { + + &__image { width: 3rem; height: 3rem; border: 1px solid var(--color-border); |