From fab9c039af98dab2bcaa1ea4c479aa26d80c524a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=AC=A2=E4=B9=90=E9=A9=AC?=
<38058090+SkywalkerSpace@users.noreply.github.com>
Date: Thu, 15 Jun 2023 16:27:24 +0800
Subject: [PATCH] copy_file_from_history (#5486)
* copy_file_from_history
* fix SeadocCopyHistoryFile
---
.../sdoc-file-history/history-version.js | 7 ++
.../src/pages/sdoc-file-history/side-panel.js | 14 ++++
seahub/seadoc/apis.py | 69 ++++++++++++++++++-
seahub/seadoc/urls.py | 3 +-
4 files changed, 91 insertions(+), 2 deletions(-)
diff --git a/frontend/src/pages/sdoc-file-history/history-version.js b/frontend/src/pages/sdoc-file-history/history-version.js
index fb89ca4cfe..2831a72863 100644
--- a/frontend/src/pages/sdoc-file-history/history-version.js
+++ b/frontend/src/pages/sdoc-file-history/history-version.js
@@ -48,6 +48,11 @@ class HistoryVersion extends React.Component {
// nothing todo
}
+ onItemCopy = () => {
+ const { historyVersion } = this.props;
+ this.props.onCopy(historyVersion);
+ }
+
render() {
const { currentVersion, historyVersion } = this.props;
if (!currentVersion || !historyVersion) return null;
@@ -80,6 +85,7 @@ class HistoryVersion extends React.Component {
{(this.props.index !== 0) && {gettext('Restore')}}
{gettext('Download')}
+ {(this.props.index !== 0) && {gettext('Copy')}}
@@ -94,6 +100,7 @@ HistoryVersion.propTypes = {
historyVersion: PropTypes.object,
onSelectHistoryVersion: PropTypes.func.isRequired,
onRestore: PropTypes.func.isRequired,
+ onCopy: PropTypes.func.isRequired,
};
export default HistoryVersion;
diff --git a/frontend/src/pages/sdoc-file-history/side-panel.js b/frontend/src/pages/sdoc-file-history/side-panel.js
index 26607c8201..6befd3d4b2 100644
--- a/frontend/src/pages/sdoc-file-history/side-panel.js
+++ b/frontend/src/pages/sdoc-file-history/side-panel.js
@@ -115,6 +115,19 @@ class SidePanel extends Component {
this.props.onSelectHistoryVersion(historyVersion, historyVersions[historyVersionIndex + 1]);
}
+ copyHistoryFile = (historyVersion) => {
+ const { path, revFileId, ctime } = historyVersion;
+ seafileAPI.sdocCopyHistoryFile(historyRepoID, path, revFileId, ctime).then(res => {
+ let message = gettext('Successfully copied %(name)s.');
+ let filename = res.data.file_name;
+ message = message.replace('%(name)s', filename);
+ toaster.success(message);
+ }).catch(error => {
+ const errorMessage = Utils.getErrorMsg(error, true);
+ toaster.danger(gettext(errorMessage));
+ });
+ }
+
renderHistoryVersions = () => {
const { isLoading, historyVersions, errorMessage } = this.state;
if (historyVersions.length === 0) {
@@ -150,6 +163,7 @@ class SidePanel extends Component {
historyVersion={historyVersion}
onSelectHistoryVersion={this.onSelectHistoryVersion}
onRestore={this.restoreVersion}
+ onCopy={this.copyHistoryFile}
/>
);
})}
diff --git a/seahub/seadoc/apis.py b/seahub/seadoc/apis.py
index 53776aa510..d281724d07 100644
--- a/seahub/seadoc/apis.py
+++ b/seahub/seadoc/apis.py
@@ -1,4 +1,5 @@
import os
+import json
import logging
import requests
import posixpath
@@ -10,6 +11,7 @@ from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from django.utils.translation import gettext as _
from django.http import HttpResponseRedirect, HttpResponse
+from django.core.files.base import ContentFile
from seaserv import seafile_api, check_quota
@@ -22,9 +24,11 @@ from seahub.seadoc.utils import is_valid_seadoc_access_token, get_seadoc_upload_
gen_seadoc_image_parent_path, get_seadoc_asset_upload_link, get_seadoc_asset_download_link, \
can_access_seadoc_asset
from seahub.utils.file_types import SEADOC, IMAGE
-from seahub.utils import get_file_type_and_ext, normalize_file_path, PREVIEW_FILEEXT
+from seahub.utils import get_file_type_and_ext, normalize_file_path, PREVIEW_FILEEXT, \
+ gen_inner_file_get_url, gen_inner_file_upload_url
from seahub.tags.models import FileUUIDMap
from seahub.utils.error_msg import file_type_error_msg
+from seahub.utils.repo import parse_repo_perm
logger = logging.getLogger(__name__)
@@ -286,3 +290,66 @@ class SeadocDownloadImage(APIView):
filetype, fileext = get_file_type_and_ext(filename)
return HttpResponse(
content=resp.content, content_type='image/' + fileext)
+
+
+class SeadocCopyHistoryFile(APIView):
+
+ authentication_classes = (TokenAuthentication, SessionAuthentication)
+ permission_classes = (IsAuthenticated,)
+ throttle_classes = (UserRateThrottle, )
+
+ def post(self, request, repo_id):
+ username = request.user.username
+ obj_id = request.data.get('obj_id', '')
+ path = request.data.get('p', '')
+ ctime = request.data.get('ctime', '')
+
+ # only check the permissions at the repo level
+ # to prevent file can not be copied on the history page
+ if not parse_repo_perm(check_folder_permission(request, repo_id, '/')).can_copy:
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ # new file name
+ file_name = os.path.basename(path.rstrip('/'))
+ parent_dir = os.path.dirname(path)
+ new_file_name = '.'.join(file_name.split('.')[0:-1]) + \
+ '(' + str(ctime) + ').' + file_name.split('.')[-1]
+ new_file_path = posixpath.join(parent_dir, new_file_name)
+
+ # download
+ token = seafile_api.get_fileserver_access_token(repo_id,
+ obj_id, 'download', username)
+ if not token:
+ error_msg = 'file %s not found.' % obj_id
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+ download_url = gen_inner_file_get_url(token, file_name)
+ resp = requests.get(download_url)
+ content = resp.content
+ file = ContentFile(content)
+ file.name = new_file_name
+
+ # upload
+ obj_id = json.dumps({'parent_dir': parent_dir})
+ token = seafile_api.get_fileserver_access_token(
+ repo_id, obj_id, 'upload-link', username, use_onetime=True)
+ if not token:
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+ upload_link = gen_inner_file_upload_url('upload-api', token)
+ files = {
+ 'file': file,
+ 'file_name': new_file_name,
+ 'target_file': new_file_path,
+ }
+ data = {'parent_dir': parent_dir}
+ resp = requests.post(upload_link, files=files, data=data)
+ if not resp.ok:
+ logger.error(resp.text)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ return Response({
+ 'file_name': new_file_name,
+ 'file_path': new_file_path,
+ })
diff --git a/seahub/seadoc/urls.py b/seahub/seadoc/urls.py
index 52cb416489..e6d1bb5d59 100644
--- a/seahub/seadoc/urls.py
+++ b/seahub/seadoc/urls.py
@@ -1,6 +1,6 @@
from django.urls import re_path
from .apis import SeadocAccessToken, SeadocUploadLink, SeadocDownloadLink, SeadocUploadFile, \
- SeadocUploadImage, SeadocDownloadImage
+ SeadocUploadImage, SeadocDownloadImage, SeadocCopyHistoryFile
urlpatterns = [
re_path(r'^access-token/(?P[-0-9a-f]{36})/$', SeadocAccessToken.as_view(), name='seadoc_access_token'),
@@ -9,4 +9,5 @@ urlpatterns = [
re_path(r'^download-link/(?P[-0-9a-f]{36})/$', SeadocDownloadLink.as_view(), name='seadoc_download_link'),
re_path(r'^upload-image/(?P[-0-9a-f]{36})/$', SeadocUploadImage.as_view(), name='seadoc_upload_image'),
re_path(r'^download-image/(?P[-0-9a-f]{36})/(?P.*)$', SeadocDownloadImage.as_view(), name='seadoc_download_image'),
+ re_path(r'^copy-history-file/(?P[-0-9a-f]{36})/$', SeadocCopyHistoryFile.as_view(), name='seadoc_copy_history_file'),
]