diff --git a/.hgignore b/.hgignore index 50cf9c86..3d53af42 100644 --- a/.hgignore +++ b/.hgignore @@ -11,6 +11,13 @@ src/common/autoconfig.h src/common/rclversion.h src/config.log src/config.status +src/desktop/unity-lens-recoll/Makefile +src/desktop/unity-lens-recoll/autom4te.cache +src/desktop/unity-lens-recoll/bin/unity-recoll-daemon +src/desktop/unity-lens-recoll/config.log +src/desktop/unity-lens-recoll/config.status +src/desktop/unity-lens-recoll/data/recoll.lens +src/desktop/unity-lens-recoll/data/unity-lens-recoll.service src/doc/user/HTML.manifest src/doc/user/index.html src/doc/user/rcl.indexing.beaglequeue.html diff --git a/src/VERSION b/src/VERSION index 092afa15..511a76e6 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.17.0 +1.17.1 diff --git a/src/desktop/unity-lens-recoll/recollscope/rclsearch.py b/src/desktop/unity-lens-recoll/recollscope/rclsearch.py index c2aa892a..e8acbe6a 100755 --- a/src/desktop/unity-lens-recoll/recollscope/rclsearch.py +++ b/src/desktop/unity-lens-recoll/recollscope/rclsearch.py @@ -1,5 +1,7 @@ import sys +import subprocess + from gi.repository import GLib, GObject, Gio from gi.repository import Dee from gi.repository import Unity @@ -20,168 +22,195 @@ TYPING_TIMEOUT = 0 class Scope (Unity.Scope): - def __init__ (self): - Unity.Scope.__init__ (self, dbus_path=BUS_PATH) - - # Listen for changes and requests - if Unity._version == "4.0": - #print "Setting up for Unity 4.0" - self.connect("notify::active-search", - self._on_search_changed) - self.connect("notify::active-global-search", - self._on_global_search_changed) - self.connect("filters-changed", - self._on_search_changed); - else: - #print "Setting up for Unity 5.0+" - self.connect ("search-changed", self._on_search_changed) - self.connect ("filters-changed", - self._on_filters_changed) - - - # Connect to the index - self.db = recoll.connect() - - self.db.setAbstractParams(maxchars=200, - contextwords=4) - - self.timeout_id = None - - def get_search_string (self): - search = self.props.active_search - return search.props.search_string if search else None - - def get_global_search_string (self): - search = self.props.active_global_search - return search.props.search_string if search else None - - def search_finished (self): - search = self.props.active_search - if search: - search.emit("finished") - - def global_search_finished (self): - search = self.props.active_global_search - if search: - search.emit("finished") - def reset (self): - self._do_browse (self.props.results_model) - self._do_browse (self.props.global_results_model) - - def _on_filters_changed (self, scope): -# print "_on_filters_changed()" - self.queue_search_changed(Unity.SearchType.DEFAULT) + def __init__ (self): + Unity.Scope.__init__ (self, dbus_path=BUS_PATH) + # Listen for changes and requests + self.connect ("activate-uri", self.activate_uri) if Unity._version == "4.0": - def _on_search_changed (self, scope, param_spec=None): - search_string = self.get_search_string() - results = scope.props.results_model -# print "Search 4.0 changed to: '%s'" % search_string - - self._update_results_model (search_string, results) + #print "Setting up for Unity 4.0" + self.connect("notify::active-search", + self._on_search_changed) + self.connect("notify::active-global-search", + self._on_global_search_changed) + self.connect("filters-changed", + self._on_search_changed); else: - def _on_search_changed (self, scope, search, search_type, cancellable): - search_string = search.props.search_string - results = search.props.results_model -# print "Search 5.0 changed to: '%s'" % search_string - - self._update_results_model (search_string, results) - - def _on_global_search_changed (self, scope, param_spec): - search = self.get_global_search_string() - results = scope.props.global_results_model - -# print "Global search changed to: '%s'" % search - - self._update_results_model (search, results) - - def _update_results_model (self, search_string, model): - if search_string: - self._do_search (search_string, model) - else: - self._do_browse (model) - - def _do_browse (self, model): - if self.timeout_id is not None: - GObject.source_remove(self.timeout_id) - model.clear () + #print "Setting up for Unity 5.0+" + self.connect ("search-changed", self._on_search_changed) + self.connect ("filters-changed", + self._on_filters_changed) - if model is self.props.results_model: - self.search_finished() - else: - self.global_search_finished() - def _on_timeout(self, search_string, model): - if self.timeout_id is not None: - GObject.source_remove(self.timeout_id) - self.timeout_id = None - self._really_do_search(search_string, model) - if model is self.props.results_model: - self.search_finished() - else: - self.global_search_finished() + # Connect to the index + self.db = recoll.connect() - def _do_search (self, search_string, model): - if TYPING_TIMEOUT == 0: - self._really_do_search(search_string, model) - return True - - if self.timeout_id is not None: - GObject.source_remove(self.timeout_id) - self.timeout_id = \ - GObject.timeout_add(TYPING_TIMEOUT, self._on_timeout, - search_string, model) + self.db.setAbstractParams(maxchars=200, + contextwords=4) + + self.timeout_id = None + + def get_search_string (self): + search = self.props.active_search + return search.props.search_string if search else None + + def get_global_search_string (self): + search = self.props.active_global_search + return search.props.search_string if search else None + + def search_finished (self): + search = self.props.active_search + if search: + search.emit("finished") + + def global_search_finished (self): + search = self.props.active_global_search + if search: + search.emit("finished") + def reset (self): + self._do_browse (self.props.results_model) + self._do_browse (self.props.global_results_model) + + def _on_filters_changed (self, scope): +# print "_on_filters_changed()" + self.queue_search_changed(Unity.SearchType.DEFAULT) + + if Unity._version == "4.0": + def _on_search_changed (self, scope, param_spec=None): + search_string = self.get_search_string() + results = scope.props.results_model +# print "Search 4.0 changed to: '%s'" % search_string + + self._update_results_model (search_string, results) + else: + def _on_search_changed (self, scope, search, search_type, cancellable): + search_string = search.props.search_string + results = search.props.results_model +# print "Search 5.0 changed to: '%s'" % search_string + + self._update_results_model (search_string, results) + + def _on_global_search_changed (self, scope, param_spec): + search = self.get_global_search_string() + results = scope.props.global_results_model + +# print "Global search changed to: '%s'" % search + + self._update_results_model (search, results) + + def _update_results_model (self, search_string, model): + if search_string: + self._do_search (search_string, model) + else: + self._do_browse (model) + + def _do_browse (self, model): + if self.timeout_id is not None: + GObject.source_remove(self.timeout_id) + model.clear () - def _really_do_search(self, search_string, model): -# print "really_do_search:[", search_string, "]" + if model is self.props.results_model: + self.search_finished() + else: + self.global_search_finished() - model.clear () - if search_string == "": - return True + def _on_timeout(self, search_string, model): + if self.timeout_id is not None: + GObject.source_remove(self.timeout_id) + self.timeout_id = None + self._really_do_search(search_string, model) + if model is self.props.results_model: + self.search_finished() + else: + self.global_search_finished() - fcat = self.get_filter("rclcat") - for option in fcat.options: - if option.props.active: - search_string += " rclcat:" + option.props.id + def _do_search (self, search_string, model): + if TYPING_TIMEOUT == 0: + self._really_do_search(search_string, model) + return True + + if self.timeout_id is not None: + GObject.source_remove(self.timeout_id) + self.timeout_id = \ + GObject.timeout_add(TYPING_TIMEOUT, self._on_timeout, + search_string, model) - # Do the recoll thing - query = self.db.query() - try: - nres = query.execute(search_string) - except: - return + def _really_do_search(self, search_string, model): +# print "really_do_search:[", search_string, "]" - actual_results = 0 - while query.next >= 0 and query.next < nres: - try: - doc = query.fetchone() - except: - break + model.clear () + if search_string == "": + return True - # No sense in returning unusable results (until - # I get an idea of what to do with them) - if doc.ipath != "": - continue + fcat = self.get_filter("rclcat") + for option in fcat.options: + if option.props.active: + search_string += " rclcat:" + option.props.id - titleorfilename = doc.title - if titleorfilename == "": - titleorfilename = doc.filename + # Do the recoll thing + query = self.db.query() + try: + nres = query.execute(search_string) + except: + return - icon = Gio.content_type_get_icon(doc.mimetype) - if icon: - iconname = icon.get_names()[0] + actual_results = 0 + while query.next >= 0 and query.next < nres: + try: + doc = query.fetchone() + except: + break - abstract = self.db.makeDocAbstract(doc, query).encode('utf-8') + # No sense in returning unusable results (until + # I get an idea of what to do with them) + if doc.ipath != "": + mimetype = "application/x-recoll" + url = doc.url + "#" + doc.ipath + else: + mimetype = doc.mimetype + url = doc.url - model.append (doc.url, - iconname, - CATEGORY_ALL, - doc.mimetype, - titleorfilename, - abstract, - doc.url) + print "Recoll Lens: Using MIMETYPE", mimetype, \ + " URL", url - actual_results += 1 - if actual_results >= 20: - break - + titleorfilename = doc.title + if titleorfilename == "": + titleorfilename = doc.filename + + icon = Gio.content_type_get_icon(doc.mimetype) + if icon: + iconname = icon.get_names()[0] + + abstract = self.db.makeDocAbstract(doc, query).encode('utf-8') + + model.append (url, + iconname, + CATEGORY_ALL, + mimetype, + titleorfilename, + abstract, + doc.url) + + actual_results += 1 + if actual_results >= 20: + break + + + def activate_uri (self, scope, uri): + """Activation handler for uri""" + + print "Activate: %s" % uri + + # Pass all uri without fragments to the desktop handler + if uri.find("#") == -1: + # Reset browsing state when an app is launched + self.reset () + return Unity.ActivationResponse.new(Unity.HandledType.NOT_HANDLED, + uri) + + # Pass all others to recoll + proc = subprocess.Popen(["recoll", uri]) + print "Subprocess returned, going back to unity" + return Unity.ActivationResponse.new(Unity.HandledType.HIDE_DASH, "baduri") + + diff --git a/src/qtgui/main.cpp b/src/qtgui/main.cpp index 95cb425a..2f49ac05 100644 --- a/src/qtgui/main.cpp +++ b/src/qtgui/main.cpp @@ -203,7 +203,7 @@ static int op_flags; static const char usage [] = "\n" -"recoll [-h] [-c ] [-q options]\n" +"recoll [-h] [-c ] [-q query]\n" " -h : Print help and exit\n" " -c : specify config directory, overriding $RECOLL_CONFDIR\n" " [-o|l|f|a] [-t] -q 'query' : search query to be executed as if entered\n" @@ -219,6 +219,9 @@ static const char usage [] = " explicitly as -t (not ie, -at), and -q MUST\n" " be last on the command line if this is used.\n" " Use -t -h to see the additional non-gui options\n" +"recoll \n" +" This is used to open a recoll url (including an ipath), and called\n" +" typically from another search interface like the Unity Dash\n" ; static void Usage(void) @@ -245,6 +248,7 @@ int main(int argc, char **argv) string a_config; string question; + string urltoview; thisprog = argv[0]; argc--; argv++; @@ -276,11 +280,22 @@ int main(int argc, char **argv) // to the query. This is for the common case recoll -q x y z to // avoid needing quoting "x y z" if (op_flags & OPT_q) - while (argc--) { + while (argc > 0) { question += " "; question += *argv++; + argc--; } + // Else the remaining argument should be an URL to be opened + if (argc == 1) { + urltoview = *argv++;argc--; + if (urltoview.compare(0, 7, cstr_fileu)) { + Usage(); + } + } else if (argc > 0) + Usage(); + + // Translation file for Qt QString slang = QLocale::system().name().left(2); QTranslator qt(0); @@ -375,6 +390,9 @@ int main(int argc, char **argv) mainWindow->sSearch->searchTypCMB->setCurrentIndex(int(stype)); mainWindow-> sSearch->setSearchString(QString::fromLocal8Bit(question.c_str())); + } else if (!urltoview.empty()) { + LOGDEB(("MAIN: got urltoview [%s]\n", urltoview.c_str())); + mainWindow->setUrlToView(QString::fromLocal8Bit(urltoview.c_str())); } return app.exec(); } diff --git a/src/qtgui/rclmain_w.cpp b/src/qtgui/rclmain_w.cpp index 7d1065d5..a2878aff 100644 --- a/src/qtgui/rclmain_w.cpp +++ b/src/qtgui/rclmain_w.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -78,6 +79,7 @@ using std::pair; #include "rtitool.h" #include "indexer.h" #include "rclzg.h" +#include "fileudi.h" using namespace confgui; @@ -389,6 +391,59 @@ void RclMain::initDbOpen() // command line argument if (!nodb && sSearch->hasSearchString()) QTimer::singleShot(0, sSearch, SLOT(startSimpleSearch())); + + if (!m_urltoview.isEmpty()) + viewUrl(); +} + +// Start native viewer or preview for input Doc. This is used allow +// the using Recoll result docs with an ipath in another app. We act +// as a proxy to extract the data and start a viewer. +// The Url are encoded as file://path#ipath +void RclMain::viewUrl() +{ + if (m_urltoview.isEmpty() || !rcldb) + return; + + QUrl qurl(m_urltoview); + LOGDEB(("RclMain::viewUrl: Path [%s] fragment [%s]\n", + (const char *)qurl.path().toLocal8Bit(), + (const char *)qurl.fragment().toLocal8Bit())); + + /* In theory, the url might not be for a file managed by the fs + indexer so that the make_udi() call here would be + wrong(). When/if this happens we'll have to hide this part + inside internfile and have some url magic to indicate the + appropriate indexer/identification scheme */ + string udi; + make_udi((const char *)qurl.path().toLocal8Bit(), + (const char *)qurl.fragment().toLocal8Bit(), udi); + + Rcl::Doc doc; + if (!rcldb->getDoc(udi, doc) || doc.pc == -1) + return; + + // Start a native viewer if the mimetype has one defined, else a + // preview. + string apptag; + doc.getmeta(Rcl::Doc::keyapptg, &apptag); + string viewer = theconfig->getMimeViewerDef(doc.mimetype, apptag); + if (viewer.empty()) { + startPreview(doc); + } else { + startNativeViewer(doc); + // We have a problem here because xdg-open will exit + // immediately after starting the command instead of waiting + // for it, so we can't wait either and we don't know when we + // can exit (deleting the temp file). As a bad workaround we + // sleep some time then exit. The alternative would be to just + // prevent the temp file deletion completely, leaving it + // around forever. Better to let the user save a copy if he + // wants I think. + hide(); + sleep(10); + fileExit(); + } } void RclMain::focusToSearch() diff --git a/src/qtgui/rclmain_w.h b/src/qtgui/rclmain_w.h index 7aac7b33..3710ac95 100644 --- a/src/qtgui/rclmain_w.h +++ b/src/qtgui/rclmain_w.h @@ -79,6 +79,12 @@ public: virtual bool eventFilter(QObject *target, QEvent *event); QString getQueryDescription(); + /** This is only called from main() to set an URL to be displayed (using + recoll as a doc extracter for embedded docs */ + virtual void setUrlToView(const QString& u) {m_urltoview = u;} + /** Same usage: actually display the current urltoview */ + virtual void viewUrl(); + public slots: virtual bool close(); virtual void fileExit(); @@ -169,6 +175,10 @@ private: RefCntr m_source; IndexerState m_indexerState; + // If set on init, will be displayed either through ext app, or + // preview (if no ext app set) + QString m_urltoview; + virtual void init(); virtual void previewPrevOrNextInTab(Preview *, int sid, int docnum, bool next);