unity recoll smart scope sort of working

This commit is contained in:
Jean-Francois Dockes 2013-11-10 17:10:09 +01:00
parent ceb2c086f4
commit f95eb7c293

View file

@ -15,6 +15,7 @@ import sys
import time import time
import urllib.parse import urllib.parse
import hashlib import hashlib
import subprocess
from gi.repository import GLib, GObject, Gio from gi.repository import GLib, GObject, Gio
from gi.repository import Accounts, Signon from gi.repository import Accounts, Signon
@ -46,13 +47,34 @@ except:
APP_NAME = "unity-scope-recoll" APP_NAME = "unity-scope-recoll"
LOCAL_PATH = "/usr/share/locale/" LOCAL_PATH = "/usr/share/locale/"
locale.setlocale(locale.LC_ALL, '')
gettext.bindtextdomain(APP_NAME, LOCAL_PATH) gettext.bindtextdomain(APP_NAME, LOCAL_PATH)
gettext.textdomain(APP_NAME) gettext.textdomain(APP_NAME)
_ = gettext.gettext _ = gettext.gettext
THEME = "/usr/share/icons/unity-icon-theme/places/svg/" GROUP_NAME = 'org.recoll.Unity.Scope.File.Recoll'
UNIQUE_PATH = '/org/recoll/unity/scope/file/recoll'
SEARCH_URI = ''
SEARCH_HINT = _('Search Recoll index')
NO_RESULTS_HINT = _('Sorry, there are no documents in the Recoll index that match your search.')
PROVIDER_CREDITS = _('Powered by Recoll')
SVG_DIR = '/usr/share/icons/unity-icon-theme/places/svg/'
PROVIDER_ICON = SVG_DIR+'service-recoll.svg'
DEFAULT_RESULT_ICON = 'recoll'
DEFAULT_RESULT_TYPE = Unity.ResultType.PERSONAL
c1 = {'id': 'documents',
'name': _('Documents'),
'icon': SVG_DIR + 'group-installed.svg',
'renderer': Unity.CategoryRenderer.VERTICAL_TILE}
c2 = {'id': 'folders',
'name': _('Folders'),
'icon': SVG_DIR + 'group-folders.svg',
'renderer': Unity.CategoryRenderer.VERTICAL_TILE}
CATEGORIES = [c1, c2]
FILTERS = []
EXTRA_METADATA = []
UNITY_TYPE_TO_RECOLL_CLAUSE = { UNITY_TYPE_TO_RECOLL_CLAUSE = {
"documents" : "rclcat:message rclcat:spreadsheet rclcat:text", "documents" : "rclcat:message rclcat:spreadsheet rclcat:text",
@ -61,11 +83,11 @@ UNITY_TYPE_TO_RECOLL_CLAUSE = {
"audio":"rclcat:media", "audio":"rclcat:media",
"videos":"rclcat:media", "videos":"rclcat:media",
"presentations" : "rclcat:presentation", "presentations" : "rclcat:presentation",
"other":"rclcat:other" "other":"rclcat:other",
} }
# Icon names for some recoll mime types which don't have standard icon by the # Icon names for some recoll mime types which don't have standard icon
# normal method # by the normal method
SPEC_MIME_ICONS = {'application/x-fsdirectory' : 'gnome-fs-directory.svg', SPEC_MIME_ICONS = {'application/x-fsdirectory' : 'gnome-fs-directory.svg',
'inode/directory' : 'gnome-fs-directory.svg', 'inode/directory' : 'gnome-fs-directory.svg',
'message/rfc822' : 'mail-read', 'message/rfc822' : 'mail-read',
@ -108,82 +130,132 @@ def _get_thumbnail_path(url):
THUMBDIRS = THUMBDIRS[0:1] THUMBDIRS = THUMBDIRS[0:1]
# Check in appropriate directories to see if the thumbnail file exists # Check in appropriate directories to see if the thumbnail file exists
#print("_get_thumbnail: thumbname: [%s]" % (thumbname,)) #print("_get_thumbnail: thumbname: [%s]" % thumbname, file=sys.stderr)
for topdir in THUMBDIRS: for topdir in THUMBDIRS:
for dir in ("large", "normal"): for dir in ("large", "normal"):
tpath = os.path.join(topdir, dir, thumbname) tpath = os.path.join(topdir, dir, thumbname)
#print("Testing [%s]" % (tpath,)) #print("Testing [%s]" % (tpath,), file=sys.stderr)
if os.path.exists(tpath): if os.path.exists(tpath):
return tpath return tpath
return "" return ""
class RecollScopePreviewer(Unity.ResultPreviewer):
def do_run(self):
icon = Gio.ThemedIcon.new(self.result.icon_hint)
preview = Unity.GenericPreview.new(self.result.title,
self.result.comment.strip(), icon)
view_action = Unity.PreviewAction.new("open", _("Open"), None)
preview.add_action(view_action)
show_action = Unity.PreviewAction.new("show", _("Show in Folder"), None)
preview.add_action(show_action)
return preview
class RecollScope(Unity.AbstractScope): class RecollScope(Unity.AbstractScope):
__g_type_name__ = "RecollScope" __g_type_name__ = "RecollScope"
def __init__(self): def __init__(self):
super(RecollScope, self).__init__() super(RecollScope, self).__init__()
self.search_in_global = True; self.search_in_global = True;
lng, self.localecharset = locale.getdefaultlocale() lng, self.localecharset = locale.getdefaultlocale()
def do_get_group_name(self): def do_get_search_hint (self):
# The primary bus name we grab *must* match what we specify in our return SEARCH_HINT
# .scope file
return "org.recoll.Unity.Scope.File.Recoll"
def do_get_unique_name(self): def do_get_schema (self):
return "/org/recoll/unity/scope/file/recoll" #print("RecollScope: do_get_schema", file=sys.stderr)
schema = Unity.Schema.new ()
if EXTRA_METADATA:
for m in EXTRA_METADATA:
schema.add_field(m['id'], m['type'], m['field'])
#FIXME should be REQUIRED for credits
schema.add_field('provider_credits', 's',
Unity.SchemaFieldType.OPTIONAL)
return schema
def do_get_categories(self):
#print("RecollScope: do_get_categories", file=sys.stderr)
cs = Unity.CategorySet.new ()
if CATEGORIES:
for c in CATEGORIES:
cat = Unity.Category.new (c['id'], c['name'],
Gio.ThemedIcon.new(c['icon']),
c['renderer'])
cs.add (cat)
return cs
def do_get_filters(self): def do_get_filters(self):
print("RecollScope: do_get_filters", file=sys.stderr) #print("RecollScope: do_get_filters", file=sys.stderr)
filters = Unity.FilterSet.new() filters = Unity.FilterSet.new()
f = Unity.RadioOptionFilter.new ("modified", _("Last modified"), Gio.ThemedIcon.new("input-keyboard-symbolic"), False) f = Unity.RadioOptionFilter.new(
"modified", _("Last modified"),
Gio.ThemedIcon.new("input-keyboard-symbolic"), False)
f.add_option ("last-7-days", _("Last 7 days"), None) f.add_option ("last-7-days", _("Last 7 days"), None)
f.add_option ("last-30-days", _("Last 30 days"), None) f.add_option ("last-30-days", _("Last 30 days"), None)
f.add_option ("last-year", _("Last year"), None); f.add_option ("last-year", _("Last year"), None);
filters.add(f) filters.add(f)
f2 = Unity.CheckOptionFilter.new ("type", _("Type"), Gio.ThemedIcon.new("input-keyboard-symbolic"), False)
f2.add_option ("media", _("Media"), None) f2 = Unity.CheckOptionFilter.new (
f2.add_option ("message", _("Message"), None) "type", _("Type"), Gio.ThemedIcon.new("inpt-keyboard-symbolic"), False)
f2.add_option ("presentation", _("Presentation"), None) f2.add_option ("documents", _("Documents"), None)
f2.add_option ("spreadsheet", _("Spreadsheet"), None) f2.add_option ("folders", _("Folders"), None)
f2.add_option ("text", _("Text"), None) f2.add_option ("images", _("Images"), None)
f2.add_option ("audio", _("Audio"), None)
f2.add_option ("videos", _("Videos"), None)
f2.add_option ("presentations", _("Presentations"), None)
f2.add_option ("other", _("Other"), None) f2.add_option ("other", _("Other"), None)
filters.add (f2) filters.add (f2)
f3 = Unity.MultiRangeFilter.new (
"size", _("Size"), Gio.ThemedIcon.new("inpt-keyboard-symbolic"), False)
f3.add_option ("1kb", _("1KB"), None)
f3.add_option ("100kb", _("100KB"), None)
f3.add_option ("1mb", _("1MB"), None)
f3.add_option ("10mb", _("10MB"), None)
f3.add_option ("100mb", _("100MB"), None)
f3.add_option ("1gb", _("1GB"), None)
f3.add_option (">1gb", _(">1GB"), None)
filters.add (f3)
return filters return filters
def do_get_categories(self): def do_get_group_name(self):
print("RecollScope: do_get_categories", file=sys.stderr) return GROUP_NAME
cats = Unity.CategorySet.new()
cats.add (Unity.Category.new ('global',
_("Files & Folders"),
Gio.ThemedIcon.new(THEME + "group-folders.svg"),
Unity.CategoryRenderer.VERTICAL_TILE))
cats.add (Unity.Category.new ('recent',
_("Recent"),
Gio.ThemedIcon.new(THEME + "group-recent.svg"),
Unity.CategoryRenderer.VERTICAL_TILE))
cats.add (Unity.Category.new ('downloads',
_("Downloads"),
Gio.ThemedIcon.new(THEME + "group-downloads.svg"),
Unity.CategoryRenderer.VERTICAL_TILE))
cats.add (Unity.Category.new ('folders',
_("Folders"),
Gio.ThemedIcon.new(THEME + "group-folders.svg"),
Unity.CategoryRenderer.VERTICAL_TILE))
return cats
def do_get_schema (self): def do_get_unique_name(self):
print("RecollScope: do_get_schema", file=sys.stderr) return UNIQUE_PATH
schema = Unity.Schema.new ()
return schema
def do_create_search_for_query(self, search_context): def do_create_search_for_query(self, search_context):
print("RecollScope: do_create_search_for query", file=sys.stderr) #print("RecollScope: do_create_search_for query", file=sys.stderr)
return RecollScopeSearch(search_context) return RecollScopeSearch(search_context)
def do_activate(self, result, metadata, id):
print("RecollScope: do_activate. id [%s] uri [%s]" % (id,uri),
file=sys.stderr)
if id == 'show':
filename = result.uri
dirname = os.path.dirname(filename)
os.system("xdg-open '%s'" % str(dirname))
else:
uri = result.uri
# Pass all uri without fragments to the desktop handler
if uri.find("#") == -1:
return Unity.ActivationResponse(handled=Unity.HandledType.NOT_HANDLED,
goto_uri=uri)
# Pass all others to recoll
proc = subprocess.Popen(["recoll", uri])
ret = Unity.ActivationResponse(handled=Unity.HandledType.HIDE_DASH,
goto_uri=None)
return ret
def do_create_previewer(self, result, metadata):
#print("RecollScope: do_create_previewer", file=sys.stderr)
previewer = RecollScopePreviewer()
previewer.set_scope_result(result)
previewer.set_search_metadata(metadata)
return previewer
class RecollScopeSearch(Unity.ScopeSearchBase): class RecollScopeSearch(Unity.ScopeSearchBase):
__g_type_name__ = "RecollScopeSearch" __g_type_name__ = "RecollScopeSearch"
@ -212,7 +284,9 @@ class RecollScopeSearch(Unity.ScopeSearchBase):
#print("RecollScopeSearch: do_run", file=sys.stderr) #print("RecollScopeSearch: do_run", file=sys.stderr)
context = self.search_context context = self.search_context
filters = context.filter_state filters = context.filter_state
search_string = context.search_query search_string = context.search_query.strip()
if not search_string or search_string is None:
return
result_set = context.result_set result_set = context.result_set
# Get the list of documents # Get the list of documents
@ -227,17 +301,20 @@ class RecollScopeSearch(Unity.ScopeSearchBase):
datef = self.date_filter(filters) datef = self.date_filter(filters)
sizef = self.size_filter(filters) sizef = self.size_filter(filters)
search_string = " ".join((search_string, catgf, datef, sizef)) search_string = " ".join((search_string, catgf, datef, sizef))
else:
print("RecollScopeSearch::do_run: Search: [%s]" % search_string) print("RecollScopeSearch: GLOBAL", file=sys.stderr)
# Do the recoll thing # Do the recoll thing
try: try:
query = self.db.query() query = self.db.query()
nres = query.execute(search_string) nres = query.execute(search_string)
except Exception as msg: except Exception as msg:
print("recoll query execute error: %s" % msg) print("recoll query execute error: %s" % msg, file=sys.stderr)
return return
print("RecollScopeSearch::do_run: [%s] -> %d results" %
(search_string, nres), file=sys.stderr)
actual_results = 0 actual_results = 0
for i in range(nres): for i in range(nres):
try: try:
@ -251,29 +328,19 @@ class RecollScopeSearch(Unity.ScopeSearchBase):
if titleorfilename is None: if titleorfilename is None:
titleorfilename = "doc.title and doc.filename are none !" titleorfilename = "doc.title and doc.filename are none !"
# Results with an ipath get a special mime type so that they
# get opened by starting a recoll instance.
url, mimetype, iconname = self.icon_for_type (doc) url, mimetype, iconname = self.icon_for_type (doc)
try: try:
abstract = self.db.makeDocAbstract(doc, query) abstract = self.db.makeDocAbstract(doc, query)
except: except:
break pass
# Ok, I don't understand this category thing for now... # Ok, I don't understand this category thing for now...
if is_global:
category = 0 category = 0
else:
if doc.mimetype == "inode/directory" or \
"application/x-fsdirectory":
category = 3
else:
category = 1
#print({"uri":url,"icon":iconname,"category":category, #print({"uri":url,"icon":iconname,"category":category,
#"mimetype":mimetype, "title":titleorfilename, # "mimetype":mimetype, "title":titleorfilename,
#"comment":abstract, # "comment":abstract,
#"dnd_uri":doc.url}) # "dnd_uri":doc.url}, file=sys.stderr)
result_set.add_result( result_set.add_result(
uri=url, uri=url,
@ -289,9 +356,8 @@ class RecollScopeSearch(Unity.ScopeSearchBase):
if actual_results >= MAX_RESULTS: if actual_results >= MAX_RESULTS:
break break
def date_filter (self, filters): def date_filter (self, filters):
#print("RecollScopeSearch: date_filter") print("RecollScopeSearch: date_filter", file=sys.stderr)
dateopt = "" dateopt = ""
f = filters.get_filter_by_id("modified") f = filters.get_filter_by_id("modified")
if f != None: if f != None:
@ -303,38 +369,43 @@ class RecollScopeSearch(Unity.ScopeSearchBase):
dateopt = "date:P1M/" dateopt = "date:P1M/"
elif o.props.id == "last-7-days": elif o.props.id == "last-7-days":
dateopt = "date:P7D/" dateopt = "date:P7D/"
#print("RecollScopeSearch: date_filter: return [%s]" % dateopt) #print("RecollScopeSearch::date_filter:[%s]" % dateopt, file=sys.stderr)
# Until we fix the recoll bug: return dateopt
# return dateopt
return ""
def catg_filter(self, filters): def catg_filter(self, filters):
print("RecollScopeSearch::catg_filter", file=sys.stderr)
f = filters.get_filter_by_id("type") f = filters.get_filter_by_id("type")
if not f: return "" if not f: return ""
if not f.props.filtering: if not f.props.filtering:
return "" return ""
ss = "" ss = ""
for fopt in f.options: for fopt in f.options:
print(fopt.props.id, file=sys.stderr)
if fopt.props.active: if fopt.props.active:
if fopt.props.id in UNITY_TYPE_TO_RECOLL_CLAUSE: if fopt.props.id in UNITY_TYPE_TO_RECOLL_CLAUSE:
ss += " " + UNITY_TYPE_TO_RECOLL_CLAUSE[fopt.props.id] ss += " " + UNITY_TYPE_TO_RECOLL_CLAUSE[fopt.props.id]
print("RecollScopSearch: catg_filter: [%s]" % ss) #print("RecollScopSearch::catg_filter:[%s]" % ss, file=sys.stderr)
return ss return ss
def size_filter(self, filters): def size_filter(self, filters):
print("size_filter", file=sys.stderr) print("RecollScopeSearch::size_filter", file=sys.stderr)
f = filters.get_filter_by_id("size") f = filters.get_filter_by_id("size")
if not f: return "" if not f: return ""
print("size_filter got f", file=sys.stderr)
if not f.props.filtering: if not f.props.filtering:
return "" return ""
print("size_filter f filtering", file=sys.stderr) min = f.get_first_active()
ss = "" max = f.get_last_active()
for fopt in f.options: if min.props.id == max.props.id:
print(fopt.props.id, file=sys.stderr) # Take it as < except if it's >1gb
if max.props.id == ">1gb":
print("RecollScopeSearch: size_filter: [%s]" % ss) ss = " size>1g"
else:
ss = " size<" + min.props.id
else:
if max.props.id == ">1gb":
ss = "size>" + min.props.id
else:
ss = " size>" + min.props.id + " size<" + max.props.id
#print("RecollScopeSearch::size_filter: [%s]" % ss, file=sys.stderr)
return ss return ss
# Send back a useful icon depending on the document type # Send back a useful icon depending on the document type
@ -374,4 +445,3 @@ class RecollScopeSearch(Unity.ScopeSearchBase):
def load_scope(): def load_scope():
return RecollScope() return RecollScope()