diff --git a/base/context_processors.py b/base/context_processors.py index 4546ad231d..4294c2b5eb 100644 --- a/base/context_processors.py +++ b/base/context_processors.py @@ -13,6 +13,11 @@ try: except ImportError: BUSINESS_MODE = False +try: + from settings import ENABLE_FILE_SEARCH +except ImportError: + ENABLE_FILE_SEARCH = False + def base(request): """ Add seahub base configure to the context. @@ -37,5 +42,6 @@ def base(request): 'site_name': SITE_NAME, 'enable_signup': ENABLE_SIGNUP, 'max_file_name': MAX_FILE_NAME, + 'enable_file_search': ENABLE_FILE_SEARCH, } diff --git a/base/models.py b/base/models.py index d075524780..7b086ef184 100644 --- a/base/models.py +++ b/base/models.py @@ -181,4 +181,17 @@ class DirFilesLastModifiedInfo(models.Model): last_modified_info = models.TextField() class Meta: - unique_together = ('repo_id', 'parent_dir_hash') \ No newline at end of file + unique_together = ('repo_id', 'parent_dir_hash') + +class FileLastModifiedInfo(models.Model): + repo_id = models.CharField(max_length=36, db_index=True) + file_id = models.CharField(max_length=40) + + file_path = models.TextField() + file_path_hash = models.CharField(max_length=12) + + last_modified = models.BigIntegerField() + email = models.EmailField() + + class Meta: + unique_together = ('repo_id', 'file_path_hash') \ No newline at end of file diff --git a/media/css/seahub.css b/media/css/seahub.css index 4e1480575e..c0719b1926 100644 --- a/media/css/seahub.css +++ b/media/css/seahub.css @@ -1971,6 +1971,41 @@ textarea:-moz-placeholder {/* for FF */ background:#f7f7f8; border-radius:2px; } + #search-results { padding-top:15px; +} + +#search-results a { + font-weight: normal; +} + +#search-results b { + font-weight: bold; +} + +.search-results-item { + margin-top: 20px; + clear: left; +} + +.search-results-item .file-icon { + display: block; + float: left; +} + +.search-results-item .title { + margin: 0 0 0 40px; +} + +.search-results-item .title a, +.search-results-item .title img, +.search-results-item .title span +{ + vertical-align: middle; +} + +.search-results-item .content { + clear: left; + margin: 0 0 0 40px; } \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 359d71c739..8ed2360354 100644 --- a/templates/base.html +++ b/templates/base.html @@ -87,7 +87,7 @@ {% block nav %}{% endblock %} - {% if request.user.is_authenticated %} + {% if enable_file_search and request.user.is_authenticated %}
diff --git a/templates/search_results.html b/templates/search_results.html index f532be6168..e811d01465 100644 --- a/templates/search_results.html +++ b/templates/search_results.html @@ -16,26 +16,39 @@

{% trans 'No result found' %}

{% else %}

{% blocktrans count counter=total %}{{ total }} result{% plural %}{{ total }} results{% endblocktrans%}

- - - - - - - - {% for file in results %} - - - - - - - {% endfor %} -
{% trans 'Name' %}{% trans 'Library' %}{% trans 'Owner' %}
{% trans {{ file.name }}{{ file.repo.name }} - {% avatar file.repo.owner 20 %} - {{ file.repo.owner|email2nickname }} -
- {% if total > 25 %} + + {% if total > per_page %}
{% if current_page != 1 %} {% trans "Previous"%} diff --git a/urls.py b/urls.py index b8b2b47c97..69c90ff2ce 100644 --- a/urls.py +++ b/urls.py @@ -103,7 +103,6 @@ urlpatterns = patterns('', url(r'^sys/orgadmin/$', sys_org_admin, name='sys_org_admin'), url(r'^sys/groupadmin/$', sys_group_admin, name='sys_group_admin'), - url(r'^search/$', search, name='search'), ) if settings.SERVE_STATIC: @@ -128,3 +127,8 @@ else: url(r'^pubinfo/groups/$', pubgrp, name='pubgrp'), url(r'^pubinfo/users/$', pubuser, name='pubuser'), ) + +if getattr(settings, 'ENABLE_FILE_SEARCH', False): + urlpatterns += patterns('', + url(r'^search/$', search, name='search'), + ) \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py index 866a1af90d..9c61c6f354 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -15,7 +15,7 @@ from django.template import RequestContext from django.utils.hashcompat import sha_constructor, md5_constructor from django.utils.translation import ugettext as _ -from base.models import FileContributors, UserStarredFiles, DirFilesLastModifiedInfo +from base.models import FileContributors, UserStarredFiles, DirFilesLastModifiedInfo, FileLastModifiedInfo from htmldiff import HtmlDiff @@ -406,6 +406,54 @@ def check_and_get_org_by_group(group_id, user): return org, base_template +def calc_file_last_modified(repo_id, file_path, file_path_hash, file_id): + try: + # get the lastest one file revision + commits = seafserv_threaded_rpc.list_file_revisions(repo_id, file_path, 1, -1) + except SearpcError, e: + return '', 0 + + if not commits: + return '', 0 + + email, last_modified = commits[0].creator_name, commits[0].ctime + + info = FileLastModifiedInfo(repo_id=repo_id, + file_path=file_path, + file_path_hash=file_path_hash, + file_id=file_id, + last_modified=last_modified, + email=email) + try: + info.save() + except IntegrityError: + pass + + return email, last_modified + +def get_file_last_modified(repo_id, file_path): + email = '' + last_modified = 0 + file_path_hash = calc_file_path_hash(file_path) + file_id = seafserv_threaded_rpc.get_file_id_by_path(repo_id, file_path) + try: + fc = FileLastModifiedInfo.objects.get(repo_id=repo_id, + file_path_hash=file_path_hash) + except FileLastModifiedInfo.DoesNotExist: + # has no cache yet + user, last_modified = calc_file_last_modified(repo_id, file_path, file_path_hash, file_id) + else: + # cache found + if fc.file_id != file_id: + # but cache is outdated + fc.delete() + user, last_modified = calc_file_last_modified(repo_id, file_path, file_path_hash, file_id) + else: + # cache is valid + user, last_modified = fc.email, fc.last_modified + + return user, last_modified + def get_file_contributors_from_revisions(repo_id, file_path): """Inspect the file history and get a list of users who have modified the it. @@ -413,7 +461,7 @@ def get_file_contributors_from_revisions(repo_id, file_path): """ commits = [] try: - commits = seafserv_threaded_rpc.list_file_revisions(repo_id, file_path, -1) + commits = seafserv_threaded_rpc.list_file_revisions(repo_id, file_path, -1, -1) except SearpcError, e: return [], 0, '' @@ -770,48 +818,58 @@ def is_textual_file(file_type): else: return False +if getattr(settings, 'ENABLE_FILE_SEARCH', False): + from seafes import es_get_conn, es_search -from seafes import es_get_conn, es_search + conn = es_get_conn() + def search_file_by_name(request, keyword, start, size): + owned_repos, shared_repos, groups_repos, pub_repo_list = get_user_repos(request.user) -conn = es_get_conn() + # unify the repo.owner property + for repo in owned_repos: + repo.owner = request.user.username + for repo in shared_repos: + repo.owner = repo.user + for repo in pub_repo_list: + repo.owner = repo.user -def search_file_by_name(request, keyword, start, size): - owned_repos, shared_repos, groups_repos, pub_repo_list = get_user_repos(request.user) + pubrepo_id_map = {} + for repo in pub_repo_list: + # fix pub repo obj attr name mismatch in seafile/lib/repo.vala + repo.id = repo.repo_id + repo.name = repo.repo_name + pubrepo_id_map[repo.id] = repo - # unify the repo.owner property - for repo in owned_repos: - repo.owner = request.user.username - for repo in shared_repos: - repo.owner = repo.user - for repo in pub_repo_list: - repo.owner = repo.user + # get a list of pure non-pub repos + nonpub_repo_list = [] + for repo in owned_repos + shared_repos + groups_repos: + if repo.id not in pubrepo_id_map: + nonpub_repo_list.append(repo) - pubrepo_id_map = {} - for repo in pub_repo_list: - # fix pub repo obj attr name mismatch in seafile/lib/repo.vala - repo.id = repo.repo_id - repo.name = repo.repo_name - pubrepo_id_map[repo.id] = repo + nonpub_repo_ids = [ repo.id for repo in nonpub_repo_list] - # get a list of pure non-pub repos - nonpub_repo_list = [] - for repo in owned_repos + shared_repos + groups_repos: - if repo.id not in pubrepo_id_map: - nonpub_repo_list.append(repo) + files_found, total = es_search(conn, nonpub_repo_ids, keyword, start, size) - nonpub_repo_ids = [ repo.id for repo in nonpub_repo_list] + if len(files_found) > 0: + # construt a (id, repo) hash table for fast lookup + repo_id_map = {} + for repo in nonpub_repo_list: + repo_id_map[repo.id] = repo - files_found, total = es_search(conn, nonpub_repo_ids, keyword, start, size) + repo_id_map.update(pubrepo_id_map) - if len(files_found) > 0: - # construt a (id, repo) hash table for fast lookup - repo_id_map = {} - for repo in nonpub_repo_list: - repo_id_map[repo.id] = repo + for f in files_found: + repo = repo_id_map.get(f['repo_id'].encode('UTF-8'), None) + if repo: + f['repo'] = repo + f['exists'] = True + f['last_modified_by'], f['last_modified'] = get_file_last_modified(f['repo_id'], f['fullpath']) + else: + f['exists'] = False - repo_id_map.update(pubrepo_id_map) + files_found = filter(lambda f: f['exists'], files_found) - for f in files_found: - f['repo'] = repo_id_map[f['repo_id'].encode('UTF-8')] - - return files_found, total + return files_found, total +else: + def search_file_by_name(*args): + pass