From f94c4c5a611ce36779fc6d0da2658ac5c003a45e Mon Sep 17 00:00:00 2001 From: zhengxie Date: Thu, 3 Jan 2013 19:28:57 +0800 Subject: [PATCH] Added dir shared link feature --- media/css/seahub.css | 9 +- media/img/download-blue.png | Bin 0 -> 3138 bytes media/img/upload.png | Bin 503 -> 3125 bytes share/models.py | 3 +- share/templates/repo/share_admin.html | 13 +- share/views.py | 53 ++++--- templates/file_view.html | 117 +-------------- templates/repo.html | 62 +++++--- templates/shared_file_view.html | 13 ++ templates/snippets/shared_link_js.html | 117 +++++++++++++++ templates/view_shared_dir.html | 157 ++++++++++++++++++++ thirdpart/seaserv/__init__.py | 2 +- thirdpart/seaserv/service.py | 7 + urls.py | 6 +- utils/__init__.py | 10 ++ views.py | 192 +++++++++++++++++++++---- 16 files changed, 577 insertions(+), 184 deletions(-) create mode 100644 media/img/download-blue.png create mode 100644 templates/snippets/shared_link_js.html create mode 100644 templates/view_shared_dir.html diff --git a/media/css/seahub.css b/media/css/seahub.css index b0ef7f9fcb..8408fe5354 100644 --- a/media/css/seahub.css +++ b/media/css/seahub.css @@ -896,10 +896,14 @@ textarea:-moz-placeholder {/* for FF */ .repo-op .op-btn { *margin-left:5px;/* for ie 7*/ } -#upload-file { +#upload-file{ padding-left:19px; background-image:url('../img/upload.png?v=1'); } +#download-dir { + padding-left:19px; + background-image:url('../img/download-blue.png?t=1352500800'); +} #add-new-dir { padding-left:23px; background-image:url('../img/folder-add.png'); @@ -1364,7 +1368,8 @@ textarea:-moz-placeholder {/* for FF */ color:#444; text-align:right; } -#shared-link { +#shared-link, +#shared-link-text { border:0; } .file-op { diff --git a/media/img/download-blue.png b/media/img/download-blue.png new file mode 100644 index 0000000000000000000000000000000000000000..f24f147c4aa2f2a3fa4a50ef2a28512ad89521ee GIT binary patch literal 3138 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 literal 0 HcmV?d00001 diff --git a/media/img/upload.png b/media/img/upload.png index 54f837c4b42fcbb38f7e6e51b29af752965a878c..78512522dbca6853f27509dc1ffdbbdf49f28d26 100644 GIT binary patch delta 3100 zcmV+%4CC|n1GN~CB!2{FK}|sb0I`n?{9y$E0182ALqkwWLqi}?a&Km7Y-IodNDZBq zcT`i^7KhKhH@(mjA|NI78hQyJ(mO~M1W}1efKUR4geG=G1x6GRDOO}uzyU{xB4b4q z3xk4U*9r0vP{zSgL`CJ@jB5$+tu^!Bn*GOF-`VH4*V$+9eSg=w08spb1j2M!0f022 zSQPH-!CVp(%f$Br7!UytSOLJ{W@ZFO_(THK{JlMynW#v{v-a*TfMmPdEWc1DbJqWV zks>!kBnAKqMb$PuekK>?0+ds;#ThdH1j_W4DKdsJG8Ul;qO2n0#IJ1jr{*iW$(WZW z6^O6dCS`@&W(|l*Bo_at@36IcS~$c&2CMiUf&CVx|$-m}%wc$Y%QSvs3wEF)NhA zP2q@mOwaVR4301tfUj?sMFAyQZJB71?X4W`?OE35Xn*;?+20<1Q~f>2diz!JChX6i zL5!YR-)!uxFTEB3_9R-H9kafbRRFYY20-QEtWWPC0P+O@wBDTa9)@gQlEmT+Cku=0 z>}+!ZpKC5F=r{YHf^W*dhB^0{%kKMDJEj*ufg?#3Gi62Prl+P$M9j<#4wuhl{r3?6 z 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 - {% trans "File"%} + {% trans "File or Folder"%} {% trans "Library"%} {% trans "Visits"%} {% trans "Operations"%} {% for fs in fileshares %} - {{ fs.filename }} + {% if fs.s_type == 'f' %} + {{ fs.filename }} + {% else %} + {{ fs.filename }} + {% endif %} {{ fs.repo.name }} {{ fs.view_cnt }} - {% trans "View" %} + {% trans "View" %} {% trans "Remove"%} @@ -173,8 +177,7 @@ $('.cancel-share').click(function() { }); $(".view-link").click(function() { - var token = $(this).attr('data'), - link = '{{ protocol }}://' + '{{ domain }}{{ SITE_ROOT }}f/' + token + '/'; + var link = $(this).attr('data'); $('#link').before('

' + link + '

'); $('#shared-link').val(link).css('width', $('#link').prev().width() + 2); $("#link").modal({appendTo:'#main'}); diff --git a/share/views.py b/share/views.py index f7f52f3a3c..64b0e38452 100644 --- a/share/views.py +++ b/share/views.py @@ -3,7 +3,8 @@ import os import simplejson as json from django.core.mail import send_mail from django.core.urlresolvers import reverse -from django.http import HttpResponse, HttpResponseRedirect, Http404 +from django.http import HttpResponse, HttpResponseRedirect, Http404, \ + HttpResponseBadRequest from django.shortcuts import render_to_response from django.template import Context, loader, RequestContext from django.utils.translation import ugettext as _ @@ -25,12 +26,13 @@ from seahub.contacts.signals import mail_sended from seahub.share.models import FileShare from seahub.views import validate_owner, is_registered_user from seahub.utils import render_permission_error, string2list, render_error, \ - gen_token + gen_token, gen_shared_link try: from seahub.settings import CLOUD_MODE except ImportError: CLOUD_MODE = False +from seahub.settings import SITE_ROOT @login_required def share_repo(request): @@ -176,7 +178,6 @@ def repo_remove_share(request): next = request.META.get('HTTP_REFERER', None) if not next: - from seahub.settings import SITE_ROOT next = SITE_ROOT return HttpResponseRedirect(next) @@ -233,13 +234,17 @@ def share_admin(request): # link.repo_name = repo.name # link.remain_time = anon_share_token_generator.get_remain_time(link.token) - # File shared links + # Shared links fileshares = FileShare.objects.filter(username=username) p_fileshares = [] # personal file share for fs in fileshares: - if is_personal_repo(fs.repo_id): - # only list files in personal repos - fs.filename = os.path.basename(fs.path) + if is_personal_repo(fs.repo_id): # only list files in personal repos + if fs.s_type == 'f': + fs.filename = os.path.basename(fs.path) + fs.shared_link = gen_shared_link(request, fs.token, 'f') + else: + fs.filename = os.path.basename(fs.path[:-1]) + fs.shared_link = gen_shared_link(request, fs.token, 'd') fs.repo = get_repo(fs.repo_id) p_fileshares.append(fs) @@ -248,8 +253,6 @@ def share_admin(request): "shared_repos": shared_repos, # "out_links": out_links, "fileshares": p_fileshares, - "protocol": request.is_secure() and 'https' or 'http', - "domain": RequestSite(request).domain, }, context_instance=RequestContext(request)) @login_required @@ -371,17 +374,32 @@ def remove_anonymous_share(request, token): @login_required def get_shared_link(request): """ - Handle ajax request to generate file shared link. + Handle ajax request to generate file or dir shared link. """ if not request.is_ajax(): raise Http404 content_type = 'application/json; charset=utf-8' - repo_id = request.GET.get('repo_id') - path = request.GET.get('p', '/') - if path[-1] == '/': - path = path[:-1] + repo_id = request.GET.get('repo_id', '') + share_type = request.GET.get('type', 'f') # `f` or `d` + path = request.GET.get('p', '') + if not (repo_id and path): + err = _('Invalid arguments') + data = json.dumps([{'error': err}]) + return HttpResponse(data, status=400, content_type=content_type) + + if share_type == 'f': + if path[-1] == '/': # cut out last '/' at end of path + path = path[:-1] + else: + if path == '/': # can not share root dir + err = _('Can not share root dir.') + data = json.dumps([{'error': err}]) + return HttpResponse(data, status=400, content_type=content_type) + else: + if path[-1] != '/': # append '/' at end of path + path += '/' l = FileShare.objects.filter(repo_id=repo_id).filter( username=request.user.username).filter(path=path) @@ -396,6 +414,7 @@ def get_shared_link(request): fs.repo_id = repo_id fs.path = path fs.token = token + fs.s_type = 'f' if share_type == 'f' else 'd' try: fs.save() @@ -403,8 +422,10 @@ def get_shared_link(request): err = _('Failed to get the link, please retry it.') data = json.dumps([{'error': err}]) return HttpResponse(data, status=500, content_type=content_type) - - data = json.dumps([{'token': token}]) + + shared_link = gen_shared_link(request, token, fs.s_type) + + data = json.dumps([{'token': token, 'shared_link': shared_link}]) return HttpResponse(data, status=200, content_type=content_type) @login_required diff --git a/templates/file_view.html b/templates/file_view.html index d2f570e441..1584866819 100644 --- a/templates/file_view.html +++ b/templates/file_view.html @@ -149,122 +149,9 @@ $('#view-original, #download').click(function() { $('#edit').click(function() { location.href = $(this).attr('data'); }); -function showLink() { - $('#get-shared-link').addClass('hide'); - $('#shared-link, #send-shared-link, #rm-shared-link').removeClass('hide'); -} -function hideLink() { - $('#shared-link, #send-shared-link, #rm-shared-link').addClass('hide'); - $('#get-shared-link').removeClass('hide'); -} -function setLinkWidth() { - var link = $('#shared-link'); - link.before('

' + link.val() + '

'); - link.css('width', link.prev().width() + 2); - link.prev().remove(); -} -if ($.trim($('#shared-link').val())) { - setLinkWidth(); -} -{% if fileshare.token %} -showLink(); -{% else %} -hideLink(); -{% endif %} - -$('#get-shared-link').click(function() { - var url = $(this).attr('data'); - $.ajax({ - url: url, - dataType: 'json', - cache: false, - contentType: 'application/json; charset=utf-8', - success: function(data) { - if (data.length > 0) { - var t = data[0]['token']; - $('#rm-shared-link').attr('data', '{% url 'remove_shared_link' %}?t=' + t); - $('#shared-link, input[name="file_shared_link"]').val('{{ protocol }}://{{ domain }}{{ SITE_ROOT }}f/' + t + '/'); - setLinkWidth(); - showLink(); - } - }, - error: function(xhr, ajaxOptions, thrownError) { - var jsonVal = jQuery.parseJSON(xhr.responseText); - feedback(jsonVal[0]['error'], 'error'); - } - }); -}); - -$('#rm-shared-link').click(function() { - var url = $(this).attr('data'); - $.ajax({ - url: url, - dataType: 'json', - cache: false, - contentType: 'application/json; charset=utf-8', - success: function(data) { - hideLink(); - $('#shared-link').val(''); - } - }); -}); - -var share_list = []; -{% for contact in contacts %} -share_list.push({value:'{{ contact.contact_email }}', label:'{{ contact.contact_email }}'}); -{% endfor %} -$('#send-shared-link').click(function() { - $("#link-send-form").modal({appendTo: "#main", focus: false}); - $('#simplemodal-container').css('height', 'auto'); - addAutocomplete('#link-send-input', '#link-send-form', share_list); -}); - -$("#link-send-form").submit(function(event) { - var form = $(this), - file_shared_link = form.children('input[name="file_shared_link"]').val(), - email = $.trim(form.children('textarea[name="email"]').val()), - submit_btn = form.children('input[type="submit"]'); - - if (!email) { - apply_form_error('link-send-form', '{% trans "Please input at least an email." %}'); - return false; - } - - disable(submit_btn); - $('#link-send-form .error').addClass('hide'); - $('#sending').removeClass('hide'); - - $.ajax({ - type: "POST", - url: "{% url 'send_shared_link' %}", - dataType: 'json', - cache: false, - contentType: 'application/json; charset=utf-8', - beforeSend: prepareCSRFToken, - data: {file_shared_link: file_shared_link, email: email}, - success: function(data) { - $.modal.close(); - feedback('{% trans "Successfully sent." %}', "success"); - }, - error: function(data, textStatus, jqXHR) { - $('#sending').addClass('hide'); - enable(submit_btn); - var errors = $.parseJSON(data.responseText); - $.each(errors, function(index, value) { - if (index == 'error') { - apply_form_error('link-send-form', value); - } else { - apply_form_error('link-send-form', value[0]); - } - }); - } - }); - return false; -}); -$('#shared-link').click(function() { - $(this).select(); -}); +//share link +{% include "snippets/shared_link_js.html" %} //star $('#star').click(function() { diff --git a/templates/repo.html b/templates/repo.html index 59f4a197b2..871d57c137 100644 --- a/templates/repo.html +++ b/templates/repo.html @@ -17,10 +17,18 @@

{{repo.props.name}}

+ {% if path != '/' %} + + + + + {% endif %} + {% if path == '/' %} {% if user_perm == 'rw' %} {% endif %} + {% endif %}
{% if user_perm == 'r' %} @@ -80,13 +88,16 @@ {% endif %} {% endfor %}

- {% if user_perm == 'rw' %}
+ {% if user_perm and path != '/' %} + + {% endif %} + {% if user_perm == 'rw' %} + {% endif %}
- {% endif %} @@ -111,7 +122,8 @@ {% if user_perm %}
{% if user_perm == 'rw' %} {% trans 'More operations'%} @@ -283,15 +295,16 @@

{% trans 'Share' %}

-

{% trans 'Link: ' %}

+

{% trans 'Link: ' %}

- -
-
- - -

-

{% trans "Sending..."%}

+ +

Send Link

+
+
+ + +

+

{% trans "Sending..."%}

@@ -587,7 +600,7 @@ $('#add-new-file-form').submit(function() { $('#add-new-dir-form').submit(function() { var new_dir = $(this).find('input[name="new_dir_name"]').val(); if (!$.trim(new_dir)) { - apply_form_error('add-new-dir-form', "{% trans "Directory name can't be empty" %}"); + apply_form_error('add-new-dir-form', "{% trans "Directory name can not be empty" %}"); return false; } @@ -740,24 +753,31 @@ $('.file-share').click(function() { var filename = $(this).data('name'); function showPopup(link) { $('#file-share .op-target').html(trimFilename(filename, 30)); - $('#shared-link, #link-send-form input[name="file_shared_link"]').val(link); + $('#shared-link-text, #link-send-form input[name="file_shared_link"]').val(link); $('#main').append('

' + link + '

'); - $('#shared-link').css({'width':$('#linkwidth').width() + 2}); - $('#file-share').modal({'focus':false}); // in ff: if 'focus' is true, 'shared-link' gets the focus + $('#shared-link-text').css({'width':$('#linkwidth').width() + 2}); + $('#file-share').modal({'focus':false}); // in ff: if 'focus' is true, 'shared-link-text' gets the focus $('#linkwidth').remove(); $('#simplemodal-container').css({'width':'auto', 'height':'auto'}); } if ($(this).data('link')) { showPopup($(this).data('link')); } else { + var aj_url = ''; + if ($(this).data('type') == 'd') { + aj_url = '{% url 'get_shared_link' %}?repo_id={{ repo.id }}&p={{ path|urlencode }}' + e(filename) + '&type=d'; + } else { + aj_url = '{% url 'get_shared_link' %}?repo_id={{ repo.id }}&p={{ path|urlencode }}' + e(filename); + } + $.ajax({ - url: '{% url 'get_shared_link' %}?repo_id={{ repo.id }}&p={{ path|urlencode }}' + e(filename), + url: aj_url, dataType: 'json', cache: false, contentType: 'application/json; charset=utf-8', success: function(data) { if (data.length > 0) { - showPopup('{{ protocol }}://{{ domain }}{{ SITE_ROOT }}f/' + data[0]['token'] + '/'); + showPopup(data[0]['shared_link']); } }, error: function(xhr, ajaxOptions, thrownError) { @@ -824,9 +844,15 @@ $("#link-send-form").submit(function() { }); return false; }); -$('#shared-link').click(function() { $(this).select(); }); +$('#shared-link-text').click(function() { $(this).select(); }); + +$('#download-dir').click(function() { + location.href = $(this).attr('data'); +}); +{% include "snippets/shared_link_js.html" %} {% include "snippets/list_commit_detail.html" %} {% include "snippets/bottom_bar.html" %} + {% include 'snippets/file_upload_progress_js.html' %} {% endblock %} diff --git a/templates/shared_file_view.html b/templates/shared_file_view.html index 08087aec8c..85a87d8c9b 100644 --- a/templates/shared_file_view.html +++ b/templates/shared_file_view.html @@ -13,7 +13,20 @@

{{ file_name }}

+ {% if zipped %} +

+ {% trans "Current path: "%} + {% for name, link in zipped %} + {% if not forloop.last %} + {{ name }} / + {% else %} + {{ name }} + {% endif %} + {% endfor %} +

+ {% else %}

{% trans "Shared by: " %}{{ username|email2nickname }}

+ {% endif %} {% if filetype == 'Text' or filetype == 'Image' or filetype == 'SVG' or filetype == 'Markdown' %} diff --git a/templates/snippets/shared_link_js.html b/templates/snippets/shared_link_js.html new file mode 100644 index 0000000000..46d81b914d --- /dev/null +++ b/templates/snippets/shared_link_js.html @@ -0,0 +1,117 @@ +{% load i18n %} +{% load url from future %} +function showLink() { + $('#get-shared-link').addClass('hide'); + $('#shared-link, #send-shared-link, #rm-shared-link').removeClass('hide'); +} +function hideLink() { + $('#shared-link, #send-shared-link, #rm-shared-link').addClass('hide'); + $('#get-shared-link').removeClass('hide'); +} +function setLinkWidth() { + var link = $('#shared-link'); + link.before('

' + link.val() + '

'); + link.css('width', link.prev().width() + 2); + link.prev().remove(); +} +if ($.trim($('#shared-link').val())) { + setLinkWidth(); +} +{% if fileshare.token %} +showLink(); +{% else %} +hideLink(); +{% endif %} + +$('#get-shared-link').click(function() { + var url = $(this).attr('data'); + $.ajax({ + url: url, + dataType: 'json', + cache: false, + contentType: 'application/json; charset=utf-8', + success: function(data) { + if (data.length > 0) { + var t = data[0]['token']; + $('#rm-shared-link').attr('data', '{% url 'remove_shared_link' %}?t=' + t); + $('#shared-link, input[name="file_shared_link"]').val(data[0]['shared_link']); + setLinkWidth(); + showLink(); + } + }, + error: function(xhr, ajaxOptions, thrownError) { + var jsonVal = jQuery.parseJSON(xhr.responseText); + feedback(jsonVal[0]['error'], 'error'); + } + }); +}); + +$('#rm-shared-link').click(function() { + var url = $(this).attr('data'); + $.ajax({ + url: url, + dataType: 'json', + cache: false, + contentType: 'application/json; charset=utf-8', + success: function(data) { + hideLink(); + $('#shared-link').val(''); + } + }); +}); + +var share_list = []; +{% for contact in contacts %} +share_list.push({value:'{{ contact.contact_email }}', label:'{{ contact.contact_email }}'}); +{% endfor %} +$('#send-shared-link').click(function() { + $("#link-send-form").modal({appendTo: "#main", focus: false}); + $('#simplemodal-container').css('height', 'auto'); + addAutocomplete('#link-send-input', '#link-send-form', share_list); +}); + +$("#link-send-form").submit(function(event) { + var form = $(this), + file_shared_link = form.children('input[name="file_shared_link"]').val(), + email = $.trim(form.children('textarea[name="email"]').val()), + submit_btn = form.children('input[type="submit"]'); + + if (!email) { + apply_form_error('link-send-form', '{% trans "Please input at least an email." %}'); + return false; + } + + disable(submit_btn); + $('#link-send-form .error').addClass('hide'); + $('#sending').removeClass('hide'); + + $.ajax({ + type: "POST", + url: "{% url 'send_shared_link' %}", + dataType: 'json', + cache: false, + contentType: 'application/json; charset=utf-8', + beforeSend: prepareCSRFToken, + data: {file_shared_link: file_shared_link, email: email}, + success: function(data) { + $.modal.close(); + feedback('{% trans "Successfully sent." %}', "success"); + }, + error: function(data, textStatus, jqXHR) { + $('#sending').addClass('hide'); + enable(submit_btn); + var errors = $.parseJSON(data.responseText); + $.each(errors, function(index, value) { + if (index == 'error') { + apply_form_error('link-send-form', value); + } else { + apply_form_error('link-send-form', value[0]); + } + }); + } + }); + return false; +}); +$('#shared-link').click(function() { + $(this).select(); +}); diff --git a/templates/view_shared_dir.html b/templates/view_shared_dir.html new file mode 100644 index 0000000000..9d1ffebfbf --- /dev/null +++ b/templates/view_shared_dir.html @@ -0,0 +1,157 @@ +{% extends base_template %} + +{% load seahub_tags i18n %} +{% load url from future %} + +{% block info_bar_message %} +{% endblock %} + +{% block main_panel %} +

{{ dir_name }}

+
+

{% trans "Shared by: " %}{{ username|email2nickname }}

+
+ +
+
+
+

+ {% trans "Current path: "%} + {% for name, link in zipped %} + {% if not forloop.last %} + {{ name }} / + {% else %} + {{ name }} + {% endif %} + {% endfor %} +

+
+ +
+ +
+ +
+ + + + + + + + + {% for dirent in dir_list %} + + + + + + + + + {% endfor %} + + {% for dirent in file_list %} + + + + + + + + + {% endfor %} +
{% trans "Name"%}{% trans "Size"%}{% trans "Operations"%}
{% trans + {{ dirent.obj_name }} + + +
{% trans + {{ dirent.props.obj_name }} + {{ dirent.file_size|filesizeformat }} + +
+ + + +{% endblock %} + +{% block extra_script %} + +{% endblock %} diff --git a/thirdpart/seaserv/__init__.py b/thirdpart/seaserv/__init__.py index 9fba98ccb0..4b84e1c1ba 100644 --- a/thirdpart/seaserv/__init__.py +++ b/thirdpart/seaserv/__init__.py @@ -16,7 +16,7 @@ from service import get_repos, get_repo, get_commits, get_branches, remove_repo, list_personal_shared_repos, is_personal_repo, list_inner_pub_repos, \ is_org_repo_owner, get_org_repo_owner, is_org_repo, get_file_size,\ list_personal_repos_by_owner, get_repo_token_nonnull, get_repo_owner, \ - server_repo_size + server_repo_size, get_file_id_by_path from service import get_binding_peerids, is_valid_filename, check_permission,\ is_passwd_set diff --git a/thirdpart/seaserv/service.py b/thirdpart/seaserv/service.py index 431f58aebd..b0ac17be60 100644 --- a/thirdpart/seaserv/service.py +++ b/thirdpart/seaserv/service.py @@ -669,6 +669,13 @@ def get_file_size(file_id): fs = 0 return fs +def get_file_id_by_path(repo_id, path): + try: + ret = seafserv_threaded_rpc.get_file_id_by_path(repo_id, path) + except SearpcError, e: + ret = '' + return ret + def get_related_users_by_repo(repo_id): """Give a repo id, returns a list of users of: - the repo owner diff --git a/urls.py b/urls.py index 344d806c45..167209af4d 100644 --- a/urls.py +++ b/urls.py @@ -57,7 +57,9 @@ urlpatterns = patterns('', (r'^repo/(?P[-0-9a-f]{36})/file/edit/$', file_edit), url(r'^repo/(?P[-0-9a-f]{36})/(?P[^/]+)/$', repo_access_file, name='repo_access_file'), - url(r'^f/(?P[^/]+)/$', view_shared_file, name='view_shared_file'), + url(r'^f/(?P[a-f0-9]{10})/$', view_shared_file, name='view_shared_file'), + url(r'^d/(?P[a-f0-9]{10})/$', view_shared_dir, name='view_shared_dir'), + url(r'^d/(?P[a-f0-9]{10})/files/$', view_file_via_shared_dir, name='view_file_via_shared_dir'), (r'^file_upload_progress_page/$', file_upload_progress_page), (r'^publicrepo/create/$', public_repo_create), (r'^events/$', events), @@ -77,7 +79,7 @@ urlpatterns = patterns('', url(r'^useradmin/password/reset/(?P[^/]+)/$', user_reset, name='user_reset'), ### Apps ### - (r'^api/', include('api.urls')), +# (r'^api/', include('api.urls')), (r'^api2/', include('api2.urls')), (r'^avatar/', include('avatar.urls')), (r'^notification/', include('notifications.urls')), diff --git a/utils/__init__.py b/utils/__init__.py index e3f1b81ea6..eddd2a1cbc 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -6,6 +6,7 @@ import random import stat import urllib2 +from django.contrib.sites.models import RequestSite from django.shortcuts import render_to_response from django.template import RequestContext from django.utils.hashcompat import sha_constructor @@ -637,3 +638,12 @@ def get_dir_starred_files(email, repo_id, parent_dir, org_id=-1): path__startswith=parent_dir, org_id=org_id) return [ f.path for f in starred_files ] + +def gen_shared_link(request, token, s_type): + http_or_https = request.is_secure() and 'https' or 'http' + domain = RequestSite(request).domain + + if s_type == 'f': + return '%s://%s%sf/%s/' % (http_or_https, domain, settings.SITE_ROOT, token) + else: + return '%s://%s%sd/%s/' % (http_or_https, domain, settings.SITE_ROOT, token) diff --git a/views.py b/views.py index 53de75cee6..b93f6bd20f 100644 --- a/views.py +++ b/views.py @@ -44,7 +44,7 @@ from seaserv import ccnet_rpc, ccnet_threaded_rpc, get_repos, get_emailusers, \ list_inner_pub_repos, get_org_groups_by_repo, is_org_repo_owner, \ get_org_repo_owner, is_passwd_set, get_file_size, check_quota, \ get_related_users_by_repo, get_related_users_by_org_repo, HtmlDiff, \ - get_session_info, get_group_repoids, get_repo_owner + get_session_info, get_group_repoids, get_repo_owner, get_file_id_by_path from pysearpc import SearpcError from signals import repo_created, repo_deleted @@ -64,7 +64,7 @@ from forms import AddUserForm, RepoCreateForm, RepoNewDirForm, RepoNewFileForm,\ FileCommentForm, RepoRenameFileForm, RepoPassowrdForm, SharedRepoCreateForm,\ SetUserQuotaForm from utils import render_permission_error, render_error, list_to_string, \ - get_httpserver_root, get_ccnetapplet_root, \ + get_httpserver_root, get_ccnetapplet_root, gen_shared_link, \ calculate_repo_last_modify, valid_previewed_file, \ check_filename_with_rename, get_accessible_repos, EMPTY_SHA1, \ get_file_revision_id_size, get_ccnet_server_addr_port, \ @@ -164,7 +164,7 @@ def get_repo_dirents(request, repo_id, commit, path): # return render_error(self.request, e.msg) org_id = -1 - if request.user.org: + if hasattr(request.user, 'org') and request.user.org: org_id = request.user.org['org_id'] starred_files = get_dir_starred_files(request.user.username, repo_id, path, org_id) @@ -173,19 +173,28 @@ def get_repo_dirents(request, repo_id, commit, path): domain = RequestSite(request).domain for dirent in dirs: + dirent.sharelink = '' + if stat.S_ISDIR(dirent.props.mode): + dpath = os.path.join(path, dirent.obj_name) + if dpath[-1] != '/': + dpath += '/' + for share in fileshares: + if dpath == share.path: + dirent.sharelink = gen_shared_link(request, share.token, 'd') + dirent.sharetoken = share.token + break dir_list.append(dirent) else: file_list.append(dirent) dirent.file_size = get_file_size(dirent.obj_id) dirent.starred = False - dirent.sharelink = '' fpath = os.path.join(path, dirent.obj_name) if fpath in starred_files: dirent.starred = True for share in fileshares: if fpath == share.path: - dirent.sharelink = '%s://%s%sf/%s/' % (http_or_https, domain, settings.SITE_ROOT, share.token) + dirent.sharelink = gen_shared_link(request, share.token, 'f') dirent.sharetoken = share.token break @@ -342,6 +351,26 @@ class RepoView(LoginRequiredMixin, CtxSwitchRequiredMixin, RepoMixin, return gen_file_upload_url(token, 'update') else: return '' + + def get_fileshare(self, repo_id, user, path): + if path == '/': # no shared link for root dir + return None + + l = FileShare.objects.filter(repo_id=repo_id).filter(\ + username=user).filter(path=path) + fileshare = l[0] if len(l) > 0 else None + return fileshare + + def get_shared_link(self, fileshare): + # dir shared link + http_or_https = self.request.is_secure() and 'https' or 'http' + domain = RequestSite(self.request).domain + + if fileshare: + dir_shared_link = gen_shared_link(self.request, fileshare.token, 'd') + else: + dir_shared_link = '' + return dir_shared_link def get_context_data(self, **kwargs): kwargs['repo'] = self.repo @@ -375,6 +404,9 @@ class RepoView(LoginRequiredMixin, CtxSwitchRequiredMixin, RepoMixin, kwargs['protocol'] = self.protocol kwargs['domain'] = self.domain kwargs['contacts'] = self.contacts + kwargs['fileshare'] = self.get_fileshare(\ + self.repo_id, self.request.user.username, self.path) + kwargs['dir_shared_link'] = self.get_shared_link(kwargs['fileshare']) return kwargs @@ -1293,13 +1325,12 @@ def repo_view_file(request, repo_id): http_or_https = request.is_secure() and 'https' or 'http' domain = RequestSite(request).domain if fileshare: - file_shared_link = '%s://%s%sf/%s/' % (http_or_https, domain, settings.SITE_ROOT, fileshare.token) + file_shared_link = gen_shared_link(request, fileshare.token, 'f') else: file_shared_link = '' # my constacts contacts = Contact.objects.filter(user_email=request.user.username) - # Get groups this repo is shared. if request.user.org: @@ -2412,7 +2443,7 @@ def view_shared_file(request, token): path = fileshare.path http_server_root = get_httpserver_root() - if path[-1] == '/': + if path[-1] == '/': # Normalize file path path = path[:-1] filename = os.path.basename(path) quote_filename = urllib2.quote(filename.encode('utf-8')) @@ -2468,7 +2499,113 @@ def view_shared_file(request, token): 'DOCUMENT_CONVERTOR_ROOT': DOCUMENT_CONVERTOR_ROOT, }, context_instance=RequestContext(request)) +def view_shared_dir(request, token): + assert token is not None # Checked by URLconf + try: + fileshare = FileShare.objects.get(token=token) + except FileShare.DoesNotExist: + raise Http404 + + username = fileshare.username + repo_id = fileshare.repo_id + path = request.GET.get('p', '') + path = fileshare.path if not path else path + if path[-1] != '/': # Normalize dir path + path += '/' + + if not path.startswith(fileshare.path): + path = fileshare.path # Can not view upper dir of shared dir + + repo = get_repo(repo_id) + if not repo: + raise Http404 + + dir_name = os.path.basename(path[:-1]) + current_commit = get_commits(repo_id, 0, 1)[0] + file_list, dir_list = get_repo_dirents(request, repo_id, current_commit, + path) + zipped = gen_path_link(path, '') + + if path == fileshare.path: # When user view the shared dir.. + # increase shared link view_cnt, + fileshare = FileShare.objects.get(token=token) + fileshare.view_cnt = F('view_cnt') + 1 + fileshare.save() + + return render_to_response('view_shared_dir.html', { + 'repo': repo, + 'token': token, + 'path': path, + 'username': username, + 'dir_name': dir_name, + 'file_list': file_list, + 'dir_list': dir_list, + 'zipped': zipped, + }, context_instance=RequestContext(request)) + +def view_file_via_shared_dir(request, token): + assert token is not None # Checked by URLconf + + try: + fileshare = FileShare.objects.get(token=token) + except FileShare.DoesNotExist: + raise Http404 + + username = fileshare.username + repo_id = fileshare.repo_id + path = request.GET.get('p', '') + if not path: + raise Http404 + + if not path.startswith(fileshare.path): # Can not view upper dir of shared dir + raise Http404 + + repo = get_repo(repo_id) + if not repo: + raise Http404 + + file_name = os.path.basename(path) + quote_filename = urllib2.quote(file_name.encode('utf-8')) + file_id = get_file_id_by_path(repo_id, path) + if not file_id: + return render_error(request, _(u'File not exists')) + + access_token = seafserv_rpc.web_get_access_token(repo.id, file_id, + 'view', '') + filetype, fileext = valid_previewed_file(file_name) + # Raw path + raw_path = gen_file_get_url(access_token, quote_filename) + # get file content + err = '' + file_content = '' + swf_exists = False + if filetype == 'Text' or filetype == 'Markdown' or filetype == 'Sf': + err, file_content, encoding = repo_file_get(raw_path) + elif filetype == 'Document' or filetype == 'PDF': + err, swf_exists = flash_prepare(raw_path, obj_id, fileext) + + zipped = gen_path_link(path, '') + + return render_to_response('shared_file_view.html', { + 'repo': repo, + 'obj_id': file_id, + 'path': path, + 'file_name': file_name, + 'shared_token': token, + 'access_token': access_token, + 'filetype': filetype, + 'fileext': fileext, + 'raw_path': raw_path, + 'username': username, + 'err': err, + 'file_content': file_content, + 'swf_exists': swf_exists, + 'DOCUMENT_CONVERTOR_ROOT': DOCUMENT_CONVERTOR_ROOT, + 'zipped': zipped, + 'token': token, + }, context_instance=RequestContext(request)) + def flash_prepare(raw_path, obj_id, doctype): curl = DOCUMENT_CONVERTOR_ROOT + 'convert' data = {'doctype': doctype, @@ -2649,22 +2786,34 @@ def repo_star_file(request, repo_id): unstar_file(request.user.username, repo_id, path) return HttpResponse(json.dumps({'success':True}), content_type=content_type) -@login_required def repo_download_dir(request, repo_id): repo = get_repo(repo_id) if not repo: return render_error(request, _(u'Library not exists')) - try: - parent_dir = request.GET['parent'] - dirname = request.GET['dirname'] - except KeyError: - return render_error(request, _(u'Invalid arguments')) + path = request.GET.get('p', '/') + if path[-1] != '/': # Normalize dir path + path += '/' - path = os.path.join(parent_dir, dirname.rstrip('/')) + if len(path) > 1: + dirname = os.path.basename(path.rstrip('/')) # Here use `rstrip` to cut out last '/' in path + else: + dirname = repo.name + + allow_download = False + fileshare_token = request.GET.get('t', '') + if fileshare_token: # download dir from dir shared link + try: + fileshare = FileShare.objects.get(token=fileshare_token) + except FileShare.DoesNotExist: + raise Http404 - permission = get_user_permission(request, repo_id) - if permission: + # Can not download upper dir of shared dir. + allow_download = True if path.startswith(fileshare.path) else False + else: + allow_download = True if get_user_permission(request, repo_id) else False + + if allow_download: dir_id = seafserv_threaded_rpc.get_dirid_by_path (repo.head_cmmt_id, path.encode('utf-8')) token = seafserv_rpc.web_get_access_token(repo_id, @@ -2672,14 +2821,9 @@ def repo_download_dir(request, repo_id): 'download-dir', request.user.username) else: - return render_permission_error(request, _(u'Unable to access file')) - - if len(path) > 1: - filename = os.path.basename(path) - else: - filename = repo.name - url = gen_file_get_url(token, filename) + return render_error(request, _(u'Unable to download "%s"') % dirname ) + url = gen_file_get_url(token, dirname) return redirect(url) def events(request):