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'), ]