Arrange so we can now open the parent of a document (e.g. chm file instead of temp copy of html page inside chm), even when the parent is itself embedded in an archive

This commit is contained in:
Jean-Francois Dockes 2012-10-12 16:54:52 +02:00
parent 382ae1f718
commit 5add2e2384
13 changed files with 222 additions and 59 deletions

View File

@ -215,7 +215,7 @@
<application>Python</application> <application>Python</application>
programming interface</link>, a <link linkend="rcl.search.kio"> programming interface</link>, a <link linkend="rcl.search.kio">
<application>KDE</application> KIO slave module</link>, and <application>KDE</application> KIO slave module</link>, and
a <application>Ubuntu Unity Lens</application> module. a <ulink url="http://bitbucket.org/medoc/recoll/wiki/UnityLens">Ubuntu Unity Lens</ulink> module.
</para> </para>
</sect1> </sect1>
@ -2825,8 +2825,29 @@ dir:recoll dir:src -dir:utils -dir:common
<title>Desktop integration</title> <title>Desktop integration</title>
<para>Being independant of the desktop type has its drawbacks: &RCL; <para>Being independant of the desktop type has its drawbacks: &RCL;
desktop integration is minimal. Here follow a few things that may desktop integration is minimal. However there are a few tools
help.</para> available:
<itemizedlist>
<listitem>
<para>The <application>KDE</application> KIO Slave was
described in a <link linkend="rcl.search.kio">previous
section</link>.</para>
</listitem>
<listitem>
<para>If you use a recent version of Ubuntu Linux, you may
find the <ulink
url="http://bitbucket.org/medoc/recoll/wiki/UnityLens">Ubuntu Unity
Lens</ulink> module useful.</para>
</listitem>
<listitem>
<para>There is also an independantly developed
<ulink
url="http://kde-apps.org/content/show.php/recollrunner?content=128203">
Krunner plugin</ulink>.</para>
</listitem>
</itemizedlist>
<para>Here follow a few other things that may help.</para>
<sect2 id="rcl.search.shortcut"> <sect2 id="rcl.search.shortcut">
<title>Hotkeying recoll</title> <title>Hotkeying recoll</title>
@ -2844,6 +2865,7 @@ dir:recoll dir:src -dir:utils -dir:common
<sect2 id="rcl.kicker-applet"> <sect2 id="rcl.kicker-applet">
<title>The KDE Kicker Recoll applet</title> <title>The KDE Kicker Recoll applet</title>
<para>This is probably obsolete now. Anyway:</para>
<para>The &RCL; source tree contains the source code to the <para>The &RCL; source tree contains the source code to the
<application>recoll_applet</application>, a small application derived <application>recoll_applet</application>, a small application derived
from the <application>find_applet</application>. This can be used to from the <application>find_applet</application>. This can be used to

View File

@ -190,7 +190,6 @@ class rclCHM:
def __init__(self, em): def __init__(self, em):
self.contents = [] self.contents = []
self.chm = chm.CHMFile() self.chm = chm.CHMFile()
self.currentindex = 0
self.em = em self.em = em
if rclchm_catenate: if rclchm_catenate:
self.em.setmimetype("text/plain") self.em.setmimetype("text/plain")
@ -240,7 +239,7 @@ class rclCHM:
"""Open the chm file and build the contents list by extracting and """Open the chm file and build the contents list by extracting and
parsing the Topics object""" parsing the Topics object"""
self.currentindex = 0 self.currentindex = -1
self.contents = [] self.contents = []
filename = params["filename:"] filename = params["filename:"]
@ -248,8 +247,6 @@ class rclCHM:
self.em.rclog("LoadCHM failed") self.em.rclog("LoadCHM failed")
return False return False
self.sfn = os.path.basename(filename)
#self.em.rclog("home [%s] topics [%s] title [%s]" % #self.em.rclog("home [%s] topics [%s] title [%s]" %
# (self.chm.home, self.chm.topics, self.chm.title)) # (self.chm.home, self.chm.topics, self.chm.title))
@ -293,6 +290,16 @@ class rclCHM:
else: else:
return (False, "", "", rclexecm.RclExecM.eofnow) return (False, "", "", rclexecm.RclExecM.eofnow)
if self.currentindex == -1:
# Return "self" doc
self.currentindex = 0
self.em.setmimetype('text/plain')
if len(self.contents) == 0:
eof = rclexecm.RclExecM.eofnext
else:
eof = rclexecm.RclExecM.noteof
return (True, "", "", eof)
if self.currentindex >= len(self.contents): if self.currentindex >= len(self.contents):
return (False, "", "", rclexecm.RclExecM.eofnow) return (False, "", "", rclexecm.RclExecM.eofnow)
else: else:

