Take advantage of text storage when possible to display preview data for an unaccessible document

This commit is contained in:
Jean-Francois Dockes 2019-06-16 11:49:18 +02:00
parent a647d1c344
commit be214c4a5a
6 changed files with 254 additions and 189 deletions

View File

@ -42,6 +42,7 @@ using namespace std;
#include "mh_unknown.h" #include "mh_unknown.h"
#include "mh_null.h" #include "mh_null.h"
#include "mh_xslt.h" #include "mh_xslt.h"
#include "rcldoc.h"
// Performance help: we use a pool of already known and created // Performance help: we use a pool of already known and created
// handlers. There can be several instances for a given mime type // 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 false;
return true; 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() string RecollFilter::metadataAsString()
{ {

View File

@ -179,7 +179,14 @@ extern void returnMimeHandler(RecollFilter *);
/// off recoll. /// off recoll.
extern void clearMimeHandlerCache(); extern void clearMimeHandlerCache();
namespace Rcl {
class Doc;
}
/// Can this mime type be interned ? /// Can this mime type be interned ?
extern bool canIntern(const std::string mimetype, RclConfig *cfg); 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_ */ #endif /* _MIMEHANDLER_H_INCLUDED_ */

View File

@ -406,6 +406,9 @@ void Preview::currentChanged(int index)
return; return;
} }
edit->setFocus(); edit->setFocus();
editPB->setEnabled(canOpen(&edit->m_dbdoc, theconfig));
// Disconnect the print signal and reconnect it to the current editor // Disconnect the print signal and reconnect it to the current editor
LOGDEB1("Disconnecting reconnecting print signal\n"); LOGDEB1("Disconnecting reconnecting print signal\n");
disconnect(this, SIGNAL(printCurrentPreviewRequest()), 0, 0); disconnect(this, SIGNAL(printCurrentPreviewRequest()), 0, 0);
@ -643,6 +646,7 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum)
if (CancelCheck::instance().cancelState()) if (CancelCheck::instance().cancelState())
return false; return false;
if (lthr.status != 0) { if (lthr.status != 0) {
bool canGetRawText = rcldb && rcldb->storesDocText();
QString explain; QString explain;
if (!lthr.missing.empty()) { if (!lthr.missing.empty()) {
explain = QString::fromUtf8("<br>") + explain = QString::fromUtf8("<br>") +
@ -656,7 +660,7 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum)
if (progress.wasCanceled()) { if (progress.wasCanceled()) {
QMessageBox::warning(0, "Recoll", tr("Canceled")); QMessageBox::warning(0, "Recoll", tr("Canceled"));
} else { } else {
progress.close(); progress.reset();
// Note that we can't easily check for a readable file // Note that we can't easily check for a readable file
// because it's possible that only a region is locked // because it's possible that only a region is locked
// (e.g. on Windows for an ost file the first block is // (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; QString msg;
switch (lthr.explain) { switch (lthr.explain) {
case FileInterner::FetchMissing: case FileInterner::FetchMissing:
msg = tr("Error loading the document: file missing"); msg = tr("Error loading the document: file missing.");
break; break;
case FileInterner::FetchPerm: case FileInterner::FetchPerm:
msg = tr("Error loading the document: no permission"); msg = tr("Error loading the document: no permission.");
break; break;
case FileInterner::FetchNoBackend: case FileInterner::FetchNoBackend:
msg = msg =
tr("Error loading the document: backend not configured"); tr("Error loading: backend not configured.");
break; break;
case FileInterner::InternfileOther: case FileInterner::InternfileOther:
#ifdef _WIN32 #ifdef _WIN32
@ -679,16 +683,29 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum)
"other handler error<br>" "other handler error<br>"
"Maybe the application is locking the file ?"); "Maybe the application is locking the file ?");
#else #else
msg = tr("Error loading the document: other handler error"); msg = tr("Error loading the document: other handler error.");
#endif #endif
break; break;
} }
if (canGetRawText) {
msg += tr("<br>Attempting to display from stored text.");
}
QMessageBox::warning(0, "Recoll", msg); 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. // Reset config just in case.
theconfig->setKeyDir(""); theconfig->setKeyDir("");
@ -838,6 +855,7 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum)
lthr.fdoc.text.clear(); lthr.fdoc.text.clear();
editor->m_fdoc = lthr.fdoc; editor->m_fdoc = lthr.fdoc;
editor->m_dbdoc = idoc; editor->m_dbdoc = idoc;
editPB->setEnabled(canOpen(&editor->m_dbdoc, theconfig));
if (textempty) if (textempty)
editor->displayFields(); editor->displayFields();
@ -962,8 +980,10 @@ void PreviewTextEdit::createPopupMenu(const QPoint& pos)
if (!m_dbdoc.url.empty()) { if (!m_dbdoc.url.empty()) {
popup->addAction(tr("Save document to file"), popup->addAction(tr("Save document to file"),
m_preview, SLOT(emitSaveDocToFile())); m_preview, SLOT(emitSaveDocToFile()));
popup->addAction(tr("Open document"), if (canOpen(&m_dbdoc, theconfig)) {
m_preview, SLOT(emitEditRequested())); popup->addAction(tr("Open document"),
m_preview, SLOT(emitEditRequested()));
}
} }
popup->popup(mapToGlobal(pos)); popup->popup(mapToGlobal(pos));
} }

