diff --git a/media/css/seahub.css b/media/css/seahub.css index 3a29feced9..19cac014f7 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 0000000000..f24f147c4a Binary files /dev/null and b/media/img/download-blue.png differ diff --git a/media/img/upload.png b/media/img/upload.png index 54f837c4b4..78512522db 100644 Binary files a/media/img/upload.png and b/media/img/upload.png differ diff --git a/share/models.py b/share/models.py index 7a5fa2a3f1..a9a250a17f 100644 --- a/share/models.py +++ b/share/models.py @@ -12,7 +12,7 @@ class AnonymousShare(models.Model): class FileShare(models.Model): """ - Model used for file share link. + Model used for file or dir shared link. """ username = models.EmailField(max_length=255, db_index=True) repo_id = models.CharField(max_length=36, db_index=True) @@ -20,3 +20,4 @@ class FileShare(models.Model): token = models.CharField(max_length=10, unique=True) ctime = models.DateTimeField(default=datetime.datetime.now) view_cnt = models.IntegerField(default=0) + s_type = models.CharField(max_length=2, db_index=True, default='f') # `f` or `d` diff --git a/share/templates/repo/share_admin.html b/share/templates/repo/share_admin.html index ff9a62ad98..54770815a3 100644 --- a/share/templates/repo/share_admin.html +++ b/share/templates/repo/share_admin.html @@ -80,18 +80,22 @@ {% if fileshares %} - + {% for fs in fileshares %} - + {% if fs.s_type == 'f' %} + + {% else %} + + {% endif %} @@ -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 e7838109f4..217f6c02cc 100644 --- a/templates/file_view.html +++ b/templates/file_view.html @@ -156,122 +156,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 b36eda1599..ba3b30179b 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 %} @@ -119,7 +130,8 @@ {% if user_perm %}
- {% trans 'Download' %} + {% trans 'Download' %} + {% trans "Share" %}
{% if user_perm == 'rw' %} {% trans 'More operations'%} @@ -298,15 +310,16 @@

{% trans 'Share' %}

-

{% trans 'Link: ' %}

+

{% trans 'Link: ' %}

- -
-
- - -

-

{% trans "Sending..."%}

+ +

Send Link

+
+
+ + +

+

{% trans "Sending..."%}

@@ -602,7 +615,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; } @@ -755,24 +768,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) { @@ -839,9 +859,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 69c5e1dd7e..d04ee93e1b 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -7,6 +7,7 @@ import stat import urllib2 import json +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 @@ -701,3 +702,13 @@ def calc_file_path_hash(path, bits=12): path_hash = md5_constructor(urllib2.quote(path)).hexdigest()[:bits] return path_hash + +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 7b15e04002..d532756e88 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, \ @@ -165,7 +165,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) @@ -177,20 +177,27 @@ def get_repo_dirents(request, repo_id, commit, path): for dirent in dirs: dirent.last_modified = last_modified_info.get(dirent.obj_name, 0) - + 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 dir_list.sort(lambda x, y : cmp(x.obj_name.lower(), @@ -346,6 +353,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 @@ -379,6 +406,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 @@ -1297,13 +1327,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: @@ -2416,7 +2445,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')) @@ -2472,7 +2501,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, @@ -2653,22 +2788,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, @@ -2676,14 +2823,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):