View File

@ -48,7 +48,7 @@ class rclEPUB:
def openfile(self, params): def openfile(self, params):
"""Open the EPUB file, create a contents array""" """Open the EPUB file, create a contents array"""
self.currentindex = 0 self.currentindex = -1
self.contents = [] self.contents = []
try: try:
self.book = epub.open(params["filename:"]) self.book = epub.open(params["filename:"])
@ -65,6 +65,17 @@ class rclEPUB:
return self.extractone(params["ipath:"]) return self.extractone(params["ipath:"])
def getnext(self, params): def getnext(self, params):
if self.currentindex == -1:
# Return "self" doc
self.currentindex = 0
self.em.setmimetype('text/plain')
if len(self.contents) == 0:
eof = rclexecm.RclExecM.eofnext
else:
eof = rclexecm.RclExecM.noteof
return (True, "", "", eof)
if self.currentindex >= len(self.contents): if self.currentindex >= len(self.contents):
return (False, "", "", rclexecm.RclExecM.eofnow) return (False, "", "", rclexecm.RclExecM.eofnow)
else: else:

View File

@ -60,7 +60,7 @@ class IcalExtractor:
self.em.rclog("Openfile: open: %s" % str(e)) self.em.rclog("Openfile: open: %s" % str(e))
return False return False
self.currentindex = 0 self.currentindex = -1
if usemodule == 'internal': if usemodule == 'internal':
self.contents = ICalSimpleSplitter().splitcalendar(calstr) self.contents = ICalSimpleSplitter().splitcalendar(calstr)
@ -96,6 +96,17 @@ class IcalExtractor:
return self.extractone(index) return self.extractone(index)
def getnext(self, params): def getnext(self, params):
if self.currentindex == -1:
# Return "self" doc
self.currentindex = 0
self.em.setmimetype('text/plain')
if len(self.contents) == 0:
eof = rclexecm.RclExecM.eofnext
else:
eof = rclexecm.RclExecM.noteof
return (True, "", "", eof)
if self.currentindex >= len(self.contents): if self.currentindex >= len(self.contents):
self.em.rclog("getnext: EOF hit") self.em.rclog("getnext: EOF hit")
return (False, "", "", rclexecm.RclExecM.eofnow) return (False, "", "", rclexecm.RclExecM.eofnow)

View File

@ -73,7 +73,7 @@ class InfoExtractor:
sys.exit(1); sys.exit(1);
self.currentindex = 0 self.currentindex = -1
self.contents = InfoSimpleSplitter().splitinfo(self.file, infostream) self.contents = InfoSimpleSplitter().splitinfo(self.file, infostream)
@ -90,6 +90,17 @@ class InfoExtractor:
# Extract next in list # Extract next in list
def getnext(self, params): def getnext(self, params):
if self.currentindex == -1:
# Return "self" doc
self.currentindex = 0
self.em.setmimetype('text/plain')
if len(self.contents) == 0:
eof = rclexecm.RclExecM.eofnext
else:
eof = rclexecm.RclExecM.noteof
return (True, "", "", eof)
if self.currentindex >= len(self.contents): if self.currentindex >= len(self.contents):
self.em.rclog("getnext: EOF hit") self.em.rclog("getnext: EOF hit")
return (False, "", "", rclexecm.RclExecM.eofnow) return (False, "", "", rclexecm.RclExecM.eofnow)

View File

