+ {% 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 %}
+
+
+
+
+
+
+
+
+
+ |
+ |
+ {% trans "Name"%} |
+ {% trans "Size"%} |
+ {% trans "Operations"%} |
+
+
+ {% for dirent in dir_list %}
+
+ |
+  |
+
+ {{ dirent.obj_name }}
+ |
+
+ |
+
+
+ |
+
+ {% endfor %}
+
+ {% for dirent in file_list %}
+
+ |
+  |
+
+ {{ dirent.props.obj_name }}
+ |
+
+ {{ dirent.file_size|filesizeformat }} |
+
+
+ |
+
+ {% endfor %}
+
+
+
+
+{% 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):