diff --git a/seahub/api2/endpoints/repos_batch.py b/seahub/api2/endpoints/repos_batch.py index e3beba9f3b..b9d9195663 100644 --- a/seahub/api2/endpoints/repos_batch.py +++ b/seahub/api2/endpoints/repos_batch.py @@ -22,7 +22,8 @@ from seahub.base.accounts import User from seahub.share.signals import share_repo_to_user_successful, \ share_repo_to_group_successful from seahub.utils import is_org_context, send_perm_audit_msg, \ - normalize_dir_path, get_folder_permission_recursively + normalize_dir_path, get_folder_permission_recursively, \ + normalize_file_path, check_filename_with_rename from seahub.views import check_folder_permission from seahub.settings import MAX_PATH @@ -596,3 +597,348 @@ class ReposBatchCreateDirView(APIView): result['success'].append(common_dict) return Response(result) + + +class ReposBatchCopyItemView(APIView): + + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated, ) + throttle_classes = (UserRateThrottle, ) + + def post(self, request): + """ Multi copy files/folders. + Permission checking: + 1. User must has `r/rw` permission for src folder. + 2. User must has `rw` permission for dst folder. + Parameter: + { + "src_repo_id":"7460f7ac-a0ff-4585-8906-bb5a57d2e118", + "dst_repo_id":"a3fa768d-0f00-4343-8b8d-07b4077881db", + "paths":[ + {"src_path":"/1/2/3/","dst_path":"/4/5/6/"}, + {"src_path":"/a/b/c/","dst_path":"/d/e/f/"}, + ] + } + """ + + # argument check + path_list = request.data.get('paths', None) + if not path_list: + error_msg = 'paths invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + src_repo_id = request.data.get('src_repo_id', None) + if not src_repo_id: + error_msg = 'src_repo_id invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + dst_repo_id = request.data.get('dst_repo_id', None) + if not dst_repo_id: + error_msg = 'dst_repo_id invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # resource check + src_repo = seafile_api.get_repo(src_repo_id) + if not src_repo: + error_msg = 'Library %s not found.' % src_repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + dst_repo = seafile_api.get_repo(dst_repo_id) + if not dst_repo: + error_msg = 'Library %s not found.' % dst_repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + # permission check + if check_folder_permission(request, src_repo_id, '/') is None: + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + if check_folder_permission(request, dst_repo_id, '/') is None: + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + result = {} + result['failed'] = [] + result['success'] = [] + username = request.user.username + + for path_item in path_list: + + src_path = path_item['src_path'] + src_path = normalize_dir_path(src_path) + src_parent_dir = os.path.dirname(src_path.rstrip('/')) + src_parent_dir = normalize_dir_path(src_parent_dir) + src_obj_name = os.path.basename(src_path.rstrip('/')) + + dst_path = path_item['dst_path'] + dst_path = normalize_dir_path(dst_path) + dst_parent_dir = dst_path + dst_obj_name = src_obj_name + + common_dict = { + 'src_repo_id': src_repo_id, + 'src_path': src_path, + 'dst_repo_id': dst_repo_id, + 'dst_path': dst_path, + } + + # src/dst parameter check + if src_repo_id == dst_repo_id and \ + dst_path.startswith(src_path): + error_dict = { + 'error_msg': "The destination directory is the same as the source, or is it's subfolder." + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + if src_path == '/': + error_dict = { + 'error_msg': "The source path can not be '/'." + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + if len(dst_parent_dir + dst_obj_name) > MAX_PATH: + error_dict = { + 'error_msg': "'Destination path is too long." + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + # src resource check + ## as we don't know if `src_path` stands for a file or a folder, + ## so we check both + src_dir_id = seafile_api.get_dir_id_by_path(src_repo_id, src_path) + src_file_id = seafile_api.get_file_id_by_path(src_repo_id, + normalize_file_path(src_path)) + + if not src_dir_id and not src_file_id: + error_dict = { + 'error_msg': '%s not found.' % src_path + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + # dst resource check + if not seafile_api.get_dir_id_by_path(dst_repo_id, dst_path): + error_dict = { + 'error_msg': 'Folder %s not found.' % dst_path + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + # src path permission check, user must has `r/rw` permission for src folder. + if check_folder_permission(request, src_repo_id, src_parent_dir) is None: + error_dict = { + 'error_msg': 'Permission denied.' + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + # dst path permission check, user must has `rw` permission for dst folder. + if check_folder_permission(request, dst_repo_id, dst_path) != 'rw': + error_dict = { + 'error_msg': 'Permission denied.' + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + try: + dst_obj_name = check_filename_with_rename(dst_repo_id, + dst_parent_dir, dst_obj_name) + # need_progress=0, synchronous=1 + seafile_api.copy_file(src_repo_id, src_parent_dir, src_obj_name, + dst_repo_id, dst_parent_dir, dst_obj_name, username, 0, 1) + except Exception as e: + logger.error(e) + error_dict = { + 'error_msg': 'Internal Server Error' + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + common_dict['dst_obj_name'] = dst_obj_name + result['success'].append(common_dict) + + return Response(result) + + +class ReposBatchMoveItemView(APIView): + + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated, ) + throttle_classes = (UserRateThrottle, ) + + def post(self, request): + """ Multi move files/folders. + Permission checking: + 1. User must has `rw` permission for src folder. + 2. User must has `rw` permission for dst folder. + Parameter: + { + "src_repo_id":"7460f7ac-a0ff-4585-8906-bb5a57d2e118", + "dst_repo_id":"a3fa768d-0f00-4343-8b8d-07b4077881db", + "paths":[ + {"src_path":"/1/2/3/","dst_path":"/4/5/6/"}, + {"src_path":"/a/b/c/","dst_path":"/d/e/f/"}, + ] + } + """ + + # argument check + path_list = request.data.get('paths', None) + if not path_list: + error_msg = 'paths invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + src_repo_id = request.data.get('src_repo_id', None) + if not src_repo_id: + error_msg = 'src_repo_id invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + dst_repo_id = request.data.get('dst_repo_id', None) + if not dst_repo_id: + error_msg = 'dst_repo_id invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # resource check + src_repo = seafile_api.get_repo(src_repo_id) + if not src_repo: + error_msg = 'Library %s not found.' % src_repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + dst_repo = seafile_api.get_repo(dst_repo_id) + if not dst_repo: + error_msg = 'Library %s not found.' % dst_repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + # permission check + if check_folder_permission(request, src_repo_id, '/') is None: + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + if check_folder_permission(request, dst_repo_id, '/') is None: + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + result = {} + result['failed'] = [] + result['success'] = [] + username = request.user.username + + for path_item in path_list: + + src_path = path_item['src_path'] + src_path = normalize_dir_path(src_path) + src_parent_dir = os.path.dirname(src_path.rstrip('/')) + src_parent_dir = normalize_dir_path(src_parent_dir) + src_obj_name = os.path.basename(src_path.rstrip('/')) + + dst_path = path_item['dst_path'] + dst_path = normalize_dir_path(dst_path) + dst_parent_dir = dst_path + dst_obj_name = src_obj_name + + common_dict = { + 'src_repo_id': src_repo_id, + 'src_path': src_path, + 'dst_repo_id': dst_repo_id, + 'dst_path': dst_path, + } + + # src/dst parameter check + if src_repo_id == dst_repo_id and \ + dst_path.startswith(src_path): + error_dict = { + 'error_msg': "The destination directory is the same as the source, or is it's subfolder." + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + if src_path == '/': + error_dict = { + 'error_msg': "The source path can not be '/'." + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + if len(dst_parent_dir + dst_obj_name) > MAX_PATH: + error_dict = { + 'error_msg': "'Destination path is too long." + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + # src resource check + ## as we don't know if `src_path` stands for a file or a folder, + ## so we check both + src_dir_id = seafile_api.get_dir_id_by_path(src_repo_id, src_path) + src_file_id = seafile_api.get_file_id_by_path(src_repo_id, + normalize_file_path(src_path)) + + if not src_dir_id and not src_file_id: + error_dict = { + 'error_msg': '%s not found.' % src_path + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + # dst resource check + if not seafile_api.get_dir_id_by_path(dst_repo_id, dst_path): + error_dict = { + 'error_msg': 'Folder %s not found.' % dst_path + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + # src path permission check, user must has `rw` permission for src folder. + if check_folder_permission(request, src_repo_id, src_parent_dir) != 'rw': + error_dict = { + 'error_msg': 'Permission denied.' + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + # dst path permission check, user must has `rw` permission for dst folder. + if check_folder_permission(request, dst_repo_id, dst_path) != 'rw': + error_dict = { + 'error_msg': 'Permission denied.' + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + try: + dst_obj_name = check_filename_with_rename(dst_repo_id, + dst_parent_dir, dst_obj_name) + # replace=False, username=username, need_progress=0, synchronous=1 + seafile_api.move_file(src_repo_id, src_parent_dir, src_obj_name, + dst_repo_id, dst_parent_dir, dst_obj_name, + False, username, 0, 1) + except Exception as e: + logger.error(e) + error_dict = { + 'error_msg': 'Internal Server Error' + } + common_dict.update(error_dict) + result['failed'].append(common_dict) + continue + + common_dict['dst_obj_name'] = dst_obj_name + result['success'].append(common_dict) + + return Response(result) diff --git a/seahub/urls.py b/seahub/urls.py index ddf8a10caa..2f1660de06 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -30,7 +30,8 @@ from seahub.api2.endpoints.shared_repos import SharedRepos, SharedRepo from seahub.api2.endpoints.upload_links import UploadLinks, UploadLink, \ UploadLinkUpload from seahub.api2.endpoints.repos_batch import ReposBatchView, \ - ReposBatchCopyDirView, ReposBatchCreateDirView + ReposBatchCopyDirView, ReposBatchCreateDirView, \ + ReposBatchCopyItemView, ReposBatchMoveItemView from seahub.api2.endpoints.repos import RepoView from seahub.api2.endpoints.file import FileView from seahub.api2.endpoints.file_history import FileHistoryView @@ -247,6 +248,8 @@ urlpatterns = patterns( url(r'^api/v2.1/repos/batch/$', ReposBatchView.as_view(), name='api-v2.1-repos-batch'), url(r'^api/v2.1/repos/batch-copy-dir/$', ReposBatchCopyDirView.as_view(), name='api-v2.1-repos-batch-copy-dir'), url(r'^api/v2.1/repos/batch-create-dir/$', ReposBatchCreateDirView.as_view(), name='api-v2.1-repos-batch-create-dir'), + url(r'^api/v2.1/repos/batch-copy-item/$', ReposBatchCopyItemView.as_view(), name='api-v2.1-repos-batch-copy-item'), + url(r'^api/v2.1/repos/batch-move-item/$', ReposBatchMoveItemView.as_view(), name='api-v2.1-repos-batch-move-item'), ## user::deleted repos url(r'^api/v2.1/deleted-repos/$', DeletedRepos.as_view(), name='api2-v2.1-deleted-repos'),