@ -73,7 +73,7 @@ class RarExtractor:
###### File type handler api, used by rclexecm ----------> ###### File type handler api, used by rclexecm ---------->
def openfile(self, params): def openfile(self, params):
self.currentindex = 0 self.currentindex = -1
try: try:
self.rar = RarFile(params["filename:"]) self.rar = RarFile(params["filename:"])
return True return True
@ -93,6 +93,17 @@ class RarExtractor:
return (ok, data, ipath, eof) return (ok, data, ipath, eof)
def getnext(self, params): def getnext(self, params):
if self.currentindex == -1:
# Return "self" doc
self.currentindex = 0
self.em.setmimetype('text/plain')
if len(self.rar.namelist()) == 0:
eof = rclexecm.RclExecM.eofnext
else:
eof = rclexecm.RclExecM.noteof
return (True, "", "", eof)
if self.currentindex >= len(self.rar.namelist()): if self.currentindex >= len(self.rar.namelist()):
#self.em.rclog("getnext: EOF hit") #self.em.rclog("getnext: EOF hit")
return (False, "", "", rclexecm.RclExecM.eofnow) return (False, "", "", rclexecm.RclExecM.eofnow)

View File

@ -43,7 +43,7 @@ class TarExtractor:
return (ok, docdata, ipath, iseof) return (ok, docdata, ipath, iseof)
def openfile(self, params): def openfile(self, params):
self.currentindex = 0 self.currentindex = -1
try: try:
self.tar = open(name=params["filename:"],mode='r') self.tar = open(name=params["filename:"],mode='r')
self.namen = [ y.name for y in filter(lambda z:z.isfile(),self.tar.getmembers())] self.namen = [ y.name for y in filter(lambda z:z.isfile(),self.tar.getmembers())]
@ -63,6 +63,17 @@ class TarExtractor:
return (ok, data, ipath, eof) return (ok, data, ipath, eof)
def getnext(self, params): def getnext(self, params):
if self.currentindex == -1:
# Return "self" doc
self.currentindex = 0
self.em.setmimetype('text/plain')
if len(self.namen) == 0:
eof = rclexecm.RclExecM.eofnext
else:
eof = rclexecm.RclExecM.noteof
return (True, "", "", eof)
if self.currentindex >= len(self.namen): if self.currentindex >= len(self.namen):
self.namen=[] self.namen=[]
return (False, "", "", rclexecm.RclExecM.eofnow) return (False, "", "", rclexecm.RclExecM.eofnow)

View File

@ -22,7 +22,7 @@ class WarExtractor:
###### File type handler api, used by rclexecm ----------> ###### File type handler api, used by rclexecm ---------->
def openfile(self, params): def openfile(self, params):
self.currentindex = 0 self.currentindex = -1
try: try:
self.tar = tarfile.open(params["filename:"]) self.tar = tarfile.open(params["filename:"])
return True return True
@ -40,6 +40,11 @@ class WarExtractor:
return self.extractone(tarinfo) return self.extractone(tarinfo)
def getnext(self, params): def getnext(self, params):
if self.currentindex == -1:
# Return "self" doc
self.currentindex = 0
return (True, "", "", rclexecm.RclExecM.noteof)
tarinfo = self.tar.next() tarinfo = self.tar.next()
if tarinfo is None: if tarinfo is None:
#self.em.rclog("getnext: EOF hit") #self.em.rclog("getnext: EOF hit")

View File

@ -67,7 +67,7 @@ class ZipExtractor:
###### File type handler api, used by rclexecm ----------> ###### File type handler api, used by rclexecm ---------->
def openfile(self, params): def openfile(self, params):
self.currentindex = 0 self.currentindex = -1
try: try:
self.zip = ZipFile(params["filename:"]) self.zip = ZipFile(params["filename:"])
return True return True
@ -87,6 +87,16 @@ class ZipExtractor:
return (ok, data, ipath, eof) return (ok, data, ipath, eof)
def getnext(self, params): def getnext(self, params):
if self.currentindex == -1:
# Return "self" doc
self.currentindex = 0
self.em.setmimetype('text/plain')
if len(self.zip.namelist()) == 0:
eof = rclexecm.RclExecM.eofnext
else:
eof = rclexecm.RclExecM.noteof
return (True, "", "", eof)
if self.currentindex >= len(self.zip.namelist()): if self.currentindex >= len(self.zip.namelist()):
#self.em.rclog("getnext: EOF hit") #self.em.rclog("getnext: EOF hit")
return (False, "", "", rclexecm.RclExecM.eofnow) return (False, "", "", rclexecm.RclExecM.eofnow)

View File

