mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-01 07:01:12 +00:00
Add file preview feature
This commit is contained in:
@@ -582,3 +582,15 @@ table img {
|
|||||||
#upload-cancel {
|
#upload-cancel {
|
||||||
margin-top:8px;
|
margin-top:8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* file preview */
|
||||||
|
#file-content {
|
||||||
|
border: 1px solid #999;
|
||||||
|
padding: 5px;
|
||||||
|
min-height: 200px;
|
||||||
|
white-space: pre-wrap; /* css-3 */
|
||||||
|
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||||
|
white-space: -pre-wrap; /* Opera 4-6 */
|
||||||
|
white-space: -o-pre-wrap; /* Opera 7 */
|
||||||
|
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||||
|
}
|
||||||
|
@@ -170,6 +170,13 @@ USE_SUBDOMAIN = False
|
|||||||
# account type is `personal` or `business`
|
# account type is `personal` or `business`
|
||||||
ACCOUNT_TYPE = 'personal'
|
ACCOUNT_TYPE = 'personal'
|
||||||
|
|
||||||
|
FILE_PREVIEW_MAX_SIZE = 10 * 1024 * 1024
|
||||||
|
|
||||||
|
PREVIEW_FILEEXT = {
|
||||||
|
'Document': ('ac', 'am', 'as', 'as3', 'asm', 'bat', 'c', 'cc', 'cmake', 'cpp', 'cs', 'css', 'csv', 'cxx', 'diff', 'erb', 'groovy', 'gsheet', 'h', 'haml', 'hh', 'htm', 'html', 'java', 'js', 'less', 'm', 'make', 'ml', 'mm', 'ods', 'odt', 'php', 'pl', 'properties', 'py', 'rb', 'rtf', 'sass', 'scala', 'scm', 'script', 'sh', 'sml', 'sql', 'txt', 'vi', 'vim', 'wpd', 'xls', 'xlsm', 'xlsx', 'xml', 'xsd', 'xsl', 'xslt', 'yaml'),
|
||||||
|
'Image': ('ai', 'bmp', 'eps', 'gif', 'ind', 'jpeg', 'jpg', 'png', 'ps', 'psd', 'tif', 'tiff'),
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import local_settings
|
import local_settings
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@@ -104,7 +104,7 @@
|
|||||||
{% for dirent in file_list %}
|
{% for dirent in file_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="icon-container"><img src="{{ MEDIA_URL }}img/{{ dirent.obj_name|file_icon_filter }}" alt="文件" /></td>
|
<td class="icon-container"><img src="{{ MEDIA_URL }}img/{{ dirent.obj_name|file_icon_filter }}" alt="文件" /></td>
|
||||||
<td><a class="op" href="{{ SITE_ROOT }}repo/{{ repo.props.id }}/{{ dirent.props.obj_id }}/?file_name={{ dirent.props.obj_name }}&op=view">{{ dirent.props.obj_name }}</a></td>
|
<td><a class="op" href="{{ SITE_ROOT }}repo/{{ repo.props.id }}/view/{{ dirent.props.obj_id }}/?file_name={{ dirent.props.obj_name }}&p={{ path|urlencode }}{{ dirent.obj_name|urlencode }}">{{ dirent.props.obj_name }}</a></td>
|
||||||
<td>{{ dirent.file_size|filesizeformat }}</td>
|
<td>{{ dirent.file_size|filesizeformat }}</td>
|
||||||
<td>
|
<td>
|
||||||
<img src="{{ MEDIA_URL }}img/more-option.png" alt="更多操作" class="more-op hide" />
|
<img src="{{ MEDIA_URL }}img/more-option.png" alt="更多操作" class="more-op hide" />
|
||||||
|
71
templates/repo_view_file.html
Normal file
71
templates/repo_view_file.html
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
{% extends "myhome_base.html" %}
|
||||||
|
{% load seahub_tags %}
|
||||||
|
|
||||||
|
{% block info_bar_message %}
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% else %}
|
||||||
|
<div id="info-bar">
|
||||||
|
<span class="info">当前链接会在短期内失效,欢迎您 <a href="http://seafile.com/" target="_blank">加入Seafile </a>体验更多功能。</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main_panel %}
|
||||||
|
<h2 class="subject">
|
||||||
|
{{repo.props.name}}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="side fright">
|
||||||
|
<h3>操作</h3>
|
||||||
|
<p><a href="{{ SITE_ROOT }}repo/{{ repo.id }}/{{ obj_id }}/?file_name={{ file_name }}&op=view" target="_blank">查看原始文件</a></p>
|
||||||
|
<p><a href="{{ SITE_ROOT }}repo/{{ repo.id }}/{{ obj_id }}/?file_name={{ file_name }}&op=download" target="_blank">下载文件</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main fleft">
|
||||||
|
<p class="path">
|
||||||
|
当前路径:
|
||||||
|
{% for name, link in zipped %}
|
||||||
|
{% if not forloop.last %}
|
||||||
|
<a href="{{ SITE_ROOT }}repo/{{ repo.id }}/?p={{ link|urlencode }}">{{ name }}</a> /
|
||||||
|
{% else %}
|
||||||
|
{{ name }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
<pre id="file-content">正在读取文件内容...</pre>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_script %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(window).load(function() {
|
||||||
|
var can_preview = "{{ can_preview }}";
|
||||||
|
var filetype = "{{ filetype }}";
|
||||||
|
|
||||||
|
if (can_preview == 'True' && filetype == 'Document') {
|
||||||
|
$.ajax({
|
||||||
|
url: '{{ SITE_ROOT }}repo/{{ repo.id }}/view/{{ obj_id }}/?file_name={{ file_name }}&t={{ token }}',
|
||||||
|
dataType: 'json',
|
||||||
|
cache: false,
|
||||||
|
contentType: 'application/json; charset=utf-8',
|
||||||
|
success: function(data) {
|
||||||
|
if (data.length > 0) {
|
||||||
|
$('#file-content').html(data[0]['content']);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, ajaxOptions, thrownError) {
|
||||||
|
var jsonVal = jQuery.parseJSON(xhr.responseText);
|
||||||
|
$('#file-content').html(jsonVal[0]['error']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
} else if (can_preview == 'True' && filetype == 'Image') {
|
||||||
|
$('#file-content').replaceWith('<img src="{{ raw_path }}" width="550" height="450"></img>');
|
||||||
|
} else {
|
||||||
|
$('#file-content').html('无法识别该文件格式,<a class="op" href="">下载文件</a>。');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
5
urls.py
5
urls.py
@@ -8,11 +8,10 @@ from seahub.views import root, peers, myhome, \
|
|||||||
activate_user, user_add, user_remove, sys_group_admin, sys_org_admin, \
|
activate_user, user_add, user_remove, sys_group_admin, sys_org_admin, \
|
||||||
ownerhome, repo_history_dir, repo_history_revert, \
|
ownerhome, repo_history_dir, repo_history_revert, \
|
||||||
user_info, repo_set_access_property, repo_access_file, \
|
user_info, repo_set_access_property, repo_access_file, \
|
||||||
repo_remove_share, repo_download, org_info, \
|
repo_remove_share, repo_download, org_info, repo_view_file, \
|
||||||
seafile_access_check, back_local, repo_history_changes, \
|
seafile_access_check, back_local, repo_history_changes, \
|
||||||
repo_upload_file, file_upload_progress, file_upload_progress_page, get_subdir, file_move, \
|
repo_upload_file, file_upload_progress, file_upload_progress_page, get_subdir, file_move, \
|
||||||
repo_new_dir, repo_rename_file, validate_filename
|
repo_new_dir, repo_rename_file, validate_filename
|
||||||
|
|
||||||
from seahub.notifications.views import notification_list
|
from seahub.notifications.views import notification_list
|
||||||
from seahub.share.views import share_admin
|
from seahub.share.views import share_admin
|
||||||
from seahub.group.views import group_list
|
from seahub.group.views import group_list
|
||||||
@@ -59,6 +58,8 @@ urlpatterns = patterns('',
|
|||||||
# (r'^repo/removefetched/(?P<user_id>[^/]+)/(?P<repo_id>[^/]+)/$', remove_fetched_repo),
|
# (r'^repo/removefetched/(?P<user_id>[^/]+)/(?P<repo_id>[^/]+)/$', remove_fetched_repo),
|
||||||
# (r'^repo/setap/(?P<repo_id>[^/]+)/$', repo_set_access_property),
|
# (r'^repo/setap/(?P<repo_id>[^/]+)/$', repo_set_access_property),
|
||||||
(r'^repo/(?P<repo_id>[^/]+)/(?P<obj_id>[^/]+)/$', repo_access_file),
|
(r'^repo/(?P<repo_id>[^/]+)/(?P<obj_id>[^/]+)/$', repo_access_file),
|
||||||
|
(r'^repo/(?P<repo_id>[^/]+)/view/(?P<obj_id>[^/]+)/$', repo_view_file),
|
||||||
|
|
||||||
(r'^download/repo/$', repo_download),
|
(r'^download/repo/$', repo_download),
|
||||||
(r'^file/move/get_subdir/$', get_subdir),
|
(r'^file/move/get_subdir/$', get_subdir),
|
||||||
(r'^file/move/$', file_move),
|
(r'^file/move/$', file_move),
|
||||||
|
15
utils.py
15
utils.py
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
import settings
|
import settings
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
@@ -17,6 +18,9 @@ from seaserv import seafserv_rpc, ccnet_threaded_rpc, seafserv_threaded_rpc, \
|
|||||||
|
|
||||||
EMPTY_SHA1 = '0000000000000000000000000000000000000000'
|
EMPTY_SHA1 = '0000000000000000000000000000000000000000'
|
||||||
|
|
||||||
|
import settings
|
||||||
|
from settings import PREVIEW_FILEEXT
|
||||||
|
|
||||||
def go_permission_error(request, msg=None):
|
def go_permission_error(request, msg=None):
|
||||||
"""
|
"""
|
||||||
Return permisson error page.
|
Return permisson error page.
|
||||||
@@ -225,3 +229,14 @@ def get_accessible_repos(request, repo):
|
|||||||
repo.props.has_subdir = check_has_subdir(repo)
|
repo.props.has_subdir = check_has_subdir(repo)
|
||||||
|
|
||||||
return accessible_repos
|
return accessible_repos
|
||||||
|
|
||||||
|
def valid_previewed_file(filename):
|
||||||
|
"""
|
||||||
|
Check whether file can preview on web
|
||||||
|
|
||||||
|
"""
|
||||||
|
fileExt = os.path.splitext(filename)[1][1:]
|
||||||
|
for filetype in PREVIEW_FILEEXT.keys():
|
||||||
|
if fileExt in PREVIEW_FILEEXT.get(filetype):
|
||||||
|
return (True, filetype)
|
||||||
|
return (False, '')
|
||||||
|
105
views.py
105
views.py
@@ -3,7 +3,9 @@ import settings
|
|||||||
import os
|
import os
|
||||||
import stat
|
import stat
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import urllib2
|
||||||
from urllib import quote
|
from urllib import quote
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
@@ -34,9 +36,10 @@ from seahub.notifications.models import UserNotification
|
|||||||
from forms import AddUserForm
|
from forms import AddUserForm
|
||||||
from utils import go_permission_error, go_error, list_to_string, \
|
from utils import go_permission_error, go_error, list_to_string, \
|
||||||
get_httpserver_root, get_ccnetapplet_root, gen_token, \
|
get_httpserver_root, get_ccnetapplet_root, gen_token, \
|
||||||
calculate_repo_last_modify, \
|
calculate_repo_last_modify, valid_previewed_file, \
|
||||||
check_filename_with_rename, get_accessible_repos, EMPTY_SHA1
|
check_filename_with_rename, get_accessible_repos, EMPTY_SHA1
|
||||||
from seahub.profile.models import Profile
|
from seahub.profile.models import Profile
|
||||||
|
from settings import FILE_PREVIEW_MAX_SIZE
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def root(request):
|
def root(request):
|
||||||
@@ -751,6 +754,104 @@ def repo_del_file(request, repo_id):
|
|||||||
url = reverse('repo', args=[repo_id]) + ('?p=%s' % parent_dir)
|
url = reverse('repo', args=[repo_id]) + ('?p=%s' % parent_dir)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def repo_view_file(request, repo_id, obj_id):
|
||||||
|
http_server_root = get_httpserver_root()
|
||||||
|
filename = urllib2.quote(request.GET.get('file_name', '').encode('utf-8'))
|
||||||
|
|
||||||
|
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)
|
||||||
|
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", "<br />", escape(u_content))
|
||||||
|
l.append(d)
|
||||||
|
data = json.dumps(l)
|
||||||
|
return HttpResponse(data, status=200, content_type=content_type)
|
||||||
|
|
||||||
|
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):
|
||||||
|
share_to_me = True
|
||||||
|
else:
|
||||||
|
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
|
||||||
|
|
||||||
|
# generate path and link
|
||||||
|
path = request.GET.get('p', '/')
|
||||||
|
if path[-1] != '/':
|
||||||
|
path = path + '/'
|
||||||
|
zipped = gen_path_link(path, repo.name)
|
||||||
|
|
||||||
|
# filename
|
||||||
|
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,
|
||||||
|
filename, 'view',
|
||||||
|
token,
|
||||||
|
request.user.username)
|
||||||
|
|
||||||
|
return render_to_response('repo_view_file.html', {
|
||||||
|
'repo': repo,
|
||||||
|
'obj_id': obj_id,
|
||||||
|
'file_name': filename,
|
||||||
|
'zipped': zipped,
|
||||||
|
'token': token,
|
||||||
|
'can_preview': can_preview,
|
||||||
|
'filetype': filetype,
|
||||||
|
'raw_path': raw_path,
|
||||||
|
}, context_instance=RequestContext(request))
|
||||||
|
|
||||||
def repo_access_file(request, repo_id, obj_id):
|
def repo_access_file(request, repo_id, obj_id):
|
||||||
if repo_id:
|
if repo_id:
|
||||||
repo = get_repo(repo_id)
|
repo = get_repo(repo_id)
|
||||||
@@ -807,7 +908,7 @@ def repo_access_file(request, repo_id, obj_id):
|
|||||||
token,
|
token,
|
||||||
request.user.username)
|
request.user.username)
|
||||||
return HttpResponseRedirect(redirect_url)
|
return HttpResponseRedirect(redirect_url)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def repo_download(request):
|
def repo_download(request):
|
||||||
repo_id = request.GET.get('repo_id', '')
|
repo_id = request.GET.get('repo_id', '')
|
||||||
|
Reference in New Issue
Block a user