diff --git a/media/css/seahub.css b/media/css/seahub.css index 20d14c1bf5..773827f690 100644 --- a/media/css/seahub.css +++ b/media/css/seahub.css @@ -3655,6 +3655,10 @@ textarea:-moz-placeholder {/* for FF */ } #user-profile .info-detail { padding-left: 6px; + +/* file lock */ +#view-hd .file-locked-icon { + vertical-align: middle; } /* shared file view */ #shared-file-view-logo { diff --git a/media/img/file-locked-32.png b/media/img/file-locked-32.png new file mode 100644 index 0000000000..fbf9418978 Binary files /dev/null and b/media/img/file-locked-32.png differ diff --git a/seahub/api2/views.py b/seahub/api2/views.py index 89cc3a08b9..e3e49f3fa8 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -66,7 +66,7 @@ from seahub.share.signals import share_repo_to_user_successful from seahub.share.views import list_shared_repos from seahub.utils import gen_file_get_url, gen_token, gen_file_upload_url, \ check_filename_with_rename, is_valid_username, EVENTS_ENABLED, \ - get_user_events, EMPTY_SHA1, get_ccnet_server_addr_port, \ + get_user_events, EMPTY_SHA1, get_ccnet_server_addr_port, is_pro_version, \ gen_block_get_url, get_file_type_and_ext, HAS_FILE_SEARCH, \ gen_file_share_link, gen_dir_share_link, is_org_context, gen_shared_link, \ get_org_user_events, calculate_repos_last_modify, send_perm_audit_msg, \ @@ -75,7 +75,7 @@ from seahub.utils.repo import get_sub_repo_abbrev_origin_path from seahub.utils.star import star_file, unstar_file from seahub.utils.file_types import IMAGE, DOCUMENT from seahub.utils.timeutils import utc_to_local -from seahub.views import validate_owner, is_registered_user, \ +from seahub.views import validate_owner, is_registered_user, check_file_lock, \ group_events_data, get_diff, create_default_library, get_owned_repo_list, \ list_inner_pub_repos, get_virtual_repos_by_owner, \ check_folder_permission, check_file_permission @@ -89,7 +89,8 @@ if HAS_OFFICE_CONVERTER: from seahub.utils import query_office_convert_status, prepare_converted_html import seahub.settings as settings from seahub.settings import THUMBNAIL_EXTENSION, THUMBNAIL_ROOT, \ - ENABLE_GLOBAL_ADDRESSBOOK + ENABLE_THUMBNAIL, THUMBNAIL_IMAGE_SIZE_LIMIT, \ + ENABLE_GLOBAL_ADDRESSBOOK, FILE_LOCK_EXPIRATION_DAYS try: from seahub.settings import CLOUD_MODE except ImportError: @@ -1279,6 +1280,15 @@ def get_dir_entrys_by_id(request, repo, path, dir_id): else: entry["size"] = dirent.size + if is_pro_version(): + entry["is_locked"] = dirent.is_locked + entry["lock_owner"] = dirent.lock_owner + entry["lock_time"] = dirent.lock_time + if username == dirent.lock_owner: + entry["locked_by_me"] = True + else: + entry["locked_by_me"] = False + entry["type"] = dtype entry["name"] = dirent.obj_name entry["id"] = dirent.obj_id @@ -1597,7 +1607,7 @@ class FileView(APIView): including create/delete/rename/view, etc. """ - authentication_classes = (TokenAuthentication, ) + authentication_classes = (TokenAuthentication, SessionAuthentication) permission_classes = (IsAuthenticated, ) throttle_classes = (UserRateThrottle, ) @@ -1620,7 +1630,8 @@ class FileView(APIView): try: file_id = seafile_api.get_file_id_by_path(repo_id, path.encode('utf-8')) - except SearpcError, e: + except SearpcError as e: + logger.error(e) return api_error(HTTP_520_OPERATION_FAILED, "Failed to get file id by path.") @@ -1655,6 +1666,10 @@ class FileView(APIView): return api_error(status.HTTP_403_FORBIDDEN, 'You do not have permission to rename file.') + is_locked, locked_by_me = check_file_lock(repo_id, path, username) + if is_locked and not locked_by_me: + return api_error(status.HTTP_403_FORBIDDEN, 'File is locked') + newname = request.POST.get('newname', '') if not newname: return api_error(status.HTTP_400_BAD_REQUEST, @@ -1692,6 +1707,10 @@ class FileView(APIView): return api_error(status.HTTP_403_FORBIDDEN, 'You do not have permission to move file.') + is_locked, locked_by_me = check_file_lock(repo_id, path, username) + if is_locked and not locked_by_me: + return api_error(status.HTTP_403_FORBIDDEN, 'File is locked') + src_dir = os.path.dirname(path) src_dir_utf8 = src_dir.encode('utf-8') src_repo_id = repo_id @@ -1774,9 +1793,42 @@ class FileView(APIView): "Operation can only be rename, create or move.") def put(self, request, repo_id, format=None): - # update file - # TODO - pass + repo = get_repo(repo_id) + if not repo: + return api_error(status.HTTP_404_NOT_FOUND, 'Repo not found.') + + path = request.DATA.get('p', '') + file_id = seafile_api.get_file_id_by_path(repo_id, path) + if not path or not file_id: + return api_error(status.HTTP_400_BAD_REQUEST, + 'Path is missing or invalid.') + + username = request.user.username + # check file access permission + if seafile_api.check_permission_by_path(repo_id, path, username) != 'rw': + return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.') + + operation = request.DATA.get('operation', '') + if operation.lower() == 'lock': + # lock file + expire = request.DATA.get('expire', FILE_LOCK_EXPIRATION_DAYS) + try: + seafile_api.lock_file(repo_id, path.lstrip('/'), username, expire) + return Response('success', status=status.HTTP_200_OK) + except SearpcError, e: + logger.error(e) + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal error') + if operation.lower() == 'unlock': + # unlock file + try: + seafile_api.unlock_file(repo_id, path.lstrip('/')) + return Response('success', status=status.HTTP_200_OK) + except SearpcError, e: + logger.error(e) + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal error') + else: + return api_error(status.HTTP_400_BAD_REQUEST, + "Operation can only be lock or unlock") def delete(self, request, repo_id, format=None): # delete file @@ -1793,6 +1845,10 @@ class FileView(APIView): if not path: return api_error(status.HTTP_400_BAD_REQUEST, 'Path is missing.') + is_locked, locked_by_me = check_file_lock(repo_id, path, username) + if is_locked and not locked_by_me: + return api_error(status.HTTP_403_FORBIDDEN, 'File is locked') + parent_dir = os.path.dirname(path) if check_folder_permission(request, repo_id, parent_dir) != 'rw': return api_error(status.HTTP_403_FORBIDDEN, 'Forbid to access this folder.') @@ -1804,7 +1860,8 @@ class FileView(APIView): seafile_api.del_file(repo_id, parent_dir_utf8, file_name_utf8, request.user.username) - except SearpcError, e: + except SearpcError as e: + logger.error(e) return api_error(HTTP_520_OPERATION_FAILED, "Failed to delete file.") @@ -1882,8 +1939,12 @@ class FileRevert(APIView): if not path: return api_error(status.HTTP_400_BAD_REQUEST, 'Path is missing.') - parent_dir = os.path.dirname(path) username = request.uset.username + is_locked, locked_by_me = check_file_lock(repo_id, path, username) + if is_locked and not locked_by_me: + return api_error(status.HTTP_403_FORBIDDEN, 'File is locked') + + parent_dir = os.path.dirname(path) if check_folder_permission(request, repo_id, parent_dir) != 'rw': return api_error(status.HTTP_403_FORBIDDEN, 'Forbid to access this folder.') @@ -1892,7 +1953,8 @@ class FileRevert(APIView): try: ret = seafserv_threaded_rpc.revert_file (repo_id, commit_id, path, request.user.username) - except SearpcError, e: + except SearpcError as e: + logger.error(e) return api_error(status.HTTP_400_BAD_REQUEST, 'Server error') return HttpResponse(json.dumps({"ret": ret}), status=200, content_type=json_content_type) diff --git a/seahub/settings.py b/seahub/settings.py index 1a9134e78a..d6aab982b2 100644 --- a/seahub/settings.py +++ b/seahub/settings.py @@ -312,6 +312,8 @@ MAX_UPLOAD_FILE_NAME_LEN = 255 MAX_FILE_NAME = MAX_UPLOAD_FILE_NAME_LEN MAX_PATH = 4096 +FILE_LOCK_EXPIRATION_DAYS = 0 + # Whether or not activate user when registration complete. # If set to ``False``, new user will be activated by admin or via activate link. ACTIVATE_AFTER_REGISTRATION = True diff --git a/seahub/templates/file_revisions.html b/seahub/templates/file_revisions.html index 89241a44a8..3af06d0fb1 100644 --- a/seahub/templates/file_revisions.html +++ b/seahub/templates/file_revisions.html @@ -52,8 +52,10 @@ {{ commit.rev_file_size|filesizeformat }} {% if commit.id != repo.head_cmmt_id %} + {% if can_revert_file %} {% trans 'Restore' %} {% endif %} + {% endif %} {% trans 'Download' %} {% trans 'View' %} {% if can_compare and not forloop.last %} diff --git a/seahub/templates/js/templates.html b/seahub/templates/js/templates.html index 5dd99a12b1..8e0aac058e 100644 --- a/seahub/templates/js/templates.html +++ b/seahub/templates/js/templates.html @@ -266,6 +266,13 @@ <% } else { %> <%- dirent.obj_name %> <% } %> + <% if (is_pro) { %> + <% if (dirent.is_locked) { %> + locked + <% } else { %> + locked + <% } %> + <% } %> @@ -276,16 +283,33 @@ <% } %> <% if (dirent.perm == 'rw') { %> - + <% if (!dirent.is_locked) { %> + + <% } else { %> + <% if (dirent.locked_by_me) { %> + + <% } %> + <% } %> <% } %> <% if (dirent.perm == 'rw') { %> {% trans <% } %> diff --git a/seahub/templates/view_file_base.html b/seahub/templates/view_file_base.html index e6c0966451..5ed1acd47e 100644 --- a/seahub/templates/view_file_base.html +++ b/seahub/templates/view_file_base.html @@ -12,6 +12,9 @@

{{ filename }} + {% if is_pro %} + {% trans + {% endif %}

@@ -41,6 +44,16 @@ + {% if can_lock_unlock_file %} + {% if not file_locked %} + + + {% elif locked_by_me %} + + + {% endif %} + {% endif %} + {% if not repo.encrypted %} {% if request.user.permissions.can_generate_shared_link %} @@ -51,7 +64,7 @@ {% endif %} - {% if request.user.is_authenticated %} + {% if request.user.is_authenticated and can_edit_file %} {% block edit_file %} {% endblock %} {% endif %} @@ -72,7 +85,7 @@ - {% include "snippets/file_share_popup.html" %} + {% include "snippets/file_share_popup.html" %}