diff --git a/forms.py b/forms.py
index 940e9d9be1..a4a73e4774 100644
--- a/forms.py
+++ b/forms.py
@@ -32,3 +32,13 @@ class AddUserForm(forms.Form):
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError(_("The two password fields didn't match."))
return self.cleaned_data
+
+class FileLinkShareForm(forms.Form):
+ """
+ Form for sharing file shared link to emails.
+ """
+
+ email = forms.CharField(max_length=512)
+ file_shared_link = forms.CharField(max_length=40)
+
+
diff --git a/media/css/seahub.css b/media/css/seahub.css
index 0e5728427b..c2e5eab207 100644
--- a/media/css/seahub.css
+++ b/media/css/seahub.css
@@ -466,6 +466,7 @@ table img {
margin-top:8px;
}
/*repo-share-form*/
+#email,
#email_or_group,
#share-link,
#added-member-name {
@@ -653,3 +654,4 @@ table img {
max-width: 550px;
max-height: 550px;
}
+
diff --git a/share/models.py b/share/models.py
index 2f826b4cf0..f707d43e8d 100644
--- a/share/models.py
+++ b/share/models.py
@@ -1,8 +1,21 @@
+import datetime
from django.db import models
class AnonymousShare(models.Model):
+ """
+ Model used for sharing repo to unregistered email.
+ """
repo_owner = models.EmailField(max_length=255)
repo_id = models.CharField(max_length=36)
anonymous_email = models.EmailField(max_length=255)
token = models.CharField(max_length=25, unique=True)
-
+
+class FileShare(models.Model):
+ """
+ Model used for file share link.
+ """
+ username = models.EmailField(max_length=255)
+ repo_id = models.CharField(max_length=36, db_index=True)
+ path = models.TextField()
+ token = models.CharField(max_length=10, unique=True)
+ ctime = models.DateTimeField(default=datetime.datetime.now)
diff --git a/templates/repo.html b/templates/repo.html
index df799839cc..46c30de988 100644
--- a/templates/repo.html
+++ b/templates/repo.html
@@ -153,9 +153,9 @@
 |
{% if view_history %}
- {{ dirent.props.obj_name }} |
+ {{ dirent.props.obj_name }} |
{% else %}
- {{ dirent.props.obj_name }} |
+ {{ dirent.props.obj_name }} |
{% endif %}
{{ dirent.file_size|filesizeformat }} |
diff --git a/templates/repo_view_file.html b/templates/repo_view_file.html
index 9451886e26..fe9bc8c1f0 100644
--- a/templates/repo_view_file.html
+++ b/templates/repo_view_file.html
@@ -25,6 +25,17 @@
查看原始文件
查看所有历史版本
下载文件
+
+ {% if not view_history %}
+ 共享
+
+ 链接:{{ file_shared_link }}
+ 获取分享链接
+
+ {% endif %}
@@ -44,17 +55,53 @@
正在读取文件内容...
+
+
+
{% endblock %}
{% block extra_script %}
{% endblock %}
diff --git a/templates/shared_link_email.html b/templates/shared_link_email.html
new file mode 100644
index 0000000000..f3fc756460
--- /dev/null
+++ b/templates/shared_link_email.html
@@ -0,0 +1,12 @@
+{% autoescape off %}
+亲爱的 {{ to_email }}:
+
+{{ email }} 在SeaCloud上共享了一个文件给你,请点击以下链接查看:
+
+{{ file_shared_link }}
+
+感谢使用我们的网站!
+
+Seafile团队
+
+{% endautoescape %}
diff --git a/templates/view_shared_file.html b/templates/view_shared_file.html
new file mode 100644
index 0000000000..f12ee86582
--- /dev/null
+++ b/templates/view_shared_file.html
@@ -0,0 +1,54 @@
+{% extends "myhome_base.html" %}
+{% load seahub_tags %}
+
+{% block main_panel %}
+
+ {{ file_name }}
+ 共享者:{{ username }}
+
+
+
+
+
+
+{% endblock %}
+
+{% block extra_script %}
+
+{% endblock %}
diff --git a/urls.py b/urls.py
index d9b60c13c1..fd61b20787 100644
--- a/urls.py
+++ b/urls.py
@@ -6,13 +6,14 @@ from seahub.views import root, peers, myhome, \
repo, repo_history, modify_token, remove_repo, sys_seafadmin, sys_useradmin, \
org_seafadmin, org_useradmin, org_group_admin, org_remove, \
activate_user, user_add, user_remove, sys_group_admin, sys_org_admin, \
- ownerhome, repo_history_revert, \
+ ownerhome, repo_history_revert, repo_file_get, \
user_info, repo_set_access_property, repo_access_file, \
repo_remove_share, repo_download, org_info, repo_view_file, \
seafile_access_check, back_local, repo_history_changes, \
- repo_upload_file, file_upload_progress, file_upload_progress_page, get_subdir, file_move, \
- repo_new_dir, repo_rename_file, validate_filename, \
- repo_create, repo_update_file, file_revisions
+ repo_upload_file, file_upload_progress, file_upload_progress_page, \
+ get_subdir, file_move, repo_new_dir, repo_rename_file, validate_filename, \
+ repo_create, repo_update_file, file_revisions, \
+ get_shared_link, view_shared_file, remove_shared_link, send_shared_link
from seahub.notifications.views import notification_list
from seahub.share.views import share_admin
from seahub.group.views import group_list
@@ -42,7 +43,11 @@ urlpatterns = patterns('',
(r'^share/', include('share.urls')),
url(r'^shareadmin/$', share_admin, name='share_admin'),
(r'^shareadmin/removeshare/$', repo_remove_share),
-
+ (r'^sharedlink/get/$', get_shared_link),
+ (r'^sharedlink/remove/$', remove_shared_link),
+ (r'^sharedlink/send/$', send_shared_link),
+ (r'^f/(?P[^/]+)/$', view_shared_file),
+
(r'^file_upload_progress/$', file_upload_progress),
(r'^file_upload_progress_page/$', file_upload_progress_page),
(r'^repo/new_dir/$', repo_new_dir),
@@ -61,7 +66,8 @@ urlpatterns = patterns('',
# (r'^repo/removefetched/(?P[^/]+)/(?P[^/]+)/$', remove_fetched_repo),
# (r'^repo/setap/(?P[^/]+)/$', repo_set_access_property),
url(r'^repo/(?P[^/]+)/(?P[^/]+)/$', repo_access_file, name='repo_access_file'),
- (r'^repo/(?P[^/]+)/view/(?P[^/]+)/$', repo_view_file),
+ (r'^repo/(?P[^/]+)/files/$', repo_view_file),
+ (r'^repo/(?P[^/]+)/file/get/$', repo_file_get),
(r'^download/repo/$', repo_download),
(r'^file/move/get_subdir/$', get_subdir),
diff --git a/utils.py b/utils.py
index 0b529065d7..0b26dcab57 100644
--- a/utils.py
+++ b/utils.py
@@ -68,13 +68,13 @@ def get_ccnetapplet_root():
ccnet_applet_root = settings.CCNET_APPLET_ROOT
return ccnet_applet_root
-def gen_token():
+def gen_token(max_length=5):
"""
- Generate short token used for owner to access repo file.
+ Generate a random token.
"""
- token = sha_constructor(settings.SECRET_KEY + unicode(time.time())).hexdigest()[:5]
+ token = sha_constructor(settings.SECRET_KEY + unicode(time.time())).hexdigest()[:max_length]
return token
def validate_group_name(group_name):
diff --git a/views.py b/views.py
index 9554a1c764..7218e0a0a0 100644
--- a/views.py
+++ b/views.py
@@ -24,6 +24,7 @@ from auth.decorators import login_required
from auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm, \
PasswordChangeForm
from auth.tokens import default_token_generator
+from share.models import FileShare
from seaserv import ccnet_rpc, ccnet_threaded_rpc, get_groups, get_users, get_repos, \
get_repo, get_commits, get_branches, \
seafserv_threaded_rpc, seafserv_rpc, get_binding_peerids, get_ccnetuser, \
@@ -33,7 +34,7 @@ from pysearpc import SearpcError
from seahub.base.accounts import CcnetUser
from seahub.contacts.models import Contact
from seahub.notifications.models import UserNotification
-from forms import AddUserForm
+from forms import AddUserForm, FileLinkShareForm
from utils import go_permission_error, go_error, list_to_string, \
get_httpserver_root, get_ccnetapplet_root, gen_token, \
calculate_repo_last_modify, valid_previewed_file, \
@@ -147,8 +148,9 @@ def gen_path_link(path, repo_name):
Generate navigate paths and links in repo page.
"""
- if path[-1:] != '/':
+ if path and path[-1] != '/':
path += '/'
+
paths = []
links = []
if path and path != '/':
@@ -210,7 +212,6 @@ def render_repo(request, repo_id, error=''):
# query repo infomation
repo_size = seafserv_threaded_rpc.server_repo_size(repo_id)
-# latest_commit = get_commits(repo_id, 0, 1)[0]
# get repo dirents
dirs = []
@@ -789,61 +790,37 @@ def repo_del_file(request, repo_id):
url = reverse('repo', args=[repo_id]) + ('?p=%s' % parent_dir)
return HttpResponseRedirect(url)
-def repo_view_file(request, repo_id, obj_id):
+def repo_view_file(request, repo_id):
+ """
+ Preview file on web, including files in current worktree and history.
+ """
http_server_root = get_httpserver_root()
- filename = urllib2.quote(request.GET.get('file_name', '').encode('utf-8'))
+ path = request.GET.get('p', '/')
+ if path[-1] != '/':
+ path = path + '/'
+ filename = urllib2.quote(os.path.basename(path[:-1]).encode('utf-8'))
+
commit_id = request.GET.get('commit_id', '')
view_history = True if commit_id else False
current_commit = seafserv_threaded_rpc.get_commit(commit_id)
if not current_commit:
current_commit = get_commits(repo_id, 0, 1)[0]
- if request.is_ajax():
- content_type = 'application/json; charset=utf-8'
- token = request.GET.get('t')
- tmp_str = '%s/access?repo_id=%s&id=%s&filename=%s&op=%s&t=%s&u=%s'
- redirect_url = tmp_str % (http_server_root,
- repo_id, obj_id,
- filename, 'view',
- token,
- request.user.username)
+ if view_history:
+ obj_id = request.GET.get('obj_id', '')
+ else:
try:
- proxied_request = urllib2.urlopen(redirect_url)
- if long(proxied_request.headers['Content-Length']) > FILE_PREVIEW_MAX_SIZE:
- data = json.dumps([{'error': '文件超过10M,无法在线查看。'}])
- return HttpResponse(data, status=400, content_type=content_type)
- else:
- content = proxied_request.read()
- except urllib2.HTTPError, e:
- err = 'HTTPError: 无法在线打开该文件'
- data = json.dumps([{'error': err}])
- return HttpResponse(data, status=400, content_type=content_type)
- except urllib2.URLError as e:
- err = 'URLError: 无法在线打开该文件'
- data = json.dumps([{'error': err}])
- return HttpResponse(data, status=400, content_type=content_type)
- else:
- l, d = [], {}
- try:
- # XXX: file in windows is encoded in gbk
- u_content = content.decode('gbk')
- except:
- u_content = content.decode('utf-8')
- from django.utils.html import escape
- d['content'] = re.sub("\r\n|\n", "
", escape(u_content))
- l.append(d)
- data = json.dumps(l)
- return HttpResponse(data, status=200, content_type=content_type)
+ obj_id = seafserv_rpc.get_file_by_path(repo_id, path[:-1])
+ except:
+ obj_id = None
+
+ if not obj_id:
+ return go_error(request, '文件不存在')
repo = get_repo(repo_id)
if not repo:
raise Http404
- # if a repo doesn't have access property in db, then assume it's 'own'
- repo_ap = seafserv_threaded_rpc.repo_query_access_property(repo_id)
- if not repo_ap:
- repo_ap = 'own'
-
# if a repo is shared to me, then I can view and download file no mater whether
# repo's access property is 'own' or 'public'
if check_shared_repo(request, repo_id):
@@ -852,30 +829,22 @@ def repo_view_file(request, repo_id, obj_id):
share_to_me = False
token = ''
- if repo_ap == 'own':
- # people who is owner or this repo is shared to him, can visit the repo;
- # others, just go to 404 page
- if validate_owner(request, repo_id) or share_to_me:
- # owner should get a token to visit repo
- token = gen_token()
- # put token into memory in seaf-server
- seafserv_rpc.web_save_access_token(token, obj_id)
- else:
- raise Http404
+ # people who is owner or this repo is shared to him, can visit the repo;
+ # others, just go to 404 page
+ if validate_owner(request, repo_id) or share_to_me:
+ # owner should get a token to visit repo
+ token = gen_token()
+ # put token into memory in seaf-server
+ seafserv_rpc.web_save_access_token(token, obj_id)
+ else:
+ raise Http404
- # query commit info
- commit_id = request.GET.get('commit_id', None)
- current_commit = seafserv_threaded_rpc.get_commit(commit_id)
- if not current_commit:
- current_commit = get_commits(repo.id, 0, 1)[0]
-
# generate path and link
- path = request.GET.get('p', '/')
zipped = gen_path_link(path, repo.name)
- # filename
+ # determin whether file can preview on web
can_preview, filetype = valid_previewed_file(filename)
-
+
# raw path
tmp_str = '%s/access?repo_id=%s&id=%s&filename=%s&op=%s&t=%s&u=%s'
raw_path = tmp_str % (http_server_root,
@@ -883,12 +852,26 @@ def repo_view_file(request, repo_id, obj_id):
filename, 'view',
token,
request.user.username)
-
+
+ # file share link
+ l = FileShare.objects.filter(repo_id=repo_id).filter(path=path[:-1])
+ fileshare = l[0] if len(l) > 0 else None
+
+ 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)
+ else:
+ file_shared_link = ''
+
return render_to_response('repo_view_file.html', {
'repo': repo,
'path': path,
'obj_id': obj_id,
'file_name': filename,
+ 'path': path,
'zipped': zipped,
'view_history': view_history,
'current_commit': current_commit,
@@ -896,8 +879,76 @@ def repo_view_file(request, repo_id, obj_id):
'can_preview': can_preview,
'filetype': filetype,
'raw_path': raw_path,
+ 'fileshare': fileshare,
+ 'protocol': http_or_https,
+ 'domain': domain,
+ 'file_shared_link': file_shared_link,
}, context_instance=RequestContext(request))
-
+
+def repo_file_get(request, repo_id):
+ """
+ Handle ajax request to get file content from httpserver.
+ If get current worktree file, need access_token, path and username from
+ url params.
+ If get history file, need access_token, path username and obj_id from
+ url params.
+ """
+ if not request.is_ajax():
+ return Http404
+
+ http_server_root = get_httpserver_root()
+ content_type = 'application/json; charset=utf-8'
+ access_token = request.GET.get('t')
+ path = request.GET.get('p', '/')
+ if path[-1] == '/':
+ path = path[:-1]
+
+ filename = urllib2.quote(os.path.basename(path).encode('utf-8'))
+ obj_id = request.GET.get('obj_id', '')
+ if not obj_id:
+ try:
+ obj_id = seafserv_rpc.get_file_by_path(repo_id, path)
+ except:
+ obj_id = None
+ if not obj_id:
+ data = json.dumps([{'error': '获取文件数据失败'}])
+ return HttpResponse(data, status=400, content_type=content_type)
+
+ username = request.GET.get('u', '')
+ tmp_str = '%s/access?repo_id=%s&id=%s&filename=%s&op=%s&t=%s&u=%s'
+ redirect_url = tmp_str % (http_server_root,
+ repo_id, obj_id,
+ filename, 'view',
+ access_token,
+ username)
+ try:
+ proxied_request = urllib2.urlopen(redirect_url)
+ if long(proxied_request.headers['Content-Length']) > FILE_PREVIEW_MAX_SIZE:
+ data = json.dumps([{'error': '文件超过10M,无法在线查看。'}])
+ return HttpResponse(data, status=400, content_type=content_type)
+ else:
+ content = proxied_request.read()
+ except urllib2.HTTPError, e:
+ err = 'HTTPError: 无法在线打开该文件'
+ data = json.dumps([{'error': err}])
+ return HttpResponse(data, status=400, content_type=content_type)
+ except urllib2.URLError as e:
+ err = 'URLError: 无法在线打开该文件'
+ data = json.dumps([{'error': err}])
+ return HttpResponse(data, status=400, content_type=content_type)
+ else:
+ l, d = [], {}
+ try:
+ # XXX: file in windows is encoded in gbk
+ u_content = content.decode('gbk')
+ except:
+ u_content = content.decode('utf-8')
+ from django.utils.html import escape
+ d['content'] = re.sub("\r\n|\n", "
", escape(u_content))
+ l.append(d)
+ data = json.dumps(l)
+ return HttpResponse(data, status=200, content_type=content_type)
+
def repo_access_file(request, repo_id, obj_id):
if repo_id:
repo = get_repo(repo_id)
@@ -1756,3 +1807,163 @@ def file_revisions(request, repo_id):
% (commit_id, file_name, path)
return HttpResponseRedirect(url)
+@login_required
+def get_shared_link(request):
+ """
+ Handle ajax request to generate file shared link.
+ """
+ if not request.is_ajax():
+ raise Http404
+
+ content_type = 'application/json; charset=utf-8'
+
+ repo_id = request.GET.get('repo_id')
+ obj_id = request.GET.get('obj_id')
+ path = request.GET.get('p', '/')
+ if path[-1] == '/':
+ path = path[:-1]
+
+ l = FileShare.objects.filter(repo_id=repo_id).filter(path=path)
+ if len(l) > 0:
+ fileshare = l[0]
+ token = fileshare.token
+ else:
+ token = gen_token(max_length=10)
+
+ fs = FileShare()
+ fs.username = request.user.username
+ fs.repo_id = repo_id
+ fs.path = path
+ fs.token = token
+
+ try:
+ fs.save()
+ except IntegrityError, e:
+ err = '获取分享链接失败,请重新获取'
+ data = json.dumps([{'error': err}])
+ return HttpResponse(data, status=500, content_type=content_type)
+
+ data = json.dumps([{'token': token}])
+ return HttpResponse(data, status=200, content_type=content_type)
+
+def view_shared_file(request, token):
+ """
+ Preview file via shared link.
+ """
+ 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 = fileshare.path
+
+ http_server_root = get_httpserver_root()
+ if path[-1] == '/':
+ path = path[:-1]
+ filename = os.path.basename(path)
+ quote_filename = urllib2.quote(filename.encode('utf-8'))
+
+ try:
+ obj_id = seafserv_rpc.get_file_by_path(repo_id, path)
+ except:
+ obj_id = None
+
+ if not obj_id:
+ return go_error(request, '文件不存在')
+
+ repo = get_repo(repo_id)
+ if not repo:
+ raise Http404
+
+ access_token = gen_token()
+ seafserv_rpc.web_save_access_token(access_token, obj_id)
+
+ can_preview, filetype = valid_previewed_file(filename)
+
+ # raw path
+ tmp_str = '%s/access?repo_id=%s&id=%s&filename=%s&op=%s&t=%s&u=%s'
+ raw_path = tmp_str % (http_server_root,
+ repo_id, obj_id,
+ quote_filename, 'view',
+ access_token,
+ username)
+
+ return render_to_response('view_shared_file.html', {
+ 'repo': repo,
+ 'obj_id': obj_id,
+ 'path': path,
+ 'file_name': filename,
+ 'token': token,
+ 'access_token': access_token,
+ 'can_preview': can_preview,
+ 'filetype': filetype,
+ 'raw_path': raw_path,
+ 'username': username,
+ }, context_instance=RequestContext(request))
+
+@login_required
+def remove_shared_link(request):
+ """
+ Handle ajax request to remove file shared link.
+ """
+ if not request.is_ajax():
+ raise Http404
+
+ content_type = 'application/json; charset=utf-8'
+
+ token = request.GET.get('t', '')
+ FileShare.objects.filter(token=token).delete()
+
+ msg = '删除成功'
+ data = json.dumps([{'msg': msg}])
+ return HttpResponse(data, status=200, content_type=content_type)
+
+@login_required
+def send_shared_link(request):
+ """
+ Handle ajax post request to share file shared link.
+ """
+ if not request.is_ajax() and not request.method == 'POST':
+ raise Http404
+
+ content_type = 'application/json; charset=utf-8'
+
+ form = FileLinkShareForm(request.POST)
+ if not form.is_valid():
+ err = '发送失败'
+ data = json.dumps([{'error':err}])
+ return HttpResponse(data, status=400, content_type=content_type)
+
+ email = form.cleaned_data['email']
+ file_shared_link = form.cleaned_data['file_shared_link']
+
+ # Handle the diffent separator
+ to_email_str = email.replace(';',',')
+ to_email_str = to_email_str.replace('\n',',')
+ to_email_str = to_email_str.replace('\r',',')
+ to_email_list = to_email_str.split(',')
+
+ t = loader.get_template('shared_link_email.html')
+ for to_email in to_email_list:
+ c = {
+ 'email': request.user.username,
+ 'to_email': to_email,
+ 'file_shared_link': file_shared_link,
+ }
+
+ try:
+ send_mail('您的好友通过SeaCloud分享了一个文件给您',
+ t.render(Context(c)), None, [to_email],
+ fail_silently=False)
+ except:
+ err = '发送失败'
+ data = json.dumps([{'error':err}])
+ return HttpResponse(data, status=400, content_type=content_type)
+
+ msg = '发送成功。'
+ data = json.dumps([{'msg': msg}])
+ return HttpResponse(data, status=200, content_type=content_type)