diff --git a/frontend/src/components/dirent-grid-view/dirent-grid-item.js b/frontend/src/components/dirent-grid-view/dirent-grid-item.js index 5b9f216f00..be31d9c2cc 100644 --- a/frontend/src/components/dirent-grid-view/dirent-grid-item.js +++ b/frontend/src/components/dirent-grid-view/dirent-grid-item.js @@ -199,6 +199,9 @@ class DirentGridItem extends React.Component { dirHref = siteRoot + 'library/' + this.props.repoID + '/' + this.props.currentRepoInfo.repo_name + Utils.encodePath(direntPath); } let fileHref = siteRoot + 'lib/' + this.props.repoID + '/file' + Utils.encodePath(direntPath); + if (dirent.is_sdoc_revision && dirent.revision_id) { + fileHref = siteRoot + 'lib/' + this.props.repoID + '/revisions/' + dirent.revision_id + '/'; + } let gridClass = 'grid-file-img-link cursor-pointer'; gridClass += this.state.isGridSelected ? ' grid-selected-active' : ' '; diff --git a/frontend/src/components/dirent-list-view/dirent-list-item.js b/frontend/src/components/dirent-list-view/dirent-list-item.js index 9c38729f43..5d1338dadc 100644 --- a/frontend/src/components/dirent-list-view/dirent-list-item.js +++ b/frontend/src/components/dirent-list-view/dirent-list-item.js @@ -673,6 +673,9 @@ class DirentListItem extends React.Component { dirHref = siteRoot + 'library/' + this.props.repoID + '/' + this.props.currentRepoInfo.repo_name + Utils.encodePath(direntPath); } let fileHref = siteRoot + 'lib/' + this.props.repoID + '/file' + Utils.encodePath(direntPath); + if (dirent.is_sdoc_revision && dirent.revision_id) { + fileHref = siteRoot + 'lib/' + this.props.repoID + '/revisions/' + dirent.revision_id + '/'; + } let toolTipID = ''; let tagTitle = ''; diff --git a/frontend/src/models/dirent.js b/frontend/src/models/dirent.js index 7cf615541a..e8da77b5e0 100644 --- a/frontend/src/models/dirent.js +++ b/frontend/src/models/dirent.js @@ -39,6 +39,8 @@ class Dirent { } if (Utils.isSdocFile(json.name)) { this.is_sdoc_draft = json.is_sdoc_draft || false; + this.is_sdoc_revision = json.is_sdoc_revision || false; + this.revision_id = json.revision_id || null; } } } diff --git a/frontend/src/pages/lib-content-view/lib-content-view.js b/frontend/src/pages/lib-content-view/lib-content-view.js index d4d5aaea82..e84f58553c 100644 --- a/frontend/src/pages/lib-content-view/lib-content-view.js +++ b/frontend/src/pages/lib-content-view/lib-content-view.js @@ -1264,7 +1264,10 @@ class LibContentView extends React.Component { if (this.state.currentMode === 'column' && Utils.isMarkdownFile(direntPath)) { this.showColumnMarkdownFile(direntPath); } else { - const url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(direntPath); + let url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(direntPath); + if (dirent.is_sdoc_revision && dirent.revision_id) { + url = siteRoot + 'lib/' + repoID + '/revisions/' + dirent.revision_id + '/'; + } let isWeChat = Utils.isWeChat(); if (!isWeChat) { @@ -1633,7 +1636,11 @@ class LibContentView extends React.Component { this.showColumnMarkdownFile(node.path); } } else { - const url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(node.path); + let url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(node.path); + let dirent = node.object; + if (dirent.is_sdoc_revision && dirent.revision_id) { + url = siteRoot + 'lib/' + repoID + '/revisions/' + dirent.revision_id + '/'; + } window.open(url); } } diff --git a/seahub/api2/endpoints/dir.py b/seahub/api2/endpoints/dir.py index 7508e4b0be..ef45028201 100644 --- a/seahub/api2/endpoints/dir.py +++ b/seahub/api2/endpoints/dir.py @@ -107,12 +107,14 @@ def get_dir_file_info_list(username, request_type, repo_obj, parent_dir, try: from seahub.tags.models import FileUUIDMap - from seahub.seadoc.models import SeadocDraft + from seahub.seadoc.models import SeadocDraft, SeadocRevision file_uuid_queryset = FileUUIDMap.objects.get_fileuuidmaps_by_parent_path( repo_id, parent_dir) file_uuid_list = [item.uuid for item in file_uuid_queryset] seadoc_draft_queryset = SeadocDraft.objects.list_by_doc_uuids( file_uuid_list) + seadoc_revision_queryset = SeadocRevision.objects.list_by_doc_uuids( + file_uuid_list) except Exception as e: logger.error(e) @@ -197,6 +199,13 @@ def get_dir_file_info_list(username, request_type, repo_obj, parent_dir, file_info['is_sdoc_draft'] = True else: file_info['is_sdoc_draft'] = False + sdoc_revision = seadoc_revision_queryset.filter( + doc_uuid=file_uuid_map.uuid).first() + if sdoc_revision: + file_info['is_sdoc_revision'] = True + file_info['revision_id'] = sdoc_revision.revision_id + else: + file_info['is_sdoc_revision'] = False except Exception as e: logger.error(e) diff --git a/seahub/seadoc/apis.py b/seahub/seadoc/apis.py index 3c94675d18..3706560f94 100644 --- a/seahub/seadoc/apis.py +++ b/seahub/seadoc/apis.py @@ -15,6 +15,7 @@ from django.utils.translation import gettext as _ from django.http import HttpResponseRedirect, HttpResponse from django.core.files.base import ContentFile from django.utils import timezone +from django.db import transaction from seaserv import seafile_api, check_quota @@ -995,6 +996,25 @@ class SeadocRevisions(APIView): revision_file_uuid = str(uuid.uuid4()) revision_filename = revision_file_uuid + '.sdoc' + with transaction.atomic(): + revision_uuid_map = FileUUIDMap( + uuid=revision_file_uuid, + repo_id=repo_id, + parent_path='/Revisions', + filename=revision_filename, + is_dir=False, + ) + revision_uuid_map.save() + + revision = SeadocRevision.objects.add( + doc_uuid=revision_file_uuid, + origin_doc_uuid=origin_file_uuid, + repo_id=repo_id, + origin_doc_path=path, + username=username, + origin_file_version=origin_file_id, + ) + # copy file to revision dir seafile_api.copy_file( repo_id, parent_dir, @@ -1004,24 +1024,6 @@ class SeadocRevisions(APIView): username=username, need_progress=0, synchronous=1, ) - revision_uuid_map = FileUUIDMap( - uuid=revision_file_uuid, - repo_id=repo_id, - parent_path='/Revisions', - filename=revision_filename, - is_dir=False, - ) - revision_uuid_map.save() - - revision = SeadocRevision.objects.create( - doc_uuid=revision_file_uuid, - origin_doc_uuid=origin_file_uuid, - repo_id=repo_id, - origin_doc_path=path, - username=username, - origin_file_version=origin_file_id, - ) - # copy image files origin_image_parent_path = '/images/sdoc/' + origin_file_uuid + '/' dir_id = seafile_api.get_dir_id_by_path(repo_id, origin_image_parent_path) diff --git a/seahub/seadoc/models.py b/seahub/seadoc/models.py index 4006f8f82c..639bb79f78 100644 --- a/seahub/seadoc/models.py +++ b/seahub/seadoc/models.py @@ -81,6 +81,25 @@ class SeadocDraft(models.Model): class SeadocRevisionManager(models.Manager): + def add(self, doc_uuid, origin_doc_uuid, repo_id, origin_doc_path, username, origin_file_version): + last_revision = self.filter(repo_id=repo_id).order_by('revision_id').last() + if not last_revision: + revision_id = 1 + else: + revision_id = last_revision.revision_id + 1 + return self.create( + doc_uuid=doc_uuid, + origin_doc_uuid=origin_doc_uuid, + repo_id=repo_id, + revision_id=revision_id, + origin_doc_path=origin_doc_path, + username=username, + origin_file_version=origin_file_version, + ) + + def get_by_revision_id(self, repo_id, revision_id): + return self.filter(repo_id=repo_id, revision_id=revision_id).first() + def get_by_doc_uuid(self, doc_uuid): return self.filter(doc_uuid=doc_uuid).first() @@ -113,6 +132,7 @@ class SeadocRevision(models.Model): doc_uuid = models.CharField(max_length=36, unique=True) origin_doc_uuid = models.CharField(max_length=36, db_index=True) repo_id = models.CharField(max_length=36, db_index=True) + revision_id = models.IntegerField() # revision_id in repo origin_doc_path = models.TextField() # used when origin file deleted username = models.CharField(max_length=255, db_index=True) origin_file_version = models.CharField(max_length=100) @@ -126,6 +146,7 @@ class SeadocRevision(models.Model): class Meta: db_table = 'sdoc_revision' + unique_together = ('repo_id', 'revision_id') def to_dict(self, fileuuidmap_queryset=None): from seahub.tags.models import FileUUIDMap @@ -157,6 +178,7 @@ class SeadocRevision(models.Model): 'username': self.username, 'nickname': email2nickname(self.username), 'repo_id': self.repo_id, + 'revision_id': self.revision_id, 'doc_uuid': self.doc_uuid, 'parent_path': parent_path, 'filename': filename, diff --git a/seahub/seadoc/utils.py b/seahub/seadoc/utils.py index 4c55933985..431e0a9ae2 100644 --- a/seahub/seadoc/utils.py +++ b/seahub/seadoc/utils.py @@ -178,12 +178,14 @@ def can_access_seadoc_asset(request, repo_id, path, file_uuid): return False -def is_seadoc_revision(doc_uuid): +def is_seadoc_revision(doc_uuid, revision=None): info = {} - revision = SeadocRevision.objects.get_by_doc_uuid(doc_uuid) + if not revision: + revision = SeadocRevision.objects.get_by_doc_uuid(doc_uuid) if revision: info = {'is_sdoc_revision': True} revision_info = revision.to_dict() + info['revision_id'] = revision_info['revision_id'] info['origin_doc_uuid'] = revision_info['origin_doc_uuid'] info['origin_parent_path'] = revision_info['origin_parent_path'] info['origin_filename'] = revision_info['origin_filename'] diff --git a/seahub/templates/sdoc_file_view_react.html b/seahub/templates/sdoc_file_view_react.html index bc5018dfe0..ef35f6010b 100644 --- a/seahub/templates/sdoc_file_view_react.html +++ b/seahub/templates/sdoc_file_view_react.html @@ -15,6 +15,7 @@ assetsUrl: '{{ assets_url }}', seadocAccessToken: '{{ seadoc_access_token }}', seadocServerUrl: '{{ seadoc_server_url }}', isSdocRevision: {% if is_sdoc_revision %}true{% else %}false{% endif %}, +revisionId: '{{ revision_id }}', originDocUuid: '{{ origin_doc_uuid }}', originParentPath: '{{ origin_parent_path }}', originFilename: '{{ origin_filename }}', diff --git a/seahub/templates/sdoc_revision.html b/seahub/templates/sdoc_revision.html index f11c8d6fc1..1989cd3e21 100644 --- a/seahub/templates/sdoc_revision.html +++ b/seahub/templates/sdoc_revision.html @@ -19,6 +19,7 @@ assetsUrl: '{{ assets_url }}', fileDownloadLink: '{{ file_download_link }}', isSdocRevision: {% if is_sdoc_revision %}true{% else %}false{% endif %}, + revisionId: '{{ revision_id }}', originDocUuid: '{{ origin_doc_uuid }}', originFileDownloadLink: '{{ origin_file_download_link }}', originParentPath: '{{ origin_parent_path }}', diff --git a/seahub/urls.py b/seahub/urls.py index f3b8310641..52d89a7e89 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -13,7 +13,7 @@ from seahub.views.file import view_history_file, view_trash_file,\ view_snapshot_file, view_shared_file, view_file_via_shared_dir,\ text_diff, view_raw_file, download_file, view_lib_file, \ file_access, view_lib_file_via_smart_link, view_media_file_via_share_link, \ - view_media_file_via_public_wiki + view_media_file_via_public_wiki, view_sdoc_revision from seahub.views.repo import repo_history_view, repo_snapshot, view_shared_dir, \ view_shared_upload_link, view_lib_as_wiki @@ -240,6 +240,7 @@ urlpatterns = [ ### lib (replace the old `repo` urls) ### # url(r'^lib/(?P[-0-9a-f]{36})/dir/(?P.*)$', view_lib_dir, name='view_lib_dir'), re_path(r'^lib/(?P[-0-9a-f]{36})/file(?P.*)$', view_lib_file, name='view_lib_file'), + re_path(r'^lib/(?P[-0-9a-f]{36})/revisions/(?P\d+)/$', view_sdoc_revision, name='view_sdoc_revision'), re_path(r'^wiki/lib/(?P[-0-9a-f]{36})/(?P.*)$', view_lib_as_wiki, name='view_lib_as_wiki'), re_path(r'^smart-link/(?P[-0-9a-f]{36})/(?P.*)$', view_lib_file_via_smart_link, name="view_lib_file_via_smart_link"), diff --git a/seahub/views/file.py b/seahub/views/file.py index 9b68d97bae..9e0993cbd0 100644 --- a/seahub/views/file.py +++ b/seahub/views/file.py @@ -76,7 +76,7 @@ from seahub.thumbnail.utils import extract_xmind_image, get_thumbnail_src, \ from seahub.drafts.utils import get_file_draft, \ is_draft_file, has_draft_file from seahub.seadoc.utils import get_seadoc_file_uuid, gen_seadoc_access_token, is_seadoc_revision -from seahub.seadoc.models import SeadocDraft +from seahub.seadoc.models import SeadocDraft, SeadocRevision if HAS_OFFICE_CONVERTER: from seahub.utils import ( @@ -2117,3 +2117,138 @@ def get_file_content_from_cache(file_id, repo_id, file_name): cache.set(cache_key, file_content, 24 * 60 * 60) return err_msg, file_content + +@login_required +@repo_passwd_set_required +def view_sdoc_revision(request, repo_id, revision_id): + + # resource check + repo = seafile_api.get_repo(repo_id) + if not repo: + raise Http404 + + revision = SeadocRevision.objects.get_by_revision_id(repo_id, revision_id) + if not revision: + return render_error(request, 'revision not found') + uuid_map = FileUUIDMap.objects.filter( + uuid=revision.doc_uuid).first() + if not uuid_map: + return render_error(request, _('File does not exist')) + + path = posixpath.join(uuid_map.parent_path, uuid_map.filename) + file_id = seafile_api.get_file_id_by_path(repo_id, path) + if not file_id: + return render_error(request, _('File does not exist')) + + # permission check + username = request.user.username + parent_dir = uuid_map.parent_path + filename = uuid_map.filename + + permission = check_folder_permission(request, repo_id, parent_dir) + if not permission: + return convert_repo_path_when_can_not_view_file(request, repo_id, path) + + org_id = request.user.org.org_id if is_org_context(request) else -1 + # basic file info + return_dict = { + 'is_pro': is_pro_version(), + 'repo': repo, + 'file_id': file_id, + 'last_commit_id': repo.head_cmmt_id, + 'is_repo_owner': is_repo_owner(request, repo_id, username), + 'path': path, + 'parent_dir': parent_dir, + 'filename': filename, + 'file_perm': permission, + 'highlight_keyword': settings.HIGHLIGHT_KEYWORD, + 'enable_file_comment': settings.ENABLE_FILE_COMMENT, + 'enable_watermark': ENABLE_WATERMARK, + 'share_link_force_use_password': SHARE_LINK_FORCE_USE_PASSWORD, + 'share_link_password_min_length': SHARE_LINK_PASSWORD_MIN_LENGTH, + 'share_link_password_strength_level': SHARE_LINK_PASSWORD_STRENGTH_LEVEL, + 'share_link_expire_days_default': SHARE_LINK_EXPIRE_DAYS_DEFAULT, + 'share_link_expire_days_min': SHARE_LINK_EXPIRE_DAYS_MIN, + 'share_link_expire_days_max': SHARE_LINK_EXPIRE_DAYS_MAX, + 'can_download_file': parse_repo_perm(permission).can_download, + 'seafile_collab_server': SEAFILE_COLLAB_SERVER, + } + + # check whether file is starred + is_starred = is_file_starred(username, repo_id, path, org_id) + return_dict['is_starred'] = is_starred + + # check file lock info + try: + is_locked, locked_by_me = check_file_lock(repo_id, path, username) + except Exception as e: + logger.error(e) + is_locked = False + locked_by_me = False + + if is_pro_version() and permission == 'rw': + can_lock_unlock_file = True + else: + can_lock_unlock_file = False + + return_dict['file_locked'] = is_locked + return_dict['locked_by_me'] = locked_by_me + return_dict['can_lock_unlock_file'] = can_lock_unlock_file + + # file shared link + l = FileShare.objects.filter(repo_id=repo_id).filter( + username=username).filter(path=path) + fileshare = l[0] if len(l) > 0 else None + file_shared_link = gen_file_share_link(fileshare.token) if fileshare else '' + + return_dict['fileshare'] = fileshare, + return_dict['file_shared_link'] = file_shared_link + + if parse_repo_perm(permission).can_download and \ + request.user.permissions.can_generate_share_link(): + return_dict['can_share_file'] = True + else: + return_dict['can_share_file'] = False + + # fetch file contributors and latest contributor + try: + # get real path for sub repo + real_path = repo.origin_path + path if repo.origin_path else path + dirent = seafile_api.get_dirent_by_path(repo.store_id, real_path) + if dirent: + latest_contributor, last_modified = dirent.modifier, dirent.mtime + else: + latest_contributor, last_modified = None, 0 + except Exception as e: + logger.error(e) + latest_contributor, last_modified = None, 0 + + return_dict['latest_contributor'] = latest_contributor + return_dict['last_modified'] = last_modified + + # get file type and extention + filetype, fileext = get_file_type_and_ext(filename) + return_dict['fileext'] = fileext + return_dict['filetype'] = filetype + + file_uuid = str(uuid_map.uuid) + return_dict['file_uuid'] = file_uuid + return_dict['assets_url'] = '/api/v2.1/seadoc/download-image/' + file_uuid + return_dict['seadoc_server_url'] = SEADOC_SERVER_URL + + can_edit_file = True + if parse_repo_perm(permission).can_edit_on_web is False: + can_edit_file = False + elif is_locked and not locked_by_me: + can_edit_file = False + + seadoc_perm = 'rw' if can_edit_file else 'r' + return_dict['can_edit_file'] = can_edit_file + return_dict['seadoc_access_token'] = gen_seadoc_access_token(file_uuid, filename, username, permission=seadoc_perm) + + # revision + revision_info = is_seadoc_revision(file_uuid, revision) + return_dict.update(revision_info) + + send_file_access_msg(request, repo, path, 'web') + return render(request, 'sdoc_file_view_react.html', return_dict) diff --git a/sql/mysql.sql b/sql/mysql.sql index 68bd55399d..86ef516cb5 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -1401,6 +1401,7 @@ CREATE TABLE `sdoc_draft` ( CREATE TABLE `sdoc_revision` ( `id` int(11) NOT NULL AUTO_INCREMENT, `repo_id` varchar(36) NOT NULL, + `revision_id` int(11) NOT NULL, `doc_uuid` varchar(36) NOT NULL, `origin_doc_uuid` varchar(36) NOT NULL, `origin_doc_path` longtext NOT NULL, @@ -1413,6 +1414,7 @@ CREATE TABLE `sdoc_revision` ( `updated_at` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `sdoc_revise_doc_uuid` (`doc_uuid`), + UNIQUE KEY `sdoc_revision_repo_id_revision_id` (`repo_id`, `revision_id`), KEY `sdoc_revision_repo_id` (`repo_id`), KEY `sdoc_revision_origin_doc_uuid` (`origin_doc_uuid`), KEY `sdoc_revision_username` (`username`),