mirror of
https://github.com/nextcloud/server.git
synced 2026-03-24 11:24:37 -04:00
Extract logic into separate files
Signed-off-by: Louis Chemineau <louis@chmn.me>
This commit is contained in:
parent
a28838b866
commit
a32c25e1c8
5 changed files with 253 additions and 106 deletions
34
apps/files_versions/src/utils/davClient.js
Normal file
34
apps/files_versions/src/utils/davClient.js
Normal file
|
|
@ -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)
|
||||
33
apps/files_versions/src/utils/davRequest.js
Normal file
33
apps/files_versions/src/utils/davRequest.js
Normal file
|
|
@ -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>`
|
||||
27
apps/files_versions/src/utils/logger.js
Normal file
27
apps/files_versions/src/utils/logger.js
Normal file
|
|
@ -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()
|
||||
124
apps/files_versions/src/utils/versions.js
Normal file
124
apps/files_versions/src/utils/versions.js
Normal file
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue