diff --git a/src/internfile/mimehandler.cpp b/src/internfile/mimehandler.cpp index 9ee5626e..3f1a89c7 100644 --- a/src/internfile/mimehandler.cpp +++ b/src/internfile/mimehandler.cpp @@ -42,6 +42,7 @@ using namespace std; #include "mh_unknown.h" #include "mh_null.h" #include "mh_xslt.h" +#include "rcldoc.h" // Performance help: we use a pool of already known and created // handlers. There can be several instances for a given mime type @@ -381,6 +382,24 @@ bool canIntern(const std::string mtype, RclConfig *cfg) return false; return true; } +/// Same, getting MIME from doc +bool canIntern(Rcl::Doc *doc, RclConfig *cfg) +{ + if (doc) { + return canIntern(doc->mimetype, cfg); + } + return false; +} + +/// Can this MIME type be opened (has viewer def) ? +bool canOpen(Rcl::Doc *doc, RclConfig *cfg) { + if (!doc) { + return false; + } + string apptag; + doc->getmeta(Rcl::Doc::keyapptg, &apptag); + return !cfg->getMimeViewerDef(doc->mimetype, apptag, false).empty(); +} string RecollFilter::metadataAsString() { diff --git a/src/internfile/mimehandler.h b/src/internfile/mimehandler.h index 07c85b6d..0c7a8702 100644 --- a/src/internfile/mimehandler.h +++ b/src/internfile/mimehandler.h @@ -179,7 +179,14 @@ extern void returnMimeHandler(RecollFilter *); /// off recoll. extern void clearMimeHandlerCache(); +namespace Rcl { + class Doc; +} /// Can this mime type be interned ? extern bool canIntern(const std::string mimetype, RclConfig *cfg); +/// Same, getting MIME from doc +extern bool canIntern(Rcl::Doc *doc, RclConfig *cfg); +/// Can this MIME type be opened (has viewer def) ? +extern bool canOpen(Rcl::Doc *doc, RclConfig *cfg); #endif /* _MIMEHANDLER_H_INCLUDED_ */ diff --git a/src/qtgui/preview_w.cpp b/src/qtgui/preview_w.cpp index c3740bca..a0b6f794 100644 --- a/src/qtgui/preview_w.cpp +++ b/src/qtgui/preview_w.cpp @@ -406,6 +406,9 @@ void Preview::currentChanged(int index) return; } edit->setFocus(); + + editPB->setEnabled(canOpen(&edit->m_dbdoc, theconfig)); + // Disconnect the print signal and reconnect it to the current editor LOGDEB1("Disconnecting reconnecting print signal\n"); disconnect(this, SIGNAL(printCurrentPreviewRequest()), 0, 0); @@ -643,6 +646,7 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum) if (CancelCheck::instance().cancelState()) return false; if (lthr.status != 0) { + bool canGetRawText = rcldb && rcldb->storesDocText(); QString explain; if (!lthr.missing.empty()) { explain = QString::fromUtf8("
") + @@ -656,7 +660,7 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum) if (progress.wasCanceled()) { QMessageBox::warning(0, "Recoll", tr("Canceled")); } else { - progress.close(); + progress.reset(); // Note that we can't easily check for a readable file // because it's possible that only a region is locked // (e.g. on Windows for an ost file the first block is @@ -664,14 +668,14 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum) QString msg; switch (lthr.explain) { case FileInterner::FetchMissing: - msg = tr("Error loading the document: file missing"); + msg = tr("Error loading the document: file missing."); break; case FileInterner::FetchPerm: - msg = tr("Error loading the document: no permission"); + msg = tr("Error loading the document: no permission."); break; case FileInterner::FetchNoBackend: msg = - tr("Error loading the document: backend not configured"); + tr("Error loading: backend not configured."); break; case FileInterner::InternfileOther: #ifdef _WIN32 @@ -679,16 +683,29 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum) "other handler error
" "Maybe the application is locking the file ?"); #else - msg = tr("Error loading the document: other handler error"); + msg = tr("Error loading the document: other handler error."); #endif break; } + if (canGetRawText) { + msg += tr("
Attempting to display from stored text."); + } QMessageBox::warning(0, "Recoll", msg); } } - progress.close(); - return false; + + if (canGetRawText) { + lthr.fdoc = idoc; + if (!rcldb->getDocRawText(lthr.fdoc)) { + QMessageBox::warning(0, "Recoll", + tr("Could not fetch stored text")); + progress.close(); + return false; + } + } else { + progress.close(); + } } // Reset config just in case. theconfig->setKeyDir(""); @@ -838,6 +855,7 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum) lthr.fdoc.text.clear(); editor->m_fdoc = lthr.fdoc; editor->m_dbdoc = idoc; + editPB->setEnabled(canOpen(&editor->m_dbdoc, theconfig)); if (textempty) editor->displayFields(); @@ -962,8 +980,10 @@ void PreviewTextEdit::createPopupMenu(const QPoint& pos) if (!m_dbdoc.url.empty()) { popup->addAction(tr("Save document to file"), m_preview, SLOT(emitSaveDocToFile())); - popup->addAction(tr("Open document"), - m_preview, SLOT(emitEditRequested())); + if (canOpen(&m_dbdoc, theconfig)) { + popup->addAction(tr("Open document"), + m_preview, SLOT(emitEditRequested())); + } } popup->popup(mapToGlobal(pos)); } diff --git a/src/query/reslistpager.cpp b/src/query/reslistpager.cpp index 95208444..a32d1864 100644 --- a/src/query/reslistpager.cpp +++ b/src/query/reslistpager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2007 J.F.Dockes +/* Copyright (C) 2007-2019 J.F.Dockes * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -46,13 +46,11 @@ static const string cstr_hlfontcolor(""); static const string cstr_hlendfont(""); class PlainToRichHtReslist : public PlainToRich { public: - virtual string startMatch(unsigned int) - { - return cstr_hlfontcolor; + virtual string startMatch(unsigned int) { + return cstr_hlfontcolor; } - virtual string endMatch() - { - return cstr_hlendfont; + virtual string endMatch() { + return cstr_hlendfont; } }; static PlainToRichHtReslist g_hiliter; @@ -70,8 +68,8 @@ ResListPager::ResListPager(int pagesize) void ResListPager::resultPageNext() { if (!m_docSource) { - LOGDEB("ResListPager::resultPageNext: null source\n"); - return; + LOGDEB("ResListPager::resultPageNext: null source\n"); + return; } int resCnt = m_docSource->getResCnt(); @@ -79,9 +77,9 @@ void ResListPager::resultPageNext() ", winfirst " << m_winfirst << "\n"); if (m_winfirst < 0) { - m_winfirst = 0; + m_winfirst = 0; } else { - m_winfirst += int(m_respage.size()); + m_winfirst += int(m_respage.size()); } // Get the next page of results. Note that we look ahead by one to // determine if there is actually a next page @@ -93,25 +91,25 @@ void ResListPager::resultPageNext() // Get rid of the possible excess result if (pagelen == m_pagesize + 1) { - npage.resize(m_pagesize); - pagelen--; + npage.resize(m_pagesize); + pagelen--; } if (pagelen <= 0) { - // No results ? This can only happen on the first page or if the - // actual result list size is a multiple of the page pref (else - // there would have been no Next on the last page) - if (m_winfirst > 0) { - // Have already results. Let them show, just disable the - // Next button. We'd need to remove the Next link from the page - // too. - // Restore the m_winfirst value, let the current result vector alone - m_winfirst -= int(m_respage.size()); - } else { - // No results at all (on first page) - m_winfirst = -1; - } - return; + // No results ? This can only happen on the first page or if the + // actual result list size is a multiple of the page pref (else + // there would have been no Next on the last page) + if (m_winfirst > 0) { + // Have already results. Let them show, just disable the + // Next button. We'd need to remove the Next link from the page + // too. + // Restore the m_winfirst value, let the current result vector alone + m_winfirst -= int(m_respage.size()); + } else { + // No results at all (on first page) + m_winfirst = -1; + } + return; } m_resultsInCurrentPage = pagelen; m_respage = npage; @@ -119,17 +117,17 @@ void ResListPager::resultPageNext() static string maybeEscapeHtml(const string& fld) { if (fld.compare(0, cstr_fldhtm.size(), cstr_fldhtm)) - return escapeHtml(fld); + return escapeHtml(fld); else - return fld.substr(cstr_fldhtm.size()); + return fld.substr(cstr_fldhtm.size()); } void ResListPager::resultPageFor(int docnum) { if (!m_docSource) { - LOGDEB("ResListPager::resultPageFor: null source\n"); - return; + LOGDEB("ResListPager::resultPageFor: null source\n"); + return; } int resCnt = m_docSource->getResCnt(); @@ -145,14 +143,14 @@ void ResListPager::resultPageFor(int docnum) m_hasNext = (pagelen == m_pagesize); if (pagelen <= 0) { - m_winfirst = -1; - return; + m_winfirst = -1; + return; } m_respage = npage; } void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc, - const HighlightData& hdata, const string& sh) + const HighlightData& hdata, const string& sh) { ostringstream chunk; @@ -176,10 +174,10 @@ void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc, doc.getmeta(Rcl::Doc::keytt, &titleOrFilename); doc.getmeta(Rcl::Doc::keyfn, &utf8fn); if (utf8fn.empty()) { - utf8fn = path_getsimple(url); + utf8fn = path_getsimple(url); } if (titleOrFilename.empty()) { - titleOrFilename = utf8fn; + titleOrFilename = utf8fn; } // Url for the parent directory. We strip the file:// part for local @@ -201,92 +199,88 @@ void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc, if (!doc.dmtime.empty() || !doc.fmtime.empty()) { char cdate[100]; cdate[0] = 0; - time_t mtime = doc.dmtime.empty() ? - atoll(doc.fmtime.c_str()) : atoll(doc.dmtime.c_str()); - struct tm *tm = localtime(&mtime); - strftime(cdate, 99, dateFormat().c_str(), tm); + time_t mtime = doc.dmtime.empty() ? + atoll(doc.fmtime.c_str()) : atoll(doc.dmtime.c_str()); + struct tm *tm = localtime(&mtime); + strftime(cdate, 99, dateFormat().c_str(), tm); transcode(cdate, datebuf, RclConfig::getLocaleCharset(), "UTF-8"); } // Size information. We print both doc and file if they differ a lot int64_t fsize = -1, dsize = -1; if (!doc.dbytes.empty()) - dsize = static_cast(atoll(doc.dbytes.c_str())); + dsize = static_cast(atoll(doc.dbytes.c_str())); if (!doc.fbytes.empty()) - fsize = static_cast(atoll(doc.fbytes.c_str())); + fsize = static_cast(atoll(doc.fbytes.c_str())); string sizebuf; if (dsize > 0) { - sizebuf = displayableBytes(dsize); - if (fsize > 10 * dsize && fsize - dsize > 1000) - sizebuf += string(" / ") + displayableBytes(fsize); + sizebuf = displayableBytes(dsize); + if (fsize > 10 * dsize && fsize - dsize > 1000) + sizebuf += string(" / ") + displayableBytes(fsize); } else if (fsize >= 0) { - sizebuf = displayableBytes(fsize); + sizebuf = displayableBytes(fsize); } string richabst; bool needabstract = parFormat().find("%A") != string::npos; if (needabstract && m_docSource) { - vector vabs; - m_docSource->getAbstract(doc, vabs); - m_hiliter->set_inputhtml(false); + vector vabs; + m_docSource->getAbstract(doc, vabs); + m_hiliter->set_inputhtml(false); - for (vector::const_iterator it = vabs.begin(); - it != vabs.end(); it++) { - if (!it->empty()) { - // No need to call escapeHtml(), plaintorich handles it - list lr; - // There may be data like page numbers before the snippet text. - // will be in brackets. - string::size_type bckt = it->find("]"); - if (bckt == string::npos) { - m_hiliter->plaintorich(*it, lr, hdata); - } else { - m_hiliter->plaintorich(it->substr(bckt), lr, hdata); - lr.front() = it->substr(0, bckt) + lr.front(); - } - richabst += lr.front(); - richabst += absSep(); - } - } + for (vector::const_iterator it = vabs.begin(); + it != vabs.end(); it++) { + if (!it->empty()) { + // No need to call escapeHtml(), plaintorich handles it + list lr; + // There may be data like page numbers before the snippet text. + // will be in brackets. + string::size_type bckt = it->find("]"); + if (bckt == string::npos) { + m_hiliter->plaintorich(*it, lr, hdata); + } else { + m_hiliter->plaintorich(it->substr(bckt), lr, hdata); + lr.front() = it->substr(0, bckt) + lr.front(); + } + richabst += lr.front(); + richabst += absSep(); + } + } } - // Links; + // Links; Uses utilities from mimehandler.h ostringstream linksbuf; - if (canIntern(doc.mimetype, config)) { - linksbuf << "" - << trans("Preview") << "  "; + if (canIntern(&doc, config)) { + linksbuf << "" + << trans("Preview") << "  "; } - - string apptag; - doc.getmeta(Rcl::Doc::keyapptg, &apptag); - - if (!config->getMimeViewerDef(doc.mimetype, apptag, false).empty()) { - linksbuf << "" - << trans("Open") << ""; + if (canOpen(&doc, config)) { + linksbuf << "" + << trans("Open") << ""; } ostringstream snipsbuf; if (doc.haspages) { - snipsbuf << "" - << trans("Snippets") << "  "; - linksbuf << "  " << snipsbuf.str(); + snipsbuf << "" + << trans("Snippets") << "  "; + linksbuf << "  " << snipsbuf.str(); } string collapscnt; if (doc.getmeta(Rcl::Doc::keycc, &collapscnt) && !collapscnt.empty()) { - ostringstream collpsbuf; - int clc = atoi(collapscnt.c_str()) + 1; - collpsbuf << "" - << trans("Dups") << "(" << clc << ")" << "  "; - linksbuf << "  " << collpsbuf.str(); + ostringstream collpsbuf; + int clc = atoi(collapscnt.c_str()) + 1; + collpsbuf << "" + << trans("Dups") << "(" << clc << ")" << "  "; + linksbuf << "  " << collpsbuf.str(); } // Build the result list paragraph: // Subheader: this is used by history if (!sh.empty()) - chunk << "

" << sh << "

\n

"; + chunk << "

" << sh << "

\n

"; else - chunk << "

"; + chunk << "

"; char xdocidbuf[100]; sprintf(xdocidbuf, "%lu", doc.xdocid); @@ -299,7 +293,7 @@ void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc, subs["I"] = iconurl; subs["i"] = doc.ipath; subs["K"] = !doc.meta[Rcl::Doc::keykw].empty() ? - string("[") + maybeEscapeHtml(doc.meta[Rcl::Doc::keykw]) + "]" : ""; + string("[") + maybeEscapeHtml(doc.meta[Rcl::Doc::keykw]) + "]" : ""; subs["L"] = linksbuf.str(); subs["N"] = numbuf; subs["M"] = doc.mimetype; @@ -314,8 +308,8 @@ void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc, // Let %(xx) access all metadata. HTML-neuter everything: for (const auto& entry : doc.meta) { - if (!entry.first.empty()) - subs[entry.first] = maybeEscapeHtml(entry.second); + if (!entry.first.empty()) + subs[entry.first] = maybeEscapeHtml(entry.second); } string formatted; @@ -326,7 +320,7 @@ void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc, // This was to force qt 4.x to clear the margins (which it should do // anyway because of the paragraph's style), but we finally took // the table approach for 1.15 for now (in guiutils.cpp) -// chunk << "
" << endl; +// chunk << "
" << endl; LOGDEB2("Chunk: [" << chunk.rdbuf()->str() << "]\n"); append(chunk.rdbuf()->str(), i, doc); @@ -335,9 +329,9 @@ void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc, bool ResListPager::getDoc(int num, Rcl::Doc& doc) { if (m_winfirst < 0 || m_respage.size() == 0) - return false; + return false; if (num < m_winfirst || num >= m_winfirst + int(m_respage.size())) - return false; + return false; doc = m_respage[num-m_winfirst].doc; return true; } @@ -346,12 +340,12 @@ void ResListPager::displayPage(RclConfig *config) { LOGDEB("ResListPager::displayPage. linkPrefix: " << linkPrefix() << "\n"); if (!m_docSource) { - LOGDEB("ResListPager::displayPage: null source\n"); - return; + LOGDEB("ResListPager::displayPage: null source\n"); + return; } if (m_winfirst < 0 && !pageEmpty()) { - LOGDEB("ResListPager::displayPage: sequence error: winfirst < 0\n"); - return; + LOGDEB("ResListPager::displayPage: sequence error: winfirst < 0\n"); + return; } ostringstream chunk; @@ -365,84 +359,84 @@ void ResListPager::displayPage(RclConfig *config) // accumulator // Also note that there can be results beyond the estimated resCnt. chunk << "" << endl - << "" << endl - << headerContent() - << "" << endl - << pageTop() - << "

" - << m_docSource->title() - << "   "; + << "" << endl + << headerContent() + << "" << endl + << pageTop() + << "

" + << m_docSource->title() + << "   "; if (pageEmpty()) { - chunk << trans("

No results found
"); - string reason = m_docSource->getReason(); - if (!reason.empty()) { - chunk << "

" << escapeHtml(reason) << - "

"; - } else { - HighlightData hldata; - m_docSource->getTerms(hldata); - vector uterms(hldata.uterms.begin(), hldata.uterms.end()); - if (!uterms.empty()) { - map > spellings; - suggest(uterms, spellings); - if (!spellings.empty()) { - if (o_index_stripchars) { - chunk << - trans("

Alternate spellings (accents suppressed): ") - << "

"; - } else { - chunk << - trans("

Alternate spellings: ") - << "

"; - - } + chunk << trans("

No results found
"); + string reason = m_docSource->getReason(); + if (!reason.empty()) { + chunk << "

" << escapeHtml(reason) << + "

"; + } else { + HighlightData hldata; + m_docSource->getTerms(hldata); + vector uterms(hldata.uterms.begin(), hldata.uterms.end()); + if (!uterms.empty()) { + map > spellings; + suggest(uterms, spellings); + if (!spellings.empty()) { + if (o_index_stripchars) { + chunk << + trans("