View File

@ -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 * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or * the Free Software Foundation; either version 2 of the License, or
@ -46,13 +46,11 @@ static const string cstr_hlfontcolor("<span style='color: blue;'>");
static const string cstr_hlendfont("</span>"); static const string cstr_hlendfont("</span>");
class PlainToRichHtReslist : public PlainToRich { class PlainToRichHtReslist : public PlainToRich {
public: public:
virtual string startMatch(unsigned int) virtual string startMatch(unsigned int) {
{ return cstr_hlfontcolor;
return cstr_hlfontcolor;
} }
virtual string endMatch() virtual string endMatch() {
{ return cstr_hlendfont;
return cstr_hlendfont;
} }
}; };
static PlainToRichHtReslist g_hiliter; static PlainToRichHtReslist g_hiliter;
@ -70,8 +68,8 @@ ResListPager::ResListPager(int pagesize)
void ResListPager::resultPageNext() void ResListPager::resultPageNext()
{ {
if (!m_docSource) { if (!m_docSource) {
LOGDEB("ResListPager::resultPageNext: null source\n"); LOGDEB("ResListPager::resultPageNext: null source\n");
return; return;
} }
int resCnt = m_docSource->getResCnt(); int resCnt = m_docSource->getResCnt();
@ -79,9 +77,9 @@ void ResListPager::resultPageNext()
", winfirst " << m_winfirst << "\n"); ", winfirst " << m_winfirst << "\n");
if (m_winfirst < 0) { if (m_winfirst < 0) {
m_winfirst = 0; m_winfirst = 0;
} else { } 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 // Get the next page of results. Note that we look ahead by one to
// determine if there is actually a next page // determine if there is actually a next page
@ -93,25 +91,25 @@ void ResListPager::resultPageNext()
// Get rid of the possible excess result // Get rid of the possible excess result
if (pagelen == m_pagesize + 1) { if (pagelen == m_pagesize + 1) {
npage.resize(m_pagesize); npage.resize(m_pagesize);
pagelen--; pagelen--;
} }
if (pagelen <= 0) { if (pagelen <= 0) {
// No results ? This can only happen on the first page or if the // 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 // actual result list size is a multiple of the page pref (else
// there would have been no Next on the last page) // there would have been no Next on the last page)
if (m_winfirst > 0) { if (m_winfirst > 0) {
// Have already results. Let them show, just disable the // Have already results. Let them show, just disable the
// Next button. We'd need to remove the Next link from the page // Next button. We'd need to remove the Next link from the page
// too. // too.
// Restore the m_winfirst value, let the current result vector alone // Restore the m_winfirst value, let the current result vector alone
m_winfirst -= int(m_respage.size()); m_winfirst -= int(m_respage.size());
} else { } else {
// No results at all (on first page) // No results at all (on first page)
m_winfirst = -1; m_winfirst = -1;
} }
return; return;
} }
m_resultsInCurrentPage = pagelen; m_resultsInCurrentPage = pagelen;
m_respage = npage; m_respage = npage;
@ -119,17 +117,17 @@ void ResListPager::resultPageNext()
static string maybeEscapeHtml(const string& fld) static string maybeEscapeHtml(const string& fld)
{ {
if (fld.compare(0, cstr_fldhtm.size(), cstr_fldhtm)) if (fld.compare(0, cstr_fldhtm.size(), cstr_fldhtm))
return escapeHtml(fld); return escapeHtml(fld);
else else
return fld.substr(cstr_fldhtm.size()); return fld.substr(cstr_fldhtm.size());
} }
void ResListPager::resultPageFor(int docnum) void ResListPager::resultPageFor(int docnum)
{ {
if (!m_docSource) { if (!m_docSource) {
LOGDEB("ResListPager::resultPageFor: null source\n"); LOGDEB("ResListPager::resultPageFor: null source\n");
return; return;
} }
int resCnt = m_docSource->getResCnt(); int resCnt = m_docSource->getResCnt();
@ -145,14 +143,14 @@ void ResListPager::resultPageFor(int docnum)
m_hasNext = (pagelen == m_pagesize); m_hasNext = (pagelen == m_pagesize);
if (pagelen <= 0) { if (pagelen <= 0) {
m_winfirst = -1; m_winfirst = -1;
return; return;
} }
m_respage = npage; m_respage = npage;
} }
void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc, void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc,
const HighlightData& hdata, const string& sh) const HighlightData& hdata, const string& sh)
{ {
ostringstream chunk; 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::keytt, &titleOrFilename);
doc.getmeta(Rcl::Doc::keyfn, &utf8fn); doc.getmeta(Rcl::Doc::keyfn, &utf8fn);
if (utf8fn.empty()) { if (utf8fn.empty()) {
utf8fn = path_getsimple(url); utf8fn = path_getsimple(url);
} }
if (titleOrFilename.empty()) { if (titleOrFilename.empty()) {
titleOrFilename = utf8fn; titleOrFilename = utf8fn;
} }
// Url for the parent directory. We strip the file:// part for local // 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()) { if (!doc.dmtime.empty() || !doc.fmtime.empty()) {
char cdate[100]; char cdate[100];
cdate[0] = 0; cdate[0] = 0;
time_t mtime = doc.dmtime.empty() ? time_t mtime = doc.dmtime.empty() ?
atoll(doc.fmtime.c_str()) : atoll(doc.dmtime.c_str()); atoll(doc.fmtime.c_str()) : atoll(doc.dmtime.c_str());
struct tm *tm = localtime(&mtime); struct tm *tm = localtime(&mtime);
strftime(cdate, 99, dateFormat().c_str(), tm); strftime(cdate, 99, dateFormat().c_str(), tm);
transcode(cdate, datebuf, RclConfig::getLocaleCharset(), "UTF-8"); transcode(cdate, datebuf, RclConfig::getLocaleCharset(), "UTF-8");
} }
// Size information. We print both doc and file if they differ a lot // Size information. We print both doc and file if they differ a lot
int64_t fsize = -1, dsize = -1; int64_t fsize = -1, dsize = -1;
if (!doc.dbytes.empty()) if (!doc.dbytes.empty())
dsize = static_cast<int64_t>(atoll(doc.dbytes.c_str())); dsize = static_cast<int64_t>(atoll(doc.dbytes.c_str()));
if (!doc.fbytes.empty()) if (!doc.fbytes.empty())
fsize = static_cast<int64_t>(atoll(doc.fbytes.c_str())); fsize = static_cast<int64_t>(atoll(doc.fbytes.c_str()));
string sizebuf; string sizebuf;
if (dsize > 0) { if (dsize > 0) {
sizebuf = displayableBytes(dsize); sizebuf = displayableBytes(dsize);
if (fsize > 10 * dsize && fsize - dsize > 1000) if (fsize > 10 * dsize && fsize - dsize > 1000)
sizebuf += string(" / ") + displayableBytes(fsize); sizebuf += string(" / ") + displayableBytes(fsize);
} else if (fsize >= 0) { } else if (fsize >= 0) {
sizebuf = displayableBytes(fsize); sizebuf = displayableBytes(fsize);
} }
string richabst; string richabst;
bool needabstract = parFormat().find("%A") != string::npos; bool needabstract = parFormat().find("%A") != string::npos;
if (needabstract && m_docSource) { if (needabstract && m_docSource) {
vector<string> vabs; vector<string> vabs;
m_docSource->getAbstract(doc, vabs); m_docSource->getAbstract(doc, vabs);
m_hiliter->set_inputhtml(false); m_hiliter->set_inputhtml(false);
for (vector<string>::const_iterator it = vabs.begin(); for (vector<string>::const_iterator it = vabs.begin();
it != vabs.end(); it++) { it != vabs.end(); it++) {
if (!it->empty()) { if (!it->empty()) {
// No need to call escapeHtml(), plaintorich handles it // No need to call escapeHtml(), plaintorich handles it
list<string> lr; list<string> lr;
// There may be data like page numbers before the snippet text. // There may be data like page numbers before the snippet text.
// will be in brackets. // will be in brackets.
string::size_type bckt = it->find("]"); string::size_type bckt = it->find("]");
if (bckt == string::npos) { if (bckt == string::npos) {
m_hiliter->plaintorich(*it, lr, hdata); m_hiliter->plaintorich(*it, lr, hdata);
} else { } else {
m_hiliter->plaintorich(it->substr(bckt), lr, hdata); m_hiliter->plaintorich(it->substr(bckt), lr, hdata);
lr.front() = it->substr(0, bckt) + lr.front(); lr.front() = it->substr(0, bckt) + lr.front();
} }
richabst += lr.front(); richabst += lr.front();
richabst += absSep(); richabst += absSep();
} }
} }
} }
// Links; // Links; Uses utilities from mimehandler.h
ostringstream linksbuf; ostringstream linksbuf;
if (canIntern(doc.mimetype, config)) { if (canIntern(&doc, config)) {
linksbuf << "<a href=\""<< linkPrefix()<< "P" << docnumforlinks << "\">" linksbuf << "<a href=\""<< linkPrefix()<< "P" << docnumforlinks << "\">"
<< trans("Preview") << "</a>&nbsp;&nbsp;"; << trans("Preview") << "</a>&nbsp;&nbsp;";
} }
if (canOpen(&doc, config)) {
string apptag; linksbuf << "<a href=\"" <<linkPrefix() + "E" <<docnumforlinks << "\">"
doc.getmeta(Rcl::Doc::keyapptg, &apptag); << trans("Open") << "</a>";
if (!config->getMimeViewerDef(doc.mimetype, apptag, false).empty()) {
linksbuf << "<a href=\"" <<linkPrefix() + "E" <<docnumforlinks << "\">"
<< trans("Open") << "</a>";
} }
ostringstream snipsbuf; ostringstream snipsbuf;
if (doc.haspages) { if (doc.haspages) {
snipsbuf << "<a href=\"" <<linkPrefix()<<"A" << docnumforlinks << "\">" snipsbuf << "<a href=\"" <<linkPrefix()<<"A" << docnumforlinks << "\">"
<< trans("Snippets") << "</a>&nbsp;&nbsp;"; << trans("Snippets") << "</a>&nbsp;&nbsp;";
linksbuf << "&nbsp;&nbsp;" << snipsbuf.str(); linksbuf << "&nbsp;&nbsp;" << snipsbuf.str();
} }
string collapscnt; string collapscnt;
if (doc.getmeta(Rcl::Doc::keycc, &collapscnt) && !collapscnt.empty()) { if (doc.getmeta(Rcl::Doc::keycc, &collapscnt) && !collapscnt.empty()) {
ostringstream collpsbuf; ostringstream collpsbuf;
int clc = atoi(collapscnt.c_str()) + 1; int clc = atoi(collapscnt.c_str()) + 1;
collpsbuf << "<a href=\""<<linkPrefix()<<"D" << docnumforlinks << "\">" collpsbuf << "<a href=\""<<linkPrefix()<<"D" << docnumforlinks << "\">"
<< trans("Dups") << "(" << clc << ")" << "</a>&nbsp;&nbsp;"; << trans("Dups") << "(" << clc << ")" << "</a>&nbsp;&nbsp;";
linksbuf << "&nbsp;&nbsp;" << collpsbuf.str(); linksbuf << "&nbsp;&nbsp;" << collpsbuf.str();
} }
// Build the result list paragraph: // Build the result list paragraph:
// Subheader: this is used by history // Subheader: this is used by history
if (!sh.empty()) if (!sh.empty())
chunk << "<p style='clear: both;'><b>" << sh << "</p>\n<p>"; chunk << "<p style='clear: both;'><b>" << sh << "</p>\n<p>";
else else
chunk << "<p style='margin: 0px;padding: 0px;clear: both;'>"; chunk << "<p style='margin: 0px;padding: 0px;clear: both;'>";
char xdocidbuf[100]; char xdocidbuf[100];
sprintf(xdocidbuf, "%lu", doc.xdocid); sprintf(xdocidbuf, "%lu", doc.xdocid);
@ -299,7 +293,7 @@ void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc,
subs["I"] = iconurl; subs["I"] = iconurl;
subs["i"] = doc.ipath; subs["i"] = doc.ipath;
subs["K"] = !doc.meta[Rcl::Doc::keykw].empty() ? 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["L"] = linksbuf.str();
subs["N"] = numbuf; subs["N"] = numbuf;
subs["M"] = doc.mimetype; 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: // Let %(xx) access all metadata. HTML-neuter everything:
for (const auto& entry : doc.meta) { for (const auto& entry : doc.meta) {
if (!entry.first.empty()) if (!entry.first.empty())
subs[entry.first] = maybeEscapeHtml(entry.second); subs[entry.first] = maybeEscapeHtml(entry.second);
} }
string formatted; 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 // 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 // anyway because of the paragraph's style), but we finally took
// the table approach for 1.15 for now (in guiutils.cpp) // the table approach for 1.15 for now (in guiutils.cpp)
// chunk << "<br style='clear:both;height:0;line-height:0;'>" << endl; // chunk << "<br style='clear:both;height:0;line-height:0;'>" << endl;
LOGDEB2("Chunk: [" << chunk.rdbuf()->str() << "]\n"); LOGDEB2("Chunk: [" << chunk.rdbuf()->str() << "]\n");
append(chunk.rdbuf()->str(), i, doc); 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) bool ResListPager::getDoc(int num, Rcl::Doc& doc)
{ {
if (m_winfirst < 0 || m_respage.size() == 0) if (m_winfirst < 0 || m_respage.size() == 0)
return false; return false;
if (num < m_winfirst || num >= m_winfirst + int(m_respage.size())) if (num < m_winfirst || num >= m_winfirst + int(m_respage.size()))
return false; return false;
doc = m_respage[num-m_winfirst].doc; doc = m_respage[num-m_winfirst].doc;
return true; return true;
} }
@ -346,12 +340,12 @@ void ResListPager::displayPage(RclConfig *config)
{ {
LOGDEB("ResListPager::displayPage. linkPrefix: " << linkPrefix() << "\n"); LOGDEB("ResListPager::displayPage. linkPrefix: " << linkPrefix() << "\n");
if (!m_docSource) { if (!m_docSource) {
LOGDEB("ResListPager::displayPage: null source\n"); LOGDEB("ResListPager::displayPage: null source\n");
return; return;
} }
if (m_winfirst < 0 && !pageEmpty()) { if (m_winfirst < 0 && !pageEmpty()) {
LOGDEB("ResListPager::displayPage: sequence error: winfirst < 0\n"); LOGDEB("ResListPager::displayPage: sequence error: winfirst < 0\n");
return; return;
} }
ostringstream chunk; ostringstream chunk;
@ -365,84 +359,84 @@ void ResListPager::displayPage(RclConfig *config)
// accumulator // accumulator
// Also note that there can be results beyond the estimated resCnt. // Also note that there can be results beyond the estimated resCnt.
chunk << "<html><head>" << endl chunk << "<html><head>" << endl
<< "<meta http-equiv=\"content-type\"" << "<meta http-equiv=\"content-type\""
<< " content=\"text/html; charset=utf-8\">" << endl << " content=\"text/html; charset=utf-8\">" << endl
<< headerContent() << headerContent()
<< "</head><body>" << endl << "</head><body>" << endl
<< pageTop() << pageTop()
<< "<p><span style=\"font-size:110%;\"><b>" << "<p><span style=\"font-size:110%;\"><b>"
<< m_docSource->title() << m_docSource->title()
<< "</b></span>&nbsp;&nbsp;&nbsp;"; << "</b></span>&nbsp;&nbsp;&nbsp;";
if (pageEmpty()) { if (pageEmpty()) {
chunk << trans("<p><b>No results found</b><br>"); chunk << trans("<p><b>No results found</b><br>");
string reason = m_docSource->getReason(); string reason = m_docSource->getReason();
if (!reason.empty()) { if (!reason.empty()) {
chunk << "<blockquote>" << escapeHtml(reason) << chunk << "<blockquote>" << escapeHtml(reason) <<
"</blockquote></p>"; "</blockquote></p>";
} else { } else {
HighlightData hldata; HighlightData hldata;
m_docSource->getTerms(hldata); m_docSource->getTerms(hldata);
vector<string> uterms(hldata.uterms.begin(), hldata.uterms.end()); vector<string> uterms(hldata.uterms.begin(), hldata.uterms.end());
if (!uterms.empty()) { if (!uterms.empty()) {
map<string, vector<string> > spellings; map<string, vector<string> > spellings;
suggest(uterms, spellings); suggest(uterms, spellings);
if (!spellings.empty()) { if (!spellings.empty()) {
if (o_index_stripchars) { if (o_index_stripchars) {
chunk << chunk <<
trans("<p><i>Alternate spellings (accents suppressed): </i>") trans("<p><i>Alternate spellings (accents suppressed): </i>")
<< "<br /><blockquote>"; << "<br /><blockquote>";
} else { } else {
chunk << chunk <<
trans("<p><i>Alternate spellings: </i>") trans("<p><i>Alternate spellings: </i>")
<< "<br /><blockquote>"; << "<br /><blockquote>";
} }
for (const auto& entry: spellings) { for (const auto& entry: spellings) {
chunk << "<b>" << entry.first << "</b> : "; chunk << "<b>" << entry.first << "</b> : ";
for (const auto& spelling : entry.second) { for (const auto& spelling : entry.second) {
chunk << spelling << " "; chunk << spelling << " ";
} }
chunk << "<br />"; chunk << "<br />";
} }
chunk << "</blockquote></p>"; chunk << "</blockquote></p>";
} }
} }
} }
} else { } else {
unsigned int resCnt = m_docSource->getResCnt(); unsigned int resCnt = m_docSource->getResCnt();
if (m_winfirst + m_respage.size() < resCnt) { if (m_winfirst + m_respage.size() < resCnt) {
chunk << trans("Documents") << " <b>" << m_winfirst + 1 chunk << trans("Documents") << " <b>" << m_winfirst + 1
<< "-" << m_winfirst + m_respage.size() << "</b> " << "-" << m_winfirst + m_respage.size() << "</b> "
<< trans("out of at least") << " " << trans("out of at least") << " "
<< resCnt << " " << trans("for") << " " ; << resCnt << " " << trans("for") << " " ;
} else { } else {
chunk << trans("Documents") << " <b>" chunk << trans("Documents") << " <b>"
<< m_winfirst + 1 << "-" << m_winfirst + m_respage.size() << m_winfirst + 1 << "-" << m_winfirst + m_respage.size()
<< "</b> " << trans("for") << " "; << "</b> " << trans("for") << " ";
} }
} }
chunk << detailsLink(); chunk << detailsLink();
if (hasPrev() || hasNext()) { if (hasPrev() || hasNext()) {
chunk << "&nbsp;&nbsp;"; chunk << "&nbsp;&nbsp;";
if (hasPrev()) { if (hasPrev()) {
chunk << "<a href=\"" << linkPrefix() + prevUrl() + "\"><b>" chunk << "<a href=\"" << linkPrefix() + prevUrl() + "\"><b>"
<< trans("Previous") << trans("Previous")
<< "</b></a>&nbsp;&nbsp;&nbsp;"; << "</b></a>&nbsp;&nbsp;&nbsp;";
} }
if (hasNext()) { if (hasNext()) {
chunk << "<a href=\"" << linkPrefix() + nextUrl() + "\"><b>" chunk << "<a href=\"" << linkPrefix() + nextUrl() + "\"><b>"
<< trans("Next") << trans("Next")
<< "</b></a>"; << "</b></a>";
} }
} }
chunk << "</p>" << endl; chunk << "</p>" << endl;
append(chunk.rdbuf()->str()); append(chunk.rdbuf()->str());
chunk.rdbuf()->str(""); chunk.rdbuf()->str("");
if (pageEmpty()) if (pageEmpty())
return; return;
HighlightData hdata; HighlightData hdata;
m_docSource->getTerms(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 // Emit data for result entry paragraph. Do it in chunks that make sense
// html-wise, else our client may get confused // html-wise, else our client may get confused
for (int i = 0; i < (int)m_respage.size(); i++) { for (int i = 0; i < (int)m_respage.size(); i++) {
Rcl::Doc& doc(m_respage[i].doc); Rcl::Doc& doc(m_respage[i].doc);
string& sh(m_respage[i].subHeader); string& sh(m_respage[i].subHeader);
displayDoc(config, i, doc, hdata, sh); displayDoc(config, i, doc, hdata, sh);
} }
// Footer // Footer
chunk << "<p align=\"center\">"; chunk << "<p align=\"center\">";
if (hasPrev() || hasNext()) { if (hasPrev() || hasNext()) {
if (hasPrev()) { if (hasPrev()) {
chunk << "<a href=\"" + linkPrefix() + prevUrl() + "\"><b>" chunk << "<a href=\"" + linkPrefix() + prevUrl() + "\"><b>"
<< trans("Previous") << trans("Previous")
<< "</b></a>&nbsp;&nbsp;&nbsp;"; << "</b></a>&nbsp;&nbsp;&nbsp;";
} }
if (hasNext()) { if (hasNext()) {
chunk << "<a href=\"" << linkPrefix() + nextUrl() + "\"><b>" chunk << "<a href=\"" << linkPrefix() + nextUrl() + "\"><b>"
<< trans("Next") << trans("Next")
<< "</b></a>"; << "</b></a>";
} }
} }
chunk << "</p>" << endl; chunk << "</p>" << endl;
chunk << "</body></html>" << endl; chunk << "</body></html>" << endl;
@ -515,9 +509,9 @@ string ResListPager::detailsLink()
const string &ResListPager::parFormat() const string &ResListPager::parFormat()
{ {
static const string cstr_format("<img src=\"%I\" align=\"left\">" static const string cstr_format("<img src=\"%I\" align=\"left\">"
"%R %S %L &nbsp;&nbsp;<b>%T</b><br>" "%R %S %L &nbsp;&nbsp;<b>%T</b><br>"
"%M&nbsp;%D&nbsp;&nbsp;&nbsp;<i>%U</i><br>" "%M&nbsp;%D&nbsp;&nbsp;&nbsp;<i>%U</i><br>"
"%A %K"); "%A %K");
return cstr_format; return cstr_format;
} }
@ -526,5 +520,3 @@ const string &ResListPager::dateFormat()
static const string cstr_format("&nbsp;%Y-%m-%d&nbsp;%H:%M:%S&nbsp;%z"); static const string cstr_format("&nbsp;%Y-%m-%d&nbsp;%H:%M:%S&nbsp;%z");
return cstr_format; return cstr_format;
} }

View File

@ -992,6 +992,24 @@ bool Db::open(OpenMode mode, OpenError *error)
return false; 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 // Note: xapian has no close call, we delete and recreate the db
bool Db::close() bool Db::close()
{ {

View File

@ -189,6 +189,9 @@ public:
/** Return existing stemming databases */ /** Return existing stemming databases */
vector<string> getStemLangs(); vector<string> getStemLangs();
/** Check if index stores the documents' texts. Only valid after open */
bool storesDocText();
/** Test word for spelling correction candidate: not too long, no /** Test word for spelling correction candidate: not too long, no
* special chars... * special chars...
* @param with_aspell test for use with aspell, else for xapian speller * @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 */ decide if we can update the index for it */
bool fromMainIndex(const Doc& doc); 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 /** Retrieve an index designator for the document result. This is used
* by the GUI document history feature for remembering where a * by the GUI document history feature for remembering where a
* doc comes from and allowing later retrieval (if the ext index * doc comes from and allowing later retrieval (if the ext index