]> source.dussan.org Git - nextcloud-server.git/commitdiff
Extract logic into separate files
authorLouis Chemineau <louis@chmn.me>
Thu, 10 Nov 2022 08:48:22 +0000 (09:48 +0100)
committerLouis Chemineau <louis@chmn.me>
Mon, 28 Nov 2022 16:31:27 +0000 (17:31 +0100)
Signed-off-by: Louis Chemineau <louis@chmn.me>
apps/files_versions/src/utils/davClient.js [new file with mode: 0644]
apps/files_versions/src/utils/davRequest.js [new file with mode: 0644]
apps/files_versions/src/utils/logger.js [new file with mode: 0644]
apps/files_versions/src/utils/versions.js [new file with mode: 0644]
apps/files_versions/src/views/VersionTab.vue

diff --git a/apps/files_versions/src/utils/davClient.js b/apps/files_versions/src/utils/davClient.js
new file mode 100644 (file)
index 0000000..e4bfeb1
--- /dev/null
@@ -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 (file)
index 0000000..b77cd15
--- /dev/null
@@ -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 (file)
index 0000000..4f03567
--- /dev/null
@@ -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 (file)
index 0000000..477e7ef
--- /dev/null
@@ -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,
+       }
+}
index 90664491941339f34ff100a63bb6ad32ed74fd35..8159415dfc79fe27b0f1cd5a5f2c102121c58819 100644 (file)
@@ -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">
                                                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>
 </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);