Try to give possible explanations when opening a preview fails

This commit is contained in:
Jean-Francois Dockes 2019-06-15 19:21:52 +02:00
parent e7ba872404
commit dca18bc585
12 changed files with 574 additions and 479 deletions

View File

@ -77,7 +77,8 @@ bool EXEDocFetcher::makesig(RclConfig* cnf, const Rcl::Doc& idoc, string& sig)
} }
// Lookup bckid in the config and create an appropriate fetcher. // Lookup bckid in the config and create an appropriate fetcher.
EXEDocFetcher *exeDocFetcherMake(RclConfig *config, const string& bckid) std::unique_ptr<EXEDocFetcher> exeDocFetcherMake(RclConfig *config,
const string& bckid)
{ {
// The config we only read once, not gonna change. // The config we only read once, not gonna change.
static ConfSimple *bconf; static ConfSimple *bconf;
@ -122,5 +123,5 @@ EXEDocFetcher *exeDocFetcherMake(RclConfig *config, const string& bckid)
" not found in exec path or filters dir\n"); " not found in exec path or filters dir\n");
return 0; return 0;
} }
return new EXEDocFetcher(m); return std::unique_ptr<EXEDocFetcher>(new EXEDocFetcher(m));
} }

View File

@ -17,6 +17,7 @@
#ifndef _EXEFETCHER_H_INCLUDED_ #ifndef _EXEFETCHER_H_INCLUDED_
#define _EXEFETCHER_H_INCLUDED_ #define _EXEFETCHER_H_INCLUDED_
#include <memory>
#include "fetcher.h" #include "fetcher.h"
class RclConfig; class RclConfig;
@ -35,6 +36,7 @@ class RclConfig;
* query time for previewing and opening the document. * query time for previewing and opening the document.
*/ */
class EXEDocFetcher : public DocFetcher { class EXEDocFetcher : public DocFetcher {
public:
class Internal; class Internal;
EXEDocFetcher(const Internal&); EXEDocFetcher(const Internal&);
virtual ~EXEDocFetcher() {} virtual ~EXEDocFetcher() {}
@ -42,12 +44,14 @@ class EXEDocFetcher : public DocFetcher {
virtual bool fetch(RclConfig* cnf, const Rcl::Doc& idoc, RawDoc& out); virtual bool fetch(RclConfig* cnf, const Rcl::Doc& idoc, RawDoc& out);
/** Calls stat to retrieve file signature data */ /** Calls stat to retrieve file signature data */
virtual bool makesig(RclConfig* cnf, const Rcl::Doc& idoc,std::string& sig); virtual bool makesig(RclConfig* cnf, const Rcl::Doc& idoc,std::string& sig);
friend EXEDocFetcher *exeDocFetcherMake(RclConfig *, const std::string&); friend std::unique_ptr<EXEDocFetcher>
exeDocFetcherMake(RclConfig *, const std::string&);
private: private:
Internal *m; Internal *m;
}; };
// Lookup bckid in the config and create an appropriate fetcher. // Lookup bckid in the config and create an appropriate fetcher.
EXEDocFetcher *exeDocFetcherMake(RclConfig *config, const std::string& bckid); std::unique_ptr<EXEDocFetcher> exeDocFetcherMake(RclConfig *config,
const std::string& bckid);
#endif /* _EXEFETCHER_H_INCLUDED_ */ #endif /* _EXEFETCHER_H_INCLUDED_ */

View File

@ -16,6 +16,8 @@
*/ */
#include "autoconfig.h" #include "autoconfig.h"
#include <memory>
#include "log.h" #include "log.h"
#include "rclconfig.h" #include "rclconfig.h"
#include "fetcher.h" #include "fetcher.h"
@ -23,22 +25,23 @@
#include "webqueuefetcher.h" #include "webqueuefetcher.h"
#include "exefetcher.h" #include "exefetcher.h"
DocFetcher *docFetcherMake(RclConfig *config, const Rcl::Doc& idoc) std::unique_ptr<DocFetcher> docFetcherMake(RclConfig *config,
const Rcl::Doc& idoc)
{ {
if (idoc.url.empty()) { if (idoc.url.empty()) {
LOGERR("docFetcherMakeg:: no url in doc!\n" ); LOGERR("docFetcherMakeg:: no url in doc!\n" );
return 0; return std::unique_ptr<DocFetcher>();
} }
string backend; string backend;
idoc.getmeta(Rcl::Doc::keybcknd, &backend); idoc.getmeta(Rcl::Doc::keybcknd, &backend);
if (backend.empty() || !backend.compare("FS")) { if (backend.empty() || !backend.compare("FS")) {
return new FSDocFetcher; return std::unique_ptr<DocFetcher>(new FSDocFetcher);
#ifndef DISABLE_WEB_INDEXER #ifndef DISABLE_WEB_INDEXER
} else if (!backend.compare("BGL")) { } else if (!backend.compare("BGL")) {
return new WQDocFetcher; return std::unique_ptr<DocFetcher>(new WQDocFetcher);
#endif #endif
} else { } else {
DocFetcher *f = exeDocFetcherMake(config, backend); std::unique_ptr<DocFetcher> f(exeDocFetcherMake(config, backend));
if (!f) { if (!f) {
LOGERR("DocFetcherFactory: unknown backend [" << backend << "]\n"); LOGERR("DocFetcherFactory: unknown backend [" << backend << "]\n");
} }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2012 J.F.Dockes /* Copyright (C) 2012-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
@ -19,6 +19,7 @@
#include "safesysstat.h" #include "safesysstat.h"
#include <string> #include <string>
#include <memory>
#include "rcldoc.h" #include "rcldoc.h"
@ -45,12 +46,12 @@ class RclConfig;
class DocFetcher { class DocFetcher {
public: public:
/** A RawDoc is the data for a document-holding entity either as a /** A RawDoc is the data for a document-holding entity either as a
memory block, or pointed to by a file name */ memory block, or pointed to by a file name */
struct RawDoc { struct RawDoc {
enum RawDocKind {RDK_FILENAME, RDK_DATA, RDK_DATADIRECT}; enum RawDocKind {RDK_FILENAME, RDK_DATA, RDK_DATADIRECT};
RawDocKind kind; RawDocKind kind;
std::string data; // Doc data or file name std::string data; // Doc data or file name
struct stat st; // Only used if RDK_FILENAME struct stat st; // Only used if RDK_FILENAME
}; };
/** /**
@ -73,11 +74,16 @@ public:
*/ */
virtual bool makesig(RclConfig* cnf, const Rcl::Doc& idoc, virtual bool makesig(RclConfig* cnf, const Rcl::Doc& idoc,
std::string& sig) = 0; std::string& sig) = 0;
enum Reason{FetchOk, FetchNotExist, FetchNoPerm, FetchOther};
virtual Reason testAccess(RclConfig* cnf, const Rcl::Doc& idoc) {
return FetchOther;
}
virtual ~DocFetcher() {} virtual ~DocFetcher() {}
}; };
/** Return an appropriate fetcher object given the backend string /** Return an appropriate fetcher object given the backend string
* identifier inside idoc*/ * identifier inside idoc*/
DocFetcher *docFetcherMake(RclConfig *config, const Rcl::Doc& idoc); std::unique_ptr<DocFetcher> docFetcherMake(RclConfig *config,
const Rcl::Doc& idoc);
#endif /* _FETCHER_H_INCLUDED_ */ #endif /* _FETCHER_H_INCLUDED_ */

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2012 J.F.Dockes /* Copyright (C) 2012-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
@ -28,31 +28,32 @@
using std::string; using std::string;
static bool urltopath(RclConfig* cnf, static DocFetcher::Reason urltopath(RclConfig* cnf, const Rcl::Doc& idoc,
const Rcl::Doc& idoc, string& fn, struct stat& st) string& fn, struct stat& st)
{ {
// The url has to be like file:// // The url has to be like file://
fn = fileurltolocalpath(idoc.url); fn = fileurltolocalpath(idoc.url);
if (fn.empty()) { if (fn.empty()) {
LOGERR("FSDocFetcher::fetch/sig: non fs url: [" << (idoc.url) << "]\n" ); LOGERR("FSDocFetcher::fetch/sig: non fs url: [" << idoc.url << "]\n");
return false; return DocFetcher::FetchOther;
} }
cnf->setKeyDir(path_getfather(fn)); cnf->setKeyDir(path_getfather(fn));
bool follow = false; bool follow = false;
cnf->getConfParam("followLinks", &follow); cnf->getConfParam("followLinks", &follow);
if (path_fileprops(fn, &st, follow) < 0) { if (path_fileprops(fn, &st, follow) < 0) {
LOGERR("FSDocFetcher::fetch: stat errno " << (errno) << " for [" << (fn) << "]\n" ); LOGERR("FSDocFetcher::fetch: stat errno " << errno << " for [" << fn
return false; << "]\n");
return DocFetcher::FetchNotExist;
} }
return true; return DocFetcher::FetchOk;
} }
bool FSDocFetcher::fetch(RclConfig* cnf, const Rcl::Doc& idoc, RawDoc& out) bool FSDocFetcher::fetch(RclConfig* cnf, const Rcl::Doc& idoc, RawDoc& out)
{ {
string fn; string fn;
if (!urltopath(cnf, idoc, fn, out.st)) if (urltopath(cnf, idoc, fn, out.st) != DocFetcher::FetchOk)
return false; return false;
out.kind = RawDoc::RDK_FILENAME; out.kind = RawDoc::RDK_FILENAME;
out.data = fn; out.data = fn;
return true; return true;
@ -62,10 +63,24 @@ bool FSDocFetcher::makesig(RclConfig* cnf, const Rcl::Doc& idoc, string& sig)
{ {
string fn; string fn;
struct stat st; struct stat st;
if (!urltopath(cnf, idoc, fn, st)) if (urltopath(cnf, idoc, fn, st) != DocFetcher::FetchOk)
return false; return false;
FsIndexer::makesig(&st, sig); FsIndexer::makesig(&st, sig);
return true; return true;
} }
DocFetcher::Reason FSDocFetcher::testAccess(RclConfig* cnf, const Rcl::Doc& idoc)
{
string fn;
struct stat st;
DocFetcher::Reason reason = urltopath(cnf, idoc, fn, st);
if (reason != DocFetcher::FetchOk) {
return reason;
}
if (!path_readable(fn)) {
return DocFetcher::FetchNoPerm;
}
// We have no way to know if the file is fully readable without
// trying (local Windows locks), which would take too much time.
return DocFetcher::FetchOther;
}

View File

@ -28,6 +28,7 @@ class FSDocFetcher : public DocFetcher{
/** Calls stat to retrieve file signature data */ /** Calls stat to retrieve file signature data */
virtual bool makesig(RclConfig* cnf,const Rcl::Doc& idoc, std::string& sig); virtual bool makesig(RclConfig* cnf,const Rcl::Doc& idoc, std::string& sig);
virtual DocFetcher::Reason testAccess(RclConfig* cnf, const Rcl::Doc& idoc);
virtual ~FSDocFetcher() {} virtual ~FSDocFetcher() {}
}; };

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2004 J.F.Dockes /* Copyright (C) 2004-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
@ -33,7 +33,7 @@ using std::set;
class RclConfig; class RclConfig;
namespace Rcl { namespace Rcl {
class Doc; class Doc;
} }
class Uncomp; class Uncomp;
@ -51,9 +51,9 @@ public:
FIMissingStore(const string& in); FIMissingStore(const string& in);
virtual ~FIMissingStore() {} virtual ~FIMissingStore() {}
virtual void addMissing(const string& prog, const string& mt) virtual void addMissing(const string& prog, const string& mt)
{ {
m_typesForMissing[prog].insert(mt); m_typesForMissing[prog].insert(mt);
} }
// Get simple progs list string // Get simple progs list string
virtual void getMissingExternal(string& out); virtual void getMissingExternal(string& out);
// Get progs + assoc mtypes description string // Get progs + assoc mtypes description string
@ -87,7 +87,7 @@ public:
* *
*/ */
class FileInterner { class FileInterner {
public: public:
/** Operation modifier flags */ /** Operation modifier flags */
enum Flags {FIF_none, FIF_forPreview, FIF_doUseInputMimetype}; enum Flags {FIF_none, FIF_forPreview, FIF_doUseInputMimetype};
/** Return values for internfile() */ /** Return values for internfile() */
@ -115,7 +115,7 @@ class FileInterner {
* mime type for the uncompressed version. * mime type for the uncompressed version.
*/ */
FileInterner(const string &fn, const struct stat *stp, FileInterner(const string &fn, const struct stat *stp,
RclConfig *cnf, int flags, const string *mtype = 0); RclConfig *cnf, int flags, const string *mtype = 0);
/** /**
* Alternate constructor for the case where the data is in memory. * Alternate constructor for the case where the data is in memory.
@ -138,9 +138,8 @@ class FileInterner {
~FileInterner(); ~FileInterner();
void setMissingStore(FIMissingStore *st) void setMissingStore(FIMissingStore *st) {
{ m_missingdatap = st;
m_missingdatap = st;
} }
/** /**
@ -160,9 +159,9 @@ class FileInterner {
Status internfile(Rcl::Doc& doc, const string &ipath = ""); Status internfile(Rcl::Doc& doc, const string &ipath = "");
/** Extract subdoc defined by ipath in idoc to file. See params for /** Extract subdoc defined by ipath in idoc to file. See params for
idocToFile() */ idocToFile() */
bool interntofile(TempFile& otemp, const string& tofile, bool interntofile(TempFile& otemp, const string& tofile,
const string& ipath, const string& mimetype); const string& ipath, const string& mimetype);
/** Return the file's (top level object) mimetype (useful for /** Return the file's (top level object) mimetype (useful for
* creating the pseudo-doc for container files) * creating the pseudo-doc for container files)
@ -181,17 +180,17 @@ class FileInterner {
const string& get_html() {return m_html;} const string& get_html() {return m_html;}
/** If we happen to be processing an image file and need a temp file, /** If we happen to be processing an image file and need a temp file,
we keep it around to save work for our caller, which can get it here */ we keep it around to save work for our caller, which can get it here */
TempFile get_imgtmp() {return m_imgtmp;} TempFile get_imgtmp() {return m_imgtmp;}
const string& getReason() const const string& getReason() const
{ {
return m_reason; return m_reason;
} }
bool ok() const bool ok() const
{ {
return m_ok; return m_ok;
} }
/** /**
* Get UDI for immediate parent for document. * Get UDI for immediate parent for document.
@ -234,7 +233,7 @@ class FileInterner {
* anything for a top level document. * anything for a top level document.
*/ */
static bool idocToFile(TempFile& temp, const string& tofile, static bool idocToFile(TempFile& temp, const string& tofile,
RclConfig *cnf, const Rcl::Doc& doc, RclConfig *cnf, const Rcl::Doc& doc,
bool uncompress = true); bool uncompress = true);
/** Does file appear to be the compressed version of a document? */ /** Does file appear to be the compressed version of a document? */
@ -248,7 +247,14 @@ class FileInterner {
static bool maybeUncompressToTemp(TempFile& temp, const string& fn, static bool maybeUncompressToTemp(TempFile& temp, const string& fn,
RclConfig *cnf, const Rcl::Doc& doc); RclConfig *cnf, const Rcl::Doc& doc);
private: /** Try to get a top level reason after an operation failed. This
* is just for "simple" issues, like file missing, permissions,
* etc. */
enum ErrorPossibleCause{FetchMissing, FetchPerm, FetchNoBackend,
InternfileOther};
static ErrorPossibleCause tryGetReason(RclConfig *, const Rcl::Doc&);
private:
static const unsigned int MAXHANDLERS = 20; static const unsigned int MAXHANDLERS = 20;
RclConfig *m_cfg; RclConfig *m_cfg;
string m_fn; string m_fn;
@ -287,7 +293,7 @@ class FileInterner {
void init(const string &fn, const struct stat *stp, void init(const string &fn, const struct stat *stp,
RclConfig *cnf, int flags, const string *mtype = 0); RclConfig *cnf, int flags, const string *mtype = 0);
void init(const string &data, RclConfig *cnf, int flags, void init(const string &data, RclConfig *cnf, int flags,
const string& mtype); const string& mtype);
void initcommon(RclConfig *cnf, int flags); void initcommon(RclConfig *cnf, int flags);
bool dijontorcl(Rcl::Doc&); bool dijontorcl(Rcl::Doc&);
@ -298,7 +304,7 @@ class FileInterner {
void checkExternalMissing(const string& msg, const string& mt); void checkExternalMissing(const string& msg, const string& mt);
void processNextDocError(Rcl::Doc &doc); void processNextDocError(Rcl::Doc &doc);
static bool tempFileForMT(TempFile& otemp, RclConfig *cnf, static bool tempFileForMT(TempFile& otemp, RclConfig *cnf,
const std::string& mimetype); const std::string& mimetype);
static bool topdocToFile(TempFile& otemp, const std::string& tofile, static bool topdocToFile(TempFile& otemp, const std::string& tofile,
RclConfig *cnf, const Rcl::Doc& idoc, RclConfig *cnf, const Rcl::Doc& idoc,
bool uncompress); bool uncompress);

View File

@ -62,6 +62,7 @@ void LoadThread::run()
} else { } else {
fdoc.mimetype = interner.getMimetype(); fdoc.mimetype = interner.getMimetype();
mst.getMissingExternal(missing); mst.getMissingExternal(missing);
explain = FileInterner::tryGetReason(&m_config, m_idoc);
status = -1; status = -1;
} }
} catch (CancelExcept) { } catch (CancelExcept) {

View File

@ -25,6 +25,7 @@
#include "pathut.h" #include "pathut.h"
#include "rclutil.h" #include "rclutil.h"
#include "rclconfig.h" #include "rclconfig.h"
#include "internfile.h"
/* /*
* A thread to perform the file reading / format conversion work for preview * A thread to perform the file reading / format conversion work for preview
@ -48,6 +49,7 @@ public:
Rcl::Doc fdoc; Rcl::Doc fdoc;
TempFile tmpimg; TempFile tmpimg;
std::string missing; std::string missing;
FileInterner::ErrorPossibleCause explain{FileInterner::InternfileOther};
private: private:
Rcl::Doc m_idoc; Rcl::Doc m_idoc;

View File

@ -643,7 +643,6 @@ 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) {
progress.close();
QString explain; QString explain;
if (!lthr.missing.empty()) { if (!lthr.missing.empty()) {
explain = QString::fromUtf8("<br>") + explain = QString::fromUtf8("<br>") +
@ -655,13 +654,40 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum)
lthr.fdoc.mimetype.c_str() + explain); lthr.fdoc.mimetype.c_str() + explain);
} else { } else {
if (progress.wasCanceled()) { if (progress.wasCanceled()) {
//QMessageBox::warning(0, "Recoll", tr("Canceled")); QMessageBox::warning(0, "Recoll", tr("Canceled"));
} else { } else {
QMessageBox::warning(0, "Recoll", progress.close();
tr("Error while loading file")); // 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
// readable even if Outlook is running).
QString msg;
switch (lthr.explain) {
case FileInterner::FetchMissing:
msg = tr("Error loading the document: file missing");
break;
case FileInterner::FetchPerm:
msg = tr("Error loading the document: no permission");
break;
case FileInterner::FetchNoBackend:
msg =
tr("Error loading the document: backend not configured");
break;
case FileInterner::InternfileOther:
#ifdef _WIN32
msg = tr("Error loading the document: other handler error");
#else
msg = tr("Error loading the document: "
"other handler error<br>"
"Maybe the application is locking the file ?");
#endif
break;
}
QMessageBox::warning(0, "Recoll", msg);
} }
} }
progress.close();
return false; return false;
} }
// Reset config just in case. // Reset config just in case.
@ -894,7 +920,7 @@ PreviewTextEdit::PreviewTextEdit(QWidget* parent, const char* nm, Preview *pv)
void PreviewTextEdit::onAnchorClicked(const QUrl& url) void PreviewTextEdit::onAnchorClicked(const QUrl& url)
{ {
LOGDEB("PreviewTextEdit::onAnchorClicked: " << qs2utf8s(url.toString()) LOGDEB("PreviewTextEdit::onAnchorClicked: " << qs2utf8s(url.toString())
<< std::endl); << std::endl);
if (prefs.previewActiveLinks && m_preview->m_rclmain) { if (prefs.previewActiveLinks && m_preview->m_rclmain) {
Rcl::Doc doc; Rcl::Doc doc;
doc.url = qs2utf8s(url.toString()).c_str(); doc.url = qs2utf8s(url.toString()).c_str();
@ -1016,4 +1042,3 @@ void PreviewTextEdit::print()
QTextEdit::print(&printer); QTextEdit::print(&printer);
#endif #endif
} }

View File

@ -0,0 +1,129 @@
/* Copyright (C) 2017-2019 J.F.Dockes
*
* License: GPL 2.1
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string>
#include "safesysstat.h"
using namespace std;
#include "log.h"
#include "rclinit.h"
#include "internfile.h"
#include "rclconfig.h"
#include "rcldoc.h"
static string thisprog;
static string usage =
" internfile <filename> [ipath]\n"
" \n\n"
;
static void
Usage(void)
{
cerr << thisprog << ": usage:\n" << usage;
exit(1);
}
static int op_flags;
#define OPT_q 0x1
RclConfig *config;
int main(int argc, char **argv)
{
thisprog = argv[0];
argc--; argv++;
while (argc > 0 && **argv == '-') {
(*argv)++;
if (!(**argv))
/* Cas du "adb - core" */
Usage();
while (**argv)
switch (*(*argv)++) {
default: Usage(); break;
}
argc--; argv++;
}
DebugLog::getdbl()->setloglevel(DEBDEB1);
DebugLog::setfilename("stderr");
if (argc < 1)
Usage();
string fn(*argv++);
argc--;
string ipath;
if (argc >= 1) {
ipath.append(*argv++);
argc--;
}
string reason;
config = recollinit(0, 0, 0, reason);
if (config == 0 || !config->ok()) {
string str = "Configuration problem: ";
str += reason;
fprintf(stderr, "%s\n", str.c_str());
exit(1);
}
struct stat st;
if (stat(fn.c_str(), &st)) {
perror("stat");
exit(1);
}
FileInterner interner(fn, &st, config, 0);
Rcl::Doc doc;
FileInterner::Status status = interner.internfile(doc, ipath);
switch (status) {
case FileInterner::FIDone:
case FileInterner::FIAgain:
break;
case FileInterner::FIError:
default:
fprintf(stderr, "internfile failed\n");
exit(1);
}
cout << "doc.url [[[[" << doc.url <<
"]]]]\n-----------------------------------------------------\n" <<
"doc.ipath [[[[" << doc.ipath <<
"]]]]\n-----------------------------------------------------\n" <<
"doc.mimetype [[[[" << doc.mimetype <<
"]]]]\n-----------------------------------------------------\n" <<
"doc.fmtime [[[[" << doc.fmtime <<
"]]]]\n-----------------------------------------------------\n" <<
"doc.dmtime [[[[" << doc.dmtime <<
"]]]]\n-----------------------------------------------------\n" <<
"doc.origcharset [[[[" << doc.origcharset <<
"]]]]\n-----------------------------------------------------\n" <<
"doc.meta[title] [[[[" << doc.meta["title"] <<
"]]]]\n-----------------------------------------------------\n" <<
"doc.meta[keywords] [[[[" << doc.meta["keywords"] <<
"]]]]\n-----------------------------------------------------\n" <<
"doc.meta[abstract] [[[[" << doc.meta["abstract"] <<
"]]]]\n-----------------------------------------------------\n" <<
"doc.text [[[[" << doc.text << "]]]]\n";
}