@ -126,6 +126,16 @@ bool FileInterner::getEnclosing(const string &url, const string &ipath,
return true; return true;
} }
string FileInterner::getLastIpathElt(const string& ipath)
{
string::size_type sep;
if ((sep = ipath.find_last_of(cstr_isep)) != string::npos) {
return ipath.substr(sep + 1);
} else {
return ipath;
}
}
// Uncompress input file into a temporary one, by executing the appropriate // Uncompress input file into a temporary one, by executing the appropriate
// script. // script.
static bool uncompressfile(RclConfig *conf, const string& ifn, static bool uncompressfile(RclConfig *conf, const string& ifn,

View File

@ -83,6 +83,9 @@ class FileInterner {
static bool getEnclosing(const string &url, const string &ipath, static bool getEnclosing(const string &url, const string &ipath,
string &eurl, string &eipath, string& udi); string &eurl, string &eipath, string& udi);
/** Return last element in ipath, like basename */
static std::string getLastIpathElt(const std::string& ipath);
/** Constructors take the initial step to preprocess the data object and /** Constructors take the initial step to preprocess the data object and
* create the top filter */ * create the top filter */

View File

@ -1501,13 +1501,17 @@ static bool lookForHtmlBrowser(string &exefile)
void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term) void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term)
{ {
LOGDEB(("RclMain::startNativeViewer: page %d\n", pagenum));
// Look for appropriate viewer
string apptag; string apptag;
doc.getmeta(Rcl::Doc::keyapptg, &apptag); doc.getmeta(Rcl::Doc::keyapptg, &apptag);
LOGDEB(("RclMain::startNativeViewer: mtype [%s] apptag [%s] page %d "
"term [%s] url [%s] ipath [%s]\n",
doc.mimetype.c_str(), apptag.c_str(), pagenum,
(const char *)(term.toUtf8()), doc.url.c_str(), doc.ipath.c_str()
));
// Look for appropriate viewer
string cmdplusattr = theconfig->getMimeViewerDef(doc.mimetype, apptag, string cmdplusattr = theconfig->getMimeViewerDef(doc.mimetype, apptag,
prefs.useDesktopOpen); prefs.useDesktopOpen);
if (cmdplusattr.empty()) { if (cmdplusattr.empty()) {
QMessageBox::warning(0, "Recoll", QMessageBox::warning(0, "Recoll",
tr("No external viewer configured for mime type [") tr("No external viewer configured for mime type [")
@ -1515,25 +1519,12 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term)
return; return;
} }
if (pagenum == -1) { // Separate command string and viewer attributes (if any)
pagenum = 1; ConfSimple viewerattrs;
string lterm;
if (m_source.isNotNull())
pagenum = m_source->getFirstMatchPage(doc, lterm);
if (pagenum == -1)
pagenum = 1;
else
term = QString::fromUtf8(lterm.c_str());
}
char cpagenum[20];
sprintf(cpagenum, "%d", pagenum);
// Extract possible viewer attributes
ConfSimple attrs;
string cmd; string cmd;
theconfig->valueSplitAttributes(cmdplusattr, cmd, attrs); theconfig->valueSplitAttributes(cmdplusattr, cmd, viewerattrs);
bool ignoreipath = false; bool ignoreipath = false;
if (attrs.get("ignoreipath", cmdplusattr)) if (viewerattrs.get("ignoreipath", cmdplusattr))
ignoreipath = stringToBool(cmdplusattr); ignoreipath = stringToBool(cmdplusattr);
// Split the command line // Split the command line
@ -1541,7 +1532,7 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term)
if (!stringToStrings(cmd, lcmd)) { if (!stringToStrings(cmd, lcmd)) {
QMessageBox::warning(0, "Recoll", QMessageBox::warning(0, "Recoll",
tr("Bad viewer command line for %1: [%2]\n" tr("Bad viewer command line for %1: [%2]\n"
"Please check the mimeconf file") "Please check the mimeview file")
.arg(QString::fromAscii(doc.mimetype.c_str())) .arg(QString::fromAscii(doc.mimetype.c_str()))
.arg(QString::fromLocal8Bit(cmd.c_str()))); .arg(QString::fromLocal8Bit(cmd.c_str())));
return; return;
@ -1549,23 +1540,23 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term)
// Look for the command to execute in the exec path and the filters // Look for the command to execute in the exec path and the filters
// directory // directory
string cmdpath; string execpath;
if (!ExecCmd::which(lcmd.front(), cmdpath)) { if (!ExecCmd::which(lcmd.front(), execpath)) {
cmdpath = theconfig->findFilter(lcmd.front()); execpath = theconfig->findFilter(lcmd.front());
// findFilter returns its input param if the filter is not in // findFilter returns its input param if the filter is not in
// the normal places. As we already looked in the path, we // the normal places. As we already looked in the path, we
// have no use for a simple command name here (as opposed to // have no use for a simple command name here (as opposed to
// mimehandler which will just let execvp do its thing). Erase // mimehandler which will just let execvp do its thing). Erase
// cmdpath so that the user dialog will be started further // execpath so that the user dialog will be started further
// down. // down.
if (!cmdpath.compare(lcmd.front())) if (!execpath.compare(lcmd.front()))
cmdpath.erase(); execpath.erase();
// Specialcase text/html because of the help browser need // Specialcase text/html because of the help browser need
if (cmdpath.empty() && !doc.mimetype.compare("text/html")) { if (execpath.empty() && !doc.mimetype.compare("text/html")) {
if (lookForHtmlBrowser(cmdpath)) { if (lookForHtmlBrowser(execpath)) {
lcmd.clear(); lcmd.clear();
lcmd.push_back(cmdpath); lcmd.push_back(execpath);
lcmd.push_back("%u"); lcmd.push_back("%u");
} }
} }
@ -1573,7 +1564,7 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term)
// Command not found: start the user dialog to help find another one: // Command not found: start the user dialog to help find another one:
if (cmdpath.empty()) { if (execpath.empty()) {
QString mt = QString::fromAscii(doc.mimetype.c_str()); QString mt = QString::fromAscii(doc.mimetype.c_str());
QString message = tr("The viewer specified in mimeview for %1: %2" QString message = tr("The viewer specified in mimeview for %1: %2"
" is not found.\nDo you want to start the " " is not found.\nDo you want to start the "
@ -1594,22 +1585,68 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term)
// new command. // new command.
return; return;
} }
// Get rid of the command name. lcmd is now argv[1...n]
lcmd.erase(lcmd.begin());
// We may need a temp file, or not, depending on the command
// arguments and the fact that this is a subdoc or not. // Process the command arguments to determine if we need to create
bool wantsipath = (cmd.find("%i") != string::npos) || ignoreipath; // a temporary file.
// If the command has a %i parameter it will manage the
// un-embedding. Else if ipath is not empty, we need a temp file.
// This can be overridden with the "ignoreipath" attribute
bool groksipath = (cmd.find("%i") != string::npos) || ignoreipath;
// wantsfile: do we actually need a local file ? The only other
// case here is an url %u (ie: for web history).
bool wantsfile = cmd.find("%f") != string::npos; bool wantsfile = cmd.find("%f") != string::npos;
bool istempfile = false; bool wantsparentfile = cmd.find("%F") != string::npos;
string fn = fileurltolocalpath(doc.url);
string orgfn = fn; if (wantsfile && wantsparentfile) {
QMessageBox::warning(0, "Recoll",
tr("Viewer command line for %1 specifies both "
"file and parent file value: unsupported")
.arg(QString::fromAscii(doc.mimetype.c_str())));
return;
}
string url = doc.url; string url = doc.url;
string fn = fileurltolocalpath(doc.url);
Rcl::Doc pdoc;
if (wantsparentfile) {
// We want the path for the parent document. For example to
// open the chm file, not the internal page. Note that we just
// override the other file name in this case.
if (m_source.isNull() || !m_source->getEnclosing(doc, pdoc)) {
QMessageBox::warning(0, "Recoll",
tr("Cannot find parent document"));
return;
}
// Override fn with the parent's :
fn = fileurltolocalpath(pdoc.url);
// If the parent document has an ipath too, we need to create
// a temp file even if the command takes an ipath
// parameter. We have no viewer which could handle a double
// embedding. Will have to change if such a one appears.
if (!pdoc.ipath.empty()) {
groksipath = false;
}
}
bool istempfile = false;
LOGDEB(("RclMain::startNV: groksipath %d wantsf %d wantsparentf %d\n",
groksipath, wantsfile, wantsparentfile));
// If the command wants a file but this is not a file url, or // If the command wants a file but this is not a file url, or
// there is an ipath that it won't understand, we need a temp file: // there is an ipath that it won't understand, we need a temp file:
theconfig->setKeyDir(path_getfather(fn)); theconfig->setKeyDir(path_getfather(fn));
if ((wantsfile && fn.empty()) || (!wantsipath && !doc.ipath.empty())) { if (((wantsfile || wantsparentfile) && fn.empty()) ||
(!groksipath && !doc.ipath.empty())) {
TempFile temp; TempFile temp;
if (!FileInterner::idocToFile(temp, string(), theconfig, doc)) { Rcl::Doc& thedoc = wantsparentfile ? pdoc : doc;
if (!FileInterner::idocToFile(temp, string(), theconfig, thedoc)) {
QMessageBox::warning(0, "Recoll", QMessageBox::warning(0, "Recoll",
tr("Cannot extract document or create " tr("Cannot extract document or create "
"temporary file")); "temporary file"));
@ -1647,8 +1684,21 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term)
} }
} }
// Get rid of the command name. lcmd is now argv[1...n] // If we are not called with a page number (which would happen for a call
lcmd.erase(lcmd.begin()); // from the snippets window), see if we can compute a page number anyway.
if (pagenum == -1) {
pagenum = 1;
string lterm;
if (m_source.isNotNull())
pagenum = m_source->getFirstMatchPage(doc, lterm);
if (pagenum == -1)
pagenum = 1;
else // We get the match term used to compute the page
term = QString::fromUtf8(lterm.c_str());
}
char cpagenum[20];
sprintf(cpagenum, "%d", pagenum);
// Substitute %xx inside arguments // Substitute %xx inside arguments
string efftime; string efftime;
@ -1662,8 +1712,8 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term)
map<string, string> subs; map<string, string> subs;
subs["D"] = efftime; subs["D"] = efftime;
subs["f"] = fn; subs["f"] = fn;
subs["F"] = orgfn; subs["F"] = fn;
subs["i"] = doc.ipath; subs["i"] = FileInterner::getLastIpathElt(doc.ipath);
subs["M"] = doc.mimetype; subs["M"] = doc.mimetype;
subs["p"] = cpagenum; subs["p"] = cpagenum;
subs["s"] = (const char*)term.toLocal8Bit(); subs["s"] = (const char*)term.toLocal8Bit();
@ -1705,7 +1755,7 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term)
// We keep pushing back and never deleting. This can't be good... // We keep pushing back and never deleting. This can't be good...
ExecCmd *ecmd = new ExecCmd; ExecCmd *ecmd = new ExecCmd;
m_viewers.push_back(ecmd); m_viewers.push_back(ecmd);
ecmd->startExec(cmdpath, lcmd, false, false); ecmd->startExec(execpath, lcmd, false, false);
} }
void RclMain::startManual() void RclMain::startManual()

View File

@ -1,7 +1,8 @@
1 results 1 results
text/html [file:///home/dockes/projets/fulltext/testrecoll/chm/Nokia_Nseries_Help_jpn.chm] [デジタル著作権管理(DRM)] 2948 bytes text/html [file:///home/dockes/projets/fulltext/testrecoll/chm/Nokia_Nseries_Help_jpn.chm] [デジタル著作権管理(DRM)] 2948 bytes
1 results 2 results
text/html [file:///home/dockes/projets/fulltext/testrecoll/chm/soundrec.chm] [Superposer (mixer) des fichiers son] 2279 bytes text/html [file:///home/dockes/projets/fulltext/testrecoll/chm/soundrec.chm] [Superposer (mixer) des fichiers son] 2279 bytes
text/html [file:///home/dockes/projets/fulltext/testrecoll/chm/zippedsoundrec.zip] [Superposer (mixer) des fichiers son] 2279 bytes
1 results 1 results
text/html [file:///home/dockes/projets/fulltext/testrecoll/chm/Django-1.1a1-r9905.chm] [User authentication in Django] 115179 bytes text/html [file:///home/dockes/projets/fulltext/testrecoll/chm/Django-1.1a1-r9905.chm] [User authentication in Django] 115179 bytes
1 results 1 results