Alternate spellings (accents suppressed): ") + << "

"; + } else { + chunk << + trans("

Alternate spellings: ") + << "

"; + + } - for (const auto& entry: spellings) { - chunk << "" << entry.first << " : "; + for (const auto& entry: spellings) { + chunk << "" << entry.first << " : "; for (const auto& spelling : entry.second) { - chunk << spelling << " "; - } - chunk << "
"; - } - chunk << "

"; - } - } - } + chunk << spelling << " "; + } + chunk << "
"; + } + chunk << "

"; + } + } + } } else { - unsigned int resCnt = m_docSource->getResCnt(); - if (m_winfirst + m_respage.size() < resCnt) { - chunk << trans("Documents") << " " << m_winfirst + 1 - << "-" << m_winfirst + m_respage.size() << " " - << trans("out of at least") << " " - << resCnt << " " << trans("for") << " " ; - } else { - chunk << trans("Documents") << " " - << m_winfirst + 1 << "-" << m_winfirst + m_respage.size() - << " " << trans("for") << " "; - } + unsigned int resCnt = m_docSource->getResCnt(); + if (m_winfirst + m_respage.size() < resCnt) { + chunk << trans("Documents") << " " << m_winfirst + 1 + << "-" << m_winfirst + m_respage.size() << " " + << trans("out of at least") << " " + << resCnt << " " << trans("for") << " " ; + } else { + chunk << trans("Documents") << " " + << m_winfirst + 1 << "-" << m_winfirst + m_respage.size() + << " " << trans("for") << " "; + } } chunk << detailsLink(); if (hasPrev() || hasNext()) { - chunk << "  "; - if (hasPrev()) { - chunk << "" - << trans("Previous") - << "   "; - } - if (hasNext()) { - chunk << "" - << trans("Next") - << ""; - } + chunk << "  "; + if (hasPrev()) { + chunk << "" + << trans("Previous") + << "   "; + } + if (hasNext()) { + chunk << "" + << trans("Next") + << ""; + } } chunk << "

