Skip to content

Commit

Permalink
import and export sdoc
Browse files Browse the repository at this point in the history
  • Loading branch information
JoinTyang committed Aug 7, 2024
1 parent f732da8 commit 68c60d2
Show file tree
Hide file tree
Showing 8 changed files with 357 additions and 88 deletions.
120 changes: 38 additions & 82 deletions frontend/src/components/file-uploader/file-uploader.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class FileUploader extends React.Component {
if (this.props.dragAndDrop === true) {
this.resumable.enableDropOnDocument();
}
this.resumable.opts.target = seafileAPI.server + '/api2/repos/' + this.props.repoID + '/upload-file/';

this.bindCallbackHandler();
this.bindEventHandler();
Expand Down Expand Up @@ -200,27 +201,13 @@ class FileUploader extends React.Component {
});
} else {
this.setUploadFileList(this.resumable.files);
let { repoID, path } = this.props;
seafileAPI.getFileServerUploadLink(repoID, path).then(res => {
this.resumable.opts.target = res.data + '?ret-json=1';
this.resumableUpload(resumableFile);
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
this.resumableUpload(resumableFile);
}
} else {
this.setUploadFileList(this.resumable.files);
if (!this.isUploadLinkLoaded) {
this.isUploadLinkLoaded = true;
let { repoID, path } = this.props;
seafileAPI.getFileServerUploadLink(repoID, path).then(res => {
this.resumable.opts.target = res.data + '?ret-json=1';
this.resumable.upload();
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
this.resumable.upload();
}
}
};
Expand Down Expand Up @@ -555,50 +542,35 @@ class FileUploader extends React.Component {
};

onUploadRetry = (resumableFile) => {
let retryFileList = this.state.retryFileList.filter(item => {
return item.uniqueIdentifier !== resumableFile.uniqueIdentifier;
});
let uploadFileList = this.state.uploadFileList.map(item => {
if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) {
item.error = null;
this.retryUploadFile(item);
}
return item;
});

seafileAPI.getFileServerUploadLink(this.props.repoID, this.props.path).then(res => {
this.resumable.opts.target = res.data + '?ret-json=1';

let retryFileList = this.state.retryFileList.filter(item => {
return item.uniqueIdentifier !== resumableFile.uniqueIdentifier;
});
let uploadFileList = this.state.uploadFileList.map(item => {
if (item.uniqueIdentifier === resumableFile.uniqueIdentifier) {
item.error = null;
this.retryUploadFile(item);
}
return item;
});

this.setState({
retryFileList: retryFileList,
uploadFileList: uploadFileList
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
this.setState({
retryFileList: retryFileList,
uploadFileList: uploadFileList
});
};

onUploadRetryAll = () => {
this.state.retryFileList.forEach(item => {
item.error = false;
this.retryUploadFile(item);
});

seafileAPI.getFileServerUploadLink(this.props.repoID, this.props.path).then(res => {
this.resumable.opts.target = res.data + '?ret-json=1';
this.state.retryFileList.forEach(item => {
item.error = false;
this.retryUploadFile(item);
});

let uploadFileList = this.state.uploadFileList.slice(0);
this.setState({
retryFileList: [],
uploadFileList: uploadFileList
});

}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
let uploadFileList = this.state.uploadFileList.slice(0);
this.setState({
retryFileList: [],
uploadFileList: uploadFileList
});

};

retryUploadFile = (resumableFile) => {
Expand Down Expand Up @@ -634,40 +606,24 @@ class FileUploader extends React.Component {
};

replaceRepetitionFile = () => {
let { repoID, path } = this.props;
seafileAPI.getUpdateLink(repoID, path).then(res => {
this.resumable.opts.target = res.data;

let resumableFile = this.resumable.files[this.resumable.files.length - 1];
resumableFile.formData['replace'] = 1;
resumableFile.formData['target_file'] = resumableFile.formData.parent_dir + resumableFile.fileName;
this.setState({ isUploadRemindDialogShow: false });
this.setUploadFileList(this.resumable.files);
this.resumable.upload();
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
let resumableFile = this.resumable.files[this.resumable.files.length - 1];
resumableFile.formData['replace'] = 1;
resumableFile.formData['target_file'] = resumableFile.formData.parent_dir + resumableFile.fileName;
this.setState({ isUploadRemindDialogShow: false });
this.setUploadFileList(this.resumable.files);
this.resumable.upload();
};

uploadFile = () => {
let resumableFile = this.resumable.files[this.resumable.files.length - 1];
let { repoID, path } = this.props;
seafileAPI.getFileServerUploadLink(repoID, path).then((res) => { // get upload link
this.resumable.opts.target = res.data + '?ret-json=1';
this.setState({
isUploadRemindDialogShow: false,
isUploadProgressDialogShow: true,
uploadFileList: [...this.state.uploadFileList, resumableFile]
}, () => {
this.resumable.upload();
});
Utils.registerGlobalVariable('uploader', 'isUploadProgressDialogShow', true);

}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
this.setState({
isUploadRemindDialogShow: false,
isUploadProgressDialogShow: true,
uploadFileList: [...this.state.uploadFileList, resumableFile]
}, () => {
this.resumable.upload();
});
Utils.registerGlobalVariable('uploader', 'isUploadProgressDialogShow', true);
};

cancelFileUpload = () => {
Expand Down
18 changes: 17 additions & 1 deletion seahub/api2/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import datetime
import logging
from rest_framework import status
from rest_framework.authentication import BaseAuthentication
from rest_framework.authentication import BaseAuthentication, SessionAuthentication
from rest_framework.exceptions import APIException

from seaserv import ccnet_api
Expand Down Expand Up @@ -216,3 +216,19 @@ def authenticate(self, request):
return None

return user, auth[1]


class CsrfExemptSessionAuthentication(SessionAuthentication):
"""
request.POST is accessed by CsrfViewMiddleware which is enabled by default.
This means you will need to use csrf_exempt()
on your view to allow you to change the upload handlers.
DRF's SessionAuthentication uses Django's session framework
for authentication which requires CSRF to be checked.
This Class is override enforce_csrf to solve above problem
"""

def enforce_csrf(self, request):
return # To not perform the csrf check previously happening
1 change: 1 addition & 0 deletions seahub/api2/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
re_path(r'^repos/(?P<repo_id>[-0-9a-f]{36})/upload-shared-links/$', RepoUploadSharedLinks.as_view(), name="api2-repo-upload-shared-links"),
re_path(r'^repos/(?P<repo_id>[-0-9a-f]{36})/upload-shared-links/(?P<token>[a-f0-9]+)/$', RepoUploadSharedLink.as_view(), name="api2-repo-upload-shared-link"),
re_path(r'^repos/(?P<repo_id>[-0-9a-f]{36})/upload-link/$', UploadLinkView.as_view()),
re_path(r'^repos/(?P<repo_id>[-0-9a-f]{36})/upload-file/$', UploadFile.as_view()),
re_path(r'^repos/(?P<repo_id>[-0-9a-f]{36})/update-link/$', UpdateLinkView.as_view()),
re_path(r'^repos/(?P<repo_id>[-0-9a-f]{36})/upload-blks-link/$', UploadBlksLinkView.as_view()),
re_path(r'^repos/(?P<repo_id>[-0-9a-f]{36})/update-blks-link/$', UpdateBlksLinkView.as_view()),
Expand Down
147 changes: 146 additions & 1 deletion seahub/api2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
import datetime
import posixpath
import re
import uuid
from dateutil.relativedelta import relativedelta
from urllib.parse import quote
import requests
import shutil
from zipfile import is_zipfile, ZipFile

from rest_framework import parsers
from rest_framework import status
Expand All @@ -29,9 +33,10 @@
from django.template.defaultfilters import filesizeformat
from django.utils import timezone
from django.utils.translation import gettext as _
from django.core.files.uploadhandler import TemporaryFileUploadHandler

from .throttling import ScopedRateThrottle, AnonRateThrottle, UserRateThrottle
from .authentication import TokenAuthentication
from .authentication import TokenAuthentication, CsrfExemptSessionAuthentication
from .serializers import AuthTokenSerializer
from .utils import get_diff_details, to_python_boolean, \
api_error, get_file_size, prepare_starred_files, is_web_request, \
Expand Down Expand Up @@ -109,6 +114,7 @@
ENABLE_RESET_ENCRYPTED_REPO_PASSWORD, SHARE_LINK_EXPIRE_DAYS_MAX, \
SHARE_LINK_EXPIRE_DAYS_MIN, SHARE_LINK_EXPIRE_DAYS_DEFAULT
from seahub.subscription.utils import subscription_check
from seahub.seadoc.utils import get_seadoc_file_uuid, gen_seadoc_image_parent_path, get_seadoc_asset_upload_link

try:
from seahub.settings import CLOUD_MODE
Expand Down Expand Up @@ -1999,6 +2005,145 @@ def get(self, request, repo_id, format=None):

return Response(url)


class UploadFile(APIView):
authentication_classes = (TokenAuthentication, CsrfExemptSessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )

def post(self, request, repo_id):
# use TemporaryFileUploadHandler, which contains TemporaryUploadedFile
# TemporaryUploadedFile has temporary_file_path() method
# in order to change upload_handlers, we must exempt csrf check
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
username = request.user.username
relative_path = request.data.get('relative_path', '/').strip('/')
parent_dir = request.data.get('parent_dir', '/')
replace = request.data.get('replace', 'False')
try:
replace = to_python_boolean(replace)
except ValueError:
error_msg = 'replace invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

file = request.FILES.get('file', None)
if not file:
error_msg = 'file can not be found.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

repo = seafile_api.get_repo(repo_id)
if not repo:
error_msg = 'Library %s not found.' % repo_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)

dir_id = seafile_api.get_dir_id_by_path(repo_id, parent_dir)
if not dir_id:
error_msg = 'Folder %s not found.' % parent_dir
return api_error(status.HTTP_404_NOT_FOUND, error_msg)

if parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_upload is False:
return api_error(status.HTTP_403_FORBIDDEN, 'You do not have permission to access this folder.')

if check_quota(repo_id) < 0:
return api_error(HTTP_443_ABOVE_QUOTA, _("Out of quota."))

uploaded_temp_path = file.temporary_file_path()

filename = file.name
extension = filename.split('.')[-1].lower()

obj_id = json.dumps({'parent_dir': parent_dir})
try:
token = seafile_api.get_fileserver_access_token(repo_id, obj_id, 'upload', username, use_onetime=False)
except Exception as e:
if str(e) == 'Too many files in library.':
error_msg = _("The number of files in library exceeds the limit")
return api_error(HTTP_447_TOO_MANY_FILES_IN_LIBRARY, error_msg)
else:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

if not token:
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

upload_link = gen_file_upload_url(token, 'upload-api')
upload_link += '?ret-json=1'
if replace:
upload_link += '&replace=1'

# upload file
if extension == 'zsdoc' and is_zipfile(uploaded_temp_path):
tmp_dir = str(uuid.uuid4())
tmp_extracted_path = os.path.join('/tmp/seahub', str(repo_id), 'sdoc_zip_extracted/', tmp_dir)
try:
with ZipFile(uploaded_temp_path) as zip_file:
zip_file.extractall(tmp_extracted_path)
except Exception as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')

sdoc_file_name = filename.replace('zsdoc', 'sdoc')
new_file_path = os.path.join(parent_dir, relative_path, sdoc_file_name)

data = {'parent_dir': parent_dir, 'target_file': new_file_path, 'relative_path': relative_path}
if replace:
data['replace'] = 1
sdoc_file_path = os.path.join(tmp_extracted_path, 'content.json')
new_sdoc_file_path = os.path.join(tmp_extracted_path, sdoc_file_name)
os.rename(sdoc_file_path, new_sdoc_file_path)

files = {'file': open(new_sdoc_file_path, 'rb')}
resp = requests.post(upload_link, files=files, data=data)
if not resp.ok:
logger.error('save file: %s failed: %s' % (filename, resp.text))
return api_error(resp.status_code, resp.content)

sdoc_name = json.loads(resp.content)[0].get('name')
new_sdoc_file_path = os.path.join(parent_dir, relative_path, sdoc_name)
doc_uuid = get_seadoc_file_uuid(repo, new_sdoc_file_path)

# upload sdoc images
image_dir = os.path.join(tmp_extracted_path, 'images/')
batch_upload_sdoc_images(doc_uuid, repo_id, username, image_dir)

# remove tmp file
if os.path.exists(tmp_extracted_path):
shutil.rmtree(tmp_extracted_path)

return Response(json.loads(resp.content))
else:
files = {'file': file}
new_file_path = posixpath.join(parent_dir, filename)
data = {'parent_dir': parent_dir, 'target_file': new_file_path, 'relative_path': relative_path}
if replace:
data['replace'] = 1
resp = requests.post(upload_link, files=files, data=data)
if not resp.ok:
logger.error('save file: %s failed: %s' % (filename, resp.text))
return api_error(resp.status_code, resp.content)

return Response(json.loads(resp.content))


def batch_upload_sdoc_images(doc_uuid, repo_id, username, image_dir):
parent_path = gen_seadoc_image_parent_path(doc_uuid, repo_id, username)
upload_link = get_seadoc_asset_upload_link(repo_id, parent_path, username)

file_list = os.listdir(image_dir)

for filename in file_list:
file_path = posixpath.join(parent_path, filename)
image_path = os.path.join(image_dir, filename)
image_file = open(image_path, 'rb')
files = {'file': image_file}
data = {'parent_dir': parent_path, 'filename': filename, 'target_file': file_path}
resp = requests.post(upload_link, files=files, data=data)
if not resp.ok:
logger.warning('upload sdoc image: %s failed: %s', filename, resp.text)


class UpdateLinkView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
Expand Down
Loading

0 comments on commit 68c60d2

Please sign in to comment.