" << endl; append(chunk.rdbuf()->str()); chunk.rdbuf()->str(""); if (pageEmpty()) - return; + return; HighlightData hdata; m_docSource->getTerms(hdata); @@ -450,24 +444,24 @@ void ResListPager::displayPage(RclConfig *config) // Emit data for result entry paragraph. Do it in chunks that make sense // html-wise, else our client may get confused for (int i = 0; i < (int)m_respage.size(); i++) { - Rcl::Doc& doc(m_respage[i].doc); - string& sh(m_respage[i].subHeader); - displayDoc(config, i, doc, hdata, sh); + Rcl::Doc& doc(m_respage[i].doc); + string& sh(m_respage[i].subHeader); + displayDoc(config, i, doc, hdata, sh); } // Footer chunk << "

"; if (hasPrev() || hasNext()) { - if (hasPrev()) { - chunk << "" - << trans("Previous") - << "   "; - } - if (hasNext()) { - chunk << "" - << trans("Next") - << ""; - } + if (hasPrev()) { + chunk << "" + << trans("Previous") + << "   "; + } + if (hasNext()) { + chunk << "" + << trans("Next") + << ""; + } } chunk << "

" << endl; chunk << "" << endl; @@ -515,9 +509,9 @@ string ResListPager::detailsLink() const string &ResListPager::parFormat() { static const string cstr_format("" - "%R %S %L   %T
" - "%M %D   %U
" - "%A %K"); + "%R %S %L   %T
" + "%M %D   %U
" + "%A %K"); return cstr_format; } @@ -526,5 +520,3 @@ const string &ResListPager::dateFormat() static const string cstr_format(" %Y-%m-%d %H:%M:%S %z"); return cstr_format; } - - diff --git a/src/rcldb/rcldb.cpp b/src/rcldb/rcldb.cpp index acfcee83..dbd6db53 100644 --- a/src/rcldb/rcldb.cpp +++ b/src/rcldb/rcldb.cpp @@ -992,6 +992,24 @@ bool Db::open(OpenMode mode, OpenError *error) return false; } +bool Db::storesDocText() +{ + if (!m_ndb || !m_ndb->m_isopen) { + LOGERR("Db::storesDocText: called on non-opened db\n"); + return false; + } + return m_ndb->m_storetext; +} + +bool Db::getDocRawText(Doc& doc) +{ + if (!m_ndb || !m_ndb->m_isopen) { + LOGERR("Db::getDocRawText: called on non-opened db\n"); + return false; + } + return m_ndb->getRawText(doc.xdocid, doc.text); +} + // Note: xapian has no close call, we delete and recreate the db bool Db::close() { diff --git a/src/rcldb/rcldb.h b/src/rcldb/rcldb.h index 24e767e3..a234c319 100644 --- a/src/rcldb/rcldb.h +++ b/src/rcldb/rcldb.h @@ -189,6 +189,9 @@ public: /** Return existing stemming databases */ vector getStemLangs(); + /** Check if index stores the documents' texts. Only valid after open */ + bool storesDocText(); + /** Test word for spelling correction candidate: not too long, no * special chars... * @param with_aspell test for use with aspell, else for xapian speller @@ -341,6 +344,12 @@ public: decide if we can update the index for it */ bool fromMainIndex(const Doc& doc); + /** Retrieve the stored doc text. This returns false if the index does not + store raw text or other problems (discriminate with storesDocText(). + On success, the data is stored in doc.text + */ + bool getDocRawText(Doc& doc); + /** Retrieve an index designator for the document result. This is used * by the GUI document history feature for remembering where a * doc comes from and allowing later retrieval (if the ext index