diff --git a/.hgignore b/.hgignore index 10417b07..a5e19eea 100644 --- a/.hgignore +++ b/.hgignore @@ -2,117 +2,71 @@ syntax: glob *.dep *.dep.stamp *.o +.libs +*.la +stamp-h1 +.dirstamp +libtool +.deps +*.lo *~ \#* +*.obj +*.sdf +*.tlog +*.lib +*.idb +*.log +*.pdb +.vs +*.exe +*.ilk ptrans -src/Makefile -src/autom4te.cache -src/bincimapmime/alldeps -src/common/alldeps -src/common/autoconfig.h -src/common/rclversion.h +src/aclocal.m4 +src/compile +src/config.guess +src/config.sub +src/depcomp +src/install-sh +src/Makefile.in +src/ltmain.sh +src/m4/libtool.m4 +src/m4/ltoptions.m4 +src/m4/ltsugar.m4 +src/m4/ltversion.m4 +src/m4/lt~obsolete.m4 +src/missing src/config.log src/config.status src/configure -src/desktop/unity-lens-recoll/Makefile -src/desktop/unity-lens-recoll/autom4te.cache -src/desktop/unity-lens-recoll/bin/unity-recoll-daemon -src/desktop/unity-lens-recoll/config.log -src/desktop/unity-lens-recoll/config.status -src/desktop/unity-lens-recoll/data/recoll.lens -src/desktop/unity-lens-recoll/data/unity-lens-recoll.service -src/doc/user/HTML.manifest -src/doc/user/RCL.INDEXING.CONFIG.html -src/doc/user/RCL.INDEXING.EXTATTR.html -src/doc/user/RCL.INDEXING.EXTTAGS.html -src/doc/user/RCL.INDEXING.MONITOR.html -src/doc/user/RCL.INDEXING.PERIODIC.html -src/doc/user/RCL.INDEXING.STORAGE.html -src/doc/user/RCL.INDEXING.WEBQUEUE.html -src/doc/user/RCL.INDEXING.html -src/doc/user/RCL.INSTALL.BUILDING.html -src/doc/user/RCL.INSTALL.CONFIG.html -src/doc/user/RCL.INSTALL.EXTERNAL.html -src/doc/user/RCL.INSTALL.html -src/doc/user/RCL.INTRODUCTION.RECOLL.html -src/doc/user/RCL.INTRODUCTION.SEARCH.html -src/doc/user/RCL.INTRODUCTION.html -src/doc/user/RCL.KICKER-APPLET.html -src/doc/user/RCL.PROGRAM.API.html -src/doc/user/RCL.PROGRAM.FIELDS.html -src/doc/user/RCL.PROGRAM.html -src/doc/user/RCL.SEARCH.ANCHORWILD.html -src/doc/user/RCL.SEARCH.CASEDIAC.html -src/doc/user/RCL.SEARCH.COMMANDLINE.html -src/doc/user/RCL.SEARCH.COMPLEX.html -src/doc/user/RCL.SEARCH.CUSTOM.html -src/doc/user/RCL.SEARCH.DESKTOP.html -src/doc/user/RCL.SEARCH.HISTORY.html -src/doc/user/RCL.SEARCH.KIO.html -src/doc/user/RCL.SEARCH.LANG.html -src/doc/user/RCL.SEARCH.MULTIDB.html -src/doc/user/RCL.SEARCH.PREVIEW.html -src/doc/user/RCL.SEARCH.PTRANS.html -src/doc/user/RCL.SEARCH.RESLIST.html -src/doc/user/RCL.SEARCH.SORT.html -src/doc/user/RCL.SEARCH.TERMEXPLORER.html -src/doc/user/RCL.SEARCH.TIPS.html -src/doc/user/RCL.SEARCH.WILDCARDS.html -src/doc/user/RCL.SEARCH.html -src/doc/user/RCL.SEARCHKCL.html -src/doc/user/RCL.SEARCHKIO.SEARCHABLEDOCS.html -src/doc/user/RCL.SEARCHKIO.html -src/doc/user/index.html -src/doc/user/usermanual-xml.html -src/doc/user/usermanual.aux -src/doc/user/usermanual.html -src/doc/user/usermanual.html-text -src/doc/user/usermanual.log -src/doc/user/usermanual.out +src/Makefile +src/autom4te.cache +src/common/autoconfig.h +src/common/rclversion.h +src/doc/user/webhelp/docs/* src/doc/user/usermanual.pdf -src/doc/user/usermanual.tex-pdf -src/doc/user/usermanual.tex-pdf-tmp -src/doc/user/usermanual.txt src/filters/rclexecm.pyc src/filters/rcllatinclass.pyc -src/index/alldeps -src/index/alldeps.stamp -src/index/recollindex -src/internfile/alldeps +src/recollindex src/kde/kioslave/kio_recoll/builddir -src/lib/Makefile -src/lib/alldeps -src/lib/librecoll.a -src/lib/librecoll.so* -src/lib/mkMake -src/mk/localdefs -src/mk/sysconf +src/python/recoll/Makefile src/python/recoll/build src/python/recoll/recoll/__init__.pyc src/python/recoll/setup.py src/python/samples/recollgui/rclmain.py src/python/samples/recollgui/rclmain.pyc +src/recollq +src/xadump src/qtgui/.moc/* src/qtgui/.obj/* src/qtgui/.ui/* src/qtgui/Makefile src/qtgui/qrc_recoll.cpp src/qtgui/recoll -src/qtgui/recoll.app src/qtgui/recoll.pro -src/query/alldeps -src/query/location.hh -src/query/position.hh -src/query/recollq -src/query/stack.hh -src/query/wasaparse.output -src/query/wasaparse.tab.cpp -src/query/wasaparse.tab.h -src/query/xadump -src/recollinstall +src/qtgui/recoll.app src/sampleconf/rclmon.sh src/sampleconf/recoll.conf -src/utils/alldeps tests/casediac/aspdict.en.rws tests/casediac/idxstatus.txt tests/casediac/index.pid @@ -137,3 +91,7 @@ tests/xattr/mimeview website/usermanual/* website/idxthreads/forkingRecoll.html website/idxthreads/xapDocCopyCrash.html +website/pages/recoll-mingw.html +website/pages/recoll-webui-install-wsgi.html +website/pages/recoll-windows.html +website/pages/recoll-windows-faq.html diff --git a/packaging/debian/buildppa.sh b/packaging/debian/buildppa.sh index 521a3dc2..3c9252c3 100644 --- a/packaging/debian/buildppa.sh +++ b/packaging/debian/buildppa.sh @@ -4,7 +4,7 @@ # For the kio: (and kdesdk?) # sudo apt-get install pkg-kde-tools cdbs -RCLVERS=1.21.0 +RCLVERS=1.22.4 LENSVERS=1.19.10.3543 SCOPEVERS=1.20.2.4 PPAVERS=1 @@ -19,7 +19,7 @@ case $RCLVERS in 1.14*) PPANAME=recoll-ppa;; *) PPANAME=recoll15-ppa;; esac -PPANAME=recollexp-ppa +#PPANAME=recollexp-ppa echo "PPA: $PPANAME. Type CR if Ok, else ^C" read rep @@ -38,10 +38,12 @@ check_recoll_orig() fi } -####### QT4 +# Note: recoll 1.22+ builds on precise fail. precise stays at 1.21 + +####### QT debdir=debian # Note: no new releases for lucid: no webkit. Or use old debianrclqt4 dir. -#series="precise trusty utopic vivid" +series="trusty xenial yakkety" series= if test "X$series" != X ; then @@ -70,8 +72,8 @@ for series in $series ; do done ### KIO -series="precise trusty utopic vivid" -series=vivid +series="trusty xenial yakkety" +series="xenial yakkety" debdir=debiankio topdir=kio-recoll-${RCLVERS} @@ -89,18 +91,23 @@ if test "X$series" != X ; then cd .. fi fi -for series in $series ; do +for svers in $series ; do rm -rf $topdir/debian cp -rp ${debdir}/ $topdir/debian || exit 1 - sed -e s/SERIES/$series/g \ + sed -e s/SERIES/$svers/g \ -e s/PPAVERS/${PPAVERS}/g \ < ${debdir}/changelog > $topdir/debian/changelog ; - + if test $svers = "trusty" ; then + mv -f $topdir/debian/control-4 $topdir/debian/control + mv -f $topdir/debian/rules-4 $topdir/debian/rules + else + rm -f $topdir/debian/control-4 $topdir/debian/rules-4 + fi (cd $topdir;debuild -S -sa) || exit 1 - dput $PPANAME kio-recoll_${RCLVERS}-0~ppa${PPAVERS}~${series}1_source.changes + dput $PPANAME kio-recoll_${RCLVERS}-0~ppa${PPAVERS}~${svers}1_source.changes done @@ -140,7 +147,7 @@ for series in $series ; do done ### Unity Scope -series="trusty utopic vivid" +series="trusty xenial yakkety" series= debdir=debianunityscope @@ -155,9 +162,14 @@ if test "X$series" != X ; then mv unity-scope-recoll-${SCOPEVERS}.tar.gz \ unity-scope-recoll_${SCOPEVERS}.orig.tar.gz else - fatal "Can find neither " \ - "unity-scope-recoll_${SCOPEVERS}.orig.tar.gz nor " \ - "unity-scope-recoll-${SCOPEVERS}.tar.gz" + if test -f $RCLDOWNLOAD/unity-scope-recoll-${SCOPEVERS}.tar.gz;then + cp -p $RCLDOWNLOAD/unity-scope-recoll-${SCOPEVERS}.tar.gz \ + unity-scope-recoll_${SCOPEVERS}.orig.tar.gz || fatal copy + else + fatal "Can find neither " \ + "unity-scope-recoll_${SCOPEVERS}.orig.tar.gz nor " \ + "$RCLDOWNLOAD/unity-scope-recoll-${SCOPEVERS}.tar.gz" + fi fi fi test -d $topdir || tar xvzf unity-scope-recoll_${SCOPEVERS}.orig.tar.gz \ diff --git a/packaging/debian/debian/changelog b/packaging/debian/debian/changelog index d76e1075..32f27329 100644 --- a/packaging/debian/debian/changelog +++ b/packaging/debian/debian/changelog @@ -1,3 +1,63 @@ +recoll (1.22.4-1~ppaPPAVERS~SERIES1) SERIES; urgency=low + + * Fix advanced search 'start search' not doing anything with qt5 + + -- Jean-Francois Dockes Tue, 25 Nov 2016 11:38:00 +0100 + +recoll (1.22.3-1~ppaPPAVERS~SERIES1) SERIES; urgency=low + + * + + -- Jean-Francois Dockes Tue, 21 Jun 2016 15:11:00 +0200 + +recoll (1.22.2-1~ppaPPAVERS~SERIES1) SERIES; urgency=low + + * Packaging fixes. + + -- Jean-Francois Dockes Thu, 16 Jun 2016 09:11:00 +0200 + +recoll (1.22.1-1~ppaPPAVERS~SERIES1) SERIES; urgency=low + + * Fixed two GUI crashes + * Small other fixes + + -- Jean-Francois Dockes Tue, 14 Jun 2016 17:31:00 +0200 + +recoll (1.22.0-1~ppaPPAVERS~SERIES1) SERIES; urgency=low + + * New build based on autotools + * Synonyms + * Windows port + + -- Jean-Francois Dockes Tue, 05 Apr 2016 16:01:00 +0200 + +recoll (1.21.5-1~ppaPPAVERS~SERIES1) SERIES; urgency=low + + * Fix query language parser for multiple mime or rclcat entries + + -- Jean-Francois Dockes Fri, 29 Jan 2016 14:48:00 +0200 + +recoll (1.21.4-1~ppaPPAVERS~SERIES1) SERIES; urgency=low + + * Show confirmation dialog when opening temp files + small bug fixes + + -- Jean-Francois Dockes Tue, 12 Jan 2016 16:19:00 +0200 + +recoll (1.21.3-1~ppaPPAVERS~SERIES1) SERIES; urgency=low + + * Fix webcache size being capped at 1 GB + + -- Jean-Francois Dockes Sat, 31 Oct 2015 09:26:00 +0200 + +recoll (1.21.2-1~ppaPPAVERS~SERIES1) SERIES; urgency=low + + * New special indexing dialog in GUI + * Fix advanced search dialog "Any Clause" mode + * Fixed a few bounds issues catched by Windows VC++ + * Miscellaneous other minor fixes + + -- Jean-Francois Dockes Tue, 01 Oct 2015 09:40:00 +0200 + recoll (1.21.0-1~ppaPPAVERS~SERIES1) SERIES; urgency=low * New bison-based query parser. diff --git a/packaging/debian/debian/control b/packaging/debian/debian/control index 2b89bd8c..7263aea4 100644 --- a/packaging/debian/debian/control +++ b/packaging/debian/debian/control @@ -4,7 +4,7 @@ Priority: optional Maintainer: Jean-Francois Dockes Build-Depends: autotools-dev, debhelper (>= 7), - hardening-wrapper, + dh-python, bison, libqt4-dev, libqtwebkit-dev, @@ -13,7 +13,7 @@ Build-Depends: autotools-dev, libz-dev, python-all-dev (>= 2.6.6-3~), python3-all-dev, -Standards-Version: 3.9.4 +Standards-Version: 3.9.6 X-Python-Version: >= 2.7 Homepage: http://www.lesbonscomptes.com/recoll Vcs-Git: git://anonscm.debian.org/collab-maint/recoll.git @@ -21,10 +21,10 @@ Vcs-Browser: http://anonscm.debian.org/gitweb/?p=collab-maint/recoll.git;a=summa Package: recoll Architecture: any -Depends: ${misc:Depends}, ${shlibs:Depends} -Recommends: python-recoll, aspell, python, xdg-utils, xsltproc +Depends: ${misc:Depends}, ${shlibs:Depends}, python +Recommends: python-recoll, aspell, xdg-utils, xsltproc, + python-libxml2, python-libxslt1 Suggests: antiword, - catdoc, ghostscript, libimage-exiftool-perl, poppler-utils, diff --git a/packaging/debian/debian/copyright b/packaging/debian/debian/copyright index f5811875..95e5bc31 100644 --- a/packaging/debian/debian/copyright +++ b/packaging/debian/debian/copyright @@ -83,42 +83,11 @@ License: LGPL-2+ can be found in `/usr/share/common-licenses/LGPL-2' and `/usr/share/common-licenses/LGPL-2.1' and `/usr/share/common-licenses/LGPL-3'. -Files: qtgui/q3richtext_p.h -Copyright: 1992-2007, Trolltech ASA. All rights reserved -License: - This file is part of the Qt3Support module of the Qt Toolkit. - . - This file may be used under the terms of the GNU General Public License - version 2.0 as published by the Free Software Foundation and appearing in the - file LICENSE.GPL included in the packaging of this file. Please review the - following information to ensure GNU General Public Licensing requirements will - be met: http://trolltech.com/products/qt/licenses/licensing/opensource/ - . - If you are unsure which license is appropriate for your use, please review the - following information: - http://trolltech.com/products/qt/licenses/licensing/licensingoverview - or contact the sales department at sales@trolltech.com. - . - In addition, as a special exception, Trolltech gives you certain additional - rights. These rights are described in the Trolltech GPL Exception version 1.0, - which can be found at http://www.trolltech.com/products/qt/gplexception/ - and in the file GPL_EXCEPTION.txt in this package. - . - In addition, as a special exception, Trolltech, as the sole copyright - holder for Qt Designer, grants users of the Qt/Eclipse Integration - plug-in the right for the Qt/Eclipse Integration to link to - functionality provided by Qt Designer and its related libraries. - . - Trolltech reserves all rights not expressly granted herein. - . - Trolltech ASA (c) 2007 - . - This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE - WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - Files: common/uproplist.h Copyright: 1991-2006, Unicode, Inc. -License: +License: Unicode + +License: Unicode All rights reserved. Distributed under the Terms of Use in http://www.unicode.org/copyright.html . @@ -153,7 +122,9 @@ License: Files: utils/md5.* Copyright: 1991-1992, RSA Data Security, Inc. All rights reserved. -License: +License: RSA + +License:RSA MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm . License to copy and use this software is granted provided that it is @@ -176,7 +147,9 @@ License: Files: desktop/xdg-utils-1.0.1/* Copyright: 2006, Kevin Krammer , Jeremy White . -License: +License: MIT + +License: MIT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights @@ -197,6 +170,8 @@ License: Files: index/csguess.cpp Copyright: 2000-2004, Mikio Hirabayashi +License: LGPL-2.1+ + License: LGPL-2.1+ This file is part of QDBM, Quick Database Manager. . diff --git a/packaging/debian/debian/patches/fix-python-install.patch b/packaging/debian/debian/patches/fix-python-install.patch deleted file mode 100644 index e025a811..00000000 --- a/packaging/debian/debian/patches/fix-python-install.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/recollinstall.in -+++ b/recollinstall.in -@@ -149,5 +149,5 @@ - ${datadir}/recoll/translations/recoll_zh.qm || exit 1 - - --@NOPYTHON@(cd python/recoll;python setup.py install \ --@NOPYTHON@ --prefix=${REALPREFIX} ${ROOTFORPYTHON} ${OPTSFORPYTHON}) -+#@NOPYTHON@(cd python/recoll;python setup.py install \ -+#@NOPYTHON@ --prefix=${REALPREFIX} ${ROOTFORPYTHON} ${OPTSFORPYTHON}) diff --git a/packaging/debian/debian/patches/series b/packaging/debian/debian/patches/series index 977d94d2..e69de29b 100644 --- a/packaging/debian/debian/patches/series +++ b/packaging/debian/debian/patches/series @@ -1 +0,0 @@ -fix-python-install.patch diff --git a/packaging/debian/debian/rules b/packaging/debian/debian/rules index 5daf527d..5889c8cf 100755 --- a/packaging/debian/debian/rules +++ b/packaging/debian/debian/rules @@ -3,16 +3,17 @@ # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 -export DEB_BUILD_HARDENING=1 +DPKG_EXPORT_BUILDFLAGS = 1 +include /usr/share/dpkg/buildflags.mk DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) -CFLAGS = -Wall -g -#LDFLAGS = -Wl,-z,defs +CFLAGS += -Wall -g +#LDFLAGS += -Wl,-z,defs #build qt4 UI only -#export QMAKE=qmake-qt4 +export QMAKE=qmake-qt4 ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) CFLAGS += -O0 @@ -33,7 +34,7 @@ build-arch: build-stamp build-indep: build-stamp build-stamp: config.status dh_testdir - $(MAKE) + $(MAKE) -j 5 touch $@ clean: @@ -48,10 +49,12 @@ install: dh_testroot dh_prep dh_installdirs - $(MAKE) STRIP=ls prefix=$(CURDIR)/debian/tmp/usr install - # Work around recollinstall forgetting the PPT text-extract utility - install -m 0444 filters/msodump.zip $(CURDIR)/debian/tmp/usr/share/recoll/filters - install -m 0555 filters/ppt-dump.py $(CURDIR)/debian/tmp/usr/share/recoll/filters + $(MAKE) prefix=$(CURDIR)/debian/tmp/usr install + chmod a+x $(CURDIR)/debian/tmp/usr/share/recoll/examples/rclmon.sh + chmod a-x $(CURDIR)/debian/tmp/usr/share/recoll/filters/rclxslt.py + chmod a-x $(CURDIR)/debian/tmp/usr/share/recoll/filters/rclexec1.py + chmod a-x $(CURDIR)/debian/tmp/usr/share/recoll/filters/rclexec1.py + rm -f $(CURDIR)/debian/tmp/usr/lib/recoll/librecoll.la (cd python/recoll; python setup.py install \ --install-layout=deb \ --prefix=$(CURDIR)/debian/tmp/usr ) diff --git a/packaging/debian/debiankio/changelog b/packaging/debian/debiankio/changelog index 1cf089c0..ce15e57f 100644 --- a/packaging/debian/debiankio/changelog +++ b/packaging/debian/debiankio/changelog @@ -1,3 +1,21 @@ +kio-recoll (1.22.4-0~ppaPPAVERS~SERIES1) SERIES; urgency=low + + * Switch to kf5 for appropriate series. Update to 1.22.4 (same as .3) + + -- Jean-Francois Dockes Sat, 26 Nov 2016 09:46:00 +0100 + +kio-recoll (1.22.3-0~ppaPPAVERS~SERIES1) SERIES; urgency=low + + * No changes specific to the KIO, just use the 1.22 lib. Update to .3 + + -- Jean-Francois Dockes Fri, 25 Nov 2016 10:59:00 +0100 + +kio-recoll (1.22.2-0~ppaPPAVERS~SERIES1) SERIES; urgency=low + + * No changes specific to the KIO, just use the 1.22 lib. + + -- Jean-Francois Dockes Tue, 15 Jun 2016 09:57:00 +0200 + kio-recoll (1.21.0-0~ppaPPAVERS~SERIES1) SERIES; urgency=low * No changes specific to the KIO, just use the 1.21 lib. diff --git a/packaging/debian/debiankio/control b/packaging/debian/debiankio/control index a86b79f8..28bdac4c 100644 --- a/packaging/debian/debiankio/control +++ b/packaging/debian/debiankio/control @@ -3,21 +3,25 @@ Section: kde Priority: extra Maintainer: Jean-Francois Dockes Build-Depends: cdbs, - cmake, + cmake, + libtool, debhelper (>= 7), bison, - kdelibs5-dev (>= 4:4.2.2), + kdelibs5-dev (>= 4:4.2.2), + qtbase5-dev, + extra-cmake-modules, + kio-dev, pkg-kde-tools (>= 0.4.0), libxapian-dev, libz-dev -Standards-Version: 3.9.5 +Standards-Version: 3.9.7 Homepage: http://www.recoll.org/ Package: kio-recoll Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends}, recoll -Description: A Recoll KIO slave for KDE 4 - A Recoll KIO slave for KDE 4, allows performing a Recoll search by +Description: Recoll KIO slave for KDE 4 + Recoll KIO slave for KDE 4, allows performing a Recoll search by entering an appropriate URL in a KDE open dialog, or with an HTML-based interface displayed in Konqueror. The HTML-based interface is similar to the Recoll GUI QT-based interface, diff --git a/packaging/debian/debiankio/control-4 b/packaging/debian/debiankio/control-4 new file mode 100644 index 00000000..cbc1b236 --- /dev/null +++ b/packaging/debian/debiankio/control-4 @@ -0,0 +1,33 @@ +Source: kio-recoll +Section: kde +Priority: extra +Maintainer: Jean-Francois Dockes +Build-Depends: cdbs, + cmake, + libtool, + debhelper (>= 7), + bison, + kdelibs5-dev (>= 4:4.2.2), + pkg-kde-tools (>= 0.4.0), + libxapian-dev, + libz-dev +Standards-Version: 3.9.5 +Homepage: http://www.recoll.org/ + +Package: kio-recoll +Architecture: any +Depends: ${misc:Depends}, ${shlibs:Depends}, recoll +Description: A Recoll KIO slave for KDE 4 + A Recoll KIO slave for KDE 4, allows performing a Recoll search by + entering an appropriate URL in a KDE open dialog, or with an HTML-based + interface displayed in Konqueror. + The HTML-based interface is similar to the Recoll GUI QT-based interface, + slightly less powerful. It allows performing a search while staying fully + within the KDE framework: drag and drop from the result list works + normally and you have your normal choice of applications for opening files. + An alternative interface uses a directory view of search results. Due to + limitations in the current KIO slave interface, it is currently not + obviously useful. + The interface is described in more detail inside a help file which you can + access by entering recoll:/ inside the konqueror URL line (this works only + if the recoll KIO slave has been previously installed). diff --git a/packaging/debian/debiankio/copyright b/packaging/debian/debiankio/copyright index 3cb35945..fb2496a7 100644 --- a/packaging/debian/debiankio/copyright +++ b/packaging/debian/debiankio/copyright @@ -54,7 +54,7 @@ Copyright (c) 1991-2004 Unicode, Inc. COPYRIGHT AND PERMISSION NOTICE - Copyright © 1991-2006 Unicode, Inc. All rights reserved. Distributed under + Copyright © 1991-2006 Unicode, Inc. All rights reserved. Distributed under the Terms of Use in http://www.unicode.org/copyright.html. Permission is hereby granted, free of charge, to any person obtaining a diff --git a/packaging/debian/debiankio/patches/fix-cmake-build-parser.patch b/packaging/debian/debiankio/patches/fix-cmake-build-parser.patch deleted file mode 100644 index 81e01f48..00000000 --- a/packaging/debian/debiankio/patches/fix-cmake-build-parser.patch +++ /dev/null @@ -1,21 +0,0 @@ -diff -r 1e9d7ca73884 src/kde/kioslave/kio_recoll/CMakeLists.txt ---- a/kde/kioslave/kio_recoll/CMakeLists.txt Tue Jun 16 15:06:07 2015 +0200 -+++ b/kde/kioslave/kio_recoll/CMakeLists.txt Wed Jun 17 09:08:36 2015 +0200 -@@ -57,11 +57,16 @@ - - kde4_add_plugin(kio_recoll ${kio_recoll_SRCS}) - -+add_custom_target(parser -+ COMMAND make wasaparse.tab.cpp -+ WORKING_DIRECTORY ${rcltop}/query -+) - add_custom_target(rcllib - COMMAND make librecoll.a - WORKING_DIRECTORY ${rcltop}/lib - ) --add_dependencies(kio_recoll rcllib) -+add_dependencies(rcllib parser) -+add_dependencies(kio_recoll parser rcllib) - - target_link_libraries(kio_recoll recoll xapian z ${EXTRA_LIBS} ${KDE4_KIO_LIBS}) - diff --git a/packaging/debian/debiankio/patches/series b/packaging/debian/debiankio/patches/series deleted file mode 100644 index 2782ff1c..00000000 --- a/packaging/debian/debiankio/patches/series +++ /dev/null @@ -1 +0,0 @@ -fix-cmake-build-parser.patch diff --git a/packaging/debian/debiankio/rules-4 b/packaging/debian/debiankio/rules-4 new file mode 100755 index 00000000..7798525d --- /dev/null +++ b/packaging/debian/debiankio/rules-4 @@ -0,0 +1,6 @@ +#!/usr/bin/make -f + +include /usr/share/cdbs/1/rules/debhelper.mk +include /usr/share/pkg-kde-tools/makefiles/1/cdbs/kde.mk + +DEB_SRCDIR = kde/kioslave/kio_recoll-kde4 diff --git a/packaging/rpm/kio_recoll-opensuse.spec b/packaging/rpm/kio_recoll-opensuse.spec new file mode 100644 index 00000000..06ccf97a --- /dev/null +++ b/packaging/rpm/kio_recoll-opensuse.spec @@ -0,0 +1,85 @@ +# +# spec file for package kio_recoll +# +# Copyright (c) 2013 SUSE LINUX Products GmbH, Nuernberg, Germany. +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# + +%define rname recoll + +Name: kio_recoll +Version: 1.21.6 +Release: 0 +Summary: Extended Search +License: GPL-2.0+ +Summary: KIO slave for the Recoll full text search tool +Group: Productivity/Text/Utilities +Url: http://www.lesbonscomptes.com/recoll/ +Source: http://www.lesbonscomptes.com/recoll/%{rname}-%{version}.tar.gz +BuildRequires: libkde4-devel +BuildRequires: kio-devel +BuildRequires: libxapian-devel +BuildRequires: recoll = %{version} +Requires: recoll = %{version} +BuildRoot: %{_tmppath}/%{name}-%{version}-build + +%description +Recoll is a personal full text search tool for Unix/Linux. + +This package provides the kio-slave + + +%prep +%setup -q -n %{rname}-%{version} + +%build +pushd kde/kioslave/kio_recoll +%cmake_kde4 -d build +%make_jobs +popd +pushd kde/kioslave/kio_recoll-kde4 +%cmake_kde4 -d build +%make_jobs + +%install +pushd kde/kioslave/kio_recoll +%kde4_makeinstall -C build +popd +pushd kde/kioslave/kio_recoll-kde4 +%kde4_makeinstall -C build + + +%files +%defattr(-,root,root) +%{_libdir}/qt5/plugins/kio_recoll.so +%{_datadir}/kio_recoll/ +%{_datadir}/kservices5/*.protocol +%{_libdir}/kde4/kio_recoll.so +%{_datadir}/kde4/apps/kio_recoll/help.html +%{_datadir}/kde4/apps/kio_recoll/welcome.html +%{_datadir}/kde4/services/recoll.protocol +%{_datadir}/kde4/services/recollf.protocol + +%changelog +* Tue Apr 05 2016 Jean-Francois Dockes 1.21.6-0 +- Also build kde5 versions: works with Dolphin. Keep kde4 version for + Konqueror +* Sun Mar 18 2012 Jean-Francois Dockes 1.17.0-0 +- 1.17.0 +* Mon May 02 2011 Jean-Francois Dockes 1.16.2-0 +- 1.16.2 +* Mon May 02 2011 Jean-Francois Dockes 1.15.8-0 +- 1.15.8 +* Sun Mar 06 2011 Jean-Francois Dockes 1.15.5-0 +- Initial spec file for kio + diff --git a/packaging/rpm/recoll-opensuse.spec b/packaging/rpm/recoll-opensuse.spec new file mode 100644 index 00000000..ca1276dc --- /dev/null +++ b/packaging/rpm/recoll-opensuse.spec @@ -0,0 +1,123 @@ +# +# spec file for package recoll +# +# Copyright (c) 2013 SUSE LINUX Products GmbH, Nuernberg, Germany. +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# + + +Name: recoll +Version: 1.21.6 +Release: 0 +Summary: Extended Search +License: GPL-2.0+ +Summary: Extended Search +Group: Productivity/Text/Utilities +Url: http://www.lesbonscomptes.com/recoll/ +Source: http://www.lesbonscomptes.com/recoll/%{name}-%{version}.tar.gz +BuildRequires: aspell-devel +%if 0%{?suse_version} > 1320 +BuildRequires: pkgconfig(Qt5Core) +BuildRequires: pkgconfig(Qt5WebKit) +BuildRequires: pkgconfig(Qt5WebKitWidgets) +BuildRequires: pkgconfig(Qt5Xml) +BuildRequires: pkgconfig(Qt5Network) +BuildRequires: pkgconfig(Qt5PrintSupport) +%else +BuildRequires: libQtWebKit-devel +BuildRequires: libqt4-devel +%endif +BuildRequires: bison +BuildRequires: libxapian-devel +BuildRequires: python-devel +BuildRequires: update-desktop-files +# Recommends for the helpers. +Recommends: antiword +Recommends: poppler-tools +Recommends: libwpd-tools +Recommends: libxslt +Recommends: catdoc +Recommends: djvulibre +Recommends: python-mutagen +Recommends: exiftool +Recommends: perl-Image-ExifTool +Recommends: python-base +Recommends: unrar +Recommends: python-rarfile +Recommends: python-PyCHM +Recommends: unrtf +Recommends: pstotext +# Default requires for recoll itself +Requires: sed +Requires: awk +Requires: aspell +Requires: python +Suggests: kio_recoll +BuildRoot: %{_tmppath}/%{name}-%{version}-build + +%description +Recoll is a personal full text search tool for Unix/Linux. + +It is based on the very strong Xapian back-end, for which it provides a feature-rich yet easy to use front-end with a QT graphical interface. + +Recoll is free, open source, and licensed under the GPL. The current version is 1.12.0 . +Features: + +* Easy installation, few dependencies. No database daemon, web server, desktop environment or exotic language necessary. +* Will run on most unix-based systems +* Qt-based GUI. Can use either Qt 3 or Qt 4. +* Searches most common document types, emails and their attachments. +* Powerful query facilities, with boolean searches, phrases, proximity, wildcards, filter on file types and directory tree. +* Multi-language and multi-character set with Unicode based internals. +* (more detail) + +%prep +%setup -q + +%build +%if 0%{?suse_version} > 1320 +export QMAKE=qmake-qt5 +%endif +%configure --with-inotify +make %{?_smp_mflags} + +%install +make DESTDIR=%{buildroot} install %{?_smp_mflags} + +%post -p /sbin/ldconfig + +%postun -p /sbin/ldconfig + +%files +%defattr(-,root,root) +%dir %{_libdir}/recoll +%dir %{_datadir}/appdata +%{_bindir}/recoll +%{_bindir}/recollindex +%{_datadir}/recoll +%{_datadir}/applications/recoll-searchgui.desktop +%{_datadir}/appdata/recoll.appdata.xml +%{_datadir}/icons/hicolor +%{_datadir}/pixmaps/recoll.png +%{_libdir}/recoll/librecoll.so.%{version} +%{_libdir}/python2.7/site-packages/recoll +%{_libdir}/python2.7/site-packages/Recoll-1.0-py2.7.egg-info +%{_mandir}/man1/recoll.1.gz +%{_mandir}/man1/recollq.1.gz +%{_mandir}/man1/recollindex.1.gz +%{_mandir}/man5/recoll.conf.5.gz + +%changelog +* Tue Apr 05 2016 Jean-Francois Dockes 1.21.6-0 +- Recoll 1.21.6, esp. for the kde5 kio + diff --git a/packaging/rpm/recollfedora.spec b/packaging/rpm/recollfedora.spec index 0f266295..57f08e20 100644 --- a/packaging/rpm/recollfedora.spec +++ b/packaging/rpm/recollfedora.spec @@ -1,18 +1,24 @@ Summary: Desktop full text search tool with Qt GUI Name: recoll -Version: 1.20.6 +Version: 1.21.6 Release: 1%{?dist} Group: Applications/Databases License: GPLv2+ URL: http://www.lesbonscomptes.com/recoll/ Source0: http://www.lesbonscomptes.com/recoll/recoll-%{version}.tar.gz -BuildRequires: qt-devel -BuildRequires: qtwebkit-devel -BuildRequires: python-devel -BuildRequires: zlib-devel +# Source10: qmake-qt4.sh BuildRequires: aspell-devel -BuildRequires: xapian-core-devel +BuildRequires: bison BuildRequires: desktop-file-utils +# kio +BuildRequires: kdelibs4-devel +BuildRequires: qt5-qtbase-devel +BuildRequires: qt5-qtwebkit-devel +BuildRequires: extra-cmake-modules +BuildRequires: kf5-kio-devel +BuildRequires: python2-devel +BuildRequires: xapian-core-devel +BuildRequires: zlib-devel Requires: xdg-utils %description @@ -20,15 +26,30 @@ Recoll is a personal full text search package for Linux, FreeBSD and other Unix systems. It is based on a very strong back end (Xapian), for which it provides an easy to use, feature-rich, easy administration interface. -%global __provides_exclude_from ^%{_libdir}/recoll/librecoll\\.so.*$ -%global __requires_exclude ^librecoll\\.so.*$ + +%package kio +Summary: KIO support for recoll +Group: Applications/Databases +Requires: %{name} = %{version}-%{release} + +%description kio +The recoll KIO slave allows performing a recoll search by entering an +appropriate URL in a KDE open dialog, or with an HTML-based interface +displayed in Konqueror. %prep %setup -q -n %{name}-%{version} chmod 0644 utils/{conftree.cpp,conftree.h,debuglog.cpp,debuglog.h} %build -export QMAKE=qmake-qt4 +CFLAGS="%{optflags}"; export CFLAGS +CXXFLAGS="%{optflags}"; export CXXFLAGS +LDFLAGS="%{?__global_ldflags}"; export LDFLAGS + +# force use of custom/local qmake, to inject proper build flags (above) +#install -m755 -D %{SOURCE10} qmake-qt4.sh +export QMAKE=qmake-qt5 + %configure make %{?_smp_mflags} @@ -46,6 +67,28 @@ rm -f %{buildroot}/usr/share/recoll/filters/xdg-open chmod 0755 %{buildroot}/usr/share/recoll/filters/rclexecm.py chmod 0755 %{buildroot}%{_libdir}/recoll/librecoll.so.* +# kio_recoll -kde5 +( +mkdir kde/kioslave/kio_recoll/build && pushd kde/kioslave/kio_recoll/build +%cmake .. +make %{?_smp_mflags} VERBOSE=1 +make install DESTDIR=%{buildroot} +popd +) + +# kio_recoll -kde4 +( +mkdir kde/kioslave/kio_recoll-kde4/build && \ + pushd kde/kioslave/kio_recoll-kde4/build +%cmake .. +make %{?_smp_mflags} VERBOSE=1 +make install DESTDIR=%{buildroot} +popd +) + +mkdir -p %{buildroot}%{_sysconfdir}/ld.so.conf.d +echo "%{_libdir}/recoll" > %{buildroot}%{_sysconfdir}/ld.so.conf.d/%{name}-%{_arch}.conf + %post touch --no-create %{_datadir}/icons/hicolor if [ -x %{_bindir}/gtk-update-icon-cache ] ; then @@ -54,6 +97,7 @@ fi if [ -x %{_bindir}/update-desktop-database ] ; then %{_bindir}/update-desktop-database &> /dev/null fi +/sbin/ldconfig exit 0 %postun @@ -64,10 +108,12 @@ fi if [ -x %{_bindir}/update-desktop-database ] ; then %{_bindir}/update-desktop-database &> /dev/null fi +/sbin/ldconfig exit 0 %files %doc COPYING ChangeLog README +%{_sysconfdir}/ld.so.conf.d/%{name}-%{_arch}.conf %{_bindir}/%{name} %{_bindir}/%{name}index %{_datadir}/%{name} @@ -79,18 +125,52 @@ exit 0 %{python_sitearch}/recoll %{python_sitearch}/Recoll*.egg-info %{_mandir}/man1/%{name}.1* -%{_mandir}/man1/recollq.1* +%{_mandir}/man1/%{name}q.1* %{_mandir}/man1/%{name}index.1* %{_mandir}/man5/%{name}.conf.5* -%changelog -* Sat Apr 25 2015 Jean-Francois Dockes - 1.20.6-1 -- 1.20.6 +%files kio +%{_libdir}/kde4/kio_recoll.so +%{_libdir}/qt5/plugins/kio_recoll.so +%{_datadir}/kde4/apps/kio_recoll/ +%{_datadir}/kde4/services/recoll.protocol +%{_datadir}/kde4/services/recollf.protocol +%{_datadir}/kio_recoll/help.html +%{_datadir}/kio_recoll/welcome.html +%{_datadir}/kservices5/recoll.protocol +%{_datadir}/kservices5/recollf.protocol -* Fri Dec 19 2014 Jean-Francois Dockes - 1.20.1-1 +%changelog +* Mon Jan 18 2016 Terje Rosten - 1.21.4-1 +- 1.21.4 + +* Sat Oct 31 2015 Terje Rosten - 1.21.3-1 +- 1.21.3 + +* Sat Oct 03 2015 Terje Rosten - 1.21.2-1 +- 1.21.2 + +* Thu Jun 18 2015 Fedora Release Engineering - 1.20.6-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Tue May 05 2015 Terje Rosten - 1.20.6-1 +- 1.20.6 +- Fixes bz#1218161 and bz#1204464 + +* Sat May 02 2015 Kalev Lember - 1.20.5-3 +- Rebuilt for GCC 5 C++11 ABI change + +* Sat Apr 11 2015 Terje Rosten - 1.20.5-2 +- Add KIO subpackage + +* Tue Apr 07 2015 Terje Rosten - 1.20.5-1 +- 1.20.5 +- Include kio support (bz#1203257) + +* Sat Jan 10 2015 Terje Rosten - 1.20.1-1 - 1.20.1 -* Sun Nov 09 2014 Jean-Francois Dockes - 1.19.14p2-1 +* Sun Nov 09 2014 Terje Rosten - 1.19.14p2-1 - 1.19.14p2 * Sun Aug 17 2014 Fedora Release Engineering - 1.19.13-3 @@ -221,4 +301,3 @@ exit 0 * Wed Feb 1 2006 Jean-Francois Dockes 1.2.0-1 - initial packaging - diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..a2c966fe --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,654 @@ + +CXXFLAGS ?= @CXXFLAGS@ +LIBXAPIAN=@LIBXAPIAN@ +XAPIANCXXFLAGS=@XAPIANCXXFLAGS@ +LIBICONV=@LIBICONV@ +INCICONV=@INCICONV@ +LIBFAM = @LIBFAM@ +RCLLIBVERSION=@RCLLIBVERSION@ +X_CFLAGS=@X_CFLAGS@ +X_PRE_LIBS=@X_PRE_LIBS@ +X_LIBS=@X_LIBS@ +X_EXTRA_LIBS=@X_EXTRA_LIBS@ +X_LIBX11=@X_LIBX11@ +DEFS=@DEFS@ + +COMMONCPPFLAGS = -I. \ + -I$(top_srcdir)/aspell \ + -I$(top_srcdir)/bincimapmime \ + -I$(top_srcdir)/common \ + -I$(top_srcdir)/index \ + -I$(top_srcdir)/internfile \ + -I$(top_srcdir)/rcldb \ + -I$(top_srcdir)/unac \ + -I$(top_srcdir)/utils \ + -I$(top_srcdir)/xaposix \ + -DBUILDING_RECOLL + +AM_CPPFLAGS = -Wall -Wno-unused -std=c++11 \ + $(COMMONCPPFLAGS) \ + $(INCICONV) \ + $(XAPIANCXXFLAGS) \ + $(X_CFLAGS) \ + -DRECOLL_DATADIR=\"${pkgdatadir}\" \ + -D_GNU_SOURCE \ + $(DEFS) + +ACLOCAL_AMFLAGS = -I m4 + +if NOTHREADS + LIBTHREADS= +else + LIBTHREADS= $(LIBSYSTHREADS) +endif + +librcldir = $(libdir)/recoll +librcl_LTLIBRARIES = librecoll.la + +librecoll_la_SOURCES = \ +aspell/aspell-local.h \ +aspell/rclaspell.cpp \ +aspell/rclaspell.h \ +bincimapmime/config.h \ +bincimapmime/convert.cc \ +bincimapmime/convert.h \ +bincimapmime/mime-inputsource.h \ +bincimapmime/mime-parsefull.cc \ +bincimapmime/mime-parseonlyheader.cc \ +bincimapmime/mime-printbody.cc \ +bincimapmime/mime-utils.h \ +bincimapmime/mime.cc \ +bincimapmime/mime.h \ +common/beaglequeuecache.cpp \ +common/beaglequeuecache.h \ +common/conf_post.h \ +common/cstr.cpp \ +common/cstr.h \ +common/rclconfig.cpp \ +common/rclconfig.h \ +common/rclinit.cpp \ +common/rclinit.h \ +common/syngroups.cpp \ +common/syngroups.h \ +common/textsplit.cpp \ +common/textsplit.h \ +common/unacpp.cpp \ +common/unacpp.h \ +common/uproplist.h \ +common/utf8fn.cpp \ +common/utf8fn.h \ +index/beaglequeue.cpp \ +index/beaglequeue.h \ +index/bglfetcher.cpp \ +index/bglfetcher.h \ +index/checkretryfailed.cpp \ +index/checkretryfailed.h \ +index/exefetcher.cpp \ +index/exefetcher.h \ +index/fetcher.cpp \ +index/fetcher.h \ +index/fsfetcher.cpp \ +index/fsfetcher.h \ +index/fsindexer.cpp \ +index/fsindexer.h \ +index/indexer.cpp \ +index/indexer.h \ +index/mimetype.cpp \ +index/mimetype.h \ +index/rclmon.h \ +index/recollindex.h \ +index/subtreelist.cpp \ +index/subtreelist.h \ +internfile/Filter.h \ +internfile/extrameta.cpp \ +internfile/extrameta.h \ +internfile/htmlparse.cpp \ +internfile/htmlparse.h \ +internfile/indextext.h \ +internfile/internfile.cpp \ +internfile/internfile.h \ +internfile/mh_exec.cpp \ +internfile/mh_exec.h \ +internfile/mh_execm.cpp \ +internfile/mh_execm.h \ +internfile/mh_html.cpp \ +internfile/mh_html.h \ +internfile/mh_mail.cpp \ +internfile/mh_mail.h \ +internfile/mh_mbox.cpp \ +internfile/mh_mbox.h \ +internfile/mh_null.h \ +internfile/mh_symlink.h \ +internfile/mh_text.cpp \ +internfile/mh_text.h \ +internfile/mh_unknown.h \ +internfile/mimehandler.cpp \ +internfile/mimehandler.h \ +internfile/myhtmlparse.cpp \ +internfile/myhtmlparse.h \ +internfile/txtdcode.cpp \ +internfile/uncomp.cpp \ +internfile/uncomp.h \ +query/docseq.cpp \ +query/docseq.h \ +query/docseqdb.cpp \ +query/docseqdb.h \ +query/docseqdocs.h \ +query/docseqhist.cpp \ +query/docseqhist.h \ +query/dynconf.cpp \ +query/dynconf.h \ +query/filtseq.cpp \ +query/filtseq.h \ +query/plaintorich.cpp \ +query/plaintorich.h \ +query/recollq.cpp \ +query/recollq.h \ +query/reslistpager.cpp \ +query/reslistpager.h \ +query/sortseq.cpp \ +query/sortseq.h \ +query/wasaparse.ypp \ +query/wasaparseaux.cpp \ +query/wasaparserdriver.h \ +query/wasatorcl.h \ +rcldb/daterange.cpp \ +rcldb/daterange.h \ +rcldb/expansiondbs.cpp \ +rcldb/expansiondbs.h \ +rcldb/rclabstract.cpp \ +rcldb/rcldb.cpp \ +rcldb/rcldb.h \ +rcldb/rcldb_p.h \ +rcldb/rcldoc.cpp \ +rcldb/rcldoc.h \ +rcldb/rcldups.cpp \ +rcldb/rclquery.cpp \ +rcldb/rclquery.h \ +rcldb/rclquery_p.h \ +rcldb/rclterms.cpp \ +rcldb/searchdata.cpp \ +rcldb/searchdata.h \ +rcldb/searchdatatox.cpp \ +rcldb/searchdataxml.cpp \ +rcldb/stemdb.cpp \ +rcldb/stemdb.h \ +rcldb/stoplist.cpp \ +rcldb/stoplist.h \ +rcldb/synfamily.cpp \ +rcldb/synfamily.h \ +rcldb/termproc.h \ +rcldb/xmacros.h \ +unac/unac.cpp \ +unac/unac.h \ +unac/unac_version.h \ +utils/appformime.cpp \ +utils/appformime.h \ +utils/base64.cpp \ +utils/base64.h \ +utils/cancelcheck.cpp \ +utils/cancelcheck.h \ +utils/chrono.h \ +utils/chrono.cpp \ +utils/circache.cpp \ +utils/circache.h \ +utils/closefrom.cpp \ +utils/closefrom.h \ +utils/conftree.cpp \ +utils/conftree.h \ +utils/copyfile.cpp \ +utils/copyfile.h \ +utils/cpuconf.cpp \ +utils/cpuconf.h \ +utils/ecrontab.cpp \ +utils/ecrontab.h \ +utils/execmd.cpp \ +utils/execmd.h \ +utils/fileudi.cpp \ +utils/fileudi.h \ +utils/fstreewalk.cpp \ +utils/fstreewalk.h \ +utils/hldata.h \ +utils/hldata.cpp \ +utils/idfile.cpp \ +utils/idfile.h \ +utils/listmem.cpp \ +utils/listmem.h \ +utils/log.cpp \ +utils/log.h \ +utils/md5.cpp \ +utils/md5.h \ +utils/md5ut.cpp \ +utils/md5ut.h \ +utils/mimeparse.cpp \ +utils/mimeparse.h \ +utils/netcon.cpp \ +utils/netcon.h \ +utils/pathut.cpp \ +utils/pathut.h \ +utils/pxattr.cpp \ +utils/pxattr.h \ +utils/rclionice.cpp \ +utils/rclionice.h \ +utils/rclutil.h \ +utils/rclutil.cpp \ +utils/readfile.cpp \ +utils/readfile.h \ +utils/refcntr.h \ +utils/smallut.cpp \ +utils/smallut.h \ +utils/strmatcher.cpp \ +utils/strmatcher.h \ +utils/transcode.cpp \ +utils/transcode.h \ +utils/utf8iter.h \ +utils/wipedir.cpp \ +utils/wipedir.h \ +utils/workqueue.h \ +xaposix/safefcntl.h \ +xaposix/safesysstat.h \ +xaposix/safesyswait.h \ +xaposix/safeunistd.h + +BUILT_SOURCES = query/wasaparse.cpp +AM_YFLAGS = -d + +# We use -release: the lib is only shared +# between recoll programs from the same release. +# -version-info $(VERSION_INFO) +librecoll_la_LDFLAGS = -release $(VERSION) \ + -Wl,--no-undefined -Wl,--warn-unresolved-symbols + +librecoll_la_LIBADD = $(LIBXAPIAN) $(LIBICONV) $(LIBTHREADS) + +# There is probably a better way to do this. The KIO needs to be linked +# with librecoll, but librecoll is installed into a non-standard place +# (/usr/lib/recoll). Debian packaging has something against setting an +# rpath on the kio (cause it's not the same package as the lib), so I don't +# know how to link it dynamically. The other thing I don't know is how to +# force automake to build a static lib with the PIC objects. So the +# following target, which is only used from the KIO build, deletes any .a +# and .so and rebuilds the .a with the pic objs (the kio build calls +# configured --disable-static). +# Of course this is very uncomfortably close to automake/libtool internals +# and may not work on all systems. +PicStatic: $(librecoll_la_OBJECTS) + rm -f .libs/librecoll.a + rm -f .libs/librecoll.so + libtool --tag=LD --mode=link gcc -g -O -o librecoll.la \ + $(librecoll_la_OBJECTS) + +bin_PROGRAMS = recollindex +if MAKECMDLINE + bin_PROGRAMS += recollq +endif + +if MAKEXADUMP + bin_PROGRAMS += xadump +endif + +recollindex_SOURCES = \ + index/recollindex.cpp \ + index/rclmonprc.cpp \ + index/rclmonrcv.cpp \ + utils/x11mon.cpp \ + utils/x11mon.h +recollindex_LDADD = librecoll.la $(X_LIBX11) + +recollq_SOURCES = query/recollqmain.cpp +recollq_LDADD = librecoll.la + +xadump_SOURCES = query/xadump.cpp +xadump_LDADD = librecoll.la $(LIBXAPIAN) $(LIBICONV) + +# Note: I'd prefer the generated query parser files not to be distributed +# at all, but failed to achieve this +EXTRA_DIST = \ +bincimapmime/00README.recoll bincimapmime/AUTHORS bincimapmime/COPYING \ +\ +desktop/hotrecoll.py \ +desktop/recoll.appdata.xml \ +desktop/recollindex.desktop \ +desktop/recoll_index_on_ac.sh \ +desktop/recoll-searchgui.desktop \ +desktop/recoll.png desktop/recoll.svg desktop/recoll.xcf \ +\ +doc/man \ +doc/prog \ +doc/user/*.html doc/user/*.css doc/user/*.txt doc/user/*.xml doc/user/Makefile \ +\ +filters \ +\ +index/rclmon.sh \ +\ +kde/kioslave/kio_recoll/00README.txt \ +kde/kioslave/kio_recoll/CMakeLists.txt \ +kde/kioslave/kio_recoll/data/help.html \ +kde/kioslave/kio_recoll/data/searchable.html \ +kde/kioslave/kio_recoll/data/welcome.html \ +kde/kioslave/kio_recoll/dirif.cpp \ +kde/kioslave/kio_recoll/htmlif.cpp \ +kde/kioslave/kio_recoll/kio_recoll.cpp \ +kde/kioslave/kio_recoll/kio_recoll.h \ +kde/kioslave/kio_recoll/recollf.protocol \ +kde/kioslave/kio_recoll/recollnolist.protocol \ +kde/kioslave/kio_recoll/recoll.protocol \ +\ +query/location.hh query/position.hh query/stack.hh \ +\ +qtgui/advsearch.ui \ +qtgui/advsearch_w.cpp \ +qtgui/advsearch_w.h \ +qtgui/advshist.cpp \ +qtgui/advshist.h \ +qtgui/confgui/confgui.cpp \ +qtgui/confgui/confgui.h \ +qtgui/confgui/confguiindex.cpp \ +qtgui/confgui/confguiindex.h \ +qtgui/confgui/conflinkrcl.h \ +qtgui/confgui/main.cpp \ +qtgui/crontool.cpp \ +qtgui/crontool.h \ +qtgui/crontool.ui \ +qtgui/firstidx.h \ +qtgui/firstidx.ui \ +qtgui/fragbuts.cpp \ +qtgui/fragbuts.h \ +qtgui/guiutils.cpp \ +qtgui/guiutils.h \ +qtgui/i18n/recoll_cs.qm qtgui/i18n/recoll_cs.ts \ +qtgui/i18n/recoll_da.qm qtgui/i18n/recoll_da.ts \ +qtgui/i18n/recoll_de.qm qtgui/i18n/recoll_de.ts \ +qtgui/i18n/recoll_el.qm qtgui/i18n/recoll_el.ts \ +qtgui/i18n/recoll_es.qm qtgui/i18n/recoll_es.ts \ +qtgui/i18n/recoll_fr.qm qtgui/i18n/recoll_fr.ts \ +qtgui/i18n/recoll_it.qm qtgui/i18n/recoll_it.ts \ +qtgui/i18n/recoll_lt.qm qtgui/i18n/recoll_lt.ts \ +qtgui/i18n/recoll_pl.qm qtgui/i18n/recoll_pl.ts \ +qtgui/i18n/recoll_ru.qm qtgui/i18n/recoll_ru.ts \ +qtgui/i18n/recoll_tr.qm qtgui/i18n/recoll_tr.ts \ +qtgui/i18n/recoll_uk.qm qtgui/i18n/recoll_uk.ts \ +qtgui/i18n/recoll_xx.qm qtgui/i18n/recoll_xx.ts \ +qtgui/i18n/recoll_zh.qm qtgui/i18n/recoll_zh.ts \ +qtgui/i18n/recoll_zh_CN.qm qtgui/i18n/recoll_zh_CN.ts \ +qtgui/idxsched.h \ +qtgui/idxsched.ui \ +qtgui/images/asearch.png \ +qtgui/images/cancel.png \ +qtgui/images/close.png \ +qtgui/images/code-block.png \ +qtgui/images/down.png \ +qtgui/images/firstpage.png \ +qtgui/images/history.png \ +qtgui/images/nextpage.png \ +qtgui/images/prevpage.png \ +qtgui/images/recoll.icns \ +qtgui/images/recoll.png \ +qtgui/images/sortparms.png \ +qtgui/images/spell.png \ +qtgui/images/table.png \ +qtgui/images/up.png \ +qtgui/main.cpp \ +qtgui/mtpics/License_sidux.txt \ +qtgui/mtpics/README \ +qtgui/mtpics/aptosid-book.png \ +qtgui/mtpics/aptosid-manual-copyright.txt \ +qtgui/mtpics/aptosid-manual.png \ +qtgui/mtpics/archive.png \ +qtgui/mtpics/book.png \ +qtgui/mtpics/bookchap.png \ +qtgui/mtpics/document.png \ +qtgui/mtpics/drawing.png \ +qtgui/mtpics/emblem-symbolic-link.png \ +qtgui/mtpics/folder.png \ +qtgui/mtpics/html.png \ +qtgui/mtpics/image.png \ +qtgui/mtpics/message.png \ +qtgui/mtpics/mozilla_doc.png \ +qtgui/mtpics/pdf.png \ +qtgui/mtpics/pidgin.png \ +qtgui/mtpics/postscript.png \ +qtgui/mtpics/presentation.png \ +qtgui/mtpics/sidux-book.png \ +qtgui/mtpics/soffice.png \ +qtgui/mtpics/source.png \ +qtgui/mtpics/sownd.png \ +qtgui/mtpics/spreadsheet.png \ +qtgui/mtpics/text-x-python.png \ +qtgui/mtpics/txt.png \ +qtgui/mtpics/video.png \ +qtgui/mtpics/wordprocessing.png \ +qtgui/multisave.cpp \ +qtgui/multisave.h \ +qtgui/preview_load.cpp \ +qtgui/preview_load.h \ +qtgui/preview_plaintorich.cpp \ +qtgui/preview_plaintorich.h \ +qtgui/preview_w.cpp \ +qtgui/preview_w.h \ +qtgui/ptrans.ui \ +qtgui/ptrans_w.cpp \ +qtgui/ptrans_w.h \ +qtgui/rclhelp.cpp \ +qtgui/rclhelp.h \ +qtgui/rclm_idx.cpp \ +qtgui/rclm_preview.cpp \ +qtgui/rclm_saveload.cpp \ +qtgui/rclm_view.cpp \ +qtgui/rclm_wins.cpp \ +qtgui/rclmain.ui \ +qtgui/rclmain_w.cpp \ +qtgui/rclmain_w.h \ +qtgui/rclzg.cpp \ +qtgui/rclzg.h \ +qtgui/recoll.h \ +qtgui/recoll.pro.in \ +qtgui/recoll.qrc \ +qtgui/reslist.cpp \ +qtgui/reslist.h \ +qtgui/respopup.cpp \ +qtgui/respopup.h \ +qtgui/restable.cpp \ +qtgui/restable.h \ +qtgui/restable.ui \ +qtgui/rtitool.cpp \ +qtgui/rtitool.h \ +qtgui/rtitool.ui \ +qtgui/searchclause_w.cpp \ +qtgui/searchclause_w.h \ +qtgui/snippets.ui \ +qtgui/snippets_w.cpp \ +qtgui/snippets_w.h \ +qtgui/specialindex.h \ +qtgui/specialindex.ui \ +qtgui/spell.ui \ +qtgui/spell_w.cpp \ +qtgui/spell_w.h \ +qtgui/ssearch_w.cpp \ +qtgui/ssearch_w.h \ +qtgui/ssearchb.ui \ +qtgui/systray.cpp \ +qtgui/systray.h \ +qtgui/ui_rclmain.h-4.5 \ +qtgui/uiprefs.ui \ +qtgui/uiprefs_w.cpp \ +qtgui/uiprefs_w.h \ +qtgui/viewaction.ui \ +qtgui/viewaction_w.cpp \ +qtgui/viewaction_w.h \ +qtgui/webcache.ui \ +qtgui/webcache.cpp \ +qtgui/webcache.h \ +qtgui/widgets/editdialog.h \ +qtgui/widgets/editdialog.ui \ +qtgui/widgets/listdialog.h \ +qtgui/widgets/listdialog.ui \ +qtgui/widgets/qxtconfirmationmessage.cpp \ +qtgui/widgets/qxtconfirmationmessage.h \ +qtgui/widgets/qxtglobal.h \ +qtgui/xmltosd.cpp \ +qtgui/xmltosd.h \ +\ +python/README.txt \ +python/recoll/Makefile.in \ +python/recoll/pyrclextract.cpp \ +python/recoll/pyrecoll.cpp \ +python/recoll/pyrecoll.h \ +python/recoll/recoll/__init__.py \ +python/recoll/recoll/rclconfig.py \ +python/recoll/setup.py.in \ +python/samples/docdups.py \ +python/samples/mutt-recoll.py \ +python/samples/rcldlkp.py \ +python/samples/rclmbox.py \ +python/samples/recollgui/Makefile \ +python/samples/recollgui/qrecoll.py \ +python/samples/recollgui/rclmain.ui \ +python/samples/recollq.py \ +python/samples/recollqsd.py \ +\ + \ +sampleconf/fields sampleconf/fragbuts.xml sampleconf/mimeconf \ +sampleconf/mimemap sampleconf/mimeview sampleconf/mimeview.mac \ +sampleconf/recoll.conf sampleconf/recoll.qss \ +\ +unac/AUTHORS unac/COPYING unac/README unac/README.recoll unac/unac.c \ +\ +VERSION + +# EXTRA_DIST: The Php Code does not build anymore. No need to ship it until +# someone fixes it: +# php/00README.txt php/recoll/config.m4 php/recoll/make.sh +# php/recoll/php_recoll.h php/recoll/recoll.cpp php/sample/shell.php + +if MAKEPYTHON +all-local: recollpython +recollpython: librecoll.la + ${MAKE} -C python/recoll libdir=$(libdir) +install-exec-local: recollpython-install +recollpython-install: + (cd python/recoll; \ + if test -f /etc/debian_version ; then \ + OPTSFORPYTHON=--install-layout=deb; \ + fi; \ + set -x; \ + python setup.py install \ + --prefix=${prefix} --root=$${DESTDIR:-/} $${OPTSFORPYTHON}) +clean-local: recollpython-clean +recollpython-clean: + rm -rf python/recoll/build +endif + +if MAKEQT +all-local: recollqt +recollqt: librecoll.la + (cd $(QTGUI); ${QMAKE} PREFIX=${prefix} recoll.pro) + $(MAKE) -C $(QTGUI) LFLAGS="$(LDFLAGS)" prefix=$(prefix) \ + exec_prefix=$(exec_prefix) libdir=$(libdir) +clean-local: recollqt-clean +recollqt-clean: + -$(MAKE) -C $(QTGUI) clean +install-exec-local: recollqt-install +recollqt-install: + $(MAKE) -C $(QTGUI) LFLAGS="$(LDFLAGS)" INSTALL_ROOT=$(DESTDIR) \ + prefix=$(prefix) exec_prefix=$(exec_prefix) libdir=$(libdir) \ + install +endif + +defconfdir = $(pkgdatadir)/examples +defconf_DATA = \ +desktop/recollindex.desktop \ +index/rclmon.sh \ +sampleconf/fragbuts.xml \ +sampleconf/fields \ +sampleconf/recoll.conf \ +sampleconf/mimeconf \ +sampleconf/recoll.qss \ +sampleconf/mimemap \ +sampleconf/mimeview + +filterdir = $(pkgdatadir)/filters +filter_DATA = \ +desktop/hotrecoll.py \ +filters/rcl7z \ +filters/rclabw \ +filters/rclaptosidman \ +filters/rclaudio \ +filters/rclcheckneedretry.sh \ +filters/rclchm \ +filters/rcldia \ +filters/rcldjvu.py \ +filters/rcldoc.py \ +filters/rcldvi \ +filters/rclepub \ +filters/rclepub1 \ +filters/rclexec1.py \ +filters/rclexecm.py \ +filters/rclfb2 \ +filters/rclgaim \ +filters/rclgnm \ +filters/rclics \ +filters/rclimg \ +filters/rclimg.py \ +filters/rclinfo \ +filters/rclkar \ +filters/rclkwd \ +filters/rcllatinclass.py \ +filters/rcllatinstops.zip \ +filters/rcllyx \ +filters/rclman \ +filters/rclpdf.py \ +filters/rclokulnote \ +filters/rclopxml.py \ +filters/rclppt.py \ +filters/rclps \ +filters/rclpurple \ +filters/rclpython \ +filters/rclrar \ +filters/rclrtf.py \ +filters/rclscribus \ +filters/rclshowinfo \ +filters/rclsiduxman \ +filters/rclsoff.py \ +filters/rclsvg.py \ +filters/rcltar \ +filters/rcltex \ +filters/rcltext.py \ +filters/rcluncomp \ +filters/rcluncomp.py \ +filters/rclwar \ +filters/rclwpd \ +filters/rclxls.py \ +filters/rclxml.py \ +filters/rclxmp.py \ +filters/rclxslt.py \ +filters/rclzip \ +filters/ppt-dump.py \ +filters/xls-dump.py \ +filters/xlsxmltocsv.py \ +filters/msodump.zip \ +python/recoll/recoll/rclconfig.py + +install-data-hook: + (cd $(DESTDIR)/$(filterdir); \ + chmod a+x rcl* ppt-dump.py xls-dump.py xlsxmltocsv.py hotrecoll.py; \ + chmod 0644 msodump.zip rclexecm.py rcllatinstops.zip rclconfig.py) + +if MAKEUSERDOC +rdocdir = $(pkgdatadir)/doc +rdoc_DATA = doc/user/usermanual.html doc/user/docbook-xsl.css +doc/user/usermanual.html: doc/user/usermanual.xml + mkdir -p doc/user + test -f doc/user/Makefile || \ + cp -p $(top_srcdir)/doc/user/Makefile doc/user + $(MAKE) -C doc/user VPATH=$(VPATH):$(VPATH)/doc/user usermanual.html +endif + +dist_man1_MANS = doc/man/recoll.1 doc/man/recollq.1 doc/man/recollindex.1 +dist_man5_MANS = doc/man/recoll.conf.5 + +dist-hook: + modified=`hg status | grep -v /$(distdir)/|grep -v debianunitysco`; \ + if test ! -z "$$modified"; then \ + echo Local directory is modified: $$modified ; exit 1; fi + hg tag -f -m "Release $(VERSION) tagged" RECOLL-$(VERSION) diff --git a/src/Makefile.in b/src/Makefile.in deleted file mode 100644 index 9c003fad..00000000 --- a/src/Makefile.in +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (C) 2005 J.F.Dockes - -prefix = @prefix@ -exec_prefix = @exec_prefix@ - -bindir = @bindir@ -libdir = @libdir@ -datadir = @datadir@ -datarootdir = @datarootdir@ -mandir = @mandir@ - -QMAKE = @QMAKE@ -QTGUI = @QTGUI@ - -RCLLIBVERSION=@RCLLIBVERSION@ - -all: configure mk/sysconf - ${MAKE} -C query wasaparse.tab.cpp - (cd lib; sh mkMake) - ${MAKE} -C lib - ${MAKE} -C index depend recollindex - @NOQTMAKE@(cd $(QTGUI); ${QMAKE} recoll.pro) - @NOQTMAKE@${MAKE} -C $(QTGUI) LFLAGS="$(LDFLAGS)" depth=.. prefix=$(prefix) exec_prefix=$(exec_prefix) libdir=$(libdir) - @NOPYTHON@${MAKE} -C python/recoll libdir=$(libdir) - ${MAKE} -C query recollq xadump - -mk/sysconf: - @echo "You need to run configure first" ; exit 1 - -static: mk/sysconf - ${MAKE} -C lib - rm -f index/recollindex - ${MAKE} -C index BSTATIC=-Wl,-Bstatic BDYNAMIC=-Wl,-Bdynamic \ - LIBXAPIANSTATICEXTRA="@LIBXAPIANSTATICEXTRA@" \ - recollindex - @NOQTMAKE@(cd $(QTGUI); $(QMAKE) recoll.pro) - @NOQTMAKE@rm -f $(QTGUI)/recoll - @NOQTMAKE@${MAKE} -C $(QTGUI) BSTATIC=-Wl,-Bstatic \ - @NOQTMAKE@ BDYNAMIC=-Wl,-Bdynamic depth=.. \ - @NOQTMAKE@ LIBXAPIANSTATICEXTRA="@LIBXAPIANSTATICEXTRA@" - -clean: - ${MAKE} -C common clean - ${MAKE} -C index clean - ${MAKE} -C internfile clean - ${MAKE} -C query clean - ${MAKE} -C utils clean - -${MAKE} -C lib clean - -${MAKE} -C python/recoll clean - @NOQTMAKE@@-${MAKE} -C $(QTGUI) clean - rm -f qtgui/Makefile qtgui/confgui/Makefile qtgui/recoll - rm -f filters/rclexecm.pyc - rm -rf qtgui/.moc qtgui/.ui qtgui/confgui/.moc qtgui/confgui/.ui - rm -rf qtgui/.obj qtgui/.moc qtgui/.ui - rm -rf python/recoll/build - rm -rf php/recoll/build php/recoll/include php/recoll/modules - rm -rf $(QTGUI)/recoll.app - -# Note: we don't remove the top Makefile, to keep the "clean" targets -# available but a "Make" won't work without a configure anyway -distclean: clean - ${MAKE} -C query distclean - -${MAKE} -C python/recoll distclean - rm -f mk/sysconf mk/localdefs sampleconf/recoll.conf \ - qtgui/recoll.pro \ - config.log config.status \ - recollinstall \ - lib/*.dep lib/mkMake lib/Makefile common/autoconfig.h - rm -f common/rclversion.h - rm -f index/alldeps lib/alldeps query/alldeps \ - bincimapmime/alldeps common/alldeps internfile/alldeps utils/alldeps - rm -rf autom4te.cache - -maintainer-clean: distclean - rm -f doc/user/*.html* doc/user/*.txt doc/user/HTML.manifest - rm -f qtgui/i18n/*.qm - -# recollinstall can be executed by the user and will compute 'normal' -# values for bindir etc., relative to the prefix argument. When executed -# here, we pass a bunch of variables in the environment, the values will -# override the computed defaults. -install: all - DESTDIR=${DESTDIR} bindir=${bindir} datadir=${datadir} \ - libdir=${libdir} mandir=${mandir} \ - /bin/sh ./recollinstall ${prefix} - -configure: VERSION configure.ac - rm -f configure - autoconf - @echo run configure and make again - exit 1 -.PHONY: all static clean distclean install diff --git a/src/VERSION b/src/VERSION index 28449774..a6c2798a 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.21.1 +1.23.0 diff --git a/src/aspell/Makefile b/src/aspell/Makefile index 554cd226..598acfca 100644 --- a/src/aspell/Makefile +++ b/src/aspell/Makefile @@ -1,19 +1,12 @@ -depth = .. -include $(depth)/mk/sysconf - PROGS = rclaspell -SRCS = rclaspell.cpp - -all: depend $(PROGS) librecoll +all: $(PROGS) RCLASPELL_OBJS= trrclaspell.o rclaspell : $(RCLASPELL_OBJS) $(CXX) $(ALL_CXXFLAGS) -o rclaspell $(RCLASPELL_OBJS) \ - $(LIBRECOLL) $(LIBXAPIAN) $(LIBICONV) + $(LIBRECOLL) trrclaspell.o : rclaspell.cpp $(CXX) $(ALL_CXXFLAGS) -DTEST_RCLASPELL -c -o trrclaspell.o \ rclaspell.cpp -include $(depth)/mk/commontargets - -include alldeps +include ../utils/utmkdefs.mk diff --git a/src/aspell/rclaspell.cpp b/src/aspell/rclaspell.cpp index f5da8aee..297fa75e 100644 --- a/src/aspell/rclaspell.cpp +++ b/src/aspell/rclaspell.cpp @@ -21,6 +21,8 @@ #ifdef RCL_USE_ASPELL +#include + #include #include #include @@ -30,9 +32,8 @@ #include "pathut.h" #include "execmd.h" #include "rclaspell.h" -#include "debuglog.h" +#include "log.h" #include "unacpp.h" -#include "ptmutex.h" using namespace std; @@ -63,7 +64,7 @@ public: }; static AspellApi aapi; -static PTMutexInit o_aapi_mutex; +static std::mutex o_aapi_mutex; #define NMTOPTR(NM, TP) \ if ((aapi.NM = TP dlsym(m_data->m_handle, #NM)) == 0) { \ @@ -84,7 +85,7 @@ public: : m_handle(0), m_speller(0) {} ~AspellData() { - LOGDEB2(("~AspellData\n")); + LOGDEB2("~AspellData\n" ); if (m_handle) { dlclose(m_handle); m_handle = 0; @@ -93,7 +94,7 @@ public: // Dumps core if I do this?? //aapi.delete_aspell_speller(m_speller); m_speller = 0; - LOGDEB2(("~AspellData: speller done\n")); + LOGDEB2("~AspellData: speller done\n" ); } } @@ -114,7 +115,7 @@ Aspell::~Aspell() bool Aspell::init(string &reason) { - PTMutexLocker locker(o_aapi_mutex); + std::unique_lock locker(o_aapi_mutex); deleteZ(m_data); // Language: we get this from the configuration, else from the NLS @@ -220,8 +221,8 @@ bool Aspell::ok() const string Aspell::dicPath() { - return path_cat(m_config->getConfDir(), - string("aspdict.") + m_lang + string(".rws")); + string ccdir = m_config->getAspellcacheDir(); + return path_cat(ccdir, string("aspdict.") + m_lang + string(".rws")); } @@ -240,9 +241,9 @@ public: {} void newData() { while (m_db.termWalkNext(m_tit, *m_input)) { - LOGDEB2(("Aspell::buildDict: term: [%s]\n", m_input->c_str())); + LOGDEB2("Aspell::buildDict: term: [" << (m_input) << "]\n" ); if (!Rcl::Db::isSpellingCandidate(*m_input)) { - LOGDEB2(("Aspell::buildDict: SKIP\n")); + LOGDEB2("Aspell::buildDict: SKIP\n" ); continue; } if (!o_index_stripchars) { @@ -253,7 +254,7 @@ public: } // Got a non-empty sort-of appropriate term, let's send it to // aspell - LOGDEB2(("Apell::buildDict: SEND\n")); + LOGDEB2("Apell::buildDict: SEND\n" ); m_input->append("\n"); return; } @@ -268,6 +269,9 @@ bool Aspell::buildDict(Rcl::Db &db, string &reason) if (!ok()) return false; + string addCreateParam; + m_config->getConfParam("aspellAddCreateParam", addCreateParam); + // We create the dictionary by executing the aspell command: // aspell --lang=[lang] create master [dictApath] string cmdstring(m_data->m_exec); @@ -277,6 +281,10 @@ bool Aspell::buildDict(Rcl::Db &db, string &reason) cmdstring += string(" ") + string("--lang=") + m_lang; args.push_back("--encoding=utf-8"); cmdstring += string(" ") + "--encoding=utf-8"; + if (!addCreateParam.empty()) { + args.push_back(addCreateParam); + cmdstring += string(" ") + addCreateParam; + } args.push_back("create"); cmdstring += string(" ") + "create"; args.push_back("master"); @@ -363,7 +371,7 @@ bool Aspell::make_speller(string& reason) bool Aspell::check(const string &iterm, string& reason) { - LOGDEB2(("Aspell::check [%s]\n", iterm.c_str())); + LOGDEB2("Aspell::check [" << (iterm) << "]\n" ); string mterm(iterm); if (!ok() || !make_speller(reason)) @@ -374,7 +382,7 @@ bool Aspell::check(const string &iterm, string& reason) if (!o_index_stripchars) { string lower; if (!unacmaybefold(mterm, lower, "UTF-8", UNACOP_FOLD)) { - LOGERR(("Aspell::check : cant lowercase input\n")); + LOGERR("Aspell::check : cant lowercase input\n" ); return false; } mterm.swap(lower); @@ -406,7 +414,7 @@ bool Aspell::suggest(Rcl::Db &db, const string &_term, if (!o_index_stripchars) { string lower; if (!unacmaybefold(mterm, lower, "UTF-8", UNACOP_FOLD)) { - LOGERR(("Aspell::check : cant lowercase input\n")); + LOGERR("Aspell::check : cant lowercase input\n" ); return false; } mterm.swap(lower); @@ -577,3 +585,4 @@ int main(int argc, char **argv) #endif // RCL_USE_ASPELL #endif // TEST_RCLASPELL test driver + diff --git a/src/autogen.sh b/src/autogen.sh new file mode 100755 index 00000000..60c50f0c --- /dev/null +++ b/src/autogen.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +aclocal +libtoolize --copy +automake --add-missing --force-missing --copy +autoconf diff --git a/src/bincimapmime/Makefile b/src/bincimapmime/Makefile index 1faa5898..218b1d52 100644 --- a/src/bincimapmime/Makefile +++ b/src/bincimapmime/Makefile @@ -1,31 +1,11 @@ -# @(#$Id: Makefile,v 1.6 2006-01-19 12:01:42 dockes Exp $ (C) 2005 J.F.Dockes - -depth = .. -include $(depth)/mk/sysconf - -LIBS = libmime.a PROGS = trbinc - -all: depend $(LIBS) - -SRCS = mime-parsefull.cc mime-parseonlyheader.cc \ - mime-printbody.cc mime.cc \ - convert.cc iodevice.cc iofactory.cc - -OBJS = mime-parsefull.o mime-parseonlyheader.o \ - mime-printbody.o mime.o \ - convert.o iodevice.o iofactory.o - -libmime.a : $(OBJS) - $(AR) ru libmime.a $(OBJS) - -.cc.o: - $(CXX) $(ALL_CXXFLAGS) -c $< +all: $(PROGS) TRBINCOBJS = trbinc.o trbinc: trbinc.o - $(CXX) -o trbinc trbinc.o libmime.a + $(CXX) -o trbinc trbinc.o $(LIBRECOLL) -include $(depth)/mk/commontargets +trbinc.o: trbinc.cc + $(CXX) $(ALL_CXXFLAGS) -c -o trbinc.o trbinc.cc -include alldeps +include ../utils/utmkdefs.mk diff --git a/src/bincimapmime/config.h b/src/bincimapmime/config.h deleted file mode 100644 index e69de29b..00000000 diff --git a/src/bincimapmime/convert.cc b/src/bincimapmime/convert.cc index 90e7c1ca..80dd892e 100644 --- a/src/bincimapmime/convert.cc +++ b/src/bincimapmime/convert.cc @@ -23,10 +23,6 @@ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * -------------------------------------------------------------------- */ -#ifdef HAVE_CONFIG_H -#include -#endif - #include "convert.h" #include @@ -47,7 +43,7 @@ BincStream::~BincStream(void) } //------------------------------------------------------------------------ -string BincStream::popString(unsigned int size) +string BincStream::popString(std::string::size_type size) { if (size > nstr.length()) size = nstr.length(); diff --git a/src/bincimapmime/convert.h b/src/bincimapmime/convert.h index 04a41acb..2ed9b3a7 100644 --- a/src/bincimapmime/convert.h +++ b/src/bincimapmime/convert.h @@ -23,19 +23,15 @@ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * -------------------------------------------------------------------- */ -#ifdef HAVE_CONFIG_H -#include -#endif - #ifndef convert_h_included #define convert_h_included +#include #include #include #include #include #include -#include #include #include @@ -98,7 +94,7 @@ namespace Binc { std::string tmp; for (std::string::const_iterator i = s.begin(); i != s.end() && i + 1 != s.end(); i += 2) { - int n; + ptrdiff_t n; unsigned char c = *i; unsigned char d = *(i + 1); @@ -127,7 +123,7 @@ namespace Binc { for (std::string::const_iterator i = s_in.begin(); i != s_in.end(); ++i) { unsigned char c = (unsigned char)*i; if (c <= 31 || c >= 127 || c == '\"' || c == '\\') - return "{" + toString(s_in.length()) + "}\r\n" + s_in; + return "{" + toString((unsigned long)s_in.length()) + "}\r\n" + s_in; } return "\"" + s_in + "\""; @@ -150,7 +146,7 @@ namespace Binc { //---------------------------------------------------------------------- inline void chomp(std::string &s_in, const std::string &chars = " \t\r\n") { - int n = s_in.length(); + std::string::size_type n = s_in.length(); while (n > 1 && chars.find(s_in[n - 1]) != std::string::npos) s_in.resize(n-- - 1); } @@ -295,7 +291,7 @@ namespace Binc { BincStream &operator << (char t); //-- - std::string popString(unsigned int size); + std::string popString(std::string::size_type size); //-- char popChar(void); diff --git a/src/bincimapmime/iodevice.cc b/src/bincimapmime/iodevice.cc deleted file mode 100644 index 6561f0ca..00000000 --- a/src/bincimapmime/iodevice.cc +++ /dev/null @@ -1,322 +0,0 @@ -/*-*-mode:c++-*-*/ -/* -------------------------------------------------------------------- - * Filename: - * src/iodevice.cc - * - * Description: - * Implementation of the IODevice class. - * -------------------------------------------------------------------- - * Copyright 2002, 2003 Andreas Aardal Hanssen - * - * 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 - * (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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. - * -------------------------------------------------------------------- - */ -#include "iodevice.h" -#include "convert.h" // BincStream -//#include "session.h" // getEnv/hasEnv - -#include -#include - -#ifndef NO_NAMESPACES -using namespace ::std; -using namespace ::Binc; -#endif /* NO_NAMESPACES */ - -//------------------------------------------------------------------------ -IODevice::IODevice(int f) : flags(f | IsEnabled), - maxInputBufferSize(0), - maxOutputBufferSize(0), - timeout(0), - readCount(0), writeCount(0), - outputLevel(ErrorLevel), - outputLevelLimit(ErrorLevel), - error(Unknown), errorString("Unknown error"), - dumpfd(0) -{ -} - -//------------------------------------------------------------------------ -IODevice::~IODevice(void) -{ -} - -//------------------------------------------------------------------------ -IODevice &IODevice::operator <<(ostream &(*source)(ostream &)) -{ - if (!(flags & IsEnabled) || outputLevel > outputLevelLimit) - return *this; - - static std::ostream &(*endl_funcptr)(ostream &) = endl; - - if (source != endl_funcptr) - return *this; - - outputBuffer << "\r\n"; - - if (dumpfd) - ::write(dumpfd, "\r\n", 2); - - if (flags & FlushesOnEndl) - flush(); - else if (flags & HasOutputLimit) - if (outputBuffer.getSize() > maxOutputBufferSize) - flush(); - - return *this; -} - -//------------------------------------------------------------------------ -bool IODevice::canRead(void) const -{ - return false; -} - -//------------------------------------------------------------------------ -void IODevice::clear() -{ - if (!(flags & IsEnabled)) - return; - - inputBuffer.clear(); - outputBuffer.clear(); -} - -//------------------------------------------------------------------------ -bool IODevice::flush() -{ - if (!(flags & IsEnabled)) - return true; - - WriteResult writeResult = WriteWait; - do { - unsigned int s = outputBuffer.getSize(); - if (s == 0) - break; - - if (!waitForWrite()) - return false; - - writeResult = write(); - if (writeResult == WriteError) - return false; - - writeCount += s - outputBuffer.getSize(); - } while (outputBuffer.getSize() > 0 && writeResult == WriteWait); - - outputBuffer.clear(); - return true; -} - -//------------------------------------------------------------------------ -void IODevice::setFlags(unsigned int f) -{ - flags |= f; -} - -//------------------------------------------------------------------------ -void IODevice::clearFlags(unsigned int f) -{ - flags &= ~f; -} - -//------------------------------------------------------------------------ -void IODevice::setMaxInputBufferSize(unsigned int max) -{ - maxInputBufferSize = max; -} - -//------------------------------------------------------------------------ -void IODevice::setMaxOutputBufferSize(unsigned int max) -{ - maxOutputBufferSize = max; -} - -//------------------------------------------------------------------------ -void IODevice::setTimeout(unsigned int t) -{ - timeout = t; - - if (t) - flags |= HasTimeout; - else - flags &= ~HasTimeout; -} - -//------------------------------------------------------------------------ -unsigned int IODevice::getTimeout(void) const -{ - return timeout; -} - -//------------------------------------------------------------------------ -void IODevice::setOutputLevel(LogLevel level) -{ - outputLevel = level; -} - -//------------------------------------------------------------------------ -IODevice::LogLevel IODevice::getOutputLevel(void) const -{ - return outputLevel; -} - -//------------------------------------------------------------------------ -void IODevice::setOutputLevelLimit(LogLevel level) -{ - outputLevelLimit = level; -} - -//------------------------------------------------------------------------ -IODevice::LogLevel IODevice::getOutputLevelLimit(void) const -{ - return outputLevelLimit; -} - -//------------------------------------------------------------------------ -bool IODevice::readStr(string *dest, unsigned int max) -{ - // If max is 0, fill the input buffer once only if it's empty. - if (!max && inputBuffer.getSize() == 0 && !fillInputBuffer()) - return false; - - // If max is != 0, wait until we have max. - while (max && inputBuffer.getSize() < max) { - if (!fillInputBuffer()) - return false; - } - - unsigned int bytesToRead = max ? max : inputBuffer.getSize(); - *dest += inputBuffer.str().substr(0, bytesToRead); - if (dumpfd) { - ::write(dumpfd, inputBuffer.str().substr(0, bytesToRead).c_str(), - bytesToRead); - } - - inputBuffer.popString(bytesToRead); - readCount += bytesToRead; - return true; -} - -//------------------------------------------------------------------------ -bool IODevice::readChar(char *dest) -{ - if (inputBuffer.getSize() == 0 && !fillInputBuffer()) - return false; - - char c = inputBuffer.popChar(); - if (dest) - *dest = c; - if (dumpfd) - ::write(dumpfd, &c, 1); - - ++readCount; - return true; -} - -//------------------------------------------------------------------------ -void IODevice::unreadChar(char c) -{ - inputBuffer.unpopChar(c); -} - -//------------------------------------------------------------------------ -void IODevice::unreadStr(const string &s) -{ - inputBuffer.unpopStr(s); -} - -//------------------------------------------------------------------------ -bool IODevice::skipTo(char c) -{ - char dest = '\0'; - do { - if (!readChar(&dest)) - return false; - if (dumpfd) - ::write(dumpfd, &dest, 1); - } while (c != dest); - - return true; -} - -//------------------------------------------------------------------------ -string IODevice::service(void) const -{ - return "nul"; -} - -//------------------------------------------------------------------------ -bool IODevice::waitForWrite(void) const -{ - return false; -} - -//------------------------------------------------------------------------ -bool IODevice::waitForRead(void) const -{ - return false; -} - -//------------------------------------------------------------------------ -IODevice::WriteResult IODevice::write(void) -{ - return WriteError; -} - -//------------------------------------------------------------------------ -bool IODevice::fillInputBuffer(void) -{ - return false; -} - -//------------------------------------------------------------------------ -IODevice::Error IODevice::getLastError(void) const -{ - return error; -} - -//------------------------------------------------------------------------ -string IODevice::getLastErrorString(void) const -{ - return errorString; -} - -//------------------------------------------------------------------------ -unsigned int IODevice::getReadCount(void) const -{ - return readCount; -} - -//------------------------------------------------------------------------ -unsigned int IODevice::getWriteCount(void) const -{ - return writeCount; -} - -//------------------------------------------------------------------------ -void IODevice::enableProtocolDumping(void) -{ -#if 0 - BincStream ss; - ss << "/tmp/bincimap-dump-" << (int) time(0) << "-" - << Session::getInstance().getIP() << "-XXXXXX"; - char *safename = strdup(ss.str().c_str()); - dumpfd = mkstemp(safename); - if (dumpfd == -1) - dumpfd = 0; - delete safename; -#endif -} diff --git a/src/bincimapmime/iodevice.h b/src/bincimapmime/iodevice.h deleted file mode 100644 index a7bd0528..00000000 --- a/src/bincimapmime/iodevice.h +++ /dev/null @@ -1,401 +0,0 @@ -/*-*-mode:c++;c-basic-offset:2-*-*/ -/* -------------------------------------------------------------------- - * Filename: - * src/iodevice.h - * - * Description: - * Declaration of the IODevice class. - * -------------------------------------------------------------------- - * Copyright 2002, 2003 Andreas Aardal Hanssen - * - * 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 - * (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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. - * -------------------------------------------------------------------- - */ -#ifndef iodevice_h_included -#define iodevice_h_included -#ifdef HAVE_CONFIG_H -#include -#endif - -#include "convert.h" // BincStream -#include -#include // ::write - -namespace Binc { - /*! - \class IODevice - \brief The IODevice class provides a framework for reading and - writing to device. - - Implement new devices by inheriting this class and overloading all - virtual methods. - - service() returns the service that the specific device is used - for. Two values are "log" and "client". - - \sa IOFactory, MultilogDevice, SyslogDevice, StdIODevice, SSLDevice - */ - class IODevice { - public: - /*! - Standard options for an IODevice. - */ - enum Flags { - None = 0, - FlushesOnEndl = 1 << 0, - HasInputLimit = 1 << 1, - HasOutputLimit = 1 << 2, - IsEnabled = 1 << 3, - HasTimeout = 1 << 4 - }; - - /*! - Errors from when an operation returned false. - */ - enum Error { - Unknown, - Timeout - }; - - /*! - Constructs an invalid IODevice. - - Instances of IODevice perform no operations, and all boolean - functions always return false. This constructor is only useful - if called from a subclass that reimplements all virtual methods. - */ - IODevice(int f = 0); - - /*! - Destructs an IODevice; does nothing. - */ - virtual ~IODevice(void); - - /*! - Clears all data in the input and output buffers. - */ - void clear(void); - - /*! - Sets one or more flags. - \param f A bitwise OR of flags from the Flags enum. - */ - void setFlags(unsigned int f); - - /*! - Clears one or more flags. - \param f A bitwise OR of flags from the Flags enum. - */ - void clearFlags(unsigned int f); - - /*! - Sets the maximum allowed input buffer size. If this size is - non-zero and exceeded, reading from the device will fail. This - functionality is used to prevent clients from forcing this class - to consume so much memory that the program crashes. - - Setting the max input buffer size to 0 disables the input size - limit. - - \param max The maximum input buffer size in bytes. - */ - void setMaxInputBufferSize(unsigned int max); - - /*! - Sets the maximum allowed output buffer size. If this size is - non-zero and exceeded, flush() is called implicitly. - - Setting the max output buffer size to 0 disables the output size - limit. This is generally discouraged. - - As a contrast to setMaxInputBufferSize(), this function is used - to bundle up consequent write calls, allowing more efficient use - of the underlying device as larger blocks of data are written at - a time. - - \param max The maximum output buffer size in bytes. - */ - void setMaxOutputBufferSize(unsigned int max); - - /*! - Sets the device's internal timeout in seconds. This timeout is - used both when waiting for data to read and for waiting for the - ability to write. - - If this timeout is exceeded, the read or write function that - triggered the timeout will fail. - - Setting the timeout to 0 disables the timeout. - - \param t The timeout in seconds. - \sa getTimeout() - */ - void setTimeout(unsigned int t); - - /*! - Returns the timeout in seconds, or 0 if there is no timeout. - - \sa setTimeout() - */ - unsigned int getTimeout(void) const; - - enum LogLevel { - ErrorLevel, - InfoLevel, - WarningLevel, - DebugLevel - }; - - /*! - Sets the output level for the following write operations on this - device. - - The output level is a number which gives the following write - operations a priority. You can use setOutputLevelLimit() to - filter the write operations valid for different operating modes. - This enables you to have certain write operations ignored. - - For instance, if the output level is set to 0, then "Hello" is - written, and the output level is set to 1, followed by writing - "Daisy", the output level limit value will decive wether only - "Hello" is written, or if also "Daisy" is written. - - A low value of the level gives higher priority, and a high level - will give low priority. The default value is 0, and write - operations that are done with output level 0 are never ignored. - - \param level The output level - \sa getOutputLevel(), setOutputLevelLimit() - */ - void setOutputLevel(LogLevel level); - - /*! - Returns the current output level. - - \sa setOutputLevel() - */ - LogLevel getOutputLevel(void) const; - - /*! - Sets the current output level limit. Write operations with a - level higher than the output level limit are ignored. - - \param level The output level limit - \sa setOutputLevel() - */ - void setOutputLevelLimit(LogLevel level); - - /*! - Returns the current output level limit. - - \sa setOutputLevelLimit() - */ - LogLevel getOutputLevelLimit(void) const; - - /*! - Returns the number of bytes that have been read from this device - since it was created. - */ - unsigned int getReadCount(void) const; - - /*! - Returns the number of bytes that have been written to this - device since it was created. - */ - unsigned int getWriteCount(void) const; - - /*! - Calling this function enables the built-in protocol dumping feature in - the device. All input and output to this device will be dumped to a file - in /tmp. - */ - void enableProtocolDumping(void); - - /*! - Writes data to the device. Depending on the value of the max - output buffer size, the data may not be written immediately. - - \sa setMaxOutputBufferSize() - */ - template IODevice &operator <<(const T &source); - - /*! - Writes data to the device. This function specializes on standard - ostream derivates, such as std::endl. - */ - IODevice &operator <<(std::ostream &(*source)(std::ostream &)); - - /*! - Returns true if data can be read from the device; otherwise - returns false. - */ - virtual bool canRead(void) const; - - /*! - Reads data from the device, and stores this in a string. Returns - true on success; otherwise returns false. - - \param dest The incoming data is stored in this string. - \param max No more than this number of bytes is read from the - device. - */ - bool readStr(std::string *dest, unsigned int max = 0); - - /*! - Reads exactly one byte from the device and stores this in a - char. Returns true on success; otherwise returns false. - - \param dest The incoming byte is stored in this char. - */ - bool readChar(char *dest = 0); - - /*! - FIXME: add docs - */ - void unreadChar(char c); - - /*! - FIXME: add docs - */ - void unreadStr(const std::string &s); - - /*! - Reads characters from the device, until and including one - certain character is found. All read characters are discarded. - - This function can be used to skip to the beginning of a line, - with the terminating character being '\n'. - - \param The certain character. - */ - bool skipTo(char c); - - /*! - Flushes the output buffer. Writes all data in the output buffer - to the device. - */ - bool flush(void); - - /*! - Returns the type of error that most recently occurred. - */ - Error getLastError(void) const; - - /*! - Returns a human readable description of the error that most - recently occurred. If no known error has occurred, this method - returns "Unknown error". - */ - std::string getLastErrorString(void) const; - - /*! - Returns the type of service provided by this device. Two valid - return values are "client" and "log". - */ - virtual std::string service(void) const; - - protected: - /*! - Waits until data can be written to the device. If the timeout is - 0, this function waits indefinitely. Otherwise, it waits until - the timeout has expired. - - If this function returns true, data can be written to the - device; otherwise, getLastError() must be checked to determine - whether a timeout occurred or whether an error with the device - prevents further writing. - */ - virtual bool waitForWrite(void) const; - - /*! - Waits until data can be read from the device. - - \sa waitForWrite() - */ - virtual bool waitForRead(void) const; - - /*! - Types of results from a write. - */ - enum WriteResult { - WriteWait = 0, - WriteDone = 1 << 0, - WriteError = 1 << 1 - }; - - /*! - Writes as much data as possible to the device. If some but not - all data was written, returns WriteWait. If all data was - written, returns WriteDone. If an error occurred, returns - WriteError. - */ - virtual WriteResult write(void); - - /*! - Reads data from the device, and stores it in the input buffer. - Returns true on success; otherwise returns false. - - This method will fail if there is no more data available, if a - timeout occurred or if an error with the device prevents more - data from being read. - - The number of bytes read from the device is undefined. - */ - virtual bool fillInputBuffer(void); - - BincStream inputBuffer; - BincStream outputBuffer; - - protected: - unsigned int flags; - unsigned int maxInputBufferSize; - unsigned int maxOutputBufferSize; - - unsigned int timeout; - - unsigned int readCount; - unsigned int writeCount; - - LogLevel outputLevel; - LogLevel outputLevelLimit; - - mutable Error error; - mutable std::string errorString; - - int dumpfd; - }; - - //---------------------------------------------------------------------- - template IODevice &IODevice::operator <<(const T &source) - { - if ((flags & IsEnabled) && outputLevel <= outputLevelLimit) { - outputBuffer << source; - - if (dumpfd) { - BincStream ss; - ss << source; - ::write(dumpfd, ss.str().c_str(), ss.getSize()); - } - - if (flags & HasInputLimit) - if (outputBuffer.getSize() > maxOutputBufferSize) - flush(); - } - - return *this; - } -} - -#endif diff --git a/src/bincimapmime/iofactory.cc b/src/bincimapmime/iofactory.cc deleted file mode 100644 index 04b6999e..00000000 --- a/src/bincimapmime/iofactory.cc +++ /dev/null @@ -1,87 +0,0 @@ -/*-*-mode:c++-*-*/ -/* -------------------------------------------------------------------- - * Filename: - * src/iofactory.cc - * - * Description: - * Implementation of the IOFactory class. - * -------------------------------------------------------------------- - * Copyright 2002, 2003 Andreas Aardal Hanssen - * - * 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 - * (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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. - * -------------------------------------------------------------------- - */ -#include "iofactory.h" -#include "iodevice.h" - -#ifndef NO_NAMESPACES -using namespace ::Binc; -using namespace ::std; -#endif /* NO_NAMESPACES */ - -//------------------------------------------------------------------------ -IOFactory::IOFactory(void) -{ -} - -//------------------------------------------------------------------------ -IOFactory::~IOFactory(void) -{ -} - -//------------------------------------------------------------------------ -IOFactory &IOFactory::getInstance(void) -{ - static IOFactory ioFactory; - return ioFactory; -} - -//------------------------------------------------------------------------ -void IOFactory::addDevice(IODevice *dev) -{ - IODevice *ioDevice = IOFactory::getInstance().devices[dev->service()]; - - // FIXME: Delete correct object. Now, only IODevice's destructor is - // called, and only IODevice's memory is freed. - if (ioDevice) - delete ioDevice; - - IOFactory::getInstance().devices[dev->service()] = dev; -} - -//------------------------------------------------------------------------ -IODevice &IOFactory::getClient(void) -{ - static IODevice nulDevice; - - IOFactory &ioFactory = IOFactory::getInstance(); - - if (ioFactory.devices.find("client") != ioFactory.devices.end()) - return *ioFactory.devices["client"]; - - return nulDevice; -} - -//------------------------------------------------------------------------ -IODevice &IOFactory::getLogger(void) -{ - static IODevice nulDevice; - - IOFactory &ioFactory = IOFactory::getInstance(); - - if (ioFactory.devices.find("log") != ioFactory.devices.end()) - return *ioFactory.devices["log"]; - return nulDevice; -} diff --git a/src/bincimapmime/iofactory.h b/src/bincimapmime/iofactory.h deleted file mode 100644 index 2c7b8157..00000000 --- a/src/bincimapmime/iofactory.h +++ /dev/null @@ -1,69 +0,0 @@ -/*-*-mode:c++-*-*/ -/* -------------------------------------------------------------------- - * Filename: - * src/iofactory.h - * - * Description: - * Declaration of the IOFactory class. - * -------------------------------------------------------------------- - * Copyright 2002, 2003 Andreas Aardal Hanssen - * - * 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 - * (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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. - * -------------------------------------------------------------------- - */ -#ifndef IOFACTORY_H_INCLUDED -#define IOFACTORY_H_INCLUDED -#include -#include - -#include "iodevice.h" - -namespace Binc { - class IOFactory { - public: - ~IOFactory(void); - - static void addDevice(IODevice *dev); - static IOFactory &getInstance(void); - static IODevice &getClient(void); - static IODevice &getLogger(void); - - private: - IOFactory(void); - - std::map devices; - }; -} - -#define bincClient \ - IOFactory::getClient() - -#if !defined (DEBUG) -#define bincError if (false) std::cout -#define bincWarning if (false) std::cout -#define bincDebug if (false) std::cout -#else -#define bincError \ - IOFactory::getLogger().setOutputLevel(IODevice::ErrorLevel);IOFactory::getLogger() -#define bincWarning \ - IOFactory::getLogger().setOutputLevel(IODevice::WarningLevel);IOFactory::getLogger() -#define bincDebug \ - IOFactory::getLogger().setOutputLevel(IODevice::DebugLevel);IOFactory::getLogger() -#endif - -#define bincInfo \ - IOFactory::getLogger().setOutputLevel(IODevice::InfoLevel);IOFactory::getLogger() - -#endif diff --git a/src/bincimapmime/mime-inputsource.h b/src/bincimapmime/mime-inputsource.h index 49752b39..977d7689 100644 --- a/src/bincimapmime/mime-inputsource.h +++ b/src/bincimapmime/mime-inputsource.h @@ -25,13 +25,19 @@ */ #ifndef mime_inputsource_h_included #define mime_inputsource_h_included +#include "autoconfig.h" +// Data source for MIME parser -#ifdef HAVE_CONFIG_H -#include -#endif +// Note about large files: we might want to change the unsigned int +// used for offsets into an off_t for intellectual satisfaction, but +// in the context of recoll, we could only get into trouble if a +// *single message* exceeded 2GB, which seems rather unlikely. When +// parsing a mailbox files, we read each message in memory and use the +// stream input source (from a memory buffer, no file offsets). When +// parsing a raw message file, it's only one message. #include -#include +#include "safeunistd.h" #include @@ -43,7 +49,7 @@ namespace Binc { inline MimeInputSource(int fd, unsigned int start = 0); virtual inline ~MimeInputSource(void); - virtual inline size_t fillRaw(char *raw, size_t nbytes); + virtual inline ssize_t fillRaw(char *raw, size_t nbytes); virtual inline void reset(void); virtual inline bool fillInputBuffer(void); @@ -81,7 +87,7 @@ namespace Binc { { } - inline size_t MimeInputSource::fillRaw(char *raw, size_t nbytes) + inline ssize_t MimeInputSource::fillRaw(char *raw, size_t nbytes) { return read(fd, raw, nbytes); } @@ -173,7 +179,7 @@ namespace Binc { class MimeInputSourceStream : public MimeInputSource { public: inline MimeInputSourceStream(istream& s, unsigned int start = 0); - virtual inline size_t fillRaw(char *raw, size_t nb); + virtual inline ssize_t fillRaw(char *raw, size_t nb); virtual inline void reset(void); private: istream& s; @@ -185,7 +191,7 @@ namespace Binc { { } - inline size_t MimeInputSourceStream::fillRaw(char *raw, size_t nb) + inline ssize_t MimeInputSourceStream::fillRaw(char *raw, size_t nb) { // Why can't streams tell how many characters were actually read // when hitting eof ? @@ -193,16 +199,16 @@ namespace Binc { s.seekg(0, ios::end); std::streampos lst = s.tellg(); s.seekg(st); - size_t nbytes = lst - st; + size_t nbytes = size_t(lst - st); if (nbytes > nb) { nbytes = nb; } if (nbytes <= 0) { - return (size_t)-1; + return (ssize_t)-1; } s.read(raw, nbytes); - return nbytes; + return static_cast(nbytes); } inline void MimeInputSourceStream::reset(void) diff --git a/src/bincimapmime/mime-parsefull.cc b/src/bincimapmime/mime-parsefull.cc index 9e9a43e3..2c81b5a7 100644 --- a/src/bincimapmime/mime-parsefull.cc +++ b/src/bincimapmime/mime-parsefull.cc @@ -23,10 +23,6 @@ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * -------------------------------------------------------------------- */ -#ifdef HAVE_CONFIG_H -#include -#endif - #include #include #include @@ -306,9 +302,9 @@ void Binc::MimePart::parseMessageRFC822(vector *members, bool Binc::MimePart::skipUntilBoundary(const string &delimiter, unsigned int *nlines, bool *eof) { - int endpos = delimiter.length(); + string::size_type endpos = delimiter.length(); char *delimiterqueue = 0; - int delimiterpos = 0; + string::size_type delimiterpos = 0; const char *delimiterStr = delimiter.c_str(); if (delimiter != "") { delimiterqueue = new char[endpos]; @@ -340,7 +336,7 @@ bool Binc::MimePart::skipUntilBoundary(const string &delimiter, delimiterpos = 0; if (compareStringToQueue(delimiterStr, delimiterqueue, - delimiterpos, endpos)) { + delimiterpos, int(endpos))) { foundBoundary = true; break; } @@ -451,7 +447,7 @@ void Binc::MimePart::parseMultipart(const string &boundary, skipUntilBoundary(delimiter, nlines, eof); if (!eof) - *boundarysize = delimiter.size(); + *boundarysize = int(delimiter.size()); postBoundaryProcessing(eof, nlines, boundarysize, foundendofpart); @@ -484,7 +480,7 @@ void Binc::MimePart::parseMultipart(const string &boundary, skipUntilBoundary(delimiter, nlines, eof); if (!*eof) - *boundarysize = delimiter.size(); + *boundarysize = int(delimiter.size()); postBoundaryProcessing(eof, nlines, boundarysize, foundendofpart); } @@ -528,7 +524,7 @@ void Binc::MimePart::parseSinglePart(const string &toboundary, // *boundarysize = _toboundary.length(); char *boundaryqueue = 0; - int endpos = _toboundary.length(); + size_t endpos = _toboundary.length(); if (toboundary != "") { boundaryqueue = new char[endpos]; memset(boundaryqueue, 0, endpos); @@ -540,7 +536,7 @@ void Binc::MimePart::parseSinglePart(const string &toboundary, string line; bool toboundaryIsEmpty = (toboundary == ""); char c; - int boundarypos = 0; + string::size_type boundarypos = 0; while (mimeSource->getChar(&c)) { if (c == '\n') { ++*nbodylines; ++*nlines; } @@ -553,8 +549,8 @@ void Binc::MimePart::parseSinglePart(const string &toboundary, boundarypos = 0; if (compareStringToQueue(_toboundaryStr, boundaryqueue, - boundarypos, endpos)) { - *boundarysize = _toboundary.length(); + boundarypos, int(endpos))) { + *boundarysize = static_cast(_toboundary.length()); break; } } diff --git a/src/bincimapmime/mime-parseonlyheader.cc b/src/bincimapmime/mime-parseonlyheader.cc index b65f9fa9..233cb858 100644 --- a/src/bincimapmime/mime-parseonlyheader.cc +++ b/src/bincimapmime/mime-parseonlyheader.cc @@ -23,10 +23,6 @@ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * -------------------------------------------------------------------- */ -#ifdef HAVE_CONFIG_H -#include -#endif - #include "mime.h" #include "mime-utils.h" #include "mime-inputsource.h" @@ -119,7 +115,7 @@ int Binc::MimePart::doParseOnlyHeader(MimeInputSource *ms, if (c == '\n') ++nlines; if (c == ':') break; if (c == '\n') { - for (int i = name.length() - 1; i >= 0; --i) + for (int i = int(name.length()) - 1; i >= 0; --i) mimeSource->ungetChar(); quit = true; diff --git a/src/bincimapmime/mime-printbody.cc b/src/bincimapmime/mime-printbody.cc index 39d5b650..6f8268f6 100644 --- a/src/bincimapmime/mime-printbody.cc +++ b/src/bincimapmime/mime-printbody.cc @@ -23,52 +23,14 @@ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * -------------------------------------------------------------------- */ -#ifdef HAVE_CONFIG_H -#include -#endif #include "mime.h" #include "mime-utils.h" #include "mime-inputsource.h" -#include "convert.h" -#include "iodevice.h" -#include "iofactory.h" - #include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef NO_NAMESPACES using namespace ::std; -#endif /* NO_NAMESPACES */ - -//------------------------------------------------------------------------ -void Binc::MimePart::printBody(IODevice &output, - unsigned int startoffset, - unsigned int length) const -{ - mimeSource->reset(); - mimeSource->seek(bodystartoffsetcrlf + startoffset); - - if (startoffset + length > bodylength) - length = bodylength - startoffset; - - char c = '\0'; - for (unsigned int i = 0; i < length; ++i) { - if (!mimeSource->getChar(&c)) - break; - - output << (char)c; - } -} void Binc::MimePart::getBody(string &s, unsigned int startoffset, diff --git a/src/bincimapmime/mime-utils.h b/src/bincimapmime/mime-utils.h index 19b10b63..93b5707f 100644 --- a/src/bincimapmime/mime-utils.h +++ b/src/bincimapmime/mime-utils.h @@ -25,11 +25,6 @@ */ #ifndef mime_utils_h_included #define mime_utils_h_included - -#ifdef HAVE_CONFIG_H -#include -#endif - #include #include #include diff --git a/src/bincimapmime/mime.cc b/src/bincimapmime/mime.cc index dfe858f7..7b1ba842 100644 --- a/src/bincimapmime/mime.cc +++ b/src/bincimapmime/mime.cc @@ -23,10 +23,6 @@ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * -------------------------------------------------------------------- */ -#ifdef HAVE_CONFIG_H -#include -#endif - #include #include #include diff --git a/src/bincimapmime/mime.h b/src/bincimapmime/mime.h index 75de6008..d047c917 100644 --- a/src/bincimapmime/mime.h +++ b/src/bincimapmime/mime.h @@ -23,10 +23,6 @@ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * -------------------------------------------------------------------- */ -#ifdef HAVE_CONFIG_H -#include -#endif - #ifndef mime_h_included #define mime_h_included #include diff --git a/src/bincimapmime/trbinc.cc b/src/bincimapmime/trbinc.cc index d11c4930..fe2db3d1 100644 --- a/src/bincimapmime/trbinc.cc +++ b/src/bincimapmime/trbinc.cc @@ -1,4 +1,4 @@ -/* Copyright (C) 2006 J.F.Dockes */ +/* Copyright (C) 2006 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 @@ -17,10 +17,10 @@ #include #include -#include #include +#include "safefcntl.h" +#include "safeunistd.h" #include -#include #include diff --git a/src/common/Makefile b/src/common/Makefile index cd37018e..c30268bc 100644 --- a/src/common/Makefile +++ b/src/common/Makefile @@ -1,33 +1,33 @@ -depth = .. -include $(depth)/mk/sysconf -# Only test executables get build in here -PROGS = unacpp textsplit rclconfig +PROGS = unacpp textsplit rclconfig syngroups -all: librecoll $(PROGS) +all: $(PROGS) UNACPP_OBJS= trunacpp.o unacpp : $(UNACPP_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o unacpp $(UNACPP_OBJS) \ - $(LIBRECOLL) $(LIBICONV) + $(CXX) $(ALL_CXXFLAGS) -o unacpp $(UNACPP_OBJS) $(LIBRECOLL) trunacpp.o : unacpp.cpp unacpp.h $(CXX) $(ALL_CXXFLAGS) -DTEST_UNACPP -c -o trunacpp.o unacpp.cpp TEXTSPLIT_OBJS= trtextsplit.o textsplit : $(TEXTSPLIT_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o textsplit $(TEXTSPLIT_OBJS) \ - $(LIBRECOLL) $(LIBICONV) + $(CXX) $(ALL_CXXFLAGS) -o textsplit $(TEXTSPLIT_OBJS) $(LIBRECOLL) trtextsplit.o : textsplit.cpp $(CXX) $(ALL_CXXFLAGS) -DTEST_TEXTSPLIT -c -o trtextsplit.o \ textsplit.cpp RCLCONFIG_OBJS= trrclconfig.o rclconfig : $(RCLCONFIG_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o rclconfig $(RCLCONFIG_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) $(ALL_CXXFLAGS) -o rclconfig $(RCLCONFIG_OBJS) $(LIBRECOLL) trrclconfig.o : rclconfig.cpp $(CXX) $(ALL_CXXFLAGS) -DTEST_RCLCONFIG -c -o trrclconfig.o \ rclconfig.cpp -include $(depth)/mk/commontargets +SYNGROUPS_OBJS= trsyngroups.o +syngroups : $(SYNGROUPS_OBJS) + $(CXX) $(ALL_CXXFLAGS) -o syngroups $(SYNGROUPS_OBJS) $(LIBRECOLL) +trsyngroups.o : syngroups.cpp + $(CXX) $(ALL_CXXFLAGS) -DTEST_SYNGROUPS -c -o trsyngroups.o \ + syngroups.cpp +include ../utils/utmkdefs.mk diff --git a/src/common/autoconfig-win.h b/src/common/autoconfig-win.h new file mode 100644 index 00000000..948f7a3e --- /dev/null +++ b/src/common/autoconfig-win.h @@ -0,0 +1,183 @@ +/* Manually edited version of autoconfig.h for windows. Many things are +overriden in the c++ code by ifdefs _WIN32 anyway */ +#ifndef _AUTOCONFIG_H_INCLUDED +#define _AUTOCONFIG_H_INCLUDED +/* Define if building universal (internal helper macro) */ +/* #undef AC_APPLE_UNIVERSAL_BUILD */ + +/* Path to the aspell api include file */ +/* #undef ASPELL_INCLUDE "aspell-local.h" */ + +/* Path to the aspell program */ +/* #define ASPELL_PROG "/usr/bin/aspell" */ + +/* No X11 session monitoring support */ +#define DISABLE_X11MON + +/* Path to the fam api include file */ +/* #undef FAM_INCLUDE */ + +/* Path to the file program */ +#define FILE_PROG "/usr/bin/file" + +/* "Have C++0x" */ +#define HAVE_CXX0X_UNORDERED 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `dl' library (-ldl). */ +#define HAVE_LIBDL 1 + +/* Define to 1 if you have the `z' library (-lz). */ +#define HAVE_LIBZ 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `mkdtemp' function. */ +/* #undef HAVE_MKDTEMP */ + +/* Define to 1 if you have the `posix_spawn,' function. */ +/* #undef HAVE_POSIX_SPAWN_ */ + +/* Define to 1 if you have the `setrlimit' function. */ +#define HAVE_SETRLIMIT 1 + +/* Has std::shared_ptr */ +#define HAVE_SHARED_PTR_STD + +/* Has std::tr1::shared_ptr */ +/* #undef HAVE_SHARED_PTR_TR1 */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SPAWN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_MOUNT_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_PARAM_H_ */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_STATFS_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_STATVFS_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_VFS_H */ + +/* "Have tr1" */ +/* #undef HAVE_TR1_UNORDERED */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_UNISTD_H */ + +/* Use multiple threads for indexing */ +#define IDX_THREADS 1 + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#define LT_OBJDIR ".libs/" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "Recoll" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "Recoll 1.23.0-x" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "recoll" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "1.23.0-x" + +/* putenv parameter is const */ +/* #undef PUTENV_ARG_CONST */ + +/* iconv parameter 2 is const char** */ +#define RCL_ICONV_INBUF_CONST 1 + +/* Real time monitoring option */ +#undef RCL_MONITOR + +/* Split camelCase words */ +/* #undef RCL_SPLIT_CAMELCASE */ + +/* Compile the aspell interface */ +/* #undef RCL_USE_ASPELL */ + +/* Compile the fam interface */ +/* #undef RCL_USE_FAM */ + +/* Compile the inotify interface */ +#define RCL_USE_INOTIFY 1 + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Use posix_spawn() */ +/* #undef USE_POSIX_SPAWN */ + +/* Enable using the system's 'file' command to id mime if we fail internally + */ +/* #undef USE_SYSTEM_FILE_COMMAND */ + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#if defined AC_APPLE_UNIVERSAL_BUILD +# if defined __BIG_ENDIAN__ +# define WORDS_BIGENDIAN 1 +# endif +#else +# ifndef WORDS_BIGENDIAN +/* # undef WORDS_BIGENDIAN */ +# endif +#endif + +/* Define to 1 if the X Window System is missing or not being used. */ +/* #undef X_DISPLAY_MISSING */ + +/* Enable large inode numbers on Mac OS X 10.5. */ +#ifndef _DARWIN_USE_64_BIT_INODE +# define _DARWIN_USE_64_BIT_INODE 1 +#endif + +/* Number of bits in a file offset, on hosts where this is settable. */ +/* #undef _FILE_OFFSET_BITS */ + +/* Define for large files, on AIX-style hosts. */ +/* #undef _LARGE_FILES */ + +#define DISABLE_WEB_INDEXER + +#include "conf_post.h" +#endif // already included diff --git a/src/common/autoconfig.h.in b/src/common/autoconfig.h.in index 71945aca..545d914c 100644 --- a/src/common/autoconfig.h.in +++ b/src/common/autoconfig.h.in @@ -1,5 +1,8 @@ /* common/autoconfig.h.in. Generated from configure.ac by autoheader. */ +/* Define if building universal (internal helper macro) */ +#undef AC_APPLE_UNIVERSAL_BUILD + /* Path to the aspell api include file */ #undef ASPELL_INCLUDE @@ -15,23 +18,45 @@ /* Path to the file program */ #undef FILE_PROG -/* Define to 1 if you have the header file. */ -#undef HAVE_SPAWN_H -#undef HAVE_POSIX_SPAWN -#undef USE_POSIX_SPAWN +/* "Have C++0x" */ +#undef HAVE_CXX0X_UNORDERED -/* Define to 1 if you have the setrlimit() call. */ -#undef HAVE_SETRLIMIT +/* Define to 1 if you have the header file. */ +#undef HAVE_DLFCN_H /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H +/* Define to 1 if you have the `dl' library (-ldl). */ +#undef HAVE_LIBDL + +/* Define to 1 if you have the `pthread' library (-lpthread). */ +#undef HAVE_LIBPTHREAD + +/* Define to 1 if you have the `z' library (-lz). */ +#undef HAVE_LIBZ + /* Define to 1 if you have the header file. */ #undef HAVE_MEMORY_H /* Define to 1 if you have the `mkdtemp' function. */ #undef HAVE_MKDTEMP +/* Define to 1 if you have the `posix_spawn,' function. */ +#undef HAVE_POSIX_SPAWN_ + +/* Define to 1 if you have the `setrlimit' function. */ +#undef HAVE_SETRLIMIT + +/* Has std::shared_ptr */ +#undef HAVE_SHARED_PTR_STD + +/* Has std::tr1::shared_ptr */ +#undef HAVE_SHARED_PTR_TR1 + +/* Define to 1 if you have the header file. */ +#undef HAVE_SPAWN_H + /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H @@ -47,6 +72,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_SYS_MOUNT_H +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_PARAM_H_ + /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STATFS_H @@ -62,9 +90,18 @@ /* Define to 1 if you have the header file. */ #undef HAVE_SYS_VFS_H +/* "Have tr1" */ +#undef HAVE_TR1_UNORDERED + /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H +/* Use multiple threads for indexing */ +#undef IDX_THREADS + +/* Define to the sub-directory where libtool stores uninstalled libraries. */ +#undef LT_OBJDIR + /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT @@ -77,6 +114,9 @@ /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME +/* Define to the home page for this package. */ +#undef PACKAGE_URL + /* Define to the version of this package. */ #undef PACKAGE_VERSION @@ -101,23 +141,40 @@ /* Compile the inotify interface */ #undef RCL_USE_INOTIFY -/* Use multiple threads for indexing */ -#undef IDX_THREADS - /* Define to 1 if you have the ANSI C header files. */ #undef STDC_HEADERS +/* Use posix_spawn() */ +#undef USE_POSIX_SPAWN + /* Enable using the system's 'file' command to id mime if we fail internally */ #undef USE_SYSTEM_FILE_COMMAND +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#if defined AC_APPLE_UNIVERSAL_BUILD +# if defined __BIG_ENDIAN__ +# define WORDS_BIGENDIAN 1 +# endif +#else +# ifndef WORDS_BIGENDIAN +# undef WORDS_BIGENDIAN +# endif +#endif + /* Define to 1 if the X Window System is missing or not being used. */ #undef X_DISPLAY_MISSING +/* Enable large inode numbers on Mac OS X 10.5. */ +#ifndef _DARWIN_USE_64_BIT_INODE +# define _DARWIN_USE_64_BIT_INODE 1 +#endif + +/* Number of bits in a file offset, on hosts where this is settable. */ #undef _FILE_OFFSET_BITS + +/* Define for large files, on AIX-style hosts. */ #undef _LARGE_FILES -#undef WORDS_BIGENDIAN - -#undef HAVE_TR1_UNORDERED -#undef HAVE_CXX0X_UNORDERED +#include "conf_post.h" diff --git a/src/common/beaglequeuecache.cpp b/src/common/beaglequeuecache.cpp index 939368d1..c14b12fb 100644 --- a/src/common/beaglequeuecache.cpp +++ b/src/common/beaglequeuecache.cpp @@ -20,7 +20,7 @@ #include "cstr.h" #include "beaglequeuecache.h" #include "circache.h" -#include "debuglog.h" +#include "log.h" #include "rclconfig.h" #include "pathut.h" #include "rcldoc.h" @@ -29,24 +29,16 @@ const string cstr_bgc_mimetype("mimetype"); BeagleQueueCache::BeagleQueueCache(RclConfig *cnf) { - string ccdir; - cnf->getConfParam("webcachedir", ccdir); - if (ccdir.empty()) - ccdir = "webcache"; - ccdir = path_tildexpand(ccdir); - // If not an absolute path, compute relative to config dir - if (ccdir.at(0) != '/') - ccdir = path_cat(cnf->getConfDir(), ccdir); + string ccdir = cnf->getWebcacheDir(); int maxmbs = 40; cnf->getConfParam("webcachemaxmbs", &maxmbs); if ((m_cache = new CirCache(ccdir)) == 0) { - LOGERR(("BeagleQueueCache: cant create CirCache object\n")); + LOGERR("BeagleQueueCache: cant create CirCache object\n" ); return; } if (!m_cache->create(off_t(maxmbs)*1000*1024, CirCache::CC_CRUNIQUE)) { - LOGERR(("BeagleQueueCache: cache file creation failed: %s\n", - m_cache->getReason().c_str())); + LOGERR("BeagleQueueCache: cache file creation failed: " << (m_cache->getReason()) << "\n" ); delete m_cache; m_cache = 0; return; @@ -66,11 +58,11 @@ bool BeagleQueueCache::getFromCache(const string& udi, Rcl::Doc &dotdoc, string dict; if (m_cache == 0) { - LOGERR(("BeagleQueueCache::getFromCache: cache is null\n")); + LOGERR("BeagleQueueCache::getFromCache: cache is null\n" ); return false; } - if (!m_cache->get(udi, dict, data)) { - LOGDEB(("BeagleQueueCache::getFromCache: get failed\n")); + if (!m_cache->get(udi, dict, &data)) { + LOGDEB("BeagleQueueCache::getFromCache: get failed\n" ); return false; } @@ -93,3 +85,4 @@ bool BeagleQueueCache::getFromCache(const string& udi, Rcl::Doc &dotdoc, dotdoc.meta[Rcl::Doc::keyudi] = udi; return true; } + diff --git a/src/common/conf_post.h b/src/common/conf_post.h new file mode 100644 index 00000000..43fa5c54 --- /dev/null +++ b/src/common/conf_post.h @@ -0,0 +1,51 @@ + +#ifdef _WIN32 +#include "safewindows.h" + + +#ifdef _MSC_VER +// gmtime is supposedly thread-safe on windows +#define gmtime_r(A, B) gmtime(A) +#define localtime_r(A,B) localtime(A) +typedef int mode_t; +#define fseeko _fseeki64 +#define ftello (off_t)_ftelli64 +#define ftruncate _chsize_s +#define PATH_MAX MAX_PATH +#define RCL_ICONV_INBUF_CONST 1 +#define HAVE_STRUCT_TIMESPEC +#define strdup _strdup +#define timegm _mkgmtime + +#else // End _MSC_VER -> Gminw + +#undef RCL_ICONV_INBUF_CONST +#define timegm portable_timegm + +#endif // GMinw only + +typedef int pid_t; +inline int readlink(const char *cp, void *buf, int cnt) +{ + return -1; +} + +#define MAXPATHLEN PATH_MAX +typedef DWORD32 u_int32_t; +typedef DWORD64 u_int64_t; +typedef unsigned __int8 u_int8_t; +typedef int ssize_t; +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#define chdir _chdir + +#define R_OK 4 +#define W_OK 2 +#ifndef X_OK +#define X_OK 4 +#endif + +#define S_ISLNK(X) false +#define lstat stat + +#endif // _WIN32 diff --git a/src/common/cstr.h b/src/common/cstr.h index c3c55ca1..7f41099b 100644 --- a/src/common/cstr.h +++ b/src/common/cstr.h @@ -80,7 +80,6 @@ DEF_CSTR(dj_keycontent, "content"); DEF_CSTR(dj_keyipath, "ipath"); DEF_CSTR(dj_keymd5, "md5"); DEF_CSTR(dj_keymt, "mimetype"); -DEF_CSTR(dj_keydocsize, "docsize"); DEF_CSTR(dj_keyanc, "rclanc"); #endif /* _CSTR_H_INCLUDED_ */ diff --git a/src/common/rclconfig.cpp b/src/common/rclconfig.cpp index 3f915452..09eaf676 100644 --- a/src/common/rclconfig.cpp +++ b/src/common/rclconfig.cpp @@ -17,42 +17,42 @@ #ifndef TEST_RCLCONFIG #include "autoconfig.h" -#include -#include #include #include +#ifndef _WIN32 #include -#include #include - -#include -#include -using std::list; - -#include -#include +#else +#include "wincodepages.h" +#endif +#include +#include "safesysstat.h" +#include "safeunistd.h" #ifdef __FreeBSD__ #include #endif +#include +#include #include #include #include #include -using namespace std; #include "cstr.h" #include "pathut.h" +#include "rclutil.h" #include "rclconfig.h" #include "conftree.h" -#include "debuglog.h" +#include "log.h" #include "smallut.h" #include "textsplit.h" #include "readfile.h" #include "fstreewalk.h" #include "cpuconf.h" +#include "execmd.h" -typedef pair RclPII; +using namespace std; // Static, logically const, RclConfig members are initialized once from the // first object build during process initialization. @@ -67,10 +67,9 @@ string RclConfig::o_origcwd; bool ParamStale::needrecompute() { - LOGDEB2(("ParamStale:: needrecompute. parent gen %d mine %d\n", - parent->m_keydirgen, savedkeydirgen)); + LOGDEB2("ParamStale:: needrecompute. parent gen " << (parent->m_keydirgen) << " mine " << (savedkeydirgen) << "\n" ); if (active && parent->m_keydirgen != savedkeydirgen) { - LOGDEB2(("ParamState:: needrecompute. conffile %p\n", conffile)); + LOGDEB2("ParamState:: needrecompute. conffile " << (conffile) << "\n" ); savedkeydirgen = parent->m_keydirgen; string newvalue; @@ -79,8 +78,7 @@ bool ParamStale::needrecompute() conffile->get(paramname, newvalue, parent->m_keydir); if (newvalue.compare(savedvalue)) { savedvalue = newvalue; - LOGDEB2(("ParamState:: needrecompute. return true newvalue [%s]\n", - newvalue.c_str())); + LOGDEB2("ParamState:: needrecompute. return true newvalue [" << (newvalue) << "]\n" ); return true; } } @@ -113,18 +111,14 @@ void RclConfig::zeroMe() { m_ptrans = 0; m_stopsuffixes = 0; m_maxsufflen = 0; - - m_oldstpsuffstate.init(0); - m_stpsuffstate.init(0); - m_skpnstate.init(0); - m_rmtstate.init(0); - m_xmtstate.init(0); - m_mdrstate.init(0); + initParamStale(0, 0); } bool RclConfig::isDefaultConfig() const { - string defaultconf = path_cat(path_canon(path_home()), ".recoll/"); + string defaultconf = path_cat(path_homedata(), + path_defaultrecollconfsubdir()); + path_catslash(defaultconf); string specifiedconf = path_canon(m_confdir); path_catslash(specifiedconf); return !defaultconf.compare(specifiedconf); @@ -150,14 +144,7 @@ RclConfig::RclConfig(const string *argcnf) } // Compute our data dir name, typically /usr/local/share/recoll - const char *cdatadir = getenv("RECOLL_DATADIR"); - if (cdatadir == 0) { - // If not in environment, use the compiled-in constant. - m_datadir = RECOLL_DATADIR; - } else { - m_datadir = cdatadir; - } - + m_datadir = path_pkgdatadir(); // We only do the automatic configuration creation thing for the default // config dir, not if it was specified through -c or RECOLL_CONFDIR bool autoconfdir = false; @@ -176,7 +163,7 @@ RclConfig::RclConfig(const string *argcnf) m_confdir = path_canon(cp); } else { autoconfdir = true; - m_confdir = path_cat(path_home(), ".recoll/"); + m_confdir = path_cat(path_homedata(), path_defaultrecollconfsubdir()); } } @@ -184,7 +171,7 @@ RclConfig::RclConfig(const string *argcnf) // want to avoid the imperfect test in isDefaultConfig() if we actually know // this is the default conf if (!autoconfdir && !isDefaultConfig()) { - if (access(m_confdir.c_str(), 0) < 0) { + if (!path_exists(m_confdir)) { m_reason = "Explicitly specified configuration " "directory must exist" " (won't be automatically created). Use mkdir first"; @@ -192,7 +179,7 @@ RclConfig::RclConfig(const string *argcnf) } } - if (access(m_confdir.c_str(), 0) < 0) { + if (!path_exists(m_confdir)) { if (!initUserConfig()) return; } @@ -204,6 +191,7 @@ RclConfig::RclConfig(const string *argcnf) // is called from the main thread at once, by constructing a config // from recollinit if (o_localecharset.empty()) { +#ifndef _WIN32 const char *cp; cp = nl_langinfo(CODESET); // We don't keep US-ASCII. It's better to use a superset @@ -221,8 +209,11 @@ RclConfig::RclConfig(const string *argcnf) // Use cp1252 instead of iso-8859-1, it's a superset. o_localecharset = string(cstr_cp1252); } - LOGDEB1(("RclConfig::getDefCharset: localecharset [%s]\n", - o_localecharset.c_str())); +#else + o_localecharset = winACPName(); +#endif + LOGDEB1("RclConfig::getDefCharset: localecharset [" << + o_localecharset << "]\n"); } const char *cp; @@ -276,20 +267,14 @@ RclConfig::RclConfig(const string *argcnf) return; // Default is no threading - m_thrConf = create_vector - (RclPII(-1, 0))(RclPII(-1, 0))(RclPII(-1, 0)); + m_thrConf = {{-1, 0}, {-1, 0}, {-1, 0}}; m_ptrans = new ConfSimple(path_cat(m_confdir, "ptrans").c_str()); m_ok = true; setKeyDir(cstr_null); - m_oldstpsuffstate.init(mimemap); - m_stpsuffstate.init(m_conf); - m_skpnstate.init(m_conf); - m_rmtstate.init(m_conf); - m_xmtstate.init(m_conf); - m_mdrstate.init(m_conf); + initParamStale(m_conf, mimemap); return; } @@ -305,24 +290,19 @@ bool RclConfig::updateMainConfig() stringsToString(m_cdirs, where); m_reason = string("No/bad main configuration file in: ") + where; m_ok = false; - m_skpnstate.init(0); - m_rmtstate.init(0); - m_xmtstate.init(0); - m_mdrstate.init(0); + initParamStale(0, 0); return false; } delete m_conf; m_conf = newconf; - m_skpnstate.init(m_conf); - m_rmtstate.init(m_conf); - m_xmtstate.init(m_conf); - m_mdrstate.init(m_conf); + initParamStale(m_conf, mimemap); setKeyDir(cstr_null); - bool nocjk = false; - if (getConfParam("nocjk", &nocjk) && nocjk == true) { + + bool bvalue = false; + if (getConfParam("nocjk", &bvalue) && bvalue == true) { TextSplit::cjkProcessing(false); } else { int ngramlen; @@ -333,14 +313,18 @@ bool RclConfig::updateMainConfig() } } - bool nonum = false; - if (getConfParam("nonumbers", &nonum) && nonum == true) { + bvalue = false; + if (getConfParam("nonumbers", &bvalue) && bvalue == true) { TextSplit::noNumbers(); } - bool fnmpathname = true; - if (getConfParam("skippedPathsFnmPathname", &fnmpathname) - && fnmpathname == false) { + bvalue = false; + if (getConfParam("dehyphenate", &bvalue)) { + TextSplit::deHyphenate(bvalue); + } + + bvalue = true; + if (getConfParam("skippedPathsFnmPathname", &bvalue) && bvalue == false) { FsTreeWalker::setNoFnmPathname(); } @@ -351,6 +335,9 @@ bool RclConfig::updateMainConfig() m_index_stripchars_init = 1; } + if (getConfParam("cachedir", m_cachedir)) { + m_cachedir = path_canon(path_tildexpand(m_cachedir)); + } return true; } @@ -433,8 +420,7 @@ bool RclConfig::getConfParam(const string &name, vector *vip, char *ep; vip->push_back(strtol(vs[i].c_str(), &ep, 0)); if (ep == vs[i].c_str()) { - LOGDEB(("RclConfig::getConfParam: bad int value in [%s]\n", - name.c_str())); + LOGDEB("RclConfig::getConfParam: bad int value in [" << (name) << "]\n" ); return false; } } @@ -444,24 +430,27 @@ bool RclConfig::getConfParam(const string &name, vector *vip, void RclConfig::initThrConf() { // Default is no threading - m_thrConf = create_vector - (RclPII(-1, 0))(RclPII(-1, 0))(RclPII(-1, 0)); + m_thrConf = {{-1, 0}, {-1, 0}, {-1, 0}}; vector vq; vector vt; if (!getConfParam("thrQSizes", &vq)) { - LOGINFO(("RclConfig::initThrConf: no thread info (queues)\n")); + LOGINFO("RclConfig::initThrConf: no thread info (queues)\n" ); goto out; } // If the first queue size is 0, autoconf is requested. if (vq.size() > 0 && vq[0] == 0) { - LOGDEB(("RclConfig::initThrConf: autoconf requested\n")); CpuConf cpus; if (!getCpuConf(cpus) || cpus.ncpus < 1) { - LOGERR(("RclConfig::initThrConf: could not retrieve cpu conf\n")); + LOGERR("RclConfig::initThrConf: could not retrieve cpu conf\n" ); cpus.ncpus = 1; } + if (cpus.ncpus != 1) { + LOGDEB("RclConfig::initThrConf: autoconf requested. " << + cpus.ncpus << " concurrent threads available.\n"); + } + // Arbitrarily set threads config based on number of CPUS. This also // depends on the IO setup actually, so we're bound to be wrong... if (cpus.ncpus == 1) { @@ -469,14 +458,11 @@ void RclConfig::initThrConf() // it seems that the best config here is no threading } else if (cpus.ncpus < 4) { // Untested so let's guess... - m_thrConf = create_vector - (RclPII(2, 2))(RclPII(2, 2))(RclPII(2, 1)); + m_thrConf = {{2, 2}, {2, 2}, {2, 1}}; } else if (cpus.ncpus < 6) { - m_thrConf = create_vector - (RclPII(2, 4))(RclPII(2, 2))(RclPII(2, 1)); + m_thrConf = {{2, 4}, {2, 2}, {2, 1}}; } else { - m_thrConf = create_vector - (RclPII(2, 5))(RclPII(2, 3))(RclPII(2, 1)); + m_thrConf = {{2, 5}, {2, 3}, {2, 1}}; } goto out; } else if (vq.size() > 0 && vq[0] < 0) { @@ -485,19 +471,19 @@ void RclConfig::initThrConf() } if (!getConfParam("thrTCounts", &vt) ) { - LOGINFO(("RclConfig::initThrConf: no thread info (threads)\n")); + LOGINFO("RclConfig::initThrConf: no thread info (threads)\n" ); goto out; } if (vq.size() != 3 || vt.size() != 3) { - LOGINFO(("RclConfig::initThrConf: bad thread info vector sizes\n")); + LOGINFO("RclConfig::initThrConf: bad thread info vector sizes\n" ); goto out; } // Normal case: record info from config m_thrConf.clear(); for (unsigned int i = 0; i < 3; i++) { - m_thrConf.push_back(RclPII(vq[i], vt[i])); + m_thrConf.push_back({vq[i], vt[i]}); } out: @@ -507,14 +493,13 @@ out: ") "; } - LOGDEB(("RclConfig::initThrConf: chosen config (ql,nt): %s\n", - sconf.str().c_str())); + LOGDEB("RclConfig::initThrConf: chosen config (ql,nt): " << (sconf.str()) << "\n" ); } pair RclConfig::getThrConf(ThrStage who) const { if (m_thrConf.size() != 3) { - LOGERR(("RclConfig::getThrConf: bad data in rclconfig\n")); + LOGERR("RclConfig::getThrConf: bad data in rclconfig\n" ); return pair(-1,-1); } return m_thrConf[who]; @@ -524,8 +509,7 @@ vector RclConfig::getTopdirs() const { vector tdl; if (!getConfParam("topdirs", &tdl)) { - LOGERR(("RclConfig::getTopdirs: no top directories in config or " - "bad list format\n")); + LOGERR("RclConfig::getTopdirs: no top directories in config or bad list format\n" ); return tdl; } @@ -536,6 +520,11 @@ vector RclConfig::getTopdirs() const return tdl; } +const string& RclConfig::getLocaleCharset() +{ + return o_localecharset; +} + // Get charset to be used for transcoding to utf-8 if unspecified by doc // For document contents: // If defcharset was set (from the config or a previous call, this @@ -609,7 +598,7 @@ typedef multiset SuffixStore; bool RclConfig::inStopSuffixes(const string& fni) { - LOGDEB2(("RclConfig::inStopSuffixes(%s)\n", fni.c_str())); + LOGDEB2("RclConfig::inStopSuffixes(" << (fni) << ")\n" ); // Beware: both needrecompute() need to be called always hence the // bizarre way we do things bool needrecompute = m_stpsuffstate.needrecompute(); @@ -618,7 +607,7 @@ bool RclConfig::inStopSuffixes(const string& fni) // Need to initialize the suffixes delete STOPSUFFIXES; if ((m_stopsuffixes = new SuffixStore) == 0) { - LOGERR(("RclConfig::inStopSuffixes: out of memory\n")); + LOGERR("RclConfig::inStopSuffixes: out of memory\n" ); return false; } // Let the old customisation have priority: if recoll_noindex @@ -634,7 +623,7 @@ bool RclConfig::inStopSuffixes(const string& fni) it != stoplist.end(); it++) { STOPSUFFIXES->insert(SfString(stringtolower(*it))); if (m_maxsufflen < it->length()) - m_maxsufflen = it->length(); + m_maxsufflen = int(it->length()); } } @@ -645,11 +634,10 @@ bool RclConfig::inStopSuffixes(const string& fni) stringtolower(fn); SuffixStore::const_iterator it = STOPSUFFIXES->find(fn); if (it != STOPSUFFIXES->end()) { - LOGDEB2(("RclConfig::inStopSuffixes: Found (%s) [%s]\n", - fni.c_str(), (*it).m_str.c_str())); + LOGDEB2("RclConfig::inStopSuffixes: Found (" << (fni) << ") [" << ((*it).m_str) << "]\n" ); return true; } else { - LOGDEB2(("RclConfig::inStopSuffixes: not found [%s]\n", fni.c_str())); + LOGDEB2("RclConfig::inStopSuffixes: not found [" << (fni) << "]\n" ); return false; } } @@ -726,18 +714,18 @@ string RclConfig::getMimeHandlerDef(const string &mtype, bool filtertypes) } if (!m_restrictMTypes.empty() && !m_restrictMTypes.count(stringtolower(mtype))) { - LOGDEB2(("RclConfig::getMimeHandlerDef: not in mime type list\n")); + LOGDEB2("RclConfig::getMimeHandlerDef: not in mime type list\n" ); return hs; } if (!m_excludeMTypes.empty() && m_excludeMTypes.count(stringtolower(mtype))) { - LOGDEB2(("RclConfig::getMimeHandlerDef: in excluded mime list\n")); + LOGDEB2("RclConfig::getMimeHandlerDef: in excluded mime list\n" ); return hs; } } if (!mimeconf->get(mtype, hs, "index")) { - LOGDEB1(("getMimeHandler: no handler for '%s'\n", mtype.c_str())); + LOGDEB1("getMimeHandler: no handler for '" << (mtype) << "'\n" ); } return hs; } @@ -824,11 +812,11 @@ bool RclConfig::getMissingHelperDesc(string& out) const void RclConfig::storeMissingHelperDesc(const string &s) { - string fmiss = path_cat(getConfDir(), "missing"); + string fmiss = path_cat(getCacheDir(), "missing"); FILE *fp = fopen(fmiss.c_str(), "w"); if (fp) { if (s.size() > 0 && fwrite(s.c_str(), s.size(), 1, fp) != 1) { - LOGERR(("storeMissingHelperDesc: fwrite failed\n")); + LOGERR("storeMissingHelperDesc: fwrite failed\n" ); } fclose(fp); } @@ -838,7 +826,7 @@ void RclConfig::storeMissingHelperDesc(const string &s) // things for speed (theses are used a lot during indexing) bool RclConfig::readFieldsConfig(const string& cnferrloc) { - LOGDEB2(("RclConfig::readFieldsConfig\n")); + LOGDEB2("RclConfig::readFieldsConfig\n" ); m_fields = new ConfStack("fields", m_cdirs, true); if (m_fields == 0 || !m_fields->ok()) { m_reason = string("No/bad fields file in: ") + cnferrloc; @@ -856,8 +844,8 @@ bool RclConfig::readFieldsConfig(const string& cnferrloc) ConfSimple attrs; FieldTraits ft; if (!valueSplitAttributes(val, ft.pfx, attrs)) { - LOGERR(("readFieldsConfig: bad config line for [%s]: [%s]\n", - it->c_str(), val.c_str())); + LOGERR("readFieldsConfig: bad config line for [" << *it << + "]: [" << val << "]\n"); return 0; } string tval; @@ -867,9 +855,11 @@ bool RclConfig::readFieldsConfig(const string& cnferrloc) ft.boost = atof(tval.c_str()); if (attrs.get("pfxonly", tval)) ft.pfxonly = stringToBool(tval); + if (attrs.get("noterms", tval)) + ft.noterms = stringToBool(tval); m_fldtotraits[stringtolower(*it)] = ft; - LOGDEB2(("readFieldsConfig: [%s] -> [%s] %d %.1f\n", - it->c_str(), ft.pfx.c_str(), ft.wdfinc, ft.boost)); + LOGDEB2("readFieldsConfig: [" << *it << "] -> [" << ft.pfx << + "] " << ft.wdfinc << " " << ft.boost << "\n"); } // Add prefixes for aliases and build alias-to-canonic map while @@ -915,9 +905,8 @@ bool RclConfig::readFieldsConfig(const string& cnferrloc) #if 0 for (map::const_iterator it = m_fldtotraits.begin(); it != m_fldtotraits.end(); it++) { - LOGDEB(("readFieldsConfig: [%s] -> [%s] %d %.1f\n", - it->c_str(), it->second.pfx.c_str(), it->second.wdfinc, - it->second.boost)); + LOGDEB("readFieldsConfig: [" << *it << "] -> [" << it->second.pfx << + "] " << it->second.wdfinc << " " << it->second.boost << "\n"); } #endif @@ -950,12 +939,10 @@ bool RclConfig::getFieldTraits(const string& _fld, const FieldTraits **ftpp, map::const_iterator pit = m_fldtotraits.find(fld); if (pit != m_fldtotraits.end()) { *ftpp = &pit->second; - LOGDEB1(("RclConfig::getFieldTraits: [%s]->[%s]\n", - _fld.c_str(), pit->second.pfx.c_str())); + LOGDEB1("RclConfig::getFieldTraits: [" << (_fld) << "]->[" << (pit->second.pfx) << "]\n" ); return true; } else { - LOGDEB1(("RclConfig::getFieldTraits: no prefix for field [%s]\n", - fld.c_str())); + LOGDEB1("RclConfig::getFieldTraits: no prefix for field [" << (fld) << "]\n" ); *ftpp = 0; return false; } @@ -977,11 +964,10 @@ string RclConfig::fieldCanon(const string& f) const string fld = stringtolower(f); map::const_iterator it = m_aliastocanon.find(fld); if (it != m_aliastocanon.end()) { - LOGDEB1(("RclConfig::fieldCanon: [%s] -> [%s]\n", - f.c_str(), it->second.c_str())); + LOGDEB1("RclConfig::fieldCanon: [" << (f) << "] -> [" << (it->second) << "]\n" ); return it->second; } - LOGDEB1(("RclConfig::fieldCanon: [%s] -> [%s]\n", f.c_str(), fld.c_str())); + LOGDEB1("RclConfig::fieldCanon: [" << (f) << "] -> [" << (fld) << "]\n" ); return fld; } @@ -990,8 +976,7 @@ string RclConfig::fieldQCanon(const string& f) const string fld = stringtolower(f); map::const_iterator it = m_aliastoqcanon.find(fld); if (it != m_aliastoqcanon.end()) { - LOGDEB1(("RclConfig::fieldQCanon: [%s] -> [%s]\n", - f.c_str(), it->second.c_str())); + LOGDEB1("RclConfig::fieldQCanon: [" << (f) << "] -> [" << (it->second) << "]\n" ); return it->second; } return fieldCanon(f); @@ -1037,8 +1022,7 @@ bool RclConfig::setMimeViewerAllEx(const string& allex) string RclConfig::getMimeViewerDef(const string &mtype, const string& apptag, bool useall) const { - LOGDEB2(("RclConfig::getMimeViewerDef: mtype [%s] apptag [%s]\n", - mtype.c_str(), apptag.c_str())); + LOGDEB2("RclConfig::getMimeViewerDef: mtype [" << (mtype) << "] apptag [" << (apptag) << "]\n" ); string hs; if (mimeview == 0) return hs; @@ -1141,38 +1125,98 @@ string RclConfig::getMimeIconPath(const string &mtype, const string &apptag) return path_cat(iconpath, iconname) + ".png"; } -string RclConfig::getDbDir() const +// Return path defined by varname. May be absolute or relative to +// confdir, with default in confdir +string RclConfig::getConfdirPath(const char *varname, const char *dflt) const { - string dbdir; - if (!getConfParam("dbdir", dbdir)) { - LOGERR(("RclConfig::getDbDir: no db directory in configuration\n")); + string result; + if (!getConfParam(varname, result)) { + result = path_cat(getConfDir(), dflt); } else { - dbdir = path_tildexpand(dbdir); + result = path_tildexpand(result); // If not an absolute path, compute relative to config dir - if (dbdir.at(0) != '/') { - LOGDEB1(("Dbdir not abs, catting with confdir\n")); - dbdir = path_cat(getConfDir(), dbdir); + if (!path_isabsolute(result)) { + result = path_cat(getConfDir(), result); } } - LOGDEB1(("RclConfig::getDbDir: dbdir: [%s]\n", dbdir.c_str())); - return path_canon(dbdir); + return path_canon(result); + +} + +string RclConfig::getCacheDir() const +{ + return m_cachedir.empty() ? getConfDir() : m_cachedir; +} + +// Return path defined by varname. May be absolute or relative to +// confdir, with default in confdir +string RclConfig::getCachedirPath(const char *varname, const char *dflt) const +{ + string result; + if (!getConfParam(varname, result)) { + result = path_cat(getCacheDir(), dflt); + } else { + result = path_tildexpand(result); + // If not an absolute path, compute relative to cache dir + if (!path_isabsolute(result)) { + result = path_cat(getCacheDir(), result); + } + } + return path_canon(result); +} + +string RclConfig::getDbDir() const +{ + return getCachedirPath("dbdir", "xapiandb"); +} +string RclConfig::getWebcacheDir() const +{ + return getCachedirPath("webcachedir", "webcache"); +} +string RclConfig::getMboxcacheDir() const +{ + return getCachedirPath("mboxcachedir", "mboxcache"); +} +string RclConfig::getAspellcacheDir() const +{ + return getCachedirPath("aspellDicDir", ""); +} + +string RclConfig::getStopfile() const +{ + return getConfdirPath("stoplistfile", "stoplist.txt"); +} + +string RclConfig::getSynGroupsFile() const +{ + return getConfdirPath("syngroupsfile", "syngroups.txt"); +} + +// The index status file is fast changing, so it's possible to put it outside +// of the config directory (for ssds, not sure this is really useful). +// To enable being quite xdg-correct we should add a getRundirPath() +string RclConfig::getIdxStatusFile() const +{ + return getCachedirPath("idxstatusfile", "idxstatus.txt"); +} +string RclConfig::getPidfile() const +{ + return path_cat(getCacheDir(), "index.pid"); } void RclConfig::urlrewrite(const string& dbdir, string& url) const { - LOGDEB2(("RclConfig::urlrewrite: dbdir [%s] url [%s]\n", - dbdir.c_str(), url.c_str())); + LOGDEB2("RclConfig::urlrewrite: dbdir [" << (dbdir) << "] url [" << (url) << "]\n" ); // Do path translations exist for this index ? if (m_ptrans == 0 || !m_ptrans->hasSubKey(dbdir)) { - LOGDEB2(("RclConfig::urlrewrite: no paths translations (m_ptrans %p)\n", - m_ptrans)); + LOGDEB2("RclConfig::urlrewrite: no paths translations (m_ptrans " << (m_ptrans) << ")\n" ); return; } string path = fileurltolocalpath(url); if (path.empty()) { - LOGDEB2(("RclConfig::urlrewrite: not file url\n")); + LOGDEB2("RclConfig::urlrewrite: not file url\n" ); return; } @@ -1186,7 +1230,7 @@ void RclConfig::urlrewrite(const string& dbdir, string& url) const // This call always succeeds because the key comes from getNames() if (m_ptrans->get(*it, npath, dbdir)) { path = path.replace(0, it->size(), npath); - url = "file://" + path; + url = path_pathtofileurl(path); } break; } @@ -1210,32 +1254,6 @@ bool RclConfig::sourceChanged() const return false; } -string RclConfig::getStopfile() const -{ - return path_cat(getConfDir(), "stoplist.txt"); -} -string RclConfig::getPidfile() const -{ - return path_cat(getConfDir(), "index.pid"); -} - -// The index status file is fast changing, so it's possible to put it outside -// of the config directory (for ssds, not sure this is really useful). -string RclConfig::getIdxStatusFile() const -{ - string path; - if (!getConfParam("idxstatusfile", path)) { - return path_cat(getConfDir(), "idxstatus.txt"); - } else { - path = path_tildexpand(path); - // If not an absolute path, compute relative to config dir - if (path.at(0) != '/') { - path = path_cat(getConfDir(), path); - } - return path_canon(path); - } -} - string RclConfig::getWebQueueDir() const { string webqueuedir; @@ -1263,6 +1281,9 @@ vector RclConfig::getSkippedPaths() const // don't do this. skpl.push_back(getDbDir()); skpl.push_back(getConfDir()); + if (getCacheDir().compare(getConfDir())) { + skpl.push_back(getCacheDir()); + } // And the web queue dir skpl.push_back(getWebQueueDir()); for (vector::iterator it = skpl.begin(); it != skpl.end(); it++) { @@ -1300,45 +1321,45 @@ vector RclConfig::getDaemSkippedPaths() const } -// Look up an executable filter. We look in $RECOLL_FILTERSDIR, -// filtersdir in config file, then let the system use the PATH +// Look up an executable filter. We add $RECOLL_FILTERSDIR, +// and filtersdir from the config file to the PATH, then use execmd::which() string RclConfig::findFilter(const string &icmd) const { // If the path is absolute, this is it - if (icmd[0] == '/') + if (path_isabsolute(icmd)) return icmd; - string cmd; - const char *cp; + const char *cp = getenv("PATH"); + if (!cp) //?? + cp = ""; + string PATH(cp); - // Filters dir from environment ? + // For historical reasons: check in personal config directory + PATH = getConfDir() + path_PATHsep() + PATH; + + string temp; + // Prepend $datadir/filters + temp = path_cat(m_datadir, "filters"); + PATH = temp + path_PATHsep() + PATH; + + // Prepend possible configuration parameter? + if (getConfParam(string("filtersdir"), temp)) { + temp = path_tildexpand(temp); + PATH = temp + path_PATHsep() + PATH; + } + + // Prepend possible environment variable if ((cp = getenv("RECOLL_FILTERSDIR"))) { - cmd = path_cat(cp, icmd); - if (access(cmd.c_str(), X_OK) == 0) - return cmd; - } - // Filters dir as configuration parameter? - if (getConfParam(string("filtersdir"), cmd)) { - cmd = path_cat(cmd, icmd); - if (access(cmd.c_str(), X_OK) == 0) - return cmd; + PATH = string(cp) + path_PATHsep() + PATH; } - // Filters dir as datadir subdir. Actually the standard case, but - // this is normally the same value found in config file (previous step) - cmd = path_cat(m_datadir, "filters"); - cmd = path_cat(cmd, icmd); - if (access(cmd.c_str(), X_OK) == 0) - return cmd; - - // Last resort for historical reasons: check in personal config - // directory - cmd = path_cat(getConfDir(), icmd); - if (access(cmd.c_str(), X_OK) == 0) - return cmd; - - // Let the shell try to find it... - return icmd; + string cmd; + if (ExecCmd::which(icmd, cmd, PATH.c_str())) { + return cmd; + } else { + // Let the shell try to find it... + return icmd; + } } /** @@ -1354,7 +1375,7 @@ bool RclConfig::getUncompressor(const string &mtype, vector& cmd) const vector tokens; stringToStrings(hs, tokens); if (tokens.empty()) { - LOGERR(("getUncompressor: empty spec for mtype %s\n", mtype.c_str())); + LOGERR("getUncompressor: empty spec for mtype " << (mtype) << "\n" ); return false; } vector::iterator it = tokens.begin(); @@ -1363,7 +1384,23 @@ bool RclConfig::getUncompressor(const string &mtype, vector& cmd) const if (stringlowercmp("uncompress", *it++)) return false; cmd.clear(); - cmd.push_back(findFilter(*it++)); + cmd.push_back(findFilter(*it)); + + // Special-case python and perl on windows: we need to also locate the + // first argument which is the script name "python somescript.py". + // On Unix, thanks to #!, we usually just run "somescript.py", but need + // the same change if we ever want to use the same cmdling as windows + if (!stringlowercmp("python", *it) || !stringlowercmp("perl", *it)) { + it++; + if (tokens.size() < 3) { + LOGERR("getUncpressor: python/perl cmd: no script?. [" << (mtype) << "]\n" ); + } else { + *it = findFilter(*it); + } + } else { + it++; + } + cmd.insert(cmd.end(), it, tokens.end()); return true; } @@ -1399,7 +1436,7 @@ bool RclConfig::initUserConfig() // Use protective 700 mode to create the top configuration // directory: documents can be reconstructed from index data. - if (access(m_confdir.c_str(), 0) < 0 && + if (!path_exists(m_confdir) && mkdir(m_confdir.c_str(), 0700) < 0) { m_reason += string("mkdir(") + m_confdir + ") failed: " + strerror(errno); @@ -1408,7 +1445,7 @@ bool RclConfig::initUserConfig() string lang = localelang(); for (int i = 0; i < ncffiles; i++) { string dst = path_cat(m_confdir, string(configfiles[i])); - if (access(dst.c_str(), 0) < 0) { + if (!path_exists(dst)) { FILE *fp = fopen(dst.c_str(), "w"); if (fp) { fprintf(fp, "%s\n", blurb); @@ -1451,6 +1488,7 @@ void RclConfig::initFrom(const RclConfig& r) return; m_reason = r.m_reason; m_confdir = r.m_confdir; + m_cachedir = r.m_cachedir; m_datadir = r.m_datadir; m_keydir = r.m_keydir; m_cdirs = r.m_cdirs; @@ -1476,16 +1514,21 @@ void RclConfig::initFrom(const RclConfig& r) m_maxsufflen = r.m_maxsufflen; m_defcharset = r.m_defcharset; - m_oldstpsuffstate.init(mimemap); - m_stpsuffstate.init(m_conf); - m_skpnstate.init(m_conf); - m_rmtstate.init(m_conf); - m_xmtstate.init(m_conf); - m_mdrstate.init(m_conf); + initParamStale(m_conf, mimemap); m_thrConf = r.m_thrConf; } +void RclConfig::initParamStale(ConfNull *cnf, ConfNull *mimemap) +{ + m_oldstpsuffstate.init(mimemap); + m_stpsuffstate.init(cnf); + m_skpnstate.init(cnf); + m_rmtstate.init(cnf); + m_xmtstate.init(cnf); + m_mdrstate.init(cnf); +} + #else // -> Test #include @@ -1497,7 +1540,8 @@ void RclConfig::initFrom(const RclConfig& r) using namespace std; -#include "debuglog.h" +#include "log.h" + #include "rclinit.h" #include "rclconfig.h" #include "cstr.h" @@ -1672,3 +1716,4 @@ int main(int argc, char **argv) } #endif // TEST_RCLCONFIG + diff --git a/src/common/rclconfig.h b/src/common/rclconfig.h index f305b0ec..95a49c29 100644 --- a/src/common/rclconfig.h +++ b/src/common/rclconfig.h @@ -16,13 +16,14 @@ */ #ifndef _RCLCONFIG_H_INCLUDED_ #define _RCLCONFIG_H_INCLUDED_ +#include "autoconfig.h" #include #include #include #include #include -#include "unordered_defs.h" +#include using std::string; using std::vector; @@ -30,7 +31,6 @@ using std::pair; using std::set; using std::map; - #include "conftree.h" #include "smallut.h" @@ -66,9 +66,9 @@ struct FieldTraits { int wdfinc; // Index time term frequency increment (default 1) double boost; // Query time boost (default 1.0) bool pfxonly; // Suppress prefix-less indexing - + bool noterms; // Don't add term to highlight data (e.g.: rclbes) FieldTraits() - : wdfinc(1), boost(1.0), pfxonly(false) + : wdfinc(1), boost(1.0), pfxonly(false), noterms(false) {} }; @@ -109,6 +109,7 @@ class RclConfig { * constructor it it is the default one (~/.recoll) and it did * not exist yet. */ string getConfDir() const {return m_confdir;} + string getCacheDir() const; /** Check if the config files were modified since we read them */ bool sourceChanged() const; @@ -171,10 +172,17 @@ class RclConfig { * need for other status */ vector getTopdirs() const; - /** Get database directory */ + string getConfdirPath(const char *varname, const char *dflt) const; + string getCachedirPath(const char *varname, const char *dflt) const; + /** Get database and other directories */ string getDbDir() const; + string getWebcacheDir() const; + string getMboxcacheDir() const; + string getAspellcacheDir() const; /** Get stoplist file name */ string getStopfile() const; + /** Get synonym groups file name */ + string getSynGroupsFile() const; /** Get indexing pid file name */ string getPidfile() const; /** Get indexing status file name */ @@ -231,6 +239,9 @@ class RclConfig { static bool valueSplitAttributes(const string& whole, string& value, ConfSimple& attrs) ; + /** Return the locale's character set */ + static const std::string& getLocaleCharset(); + /** Return icon path for mime type and tag */ string getMimeIconPath(const string &mt, const string& apptag) const; @@ -307,7 +318,7 @@ class RclConfig { string findFilter(const string& cmd) const; /** Thread config init is not done automatically because not all - programs need it and it uses debuglog so that it's better to + programs need it and it uses the debug log so that it's better to call it after primary init */ void initThrConf(); @@ -330,6 +341,11 @@ class RclConfig { int m_ok; string m_reason; // Explanation for bad state string m_confdir; // User directory where the customized files are stored + // Normally same as confdir. Set to store all bulk data elsewhere. + // Provides defaults top location for dbdir, webcachedir, + // mboxcachedir, aspellDictDir, which can still be used to + // override. + string m_cachedir; string m_datadir; // Example: /usr/local/share/recoll string m_keydir; // Current directory used for parameter fetches. int m_keydirgen; // To help with knowing when to update computed data. @@ -365,10 +381,10 @@ class RclConfig { static string o_localecharset; // Limiting set of mime types to be processed. Normally empty. ParamStale m_rmtstate; - STD_UNORDERED_SET m_restrictMTypes; + std::unordered_set m_restrictMTypes; // Exclusion set of mime types. Normally empty ParamStale m_xmtstate; - STD_UNORDERED_SET m_excludeMTypes; + std::unordered_set m_excludeMTypes; vector > m_thrConf; @@ -379,6 +395,8 @@ class RclConfig { /** Create initial user configuration */ bool initUserConfig(); + /** Init all ParamStale members */ + void initParamStale(ConfNull *cnf, ConfNull *mimemap); /** Copy from other */ void initFrom(const RclConfig& r); /** Init pointers to 0 */ diff --git a/src/common/rclinit.cpp b/src/common/rclinit.cpp index a3963987..c9dd0ae2 100644 --- a/src/common/rclinit.cpp +++ b/src/common/rclinit.cpp @@ -17,49 +17,61 @@ #include "autoconfig.h" #include +#ifdef _WIN32 +#include "safewindows.h" +#endif #include #include -#include #include #if !defined(PUTENV_ARG_CONST) #include #endif -#include "debuglog.h" +#include + +#include "log.h" #include "rclconfig.h" #include "rclinit.h" #include "pathut.h" +#include "rclutil.h" #include "unac.h" #include "smallut.h" #include "execmd.h" -static const int catchedSigs[] = {SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2}; +std::thread::id mainthread_id; -static pthread_t mainthread_id; +// Signal etc. processing. We want to be able to properly close the +// index if we are currently writing to it. +// +// This is active if the sigcleanup parameter to recollinit is set, +// which only recollindex does. We arrange for the handler to be +// called when process termination is requested either by the system +// or a user keyboard intr. +// +// The recollindex handler just sets a global termination flag (plus +// the cancelcheck thing), which are tested in all timeout loops +// etc. When the flag is seen, the main thread processing returns, and +// recollindex calls exit(). +// +// The other parameter, to recollinit(), cleanup, is set as an +// atexit() routine, it does the job of actually signalling the +// workers to stop and tidy up. It's automagically called by exit(). +#ifndef _WIN32 static void siglogreopen(int) { if (recoll_ismainthread()) - DebugLog::reopen(); + Logger::getTheLog("")->reopen(""); } -RclConfig *recollinit(RclInitFlags flags, - void (*cleanup)(void), void (*sigcleanup)(int), - string &reason, const string *argcnf) +// We would like to block SIGCHLD globally, but we can't because +// QT uses it. Have to block it inside execmd.cpp +static const int catchedSigs[] = {SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2}; +void initAsyncSigs(void (*sigcleanup)(int)) { - if (cleanup) - atexit(cleanup); - // We ignore SIGPIPE always. All pieces of code which can write to a pipe // must check write() return values. signal(SIGPIPE, SIG_IGN); - - // Make sure the locale is set. This is only for converting file names - // to utf8 for indexing. - setlocale(LC_CTYPE, ""); - - // We would like to block SIGCHLD globally, but we can't because - // QT uses it. Have to block it inside execmd.cpp // Install app signal handler if (sigcleanup) { @@ -75,46 +87,6 @@ RclConfig *recollinit(RclInitFlags flags, } } - DebugLog::getdbl()->setloglevel(DEBDEB1); - DebugLog::setfilename("stderr"); - if (getenv("RECOLL_LOGDATE")) - DebugLog::getdbl()->logdate(1); - - RclConfig *config = new RclConfig(argcnf); - if (!config || !config->ok()) { - reason = "Configuration could not be built:\n"; - if (config) - reason += config->getReason(); - else - reason += "Out of memory ?"; - return 0; - } - - // Retrieve the log file name and level - string logfilename, loglevel; - if (flags & RCLINIT_DAEMON) { - config->getConfParam(string("daemlogfilename"), logfilename); - config->getConfParam(string("daemloglevel"), loglevel); - } - if (logfilename.empty()) - config->getConfParam(string("logfilename"), logfilename); - if (loglevel.empty()) - config->getConfParam(string("loglevel"), loglevel); - - // Initialize logging - if (!logfilename.empty()) { - logfilename = path_tildexpand(logfilename); - // If not an absolute path or , compute relative to config dir - if (logfilename.at(0) != '/' && - !DebugLog::DebugLog::isspecialname(logfilename.c_str())) { - logfilename = path_cat(config->getConfDir(), logfilename); - } - DebugLog::setfilename(logfilename.c_str()); - } - if (!loglevel.empty()) { - int lev = atoi(loglevel.c_str()); - DebugLog::getdbl()->setloglevel(lev); - } // Install log rotate sig handler { struct sigaction action; @@ -127,19 +99,229 @@ RclConfig *recollinit(RclInitFlags flags, } } } +} +void recoll_exitready() +{ +} + +#else // _WIN32 -> + +// Windows signals etc. +// +// ^C can be caught by the signal() emulation, but not ^Break +// apparently, which is why we use the native approach too +// +// When a keyboard interrupt occurs, windows creates a thread inside +// the process and calls the handler. The process exits when the +// handler returns or after at most 10S +// +// This should also work, with different signals (CTRL_LOGOFF_EVENT, +// CTRL_SHUTDOWN_EVENT) when the user exits or the system shuts down). +// +// Unfortunately, this is not the end of the story. It seems that in +// recent Windows version "some kinds" of apps will not reliably +// receive the signals. "Some kind" is variably defined, for example a +// simple test program works when built with vs 2015, but not +// mingw. See the following discussion thread for tentative +// explanations, it seems that importing or not from user32.dll is the +// determining factor. +// https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/abf09824-4e4c-4f2c-ae1e-5981f06c9c6e/windows-7-console-application-has-no-way-of-trapping-logoffshutdown-event?forum=windowscompatibility +// In any case, it appears that the only reliable way to be advised of +// system shutdown or user exit is to create an "invisible window" and +// process window messages, which we now do. + +static void (*l_sigcleanup)(int); +static HANDLE eWorkFinished = INVALID_HANDLE_VALUE; + +static BOOL WINAPI CtrlHandler(DWORD fdwCtrlType) +{ + LOGDEB("CtrlHandler\n" ); + if (l_sigcleanup == 0) + return FALSE; + + switch(fdwCtrlType) { + case CTRL_C_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + { + l_sigcleanup(SIGINT); + LOGDEB0("CtrlHandler: waiting for exit ready\n" ); + DWORD res = WaitForSingleObject(eWorkFinished, INFINITE); + if (res != WAIT_OBJECT_0) { + LOGERR("CtrlHandler: exit ack wait failed\n" ); + } + LOGDEB0("CtrlHandler: got exit ready event, exiting\n" ); + return TRUE; + } + default: + return FALSE; + } +} + +LRESULT CALLBACK MainWndProc(HWND hwnd , UINT msg , WPARAM wParam, + LPARAM lParam) +{ + switch (msg) { + case WM_QUERYENDSESSION: + case WM_ENDSESSION: + case WM_DESTROY: + case WM_CLOSE: + { + l_sigcleanup(SIGINT); + LOGDEB("MainWndProc: got end message, waiting for work finished\n" ); + DWORD res = WaitForSingleObject(eWorkFinished, INFINITE); + if (res != WAIT_OBJECT_0) { + LOGERR("MainWndProc: exit ack wait failed\n" ); + } + LOGDEB("MainWindowProc: got exit ready event, exiting\n" ); + return TRUE; + } + default: + return DefWindowProc(hwnd, msg, wParam, lParam); + } + return TRUE; +} + +bool CreateInvisibleWindow() +{ + HWND hwnd; + WNDCLASS wc = {0}; + + wc.lpfnWndProc = (WNDPROC)MainWndProc; + wc.hInstance = GetModuleHandle(NULL); + wc.hIcon = LoadIcon(GetModuleHandle(NULL), "TestWClass"); + wc.lpszClassName = "TestWClass"; + RegisterClass(&wc); + + hwnd = + CreateWindowEx(0, "TestWClass", "TestWClass", WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, (HWND) NULL, (HMENU) NULL, + GetModuleHandle(NULL), (LPVOID) NULL); + if (!hwnd) { + return FALSE; + } + return TRUE; +} + +DWORD WINAPI RunInvisibleWindowThread(LPVOID lpParam) +{ + MSG msg; + CreateInvisibleWindow(); + while (GetMessage(&msg, (HWND) NULL , 0 , 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return 0; +} + +static const int catchedSigs[] = {SIGINT, SIGTERM}; +void initAsyncSigs(void (*sigcleanup)(int)) +{ + DWORD tid; + // Install app signal handler + if (sigcleanup) { + l_sigcleanup = sigcleanup; + for (unsigned int i = 0; i < sizeof(catchedSigs) / sizeof(int); i++) { + if (signal(catchedSigs[i], SIG_IGN) != SIG_IGN) { + signal(catchedSigs[i], sigcleanup); + } + } + } + HANDLE hInvisiblethread = + CreateThread(NULL, 0, RunInvisibleWindowThread, NULL, 0, &tid); + SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE); + eWorkFinished = CreateEvent(NULL, TRUE, FALSE, NULL); + if (eWorkFinished == INVALID_HANDLE_VALUE) { + LOGERR("initAsyncSigs: error creating exitready event\n" ); + } +} +void recoll_exitready() +{ + LOGDEB("recoll_exitready()\n" ); + if (!SetEvent(eWorkFinished)) { + LOGERR("recoll_exitready: SetEvent failed\n" ); + } +} + +#endif + +RclConfig *recollinit(RclInitFlags flags, + void (*cleanup)(void), void (*sigcleanup)(int), + string &reason, const string *argcnf) +{ + if (cleanup) + atexit(cleanup); + + // Make sure the locale is set. This is only for converting file names + // to utf8 for indexing. + setlocale(LC_CTYPE, ""); + + Logger::getTheLog("")->setLogLevel(Logger::LLDEB1); + + initAsyncSigs(sigcleanup); + + RclConfig *config = new RclConfig(argcnf); + if (!config || !config->ok()) { + reason = "Configuration could not be built:\n"; + if (config) + reason += config->getReason(); + else + reason += "Out of memory ?"; + return 0; + } + + // Retrieve the log file name and level. Daemon and batch indexing + // processes may use specific values, else fall back on common + // ones. + string logfilename, loglevel; + if (flags & RCLINIT_DAEMON) { + config->getConfParam(string("daemlogfilename"), logfilename); + config->getConfParam(string("daemloglevel"), loglevel); + } + if ((flags & RCLINIT_IDX) && logfilename.empty()) + config->getConfParam(string("idxlogfilename"), logfilename); + if ((flags & RCLINIT_IDX) && loglevel.empty()) + config->getConfParam(string("idxloglevel"), loglevel); + + if (logfilename.empty()) + config->getConfParam(string("logfilename"), logfilename); + if (loglevel.empty()) + config->getConfParam(string("loglevel"), loglevel); + + // Initialize logging + if (!logfilename.empty()) { + logfilename = path_tildexpand(logfilename); + // If not an absolute path or , compute relative to config dir + if (!path_isabsolute(logfilename) && + logfilename.compare("stderr")) { + logfilename = path_cat(config->getConfDir(), logfilename); + } + Logger::getTheLog("")->reopen(logfilename); + } + if (!loglevel.empty()) { + int lev = atoi(loglevel.c_str()); + Logger::getTheLog("")->setLogLevel(Logger::LogLevel(lev)); + } // Make sure the locale charset is initialized (so that multiple // threads don't try to do it at once). config->getDefCharset(); - mainthread_id = pthread_self(); + mainthread_id = std::this_thread::get_id(); - // Init unac locking - unac_init_mt(); // Init smallut and pathut static values pathut_init_mt(); smallut_init_mt(); - + rclutil_init_mt(); + + // Init execmd.h static PATH and PATHELT splitting + {string bogus; + ExecCmd::which("nosuchcmd", bogus); + } + // Init Unac translation exceptions string unacex; if (config->getConfParam("unac_except_trans", unacex) && !unacex.empty()) @@ -151,23 +333,24 @@ RclConfig *recollinit(RclInitFlags flags, // Keep threads init behind log init, but make sure it's done before // we do the vfork choice ! The latter is not used any more actually, // we always use vfork except if forbidden by config. - config->initThrConf(); + if ((flags & RCLINIT_IDX)) { + config->initThrConf(); + } bool novfork; config->getConfParam("novfork", &novfork); if (novfork) { - LOGDEB0(("rclinit: will use fork() for starting commands\n")); + LOGDEB0("rclinit: will use fork() for starting commands\n" ); ExecCmd::useVfork(false); } else { - LOGDEB0(("rclinit: will use vfork() for starting commands\n")); + LOGDEB0("rclinit: will use vfork() for starting commands\n" ); ExecCmd::useVfork(true); } #endif int flushmb; if (config->getConfParam("idxflushmb", &flushmb) && flushmb > 0) { - LOGDEB1(("rclinit: idxflushmb=%d, set XAPIAN_FLUSH_THRESHOLD to 10E6\n", - flushmb)); + LOGDEB1("rclinit: idxflushmb=" << (flushmb) << ", set XAPIAN_FLUSH_THRESHOLD to 10E6\n" ); static const char *cp = "XAPIAN_FLUSH_THRESHOLD=1000000"; #ifdef PUTENV_ARG_CONST ::putenv(cp); @@ -179,10 +362,11 @@ RclConfig *recollinit(RclInitFlags flags, return config; } -// Signals are handled by the main thread. All others should call this routine -// to block possible signals +// Signals are handled by the main thread. All others should call this +// routine to block possible signals void recoll_threadinit() { +#ifndef _WIN32 sigset_t sset; sigemptyset(&sset); @@ -190,11 +374,19 @@ void recoll_threadinit() sigaddset(&sset, catchedSigs[i]); sigaddset(&sset, SIGHUP); pthread_sigmask(SIG_BLOCK, &sset, 0); +#else + // Not sure that this is needed at all or correct under windows. + for (unsigned int i = 0; i < sizeof(catchedSigs) / sizeof(int); i++) { + if (signal(catchedSigs[i], SIG_IGN) != SIG_IGN) { + signal(catchedSigs[i], SIG_IGN); + } + } +#endif } bool recoll_ismainthread() { - return pthread_equal(pthread_self(), mainthread_id); + return std::this_thread::get_id() == mainthread_id; } diff --git a/src/common/rclinit.h b/src/common/rclinit.h index 82916f94..470da903 100644 --- a/src/common/rclinit.h +++ b/src/common/rclinit.h @@ -18,43 +18,48 @@ #define _RCLINIT_H_INCLUDED_ #include -#ifndef NO_NAMESPACES -using std::string; -#endif class RclConfig; /** * Initialize by reading configuration, opening log file, etc. - * + * * This must be called from the main thread before starting any others. It sets * up the global signal handling. other threads must call recoll_threadinit() * when starting. * - * @param flags misc modifiers + * @param flags misc modifiers. These are currently only used to customize + * the log file and verbosity. * @param cleanup function to call before exiting (atexit) - * @param sigcleanup function to call on terminal signal (INT/HUP...) This - * should typically set a flag which tells the program (recoll, - * recollindex etc.. to exit as soon as possible (after closing the db, + * @param sigcleanup function to call on terminal signal (INT/HUP...) This + * should typically set a flag which tells the program (recoll, + * recollindex etc.. to exit as soon as possible (after closing the db, * etc.). cleanup will then be called by exit(). * @param reason in case of error: output string explaining things * @param argcnf Configuration directory name from the command line (overriding * default and environment * @return the parsed configuration. */ -enum RclInitFlags {RCLINIT_NONE=0, RCLINIT_DAEMON=1}; +enum RclInitFlags {RCLINIT_NONE = 0, RCLINIT_DAEMON = 1, RCLINIT_IDX = 2}; extern RclConfig *recollinit(RclInitFlags flags, - void (*cleanup)(void), void (*sigcleanup)(int), - string &reason, const string *argcnf = 0); -inline RclConfig *recollinit(void (*cleanup)(void), void (*sigcleanup)(int), - string &reason, const string *argcnf = 0) { + void (*cleanup)(void), void (*sigcleanup)(int), + std::string& reason, const string *argcnf = 0); +inline RclConfig *recollinit(void (*cleanup)(void), void (*sigcleanup)(int), + std::string& reason, + const std::string *argcnf = 0) +{ return recollinit(RCLINIT_NONE, cleanup, sigcleanup, reason, argcnf); } -// Threads need to call this to block signals. +// Threads need to call this to block signals. // The main thread handles all signals. extern void recoll_threadinit(); // Check if main thread extern bool recoll_ismainthread(); +// Should be called while exiting asap when critical cleanup (db +// close) has been performed. Only useful for the indexer (writes to +// the db), and only actually does something on Windows. +extern void recoll_exitready(); + #endif /* _RCLINIT_H_INCLUDED_ */ diff --git a/src/common/syngroups.cpp b/src/common/syngroups.cpp new file mode 100644 index 00000000..efbc42e9 --- /dev/null +++ b/src/common/syngroups.cpp @@ -0,0 +1,250 @@ +/* Copyright (C) 2014 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef TEST_SYNGROUPS +#include "autoconfig.h" + +#include "syngroups.h" + +#include "log.h" +#include "smallut.h" + +#include +#include +#include +#include +#include + +using namespace std; + +// Note that we are storing each term twice. I don't think that the +// size could possibly be a serious issue, but if it was, we could +// reduce the storage a bit by storing (short hash)-> vector +// correspondances in the direct map, and then checking all the +// resulting groups for the input word. +// +// As it is, a word can only index one group (the last it is found +// in). It can be part of several groups though (appear in +// expansions). I really don't know what we should do with multiple +// groups anyway +class SynGroups::Internal { +public: + Internal() : ok(false) { + } + bool ok; + // Term to group num + std::unordered_map terms; + // Group num to group + vector > groups; +}; + +bool SynGroups::ok() +{ + return m && m->ok; +} + +SynGroups::~SynGroups() +{ + delete m; +} + +SynGroups::SynGroups() + : m(new Internal) +{ +} + +bool SynGroups::setfile(const string& fn) +{ + LOGDEB("SynGroups::setfile(" << (fn) << ")\n" ); + if (!m) { + m = new Internal; + if (!m) { + LOGERR("SynGroups:setfile:: new Internal failed: no mem ?\n" ); + return false; + } + } + + if (fn.empty()) { + delete m; + m = 0; + return true; + } + + ifstream input; + input.open(fn.c_str(), ios::in); + if (!input.is_open()) { + LOGERR("SynGroups:setfile:: could not open " << (fn) << " errno " << (errno) << "\n" ); + return false; + } + + string cline; + bool appending = false; + string line; + bool eof = false; + int lnum = 0; + + for (;;) { + cline.clear(); + getline(input, cline); + if (!input.good()) { + if (input.bad()) { + LOGERR("Syngroup::setfile(" << (fn) << "):Parse: input.bad()\n" ); + return false; + } + // Must be eof ? But maybe we have a partial line which + // must be processed. This happens if the last line before + // eof ends with a backslash, or there is no final \n + eof = true; + } + lnum++; + + { + string::size_type pos = cline.find_last_not_of("\n\r"); + if (pos == string::npos) { + cline.clear(); + } else if (pos != cline.length()-1) { + cline.erase(pos+1); + } + } + + if (appending) + line += cline; + else + line = cline; + + // Note that we trim whitespace before checking for backslash-eol + // This avoids invisible whitespace problems. + trimstring(line); + if (line.empty() || line.at(0) == '#') { + if (eof) + break; + continue; + } + if (line[line.length() - 1] == '\\') { + line.erase(line.length() - 1); + appending = true; + continue; + } + appending = false; + + vector words; + if (!stringToStrings(line, words)) { + LOGERR("SynGroups:setfile: " << (fn) << ": bad line " << (lnum) << ": " << (line) << "\n" ); + continue; + } + + if (words.empty()) + continue; + if (words.size() == 1) { + LOGERR("Syngroup::setfile(" << (fn) << "):single term group at line " << (lnum) << " ??\n" ); + continue; + } + + m->groups.push_back(words); + for (vector::const_iterator it = words.begin(); + it != words.end(); it++) { + m->terms[*it] = m->groups.size()-1; + } + LOGDEB1("SynGroups::setfile: group: [" << (stringsToString(m->groups.back())) << "]\n" ); + } + m->ok = true; + return true; +} + +vector SynGroups::getgroup(const string& term) +{ + vector ret; + if (!ok()) + return ret; + + std::unordered_map::const_iterator it1 = + m->terms.find(term); + if (it1 == m->terms.end()) { + LOGDEB1("SynGroups::getgroup: [" << (term) << "] not found in direct map\n" ); + return ret; + } + + unsigned int idx = it1->second; + if (idx >= m->groups.size()) { + LOGERR("SynGroups::getgroup: line index higher than line count !\n" ); + return ret; + } + return m->groups[idx]; +} + +#else + +#include "syngroups.h" +#include "log.h" + + +#include +#include +#include +#include +#include + +using namespace std; + +static char *thisprog; + +static char usage [] = + "syngroups \n" + " \n\n" + ; +static void Usage(void) +{ + fprintf(stderr, "%s: usage:\n%s", thisprog, usage); + exit(1); +} + +static int op_flags; +#define OPT_MOINS 0x1 +#define OPT_s 0x2 +#define OPT_b 0x4 + +int main(int argc, char **argv) +{ + thisprog = argv[0]; + argc--; argv++; + + if (argc != 2) { + Usage(); + } + string fn = *argv++;argc--; + string word = *argv++;argc--; + + DebugLog::getdbl()->setloglevel(DEBDEB1); + DebugLog::setfilename("stderr"); + SynGroups syns; + syns.setfile(fn); + if (!syns.ok()) { + cerr << "Initialization failed\n"; + return 1; + } + + vector group = syns.getgroup(word); + cout << group.size() << " terms in group\n"; + for (vector::const_iterator it = group.begin(); + it != group.end(); it++) { + cout << "[" << *it << "] "; + } + cout << endl; + return 0; +} + +#endif + diff --git a/src/common/syngroups.h b/src/common/syngroups.h new file mode 100644 index 00000000..62a71523 --- /dev/null +++ b/src/common/syngroups.h @@ -0,0 +1,41 @@ +/* Copyright (C) 2015 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _SYNGROUPS_H_INCLUDED_ +#define _SYNGROUPS_H_INCLUDED_ + +#include +#include + +// Manage synonym groups. This is very different from stemming and +// case/diac expansion because there is no reference form: all terms +// in a group are equivalent. +class SynGroups { +public: + SynGroups(); + ~SynGroups(); + bool setfile(const std::string& fname); + std::vector getgroup(const std::string& term); + bool ok(); +private: + class Internal; + Internal *m; + SynGroups(const SynGroups&); + SynGroups& operator=(const SynGroups&); +}; + +#endif /* _SYNGROUPS_H_INCLUDED_ */ diff --git a/src/common/textsplit.cpp b/src/common/textsplit.cpp index c41c7ce6..7b797421 100644 --- a/src/common/textsplit.cpp +++ b/src/common/textsplit.cpp @@ -24,15 +24,15 @@ #include #include #include -#include "unordered_defs.h" -using namespace std; +#include #include "textsplit.h" -#include "debuglog.h" +#include "log.h" //#define UTF8ITER_CHECK #include "utf8iter.h" #include "uproplist.h" +using namespace std; /** * Splitting a text into words. The code in this file works with utf-8 @@ -62,9 +62,9 @@ static int charclasses[charclasses_size]; // management of Unicode properties, but seems to do the job well // enough in most common cases static vector vpuncblocks; -static STD_UNORDERED_SET spunc; -static STD_UNORDERED_SET visiblewhite; -static STD_UNORDERED_SET sskip; +static std::unordered_set spunc; +static std::unordered_set visiblewhite; +static std::unordered_set sskip; class CharClassInit { public: @@ -142,6 +142,8 @@ static inline int whatcc(unsigned int c) } else { vector::iterator it = lower_bound(vpuncblocks.begin(), vpuncblocks.end(), c); + if (it == vpuncblocks.end()) + return LETTER; if (c == *it) return SPACE; if ((it - vpuncblocks.begin()) % 2 == 1) { @@ -212,15 +214,16 @@ bool TextSplit::isCJK(int c) bool TextSplit::o_processCJK = true; unsigned int TextSplit::o_CJKNgramLen = 2; bool TextSplit::o_noNumbers = false; +bool TextSplit::o_deHyphenate = false; // Final term checkpoint: do some checking (the kind which is simpler // to do here than in the main loop), then send term to our client. inline bool TextSplit::emitterm(bool isspan, string &w, int pos, - int btstart, int btend) + size_t btstart, size_t btend) { - LOGDEB2(("TextSplit::emitterm: [%s] pos %d\n", w.c_str(), pos)); + LOGDEB2("TextSplit::emitterm: [" << (w) << "] pos " << (pos) << "\n" ); - unsigned int l = w.length(); + int l = int(w.length()); #ifdef TEXTSPLIT_STATS // Update word length statistics. Do this before we filter out @@ -229,7 +232,7 @@ inline bool TextSplit::emitterm(bool isspan, string &w, int pos, m_stats.newsamp(m_wordChars); #endif - if (l > 0 && l < (unsigned)m_maxWordLength) { + if (l > 0 && l < m_maxWordLength) { // 1 byte word: we index single ascii letters and digits, but // nothing else. We might want to turn this into a test for a // single utf8 character instead ? @@ -244,12 +247,12 @@ inline bool TextSplit::emitterm(bool isspan, string &w, int pos, } } if (pos != m_prevpos || l != m_prevlen) { - bool ret = takeword(w, pos, btstart, btend); + bool ret = takeword(w, pos, int(btstart), int(btend)); m_prevpos = pos; - m_prevlen = w.length(); + m_prevlen = int(w.length()); return ret; } - LOGDEB2(("TextSplit::emitterm:dup: [%s] pos %d\n", w.c_str(), pos)); + LOGDEB2("TextSplit::emitterm:dup: [" << (w) << "] pos " << (pos) << "\n" ); } return true; } @@ -290,9 +293,9 @@ bool TextSplit::span_is_acronym(string *acronym) } - // Generate terms from span. Have to take into account the - // flags: ONLYSPANS, NOSPANS, noNumbers -bool TextSplit::words_from_span(int bp) +// Generate terms from span. Have to take into account the +// flags: ONLYSPANS, NOSPANS, noNumbers +bool TextSplit::words_from_span(size_t bp) { #if 0 cerr << "Span: [" << m_span << "] " << " w_i_s size: " << @@ -304,30 +307,44 @@ bool TextSplit::words_from_span(int bp) } cerr << endl; #endif - unsigned int spanwords = m_words_in_span.size(); + int spanwords = int(m_words_in_span.size()); int pos = m_spanpos; // Byte position of the span start - int spboffs = bp - m_span.size(); + size_t spboffs = bp - m_span.size(); - for (unsigned int i = 0; + if (o_deHyphenate && spanwords == 2 && + m_span[m_words_in_span[0].second] == '-') { + unsigned int s0 = m_words_in_span[0].first; + unsigned int l0 = m_words_in_span[0].second - m_words_in_span[0].first; + unsigned int s1 = m_words_in_span[1].first; + unsigned int l1 = m_words_in_span[1].second - m_words_in_span[1].first; + string word = m_span.substr(s0, l0) + m_span.substr(s1, l1); + if (l0 && l1) + emitterm(false, word, + m_spanpos, spboffs, spboffs + m_words_in_span[1].second); + } + + for (int i = 0; i < ((m_flags&TXTS_ONLYSPANS) ? 1 : spanwords); - i++, pos++) { + i++) { int deb = m_words_in_span[i].first; - - for (unsigned int j = ((m_flags&TXTS_ONLYSPANS) ? spanwords-1 : i); + bool noposinc = m_words_in_span[i].second == deb; + for (int j = ((m_flags&TXTS_ONLYSPANS) ? spanwords-1 : i); j < ((m_flags&TXTS_NOSPANS) ? i+1 : spanwords); j++) { int fin = m_words_in_span[j].second; //cerr << "i " << i << " j " << j << " deb " << deb << - // " fin " << fin << endl; + //" fin " << fin << endl; if (fin - deb > int(m_span.size())) break; string word(m_span.substr(deb, fin-deb)); if (!emitterm(j != i+1, word, pos, spboffs+deb, spboffs+fin)) return false; } + if (!noposinc) + ++pos; } return true; } @@ -349,12 +366,10 @@ bool TextSplit::words_from_span(int bp) * @param spanerase Set if the current span is at its end. Process it. * @param bp The current BYTE position in the stream */ -inline bool TextSplit::doemit(bool spanerase, int bp) +inline bool TextSplit::doemit(bool spanerase, size_t _bp) { - LOGDEB2(("TextSplit::doemit: sper %d bp %d spp %d spanwords %u wS %d wL %d " - "inn %d span [%s]\n", - spanerase, bp, m_spanpos, m_words_in_span.size(), - m_wordStart, m_wordLen, m_inNumber, m_span.c_str())); + int bp = int(_bp); + LOGDEB2("TextSplit::doemit: sper " << (spanerase) << " bp " << (bp) << " spp " << (m_spanpos) << " spanwords " << (m_words_in_span.size()) << " wS " << (m_wordStart) << " wL " << (m_wordLen) << " inn " << (m_inNumber) << " span [" << (m_span) << "]\n" ); if (m_wordLen) { // We have a current word. Remember it @@ -391,8 +406,8 @@ inline bool TextSplit::doemit(bool spanerase, int bp) case '\'': m_span.resize(m_span.length()-1); if (m_words_in_span.size() && - m_words_in_span.back().second > m_span.size()) - m_words_in_span.back().second = m_span.size(); + m_words_in_span.back().second > int(m_span.size())) + m_words_in_span.back().second = int(m_span.size()); if (--bp < 0) bp = 0; break; @@ -409,7 +424,7 @@ inline bool TextSplit::doemit(bool spanerase, int bp) } else { - m_wordStart = m_span.length(); + m_wordStart = int(m_span.length()); } @@ -450,12 +465,12 @@ static inline bool isdigit(int what, unsigned int flgs) */ bool TextSplit::text_to_words(const string &in) { - LOGDEB1(("TextSplit::text_to_words: docjk %d (%d) %s%s%s [%s]\n", - o_processCJK, o_CJKNgramLen, - m_flags & TXTS_NOSPANS ? " nospans" : "", - m_flags & TXTS_ONLYSPANS ? " onlyspans" : "", - m_flags & TXTS_KEEPWILD ? " keepwild" : "", - in.substr(0,50).c_str())); + LOGDEB1("TextSplit::text_to_words: docjk " << o_processCJK << "(" << + o_CJKNgramLen << ")" << + (m_flags & TXTS_NOSPANS ? " nospans" : "") << + (m_flags & TXTS_ONLYSPANS ? " onlyspans" : "") << + (m_flags & TXTS_KEEPWILD ? " keepwild" : "") << + "[" << in.substr(0,50) << "]\n"); if (in.empty()) return true; @@ -477,7 +492,7 @@ bool TextSplit::text_to_words(const string &in) nonalnumcnt++; if (c == (unsigned int)-1) { - LOGERR(("Textsplit: error occured while scanning UTF-8 string\n")); + LOGERR("Textsplit: error occured while scanning UTF-8 string\n" ); return false; } @@ -491,7 +506,7 @@ bool TextSplit::text_to_words(const string &in) // Hand off situation to the cjk routine. if (!cjk_to_words(&it, &c)) { - LOGERR(("Textsplit: scan error in cjk handler\n")); + LOGERR("Textsplit: scan error in cjk handler\n" ); return false; } @@ -626,8 +641,12 @@ bool TextSplit::text_to_words(const string &in) // Check for number like .1 if (isdigit(nextwhat, m_flags)) { m_inNumber = true; + m_wordLen += it.appendchartostring(m_span); + } else { + m_words_in_span. + push_back(pair(m_wordStart, m_wordStart)); + m_wordStart += it.appendchartostring(m_span); } - m_wordLen += it.appendchartostring(m_span); STATS_INC_WORDCHARS; break; } @@ -655,16 +674,16 @@ bool TextSplit::text_to_words(const string &in) } break; - case '#': + case '#': { + int w = whatcc(it[it.getCpos()+1]); // Keep it only at the beginning of a word (hashtag), - if (m_wordLen == 0) { + if (m_wordLen == 0 && isalphanum(w, m_flags)) { m_wordLen += it.appendchartostring(m_span); STATS_INC_WORDCHARS; break; } // or at the end (special case for c# ...) if (m_wordLen > 0) { - int w = whatcc(it[it.getCpos()+1]); if (w == SPACE || w == '\n' || w == '\r') { m_wordLen += it.appendchartostring(m_span); STATS_INC_WORDCHARS; @@ -672,6 +691,7 @@ bool TextSplit::text_to_words(const string &in) } } goto SPACE; + } break; case '\n': @@ -782,7 +802,7 @@ bool TextSplit::text_to_words(const string &in) // be better off converting the whole buffer to utf32 on entry... bool TextSplit::cjk_to_words(Utf8Iter *itp, unsigned int *cp) { - LOGDEB1(("cjk_to_words: m_wordpos %d\n", m_wordpos)); + LOGDEB1("cjk_to_words: m_wordpos " << (m_wordpos) << "\n" ); Utf8Iter &it = *itp; // We use an offset buffer to remember the starts of the utf-8 @@ -816,16 +836,16 @@ bool TextSplit::cjk_to_words(Utf8Iter *itp, unsigned int *cp) } // Take note of byte offset for this character. - boffs[nchars-1] = it.getBpos(); + boffs[nchars-1] = int(it.getBpos()); // Output all new ngrams: they begin at each existing position // and end after the new character. onlyspans->only output // maximum words, nospans=> single chars if (!(m_flags & TXTS_ONLYSPANS) || nchars == o_CJKNgramLen) { - unsigned int btend = it.getBpos() + it.getBlen(); - unsigned int loopbeg = (m_flags & TXTS_NOSPANS) ? nchars-1 : 0; - unsigned int loopend = (m_flags & TXTS_ONLYSPANS) ? 1 : nchars; - for (unsigned int i = loopbeg; i < loopend; i++) { + int btend = int(it.getBpos() + it.getBlen()); + int loopbeg = (m_flags & TXTS_NOSPANS) ? nchars-1 : 0; + int loopend = (m_flags & TXTS_ONLYSPANS) ? 1 : nchars; + for (int i = loopbeg; i < loopend; i++) { if (!takeword(it.buffer().substr(boffs[i], btend-boffs[i]), m_wordpos - (nchars-i-1), boffs[i], btend)) { @@ -846,7 +866,7 @@ bool TextSplit::cjk_to_words(Utf8Iter *itp, unsigned int *cp) // If onlyspans is set, there may be things to flush in the buffer // first if ((m_flags & TXTS_ONLYSPANS) && nchars > 0 && nchars != o_CJKNgramLen) { - unsigned int btend = it.getBpos(); // Current char is out + int btend = int(it.getBpos()); // Current char is out if (!takeword(it.buffer().substr(boffs[0], btend-boffs[0]), m_wordpos - nchars, boffs[0], btend)) { @@ -885,9 +905,8 @@ bool TextSplit::hasVisibleWhite(const string &in) Utf8Iter it(in); for (; !it.eof(); it++) { unsigned int c = (unsigned char)*it; - LOGDEB3(("TextSplit::hasVisibleWhite: testing 0x%04x\n", c)); if (c == (unsigned int)-1) { - LOGERR(("hasVisibleWhite: error while scanning UTF-8 string\n")); + LOGERR("hasVisibleWhite: error while scanning UTF-8 string\n" ); return false; } if (visiblewhite.find(c) != visiblewhite.end()) @@ -908,10 +927,8 @@ template bool u8stringToStrings(const string &s, T &tokens) unsigned int c = *it; if (visiblewhite.find(c) != visiblewhite.end()) c = ' '; - LOGDEB3(("TextSplit::stringToStrings: 0x%04x\n", c)); if (c == (unsigned int)-1) { - LOGERR(("TextSplit::stringToStrings: error while " - "scanning UTF-8 string\n")); + LOGERR("TextSplit::stringToStrings: error while scanning UTF-8 string\n" ); return false; } @@ -988,7 +1005,8 @@ bool TextSplit::stringToStrings(const string &s, vector &tokens) #include "textsplit.h" #include "readfile.h" -#include "debuglog.h" +#include "log.h" + #include "transcode.h" #include "unacpp.h" #include "termproc.h" @@ -1147,8 +1165,6 @@ int main(int argc, char **argv) } b1: argc--; argv++; } - DebugLog::getdbl()->setloglevel(DEBDEB1); - DebugLog::setfilename("stderr"); TextSplit::Flags flags = TextSplit::TXTS_NONE; @@ -1221,3 +1237,4 @@ int main(int argc, char **argv) } } #endif // TEST + diff --git a/src/common/textsplit.h b/src/common/textsplit.h index 08033ee9..d408bb2e 100644 --- a/src/common/textsplit.h +++ b/src/common/textsplit.h @@ -22,10 +22,6 @@ #include #include -using std::string; -using std::vector; -using std::pair; - class Utf8Iter; /** @@ -56,6 +52,13 @@ public: o_noNumbers = true; } + // Given [co-worker] as input, do we also generate [coworker] ? + // Set by rclconfig + static bool o_deHyphenate; + static void deHyphenate(bool on) { + o_deHyphenate = on; + } + enum Flags { // Default: will return spans and words (a_b, a, b) TXTS_NONE = 0, @@ -77,10 +80,10 @@ public: virtual ~TextSplit() {} /** Split text, emit words and positions. */ - virtual bool text_to_words(const string &in); + virtual bool text_to_words(const std::string &in); /** Process one output word: to be implemented by the actual user class */ - virtual bool takeword(const string& term, + virtual bool takeword(const std::string& term, int pos, // term pos int bts, // byte offset of first char in term int bte // byte offset of first char after term @@ -96,10 +99,10 @@ public: // Static utility functions: /** Count words in string, as the splitter would generate them */ - static int countWords(const string &in, Flags flgs = TXTS_ONLYSPANS); + static int countWords(const std::string &in, Flags flgs = TXTS_ONLYSPANS); /** Check if this is visibly not a single block of text */ - static bool hasVisibleWhite(const string &in); + static bool hasVisibleWhite(const std::string &in); /** Split text span into strings, at white space, allowing for substrings * quoted with " . Escaping with \ works as usual inside the quoted areas. @@ -108,7 +111,7 @@ public: * non-utf-8 input (iso-8859 config files work ok). This hopefully * handles all Unicode whitespace, but needs correct utf-8 input */ - static bool stringToStrings(const string &s, vector &tokens); + static bool stringToStrings(const std::string &s, std::vector &tokens); /** Is char CJK ? */ static bool isCJK(int c); @@ -179,9 +182,9 @@ private: int m_maxWordLength; // Current span. Might be jf.dockes@wanadoo.f - string m_span; + std::string m_span; - vector > m_words_in_span; + std::vector > m_words_in_span; // Current word: no punctuation at all in there. Byte offset // relative to the current span and byte length @@ -198,7 +201,7 @@ private: // It may happen that our cleanup would result in emitting the // same term twice. We try to avoid this int m_prevpos; - unsigned int m_prevlen; + int m_prevlen; #ifdef TEXTSPLIT_STATS // Stats counters. These are processed in TextSplit rather than by a @@ -212,11 +215,11 @@ private: // This processes cjk text: bool cjk_to_words(Utf8Iter *it, unsigned int *cp); - bool emitterm(bool isspan, string &term, int pos, int bs, int be); - bool doemit(bool spanerase, int bp); + bool emitterm(bool isspan, std::string &term, int pos, size_t bs,size_t be); + bool doemit(bool spanerase, size_t bp); void discardspan(); bool span_is_acronym(std::string *acronym); - bool words_from_span(int bp); + bool words_from_span(size_t bp); }; #endif /* _TEXTSPLIT_H_INCLUDED_ */ diff --git a/src/common/unacpp.cpp b/src/common/unacpp.cpp index 837786de..a613619e 100644 --- a/src/common/unacpp.cpp +++ b/src/common/unacpp.cpp @@ -24,7 +24,7 @@ #include "unacpp.h" #include "unac.h" -#include "debuglog.h" +#include "log.h" #include "utf8iter.h" bool unacmaybefold(const string &in, string &out, @@ -68,7 +68,7 @@ bool unacmaybefold(const string &in, string &out, // testing user-entered terms, so we don't really care. bool unaciscapital(const string& in) { - LOGDEB2(("unaciscapital: [%s]\n", in.c_str())); + LOGDEB2("unaciscapital: [" << (in) << "]\n" ); if (in.empty()) return false; Utf8Iter it(in); @@ -77,7 +77,7 @@ bool unaciscapital(const string& in) string lower; if (!unacmaybefold(shorter, lower, "UTF-8", UNACOP_FOLD)) { - LOGINFO(("unaciscapital: unac/fold failed for [%s]\n", in.c_str())); + LOGINFO("unaciscapital: unac/fold failed for [" << (in) << "]\n" ); return false; } Utf8Iter it1(lower); @@ -88,13 +88,13 @@ bool unaciscapital(const string& in) } bool unachasuppercase(const string& in) { - LOGDEB2(("unachasuppercase: [%s]\n", in.c_str())); + LOGDEB2("unachasuppercase: [" << (in) << "]\n" ); if (in.empty()) return false; string lower; if (!unacmaybefold(in, lower, "UTF-8", UNACOP_FOLD)) { - LOGINFO(("unachasuppercase: unac/fold failed for [%s]\n", in.c_str())); + LOGINFO("unachasuppercase: unac/fold failed for [" << (in) << "]\n" ); return false; } if (lower != in) @@ -104,13 +104,13 @@ bool unachasuppercase(const string& in) } bool unachasaccents(const string& in) { - LOGDEB2(("unachasaccents: [%s]\n", in.c_str())); + LOGDEB2("unachasaccents: [" << (in) << "]\n" ); if (in.empty()) return false; string noac; if (!unacmaybefold(in, noac, "UTF-8", UNACOP_UNAC)) { - LOGINFO(("unachasaccents: unac/unac failed for [%s]\n", in.c_str())); + LOGINFO("unachasaccents: unac/unac failed for [" << (in) << "]\n" ); return false; } if (noac != in) @@ -247,3 +247,4 @@ int main(int argc, char **argv) } #endif + diff --git a/src/common/unordered_defs.h b/src/common/unordered_defs.h deleted file mode 100644 index e82ae0c6..00000000 --- a/src/common/unordered_defs.h +++ /dev/null @@ -1,19 +0,0 @@ - -#include "autoconfig.h" - -#ifdef HAVE_CXX0X_UNORDERED -# include -# include -# define STD_UNORDERED_MAP std::unordered_map -# define STD_UNORDERED_SET std::unordered_set -#elif defined(HAVE_TR1_UNORDERED) -# include -# include -# define STD_UNORDERED_MAP std::tr1::unordered_map -# define STD_UNORDERED_SET std::tr1::unordered_set -#else -# include -# include -# define STD_UNORDERED_MAP std::map -# define STD_UNORDERED_SET std::set -#endif diff --git a/src/common/utf8fn.cpp b/src/common/utf8fn.cpp new file mode 100644 index 00000000..e427f27c --- /dev/null +++ b/src/common/utf8fn.cpp @@ -0,0 +1,24 @@ +#include "utf8fn.h" +#include "rclconfig.h" +#include "transcode.h" +#include "log.h" + +using namespace std; + +string compute_utf8fn(const RclConfig *config, const string& ifn, bool simple) +{ + string charset = config->getDefCharset(true); + string utf8fn; + int ercnt; + string lfn(simple ? path_getsimple(ifn) : ifn); + if (!transcode(lfn, utf8fn, charset, "UTF-8", &ercnt)) { + LOGERR("compute_utf8fn: fn transcode failure from [" << charset << + "] to UTF-8 for: [" << lfn << "]\n"); + } else if (ercnt) { + LOGDEB("compute_utf8fn: " << ercnt << " transcode errors from [" << + charset << "] to UTF-8 for: [" << lfn << "]\n"); + } + LOGDEB1("compute_utf8fn: transcoded from [" << lfn << "] to [" << + utf8fn << "] (" << charset << "->" << "UTF-8)\n"); + return utf8fn; +} diff --git a/src/common/utf8fn.h b/src/common/utf8fn.h new file mode 100644 index 00000000..c4f70814 --- /dev/null +++ b/src/common/utf8fn.h @@ -0,0 +1,16 @@ +#ifndef _UTF8FN_H_ +#define _UTF8FN_H_ + +#include + +class RclConfig; + +// Translate file name/path to utf8 for indexing. +// +// @param simple If true we extract and process only the simple file name +// (ignore the path) +std::string compute_utf8fn(const RclConfig *config, const std::string& ifn, + bool simple); + +#endif // _UTF8FN_H_ + diff --git a/src/configure.ac b/src/configure.ac index f103993c..0226b253 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -1,34 +1,24 @@ AC_INIT([Recoll], m4_esyscmd_s(cat VERSION)) AC_CONFIG_HEADERS([common/autoconfig.h]) +AH_BOTTOM([#include "conf_post.h"]) AC_PREREQ(2.53) AC_CONFIG_SRCDIR(index/recollindex.cpp) +AM_INIT_AUTOMAKE([1.10 no-define subdir-objects foreign]) +AC_DISABLE_STATIC +LT_INIT +AC_CONFIG_MACRO_DIR([m4]) + AC_PROG_CXX if test C$CXX = C ; then AC_MSG_ERROR([C++ compiler needed. Please install one (ie: gnu g++)]) fi +AC_PROG_YACC + +AC_PROG_LIBTOOL AC_C_BIGENDIAN -sys=`uname | tr / _ | awk -F_ '{print $1}'` - -if test ! -f mk/$sys ; then - AC_MSG_NOTICE([ - No system configuration file found in mk/ for uname = '$sys'. - Trying with Default file. - If the build fails, you'll need to write a configuration file, starting - from one of the existing ones.]) - - sys=Default -fi -(cd mk; rm -f sysconf; ln -s $sys sysconf) - -# There a few Recoll users on Mac OS X and a few things are just not worth -# supporting -if test X$sys = XDarwin ; then - NODYNLIB=# -fi - AC_SYS_LARGEFILE # OpenBSD needs sys/param.h for mount.h to compile @@ -52,6 +42,21 @@ AC_CHECK_HEADER(tr1/unordered_map,[AC_DEFINE([HAVE_TR1_UNORDERED], [],["Have tr1"])],[]) AC_CHECK_HEADER(unordered_map,[AC_DEFINE([HAVE_CXX0X_UNORDERED], [],["Have C++0x"])],[]) +AC_TRY_COMPILE([ + #include + ],[ + std::shared_ptr ptr; + ], rcl_shared_ptr_std="1", rcl_shared_ptr_std="0") +AC_TRY_COMPILE([ + #include + ],[ + std::tr1::shared_ptr ptr; + ], rcl_shared_ptr_tr1="1", rcl_shared_ptr_tr1="0") +if test X$rcl_shared_ptr_std = X1; then + AC_DEFINE(HAVE_SHARED_PTR_STD, [], [Has std::shared_ptr]) +elif test X$rcl_shared_ptr_tr1 = X1; then + AC_DEFINE(HAVE_SHARED_PTR_TR1, [], [Has std::tr1::shared_ptr]) +fi AC_LANG_POP([C++]) AC_CHECK_HEADERS([sys/mount.h sys/statfs.h sys/statvfs.h sys/vfs.h], [], [], @@ -199,12 +204,9 @@ AC_ARG_ENABLE(idxthreads, AC_HELP_STRING([--disable-idxthreads], [Disable multithread indexing.]), idxthreadsEnabled=$enableval, idxthreadsEnabled=yes) - +AM_CONDITIONAL(NOTHREADS, [test X$idxthreadsEnabled = Xno]) if test X$idxthreadsEnabled = Xyes ; then AC_DEFINE(IDX_THREADS, 1, [Use multiple threads for indexing]) - NOTHREADS="" -else - NOTHREADS="#" fi # Enable CamelCase word splitting. This is optional because it causes @@ -221,54 +223,27 @@ AC_ARG_ENABLE(camelcase, "mysql manual" (in phrases only and you could raise the phrase slack to get a match).]), camelcaseEnabled=$enableval, camelcaseEnabled=no) - if test X$camelcaseEnabled = Xyes ; then AC_DEFINE(RCL_SPLIT_CAMELCASE, 1, [Split camelCase words]) fi -# Disable building the python module. This is built by default, because -# it's really the easiest way to interface and extend recoll. It forces PIC -# objects for everything (indexing performance impact: 1%), because it's -# just not worth building the lib twice -# You can still have a non-pic recoll with: -# configure --disable-python-module; make; make install;make clean -# configure; make; cd python/recoll; make install -# +# Disable building the python module. if test X$sys != XDarwin ; then -AC_ARG_ENABLE(python-module, + AC_ARG_ENABLE(python-module, AC_HELP_STRING([--disable-python-module], - [Do not build the Python module.]), + [Do not build the Python module.]), pythonEnabled=$enableval, pythonEnabled=yes) -if test X$pythonEnabled = Xyes ; then - NOPYTHON="" else - NOPYTHON="#" -fi -else - NOPYTHON="#" + pythonEnabled=no fi -# Build PIC objects for the library ? -AC_ARG_ENABLE(pic, - AC_HELP_STRING([--disable-pic], - [Do not compile library objects as position independant code. - This is incompatible with the php or python extensions.]), - picEnabled=$enableval, picEnabled=forpython) -case $picEnabled in -forpython) picEnabled=$pythonEnabled; NOPIC=$NOPYTHON;; -yes) NOPIC="";; -*) NOPIC="#";; -esac +AM_CONDITIONAL(MAKEPYTHON, [test X$pythonEnabled = Xyes]) -if test X$pythonEnabled = Xyes -a X$picEnabled != Xyes; then - AC_MSG_ERROR([Python build needs PIC library]) -fi - -if test X$NOPIC != X; then - NODYNLIB=# -fi AC_CHECK_FUNCS(mkdtemp) +AC_CHECK_LIB([pthread], [pthread_create], [], []) +AC_CHECK_LIB([dl], [dlopen], [], []) +AC_CHECK_LIB([z], [zlibVersion], [], []) ##### Look for iconv. This can exist in either libc (ie: Linux, solaris) or ##### libiconv. We'd need a --with-libiconv= option @@ -340,7 +315,7 @@ fi #### Look for Xapian. Done in a strange way to work around autoconf # cache -XAPIAN_CONFIG=no +XAPIAN_CONFIG=${XAPIAN_CONFIG:-no} if test "$XAPIAN_CONFIG" = "no"; then AC_PATH_PROG(XAPIAN_CONFIG0, [xapian-config], no) XAPIAN_CONFIG=$XAPIAN_CONFIG0 @@ -373,8 +348,6 @@ for i in $LIBXAPIAN ; do esac done LIBXAPIAN=$tmpxaplib -# Also recent xapian libs need lz even when they think they don't... -LIBXAPIAN="$LIBXAPIAN -lz" LIBXAPIANDIR=`$XAPIAN_CONFIG --libs | awk '{print $1}'` case A"$LIBXAPIANDIR" in A-L*) LIBXAPIANDIR=`echo $LIBXAPIANDIR | sed -e 's/-L//'`;; @@ -388,6 +361,20 @@ XAPIANCXXFLAGS=`$XAPIAN_CONFIG --cxxflags` #echo LIBXAPIANSTATICEXTRA: $LIBXAPIANSTATICEXTRA #echo XAPIANCXXFLAGS: $XAPIANCXXFLAGS +AC_ARG_ENABLE(xadump, + AC_HELP_STRING([--enable-xadump], + [Enable building the xadump low level Xapian access program.]), + enableXADUMP=$enableval, enableXADUMP="no") +AM_CONDITIONAL(MAKEXADUMP, [test X$enableXADUMP = Xyes]) + +AC_ARG_ENABLE(userdoc, + AC_HELP_STRING([--disable-userdoc], + [Disable building the user manual. (Avoids the need for docbook xml/xsl files and TeX tools.]), + enableUserdoc=$enableval, enableUserdoc="yes") +AM_CONDITIONAL(MAKEUSERDOC, [test X$enableUserdoc = Xyes]) + + + #### QT # The way qt and its tools (qmake especially) are installed is very # different between systems (and maybe qt versions) @@ -413,13 +400,22 @@ AC_ARG_ENABLE(qtgui, AC_HELP_STRING([--disable-qtgui], [Disable the QT-based graphical user interface.]), enableQT=$enableval, enableQT="yes") +AM_CONDITIONAL(MAKEQT, [test X$enableQT = Xyes]) -if test "$enableQT" != "yes" ; then - NOQTMAKE="#" - NOCMDLINE="" +AC_ARG_ENABLE(recollq, + AC_HELP_STRING([--enable-recollq], + [Enable building the recollq command line query tool (recoll -t without + need for Qt). This is done by default if --disable-qtgui is set but this + option enables forcing it.]), + enableRECOLLQ=$enableval, enableRECOLLQ="no") +if test X"$enableRECOLLQ" != X ; then + AM_CONDITIONAL(MAKECMDLINE, [test X$enableRECOLLQ = Xyes]) else - NOQTMAKE="" - NOCMDLINE="#" + AM_CONDITIONAL(MAKECMDLINE, [test X$enableQT = Xno]) +fi + + +if test X$enableQT = Xyes ; then if test X$QTDIR != X ; then PATH=$PATH:$QTDIR/bin @@ -449,15 +445,13 @@ else QMAKE="${QMAKE} -spec macx-g++" fi - # Discriminate qt3/4. Qt3 qmake prints its version on stderr but we don't - # depend on this. We try to detect the qt 4 version string instead. + # Check Qt version qmakevers="`${QMAKE} --version 2>&1`" #echo "qmake version: $qmakevers" v4=`expr "$qmakevers" : '.*Qt[ ][ ]*version[ ][ ]*4.*'` v5=`expr "$qmakevers" : '.*Qt[ ][ ]*version[ ][ ]*5.*'` if test X$v4 = X0 -a X$v5 = X0; then AC_MSG_ERROR([qmake seems to be using Qt version 3 which is not supported any more]) - QTGUI=qtgui else if test X$v4 != X0 ; then AC_MSG_NOTICE([using qt version 4 user interface]) @@ -466,27 +460,6 @@ else fi QTGUI=qtgui fi - - cd $QTGUI - # We just want a .pro file: no problem with unsubstituted variables at - # this point. - test -f recoll.pro && chmod +w recoll.pro - cp recoll.pro.in recoll.pro - #echo QMAKE ${QMAKE} - ${QMAKE} recoll.pro - if test $? != 0 ; then - AC_MSG_ERROR([Cannot use qmake to generate a Makefile. Maybe you need to - check the QTDIR and QMAKESPEC environment variables?]) - fi - # is QTDIR set and do we actually need it ? - if test X$QTDIR = X ; then - QTDIRNEEDED=`grep INCPATH Makefile | grep = | grep QTDIR` - if test "X$QTDIRNEEDED" != "X" ; then - AC_MSG_ERROR([You need to set the QTDIR variable to point to the QT - installation. If there is no default mkspecs, you should also set QMAKESPEC]) - fi - fi - cd .. ##### Using Qt webkit for reslist display? Else Qt textbrowser @@ -503,8 +476,6 @@ else QMAKE_DISABLE_WEBKIT="" fi - - ##### Using QZeitGeist lib ? Default no for now AC_ARG_WITH(qzeitgeist, AC_HELP_STRING([--with-qzeitgeist], @@ -551,19 +522,21 @@ fi #echo X_CFLAGS "'$X_CFLAGS'" X_PRE_LIBS "'$X_PRE_LIBS'" X_LIBS \ # "'$X_LIBS'" X_LIBX11 "'$X_LIBX11'" X_EXTRA_LIBS "'$X_EXTRA_LIBS'" - -# We have to expand prefix in here, couldn't find a way to do it inside -# the qt gui .pro file or Makefile. This just means that you can't change -# prefix at build time. It works at install time because we dont' use the -# qtgui Makefile +# For communicating the value of RECOLL_DATADIR to non-make-based +# subpackages like python-recoll, we have to expand prefix in here, because +# things like "datadir = ${prefix}/share" (which is what we'd get by +# expanding @datadir@) don't mean a thing in Python... I guess we could +# have a piece of shell-script text to be substituted into and executed by +# setup.py for getting the value of pkgdatadir, but really... m_prefix=$prefix test "X$m_prefix" = "XNONE" && m_prefix=/usr/local m_datadir=${m_prefix}/share -QTRECOLL_DATADIR=${m_datadir}/recoll +RECOLL_DATADIR=${m_datadir}/recoll -RCLVERSION=`cat VERSION` +RCLVERSION=$PACKAGE_VERSION RCLLIBVERSION=$RCLVERSION +AC_SUBST(RECOLL_DATADIR) AC_SUBST(X_CFLAGS) AC_SUBST(X_PRE_LIBS) AC_SUBST(X_LIBS) @@ -577,20 +550,12 @@ AC_SUBST(LIBXAPIANSTATICEXTRA) AC_SUBST(LIBFAM) AC_SUBST(QMAKE) AC_SUBST(QTGUI) -AC_SUBST(QTRECOLL_DATADIR) AC_SUBST(XAPIANCXXFLAGS) -AC_SUBST(HAVE_MKDTEMP) -AC_SUBST(NOQTMAKE) -AC_SUBST(NOCMDLINE) AC_SUBST(QMAKE_ENABLE_WEBKIT) AC_SUBST(QMAKE_DISABLE_WEBKIT) AC_SUBST(QMAKE_ENABLE_ZEITGEIST) AC_SUBST(QMAKE_DISABLE_ZEITGEIST) AC_SUBST(LIBQZEITGEIST) -AC_SUBST(NOPIC) -AC_SUBST(NOTHREADS) -AC_SUBST(NOPYTHON) -AC_SUBST(NODYNLIB) AC_SUBST(RCLVERSION) AC_SUBST(RCLLIBVERSION) @@ -598,23 +563,7 @@ AC_SUBST(RCLLIBVERSION) # changing it unless necessary AC_CONFIG_FILES(Makefile) AC_CONFIG_FILES(common/rclversion.h) -AC_CONFIG_FILES(lib/mkMake) -AC_CONFIG_FILES(mk/localdefs.new:mk/localdefs.in) AC_CONFIG_FILES(python/recoll/setup.py) -AC_CONFIG_FILES(recollinstall) -AC_CONFIG_FILES(sampleconf/recoll.conf) - -for d in bincimapmime index lib query -do - rm -f $d/alldeps.stamp - cp -f /dev/null $d/alldeps -done +AC_CONFIG_FILES(python/recoll/Makefile) AC_OUTPUT - -if cmp -s mk/localdefs mk/localdefs.new ; then - rm -f mk/localdefs.new -else - mv -f mk/localdefs.new mk/localdefs -fi - diff --git a/src/desktop/recoll.ico b/src/desktop/recoll.ico new file mode 100644 index 00000000..9e53065a Binary files /dev/null and b/src/desktop/recoll.ico differ diff --git a/src/doc/man/recoll.1 b/src/doc/man/recoll.1 index 36174625..41b3bb54 100644 --- a/src/doc/man/recoll.1 +++ b/src/doc/man/recoll.1 @@ -72,7 +72,11 @@ simple search query. .PP If .B \-t -is specified, the Graphical User Interface will not be started, and results +is specified, or if +.B recoll +is called as +.B recollq +(through a link), the Graphical User Interface will not be started, and results will be printed to the standard output. Additional options understood by the .B recollq diff --git a/src/doc/man/recoll.conf.5 b/src/doc/man/recoll.conf.5 index 7cdc8216..9123005a 100644 --- a/src/doc/man/recoll.conf.5 +++ b/src/doc/man/recoll.conf.5 @@ -54,315 +54,565 @@ Where values are lists, white space is used for separation, and elements with embedded spaces can be quoted with double-quotes. .SH OPTIONS .TP -.BI "topdirs = " directories -Specifies the list of directories to index (recursively). +.BI "topdirs = "string +Space-separated list of files or +directories to recursively index. Default to ~ (indexes +$HOME). You can use symbolic links in the list, they will be followed, +independantly of the value of the followLinks variable. .TP -.BI "skippedNames = " patterns -A space-separated list of patterns for names of files or directories that -should be completely ignored. The list defined in the default file is: -.sp -.nf -*~ #* bin CVS Cache caughtspam tmp +.BI "skippedNames = "string +Files and directories which should be ignored. +White space separated list of wildcard patterns (simple ones, not paths, +must contain no / ), which will be tested against file and directory +names. The list in the default configuration does not exclude hidden +directories (names beginning with a dot), which means that it may index +quite a few things that you do not want. On the other hand, email user +agents like Thunderbird usually store messages in hidden directories, and +you probably want this indexed. One possible solution is to have '.*' in +'skippedNames', and add things like '~/.thunderbird' '~/.evolution' to +'topdirs'. Not even the file names are indexed for patterns in this +list, see the 'noContentSuffixes' variable for an alternative approach +which indexes the file names. Can be redefined for any +subtree. +.TP +.BI "noContentSuffixes = "string +List of name endings (not necessarily dot-separated suffixes) for +which we don't try MIME type identification, and don't uncompress or +index content. Only the names will be indexed. This +complements the now obsoleted recoll_noindex list from the mimemap file, +which will go away in a future release (the move from mimemap to +recoll.conf allows editing the list through the GUI). This is different +from skippedNames because these are name ending matches only (not +wildcard patterns), and the file name itself gets indexed normally. This +can be redefined for subdirectories. +.TP +.BI "skippedPaths = "string +Paths we should not go into. Space-separated list of +wildcard expressions for filesystem paths. Can contain files and +directories. The database and configuration directories will +automatically be added. The expressions are matched using 'fnmatch(3)' +with the FNM_PATHNAME flag set by default. This means that '/' characters +must be matched explicitely. You can set 'skippedPathsFnmPathname' to 0 +to disable the use of FNM_PATHNAME (meaning that '/*/dir3' will match +'/dir1/dir2/dir3'). The default value contains the usual mount point for +removable media to remind you that it is a bad idea to have Recoll work +on these (esp. with the monitor: media gets indexed on mount, all data +gets erased on unmount). Explicitely adding '/media/xxx' to the topdirs +will override this. +.TP +.BI "skippedPathsFnmPathname = "bool +Set to 0 to +override use of FNM_PATHNAME for matching skipped +paths. +.TP +.BI "daemSkippedPaths = "string +skippedPaths equivalent specific to +real time indexing. This enables having parts of the tree +which are initially indexed but not monitored. If daemSkippedPaths is +not set, the daemon uses skippedPaths. +.TP +.BI "zipSkippedNames = "string +Space-separated list of wildcard expressions for names that should +be ignored inside zip archives. This is used directly by +the zip handler, and has a function similar to skippedNames, but works +independantly. Can be redefined for subdirectories. Supported by recoll +1.20 and newer. See +https://bitbucket.org/medoc/recoll/wiki/Filtering%20out%20Zip%20archive%20members -.fi -The list can be redefined for subdirectories, but is only actually changed -for the top level ones in -.I topdirs .TP -.BI "skippedPaths = " patterns -A space-separated list of patterns for paths the indexer should not descend -into. Together with topdirs, this allows pruning the indexed tree to one's -content. -.B daemSkippedPaths -can be used to define a specific value for the real time indexing monitor. +.BI "followLinks = "bool +Follow symbolic links during +indexing. The default is to ignore symbolic links to avoid +multiple indexing of linked files. No effort is made to avoid duplication +when this option is set to true. This option can be set individually for +each of the 'topdirs' members by using sections. It can not be changed +below the 'topdirs' level. Links in the 'topdirs' list itself are always +followed. .TP -.BI "skippedPathsFnmPathname = " 0/1 -The values in the *skippedPaths variables are matched by default with -fnmatch(3), with the FNM_PATHNAME and FNM_LEADING_DIR flags. This means -that '/' characters must be matched explicitly. You can set -skippedPathsFnmPathname to 0 to disable the use of FNM_PATHNAME (meaning -that /*/dir3 will match /dir1/dir2/dir3). +.BI "indexedmimetypes = "string +Restrictive list of +indexed mime types. Normally not set (in which case all +supported types are indexed). If it is set, +only the types from the list will have their contents indexed. The names +will be indexed anyway if indexallfilenames is set (default). MIME +type names should be taken from the mimemap file. Can be redefined for +subtrees. .TP -.BI "followLinks = " boolean -Specifies if the indexer should follow -symbolic links while walking the file tree. The default is -to ignore symbolic links to avoid multiple indexing of -linked files. No effort is made to avoid duplication when -this option is set to true. This option can be set -individually for each of the -.I topdirs -members by using sections. It can not be changed below the -.I topdirs -level. +.BI "excludedmimetypes = "string +List of excluded MIME +types. Lets you exclude some types from indexing. Can be +redefined for subtrees. .TP -.BI "indexedmimetypes = " list -Recoll normally indexes any file which it knows how to read. This list lets -you restrict the indexed mime types to what you specify. If the variable is -unspecified or the list empty (the default), all supported types are -processed. +.BI "compressedfilemaxkbs = "int +Size limit for compressed +files. We need to decompress these in a +temporary directory for identification, which can be wasteful in some +cases. Limit the waste. Negative means no limit. 0 results in no +processing of any compressed file. Default 50 MB. .TP -.BI "compressedfilemaxkbs = " value -Size limit for compressed (.gz or .bz2) files. These need to be -decompressed in a temporary directory for identification, which can be very -wasteful if 'uninteresting' big compressed files are present. Negative -means no limit, 0 means no processing of any compressed file. Defaults -to \-1. +.BI "textfilemaxmbs = "int +Size limit for text +files. Mostly for skipping monster +logs. Default 20 MB. .TP -.BI "textfilemaxmbs = " value -Maximum size for text files. Very big text files are often uninteresting -logs. Set to \-1 to disable (default 20MB). +.BI "indexallfilenames = "bool +Index the file names of +unprocessed files Index the names of files the contents of +which we don't index because of an excluded or unsupported MIME +type. .TP -.BI "textfilepagekbs = " value -If this is set to other than \-1, text files will be indexed as multiple -documents of the given page size. This may be useful if you do want to -index very big text files as it will both reduce memory usage at index time -and help with loading data to the preview window. A size of a few megabytes -would seem reasonable (default: 1000 : 1MB). +.BI "usesystemfilecommand = "bool +Use a system command +for file MIME type guessing as a final step in file type +identification This is generally useful, but will usually +cause the indexing of many bogus 'text' files. See 'systemfilecommand' +for the command used. .TP -.BI "membermaxkbs = " "value in kilobytes" -This defines the maximum size for an archive member (zip, tar or rar at -the moment). Bigger entries will be skipped. Current default: 50000 (50 MB). +.BI "systemfilecommand = "string +Command used to guess +MIME types if the internal methods fails This should be a +"file -i" workalike. The file path will be added as a last parameter to +the command line. 'xdg-mime' works better than the traditional 'file' +command, and is now the configured default (with a hard-coded fallback to +'file') .TP -.BI "indexallfilenames = " boolean -Recoll indexes file names into a special section of the database to allow -specific file names searches using wild cards. This parameter decides if -file name indexing is performed only for files with mime types that would -qualify them for full text indexing, or for all files inside -the selected subtrees, independent of mime type. +.BI "processwebqueue = "bool +Decide if we process the +Web queue. The queue is a directory where the Recoll Web +browser plugins create the copies of visited pages. .TP -.BI "usesystemfilecommand = " boolean -Decide if we use the -.B "file \-i" -system command as a final step for determining the mime type for a file -(the main procedure uses suffix associations as defined in the -.B mimemap -file). This can be useful for files with suffixless names, but it will -also cause the indexing of many bogus "text" files. -.TP -.BI "processbeaglequeue = " 0/1 -If this is set, process the directory where Beagle Web browser plugins copy -visited pages for indexing. Of course, Beagle MUST NOT be running, else -things will behave strangely. -.TP -.BI "beaglequeuedir = " directory path -The path to the Beagle indexing queue. This is hard-coded in the Beagle -plugin as ~/.beagle/ToIndex so there should be no need to change it. -.TP -.BI "indexStripChars = " 0/1 -Decide if we strip characters of diacritics and convert them to lower-case -before terms are indexed. If we don't, searches sensitive to case and -diacritics can be performed, but the index will be bigger, and some -marginal weirdness may sometimes occur. The default is a stripped index -(indexStripChars = 1) for now. When using multiple indexes for a search, +.BI "textfilepagekbs = "int +Page size for text +files. If this is set, text/plain files will be divided +into documents of approximately this size. Will reduce memory usage at +index time and help with loading data in the preview window at query +time. Particularly useful with very big files, such as application or +system logs. Also see textfilemaxmbs and +compressedfilemaxkbs. +.TP +.BI "membermaxkbs = "int +Size limit for archive +members. This is passed to the filters in the environment +as RECOLL_FILTER_MAXMEMBERKB. +.TP +.BI "indexStripChars = "bool +Decide if we store +character case and diacritics in the index. If we do, +searches sensitive to case and diacritics can be performed, but the index +will be bigger, and some marginal weirdness may sometimes occur. The +default is a stripped index. When using multiple indexes for a search, this parameter must be defined identically for all. Changing the value implies an index reset. -.TP -.BI "maxTermExpand = " value -Maximum expansion count for a single term (e.g.: when using wildcards). The -default of 10000 is reasonable and will avoid queries that appear frozen -while the engine is walking the term list. -.TP -.BI "maxXapianClauses = " value -Maximum number of elementary clauses we can add to a single Xapian -query. In some cases, the result of term expansion can be multiplicative, -and we want to avoid using excessive memory. The default of 100 000 should -be both high enough in most cases and compatible with current typical -hardware configurations. -.TP -.BI "nonumbers = " 0/1 -If this set to true, no terms will be generated for numbers. For example -"123", "1.5e6", 192.168.1.4, would not be indexed ("value123" would still -be). Numbers are often quite interesting to search for, and this should -probably not be set except for special situations, ie, scientific documents -with huge amounts of numbers in them. This can only be set for a whole -index, not for a subtree. .TP -.BI "nocjk = " boolean -If this set to true, specific east asian (Chinese Korean Japanese) -characters/word splitting is turned off. This will save a small amount of -cpu if you have no CJK documents. If your document base does include such -text but you are not interested in searching it, setting -.I nocjk -may be a significant time and space saver. +.BI "nonumbers = "bool +Decides if terms will be +generated for numbers. For example "123", "1.5e6", +192.168.1.4, would not be indexed if nonumbers is set ("value123" would +still be). Numbers are often quite interesting to search for, and this +should probably not be set except for special situations, ie, scientific +documents with huge amounts of numbers in them, where setting nonumbers +will reduce the index size. This can only be set for a whole index, not +for a subtree. .TP -.BI "cjkngramlen = " value -This lets you adjust the size of n-grams used for indexing CJK text. The -default value of 2 is probably appropriate in most cases. A value of 3 -would allow more precision and efficiency on longer words, but the index -will be approximately twice as large. +.BI "dehyphenate = "bool +Determines if we index +'coworker' also when the input is 'co-worker'. This is new +in version 1.22, and on by default. Setting the variable to off allows +restoring the previous behaviour. .TP -.BI "indexstemminglanguages = " languages -A list of languages for which the stem expansion databases will be -built. See recollindex(1) for possible values. +.BI "nocjk = "bool +Decides if specific East Asian +(Chinese Korean Japanese) characters/word splitting is turned +off. This will save a small amount of CPU if you have no CJK +documents. If your document base does include such text but you are not +interested in searching it, setting nocjk may be a +significant time and space saver. .TP -.BI "defaultcharset = " charset -The name of the character set used for files that do not contain a -character set definition (ie: plain text files). This can be redefined for -any subdirectory. -.TP -.BI "unac_except_trans = " "list of utf-8 groups" -This is a list of characters, encoded in UTF-8, which should be handled -specially when converting text to unaccented lowercase. For example, in -Swedish, the letter "a with diaeresis" has full alphabet citizenship and -should not be turned into an a. -.br -Each element in the space-separated list has the special character as first -element and the translation following. The handling of both the lowercase -and upper-case versions of a character should be specified, as appartenance -to the list will turn-off both standard accent and case processing. -.br -Note that the translation is not limited to a single character. -.br -This parameter cannot be redefined for subdirectories, it is global, -because there is no way to do otherwise when querying. If you have document -sets which would need different values, you will have to index and query -them separately. +.BI "cjkngramlen = "int +This lets you adjust the size of +n-grams used for indexing CJK text. The default value of 2 is +probably appropriate in most cases. A value of 3 would allow more precision +and efficiency on longer words, but the index will be approximately twice +as large. .TP -.BI "maildefcharset = " character set name -This can be used to define the default character set specifically for email -messages which don't specify it. This is mainly useful for readpst (libpst) -dumps, which are utf-8 but do not say so. +.BI "indexstemminglanguages = "string +Languages for which to create stemming expansion +data. Stemmer names can be found by executing 'recollindex +-l', or this can also be set from a list in the GUI. .TP -.BI "localfields = " "fieldname = value:..." -This allows setting fields for all documents under a given -directory. Typical usage would be to set an "rclaptg" field, to be used in -mimeview to select a specific viewer. If several fields are to be set, they -should be separated with a colon (':') character (which there is currently -no way to escape). Ie: localfields= rclaptg=gnus:other = val, then select -specifier viewer with mimetype|tag=... in mimeview. +.BI "defaultcharset = "string +Default character +set. This is used for files which do not contain a +character set definition (e.g.: text/plain). Values found inside files, +e.g. a 'charset' tag in HTML documents, will override it. If this is not +set, the default character set is the one defined by the NLS environment +($LC_ALL, $LC_CTYPE, $LANG), or ultimately iso-8859-1 (cp-1252 in fact). +If for some reason you want a general default which does not match your +LANG and is not 8859-1, use this variable. This can be redefined for any +sub-directory. .TP -.BI "dbdir = " directory -The name of the Xapian database directory. It will be created if needed -when the database is initialized. If this is not an absolute pathname, it -will be taken relative to the configuration directory. +.BI "unac_except_trans = "string +A list of characters, +encoded in UTF-8, which should be handled specially +when converting text to unaccented lowercase. For +example, in Swedish, the letter a with diaeresis has full alphabet +citizenship and should not be turned into an a. +Each element in the space-separated list has the special character as +first element and the translation following. The handling of both the +lowercase and upper-case versions of a character should be specified, as +appartenance to the list will turn-off both standard accent and case +processing. The value is global and affects both indexing and querying. +Examples: +Swedish: +unac_except_trans = ää Ää öö Öö üü Üü ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl åå Ã…Ã¥ +. German: +unac_except_trans = ää Ää öö Öö üü Üü ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl +In French, you probably want to decompose oe and ae and nobody would type +a German ß +unac_except_trans = ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl +. The default for all until someone protests follows. These decompositions +are not performed by unac, but it is unlikely that someone would type the +composed forms in a search. +unac_except_trans = ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl .TP -.BI "idxstatusfile = " "file path" -The name of the scratch file where the indexer process updates its -status. Default: idxstatus.txt inside the configuration directory. +.BI "maildefcharset = "string +Overrides the default +character set for email messages which don't specify +one. This is mainly useful for readpst (libpst) dumps, +which are utf-8 but do not say so. .TP -.BI "maxfsoccuppc = " percentnumber -Maximum file system occupation before we -stop indexing. The value is a percentage, corresponding to -what the "Capacity" df output column shows. The default +.BI "localfields = "string +Set fields on all files +(usually of a specific fs area). Syntax is the usual: +name = value ; attr1 = val1 ; [...] +value is empty so this needs an initial semi-colon. This is useful, e.g., +for setting the rclaptg field for application selection inside +mimeview. +.TP +.BI "testmodifusemtime = "bool +Use mtime instead of +ctime to test if a file has been modified. The time is used +in addition to the size, which is always used. +Setting this can reduce re-indexing on systems where extended attributes +are used (by some other application), but not indexed, because changing +extended attributes only affects ctime. +Notes: +- This may prevent detection of change in some marginal file rename cases +(the target would need to have the same size and mtime). +- You should probably also set noxattrfields to 1 in this case, except if +you still prefer to perform xattr indexing, for example if the local +file update pattern makes it of value (as in general, there is a risk +for pure extended attributes updates without file modification to go +undetected). Perform a full index reset after changing this. + +.TP +.BI "noxattrfields = "bool +Disable extended attributes +conversion to metadata fields. This probably needs to be +set if testmodifusemtime is set. +.TP +.BI "metadatacmds = "string +Define commands to +gather external metadata, e.g. tmsu tags. +There can be several entries, separated by semi-colons, each defining +which field name the data goes into and the command to use. Don't forget the +initial semi-colon. All the field names must be different. You can use +aliases in the "field" file if necessary. +As a not too pretty hack conceded to convenience, any field name +beginning with "rclmulti" will be taken as an indication that the command +returns multiple field values inside a text blob formatted as a recoll +configuration file ("fieldname = fieldvalue" lines). The rclmultixx name +will be ignored, and field names and values will be parsed from the data. +Example: metadatacmds = ; tags = tmsu tags %f; rclmulti1 = cmdOutputsConf %f + +.TP +.BI "cachedir = "dfn +Top directory for Recoll data. Recoll data +directories are normally located relative to the configuration directory +(e.g. ~/.recoll/xapiandb, ~/.recoll/mboxcache). If 'cachedir' is set, the +directories are stored under the specified value instead (e.g. if +cachedir is ~/.cache/recoll, the default dbdir would be +~/.cache/recoll/xapiandb). This affects dbdir, webcachedir, +mboxcachedir, aspellDicDir, which can still be individually specified to +override cachedir. Note that if you have multiple configurations, each +must have a different cachedir, there is no automatic computation of a +subpath under cachedir. +.TP +.BI "maxfsoccuppc = "int +Maximum file system occupation +over which we stop indexing. The value is a percentage, +corresponding to what the "Capacity" df output column shows. The default value is 0, meaning no checking. .TP -.BI "mboxcachedir = " "directory path" -The directory where mbox message offsets cache files are held. This is -normally $RECOLL_CONFDIR/mboxcache, but it may be useful to share a -directory between different configurations. +.BI "xapiandb = "dfn +Xapian database directory +location. This will be created on first indexing. If the +value is not an absolute path, it will be interpreted as relative to +cachedir if set, or the configuration directory (-c argument or +$RECOLL_CONFDIR). If nothing is specified, the default is then +~/.recoll/xapiandb/ .TP -.BI "mboxcacheminmbs = " "value in megabytes" -The minimum mbox file size over which we cache the offsets. There is really no sense in caching offsets for small files. The default is 5 MB. +.BI "idxstatusfile = "fn +Name of the scratch file where the indexer process updates its +status. Default: idxstatus.txt inside the configuration +directory. .TP -.BI "webcachedir = " "directory path" -This is only used by the Beagle web browser plugin indexing code, and -defines where the cache for visited pages will live. Default: +.BI "mboxcachedir = "dfn +Directory location for storing mbox message offsets cache +files. This is normally 'mboxcache' under cachedir if set, +or else under the configuration directory, but it may be useful to share +a directory between different configurations. +.TP +.BI "mboxcacheminmbs = "int +Minimum mbox file size over which we cache the offsets. There is really no sense in caching offsets for small files. The +default is 5 MB. +.TP +.BI "webcachedir = "dfn +Directory where we store the archived web pages. This is only used by the web history indexing code +Default: cachedir/webcache if cachedir is set, else $RECOLL_CONFDIR/webcache .TP -.BI "webcachemaxmbs = " "value in megabytes" -This is only used by the Beagle web browser plugin indexing code, and -defines the maximum size for the web page cache. Default: 40 MB. +.BI "webcachemaxmbs = "int +Maximum size in MB of the Web archive. This is only used by the web history indexing code. +Default: 40 MB. +Reducing the size will not physically truncate the file. .TP -.BI "idxflushmb = " megabytes -Threshold (megabytes of new text data) -where we flush from memory to disk index. Setting this can -help control memory usage. A value of 0 means no explicit -flushing, letting Xapian use its own default, which is -flushing every 10000 documents (or XAPIAN_FLUSH_THRESHOLD), meaning that -memory usage depends on average document size. The default value is 10. +.BI "webqueuedir = "fn +The path to the Web indexing queue. This is +hard-coded in the plugin as ~/.recollweb/ToIndex so there should be no +need or possibility to change it. .TP -.BI "autodiacsens = " 0/1 -IF the index is not stripped, decide if we automatically trigger diacritics -sensitivity if the search term has accented characters (not in -unac_except_trans). Else you need to use the query language and the D -modifier to specify diacritics sensitivity. Default is no. +.BI "aspellDicDir = "dfn +Aspell dictionary storage directory location. The +aspell dictionary (aspdict.(lang).rws) is normally stored in the +directory specified by cachedir if set, or under the configuration +directory. .TP -.BI "autocasesens = " 0/1 -IF the index is not stripped, decide if we automatically trigger character -case sensitivity if the search term has upper-case characters in any but -the first position. Else you need to use the query language and the C -modifier to specify character-case sensitivity. Default is yes. +.BI "filtersdir = "dfn +Directory location for executable input handlers. If +RECOLL_FILTERSDIR is set in the environment, we use it instead. Defaults +to $prefix/share/recoll/filters. Can be redefined for +subdirectories. .TP -.BI "loglevel = " value -Verbosity level for recoll and recollindex. A value of 4 lists quite a lot of -debug/information messages. 3 lists only errors. -.B daemloglevel -can be used to specify a different value for the real-time indexing daemon. +.BI "iconsdir = "dfn +Directory location for icons. The only reason to +change this would be if you want to change the icons displayed in the +result list. Defaults to $prefix/share/recoll/images .TP -.BI "logfilename = " file -Where should the messages go. 'stderr' can be used as a special value. -.B daemlogfilename -can be used to specify a different value for the real-time indexing daemon. +.BI "idxflushmb = "int +Threshold (megabytes of new data) where we flush from memory to +disk index. Setting this allows some control over memory +usage by the indexer process. A value of 0 means no explicit flushing, +which lets Xapian perform its own thing, meaning flushing every +$XAPIAN_FLUSH_THRESHOLD documents created, modified or deleted: as memory +usage depends on average document size, not only document count, the +Xapian approach is is not very useful, and you should let Recoll manage +the flushes. The default value of idxflushmb is 10 MB, and may be a bit +low. If you are looking for maximum speed, you may want to experiment +with values between 20 and +80. In my experience, values beyond 100 are always counterproductive. If +you find otherwise, please drop me a note. .TP -.BI "mondelaypatterns = " "list of patterns" -This allows specify wildcard path patterns (processed with fnmatch(3) with -0 flag), to match files which change too often and for which a delay should -be observed before re-indexing. This is a space-separated list, each entry -being a pattern and a time in seconds, separated by a colon. You can use -double quotes if a path entry contains white space. Example: -.sp -mondelaypatterns = *.log:20 "this one has spaces*:10" -.TP -.BI "monixinterval = " "value in seconds -Minimum interval (seconds) for processing the indexing queue. The real time -monitor does not process each event when it comes in, but will wait this -time for the queue to accumulate to diminish overhead and in order to -aggregate multiple events to the same file. Default 30 S. +.BI "filtermaxseconds = "int +Maximum external filter execution time in +seconds. Default 1200 (20mn). Set to 0 for no limit. This +is mainly to avoid infinite loops in postscript files +(loop.ps) .TP -.BI "monauxinterval = " "value in seconds -Period (in seconds) at which the real time monitor will regenerate the -auxiliary databases (spelling, stemming) if needed. The default is one -hour. +.BI "filtermaxmbytes = "int +Maximum virtual memory space for filter processes +(setrlimit(RLIMIT_AS)), in megabytes. Note that this +includes any mapped libs (there is no reliable Linux way to limit the +data space only), so we need to be a bit generous here. Anything over +2000 will be ignored on 32 bits machines. .TP -.BI "monioniceclass, monioniceclassdata" -These allow defining the ionice class and data used by the indexer (default -class 3, no data). +.BI "thrQSizes = "string +Stage input queues configuration. There are three +internal queues in the indexing pipeline stages (file data extraction, +terms generation, index update). This parameter defines the queue depths +for each stage (three integer values). If a value of -1 is given for a +given stage, no queue is used, and the thread will go on performing the +next stage. In practise, deep queues have not been shown to increase +performance. Default: a value of 0 for the first queue tells Recoll to +perform autoconfiguration based on the detected number of CPUs (no need +for the two other values in this case). Use thrQSizes = -1 -1 -1 to +disable multithreading entirely. .TP -.BI "filtermaxseconds = " "value in seconds" -Maximum filter execution time, after which it is aborted. Some postscript -programs just loop... +.BI "thrTCounts = "string +Number of threads used for each indexing stage. The +three stages are: file data extraction, terms generation, index +update). The use of the counts is also controlled by some special values +in thrQSizes: if the first queue depth is 0, all counts are ignored +(autoconfigured); if a value of -1 is used for a queue depth, the +corresponding thread count is ignored. It makes no sense to use a value +other than 1 for the last stage because updating the Xapian index is +necessarily single-threaded (and protected by a mutex). .TP -.BI "filtersdir = " directory -A directory to search for the external filter scripts used to index some -types of files. The value should not be changed, except if you want to -modify one of the default scripts. The value can be redefined for any -subdirectory. +.BI "loglevel = "int +Log file verbosity 1-6. A value of 2 will print +only errors and warnings. 3 will print information like document updates, +4 is quite verbose and 6 very verbose. .TP -.BI "iconsdir = " directory -The name of the directory where -.B recoll -result list icons are stored. You can change this if you want different -images. +.BI "logfilename = "fn +Log file destination. Use 'stderr' (default) to write to the +console. .TP -.BI "idxabsmlen = " value -Recoll stores an abstract for each indexed file inside the database. The -text can come from an actual 'abstract' section in the document or will -just be the beginning of the document. It is stored in the index so that it -can be displayed inside the result lists without decoding the original -file. The -.I idxabsmlen -parameter defines the size of the stored abstract. The default value is 250 -bytes. The search interface gives you the choice to display this stored +.BI "idxloglevel = "int +Override loglevel for the indexer. +.TP +.BI "idxlogfilename = "fn +Override logfilename for the indexer. +.TP +.BI "daemloglevel = "int +Override loglevel for the indexer in real time +mode. The default is to use the idx... values if set, else +the log... values. +.TP +.BI "daemlogfilename = "fn +Override logfilename for the indexer in real time +mode. The default is to use the idx... values if set, else +the log... values. +.TP +.BI "idxrundir = "dfn +Indexing process current directory. The input +handlers sometimes leave temporary files in the current directory, so it +makes sense to have recollindex chdir to some temporary directory. If the +value is empty, the current directory is not changed. If the +value is (literal) tmp, we use the temporary directory as set by the +environment (RECOLL_TMPDIR else TMPDIR else /tmp). If the value is an +absolute path to a directory, we go there. +.TP +.BI "checkneedretryindexscript = "fn +Script used to heuristically check if we need to retry indexing +files which previously failed. The default script checks +the modified dates on /usr/bin and /usr/local/bin. A relative path will +be looked up in the filters dirs, then in the path. Use an absolute path +to do otherwise. +.TP +.BI "recollhelperpath = "string +Additional places to search for helper executables. This is only used on Windows for now. +.TP +.BI "idxabsmlen = "int +Length of abstracts we store while indexing. Recoll stores an abstract for each indexed file. +The text can come from an actual 'abstract' section in the +document or will just be the beginning of the document. It is stored in +the index so that it can be displayed inside the result lists without +decoding the original file. The idxabsmlen parameter +defines the size of the stored abstract. The default value is 250 +bytes. The search interface gives you the choice to display this stored text or a synthetic abstract built by extracting text around the search terms. If you always prefer the synthetic abstract, you can reduce this value and save a little space. .TP -.BI "aspellLanguage = " lang -Language definitions to use when creating the aspell dictionary. The value -must match a set of aspell language definition files. You can type "aspell -config" to see where these are installed (look for data-dir). The default -if the variable is not set is to use your desktop national language -environment to guess the value. +.BI "idxmetastoredlen = "int +Truncation length of stored metadata fields. This +does not affect indexing (the whole field is processed anyway), just the +amount of data stored in the index for the purpose of displaying fields +inside result lists or previews. The default value is 150 bytes which +may be too low if you have custom fields. .TP -.BI "noaspell = " boolean -If this is set, the aspell dictionary generation is turned off. Useful for -cases where you don't need the functionality or when it is unusable because -aspell crashes during dictionary generation. +.BI "aspellLanguage = "string +Language definitions to use when creating the aspell +dictionary. The value must match a set of aspell language +definition files. You can type "aspell dicts" to see a list The default +if this is not set is to use the NLS environment to guess the +value. .TP -.BI "mhmboxquirks = " flags -This allows definining location-related quirks for the mailbox -handler. Currently only the tbird flag is defined, and it should be set for -directories which hold Thunderbird data, as their folder format is weird. +.BI "aspellAddCreateParam = "string +Additional option and parameter to aspell dictionary creation +command. Some aspell packages may need an additional option +(e.g. on Debian Jessie: --local-data-dir=/usr/lib/aspell). See Debian bug +772415. +.TP +.BI "aspellKeepStderr = "bool +Set this to have a look at aspell dictionary creation +errors. There are always many, so this is mostly for +debugging. +.TP +.BI "noaspell = "bool +Disable aspell use. The aspell dictionary generation +takes time, and some combinations of aspell version, language, and local +terms, result in aspell crashing, so it sometimes makes sense to just +disable the thing. +.TP +.BI "monauxinterval = "int +Auxiliary database update interval. The real time +indexer only updates the auxiliary databases (stemdb, aspell) +periodically, because it would be too costly to do it for every document +change. The default period is one hour. +.TP +.BI "monixinterval = "int +Minimum interval (seconds) between processings of the indexing +queue. The real time indexer does not process each event +when it comes in, but lets the queue accumulate, to diminish overhead and +to aggregate multiple events affecting the same file. Default 30 +S. +.TP +.BI "mondelaypatterns = "string +Timing parameters for the real time indexing. Definitions for files which get a longer delay before reindexing +is allowed. This is for fast-changing files, that should only be +reindexed once in a while. A list of wildcardPattern:seconds pairs. The +patterns are matched with fnmatch(pattern, path, 0) You can quote entries +containing white space with double quotes (quote the whole entry, not the +pattern). The default is empty. +Example: mondelaypatterns = *.log:20 "*with spaces.*:30" +.TP +.BI "monioniceclass = "int +ionice class for the real time indexing process On platforms where this is supported. The default value is +3. +.TP +.BI "monioniceclassdata = "string +ionice class parameter for the real time indexing process. On platforms where this is supported. The default is +empty. +.TP +.BI "autodiacsens = "bool +auto-trigger diacritics sensitivity (raw index only). IF the index is not stripped, decide if we automatically trigger +diacritics sensitivity if the search term has accented characters (not in +unac_except_trans). Else you need to use the query language and the "D" +modifier to specify diacritics sensitivity. Default is no. +.TP +.BI "autocasesens = "bool +auto-trigger case sensitivity (raw index only). IF +the index is not stripped (see indexStripChars), decide if we +automatically trigger character case sensitivity if the search term has +upper-case characters in any but the first position. Else you need to use +the query language and the "C" modifier to specify character-case +sensitivity. Default is yes. +.TP +.BI "maxTermExpand = "int +Maximum query expansion count +for a single term (e.g.: when using wildcards). This only +affects queries, not indexing. We used to not limit this at all (except +for filenames where the limit was too low at 1000), but it is +unreasonable with a big index. Default 10000. +.TP +.BI "maxXapianClauses = "int +Maximum number of clauses +we add to a single Xapian query. This only affects queries, +not indexing. In some cases, the result of term expansion can be +multiplicative, and we want to avoid eating all the memory. Default +50000. +.TP +.BI "snippetMaxPosWalk = "int +Maximum number of positions we walk while populating a snippet for +the result list. The default of 1,000,000 may be +insufficient for very big documents, the consequence would be snippets +with possibly meaning-altering missing words. +.TP +.BI "pdfocr = "bool +Attempt OCR of PDF files with no text content if both tesseract and +pdftoppm are installed. The default is off because OCR is so +very slow. +.TP +.BI "pdfattach = "bool +Enable PDF attachment extraction by executing pdftk (if +available). This is +normally disabled, because it does slow down PDF indexing a bit even if +not one attachment is ever found. +.TP +.BI "mhmboxquirks = "string +Enable thunderbird/mozilla-seamonkey mbox format quirks Set this for the directory where the email mbox files are +stored. .SH SEE ALSO .PP diff --git a/src/doc/man/recollindex.1 b/src/doc/man/recollindex.1 index 1e9d618a..2340fd83 100644 --- a/src/doc/man/recollindex.1 +++ b/src/doc/man/recollindex.1 @@ -67,8 +67,18 @@ recollindex \- indexing command for the Recoll full text search system .B \-Z ] [ +.B \-K +] +[ +.B \-e +] +[ .B \-f ] +[ +.B \-p +pattern +] .br .B recollindex @@ -208,6 +218,14 @@ control system). will erase data for individual files from the database. The stem expansion databases will not be updated. .PP +Options +.B +\-i +and +.B +\-e +can be combined. This will first perform the purge, then the indexing. +.PP With options .B \-i or @@ -215,24 +233,30 @@ or , if no file names are given on the command line, they will be read from stdin, so that you could for example run: .PP -find /path/to/dir \-print | recollindex \-e -.PP -followed by -.PP -find /path/to/dir \-print | recollindex \-i +find /path/to/dir \-print | recollindex \-e \-i .PP to force the reindexing of a directory tree (which has to exist inside the file system area defined by .I topdirs in recoll.conf). You could mostly accomplish the same thing with .PP -.B find /path/to/dir \-print | recollindex \-f \-Z +find /path/to/dir \-print | recollindex \-Z \-i +.PP +The latter will perform a less thorough job of purging stale sub-documents +though. .PP .B recollindex \-r -mostly works like \-i, but the parameter is a single directory, which will +mostly works like +.B \-i +, but the parameter is a single directory, which will be recursively updated. This mostly does nothing more than .B find topdir | recollindex \-i -but it may be more convenient to use when started from another program. +but it may be more convenient to use when started from another +program. This retries failed files by default, use option +.B \-K +to change. One or multiple +.B \-p +options can be used to set shell-type selection patterns (e.g.: *.pdf). .PP .B recollindex \-l will list the names of available language stemmers. diff --git a/src/doc/user/Makefile b/src/doc/user/Makefile index cf7c057f..14b6c9c2 100644 --- a/src/doc/user/Makefile +++ b/src/doc/user/Makefile @@ -17,11 +17,16 @@ commonoptions=--stringparam section.autolabel 1 \ --stringparam generate.toc "book toc,title,figure,table,example,equation" -all: usermanual.html index.html usermanual.pdf +# index.html chunk format target replaced by nicer webhelp (needs separate +# make) in webhelp/ subdir +all: usermanual.html webh usermanual.pdf +webh: + make -C webhelp + usermanual.html: usermanual.xml - xsltproc ${commonoptions} \ - -o tmpfile.html "${XSLDIR}/html/docbook.xsl" usermanual.xml + xsltproc --xinclude ${commonoptions} \ + -o tmpfile.html "${XSLDIR}/html/docbook.xsl" $< -tidy -indent tmpfile.html > usermanual.html rm -f tmpfile.html @@ -29,10 +34,10 @@ index.html: usermanual.xml xsltproc ${commonoptions} \ --stringparam use.id.as.filename 1 \ --stringparam root.filename index \ - "${XSLDIR}/html/chunk.xsl" usermanual.xml + "${XSLDIR}/html/chunk.xsl" $< usermanual.pdf: usermanual.xml - dblatex usermanual.xml + dblatex $< clean: rm -f RCL.*.html usermanual.pdf usermanual.html index.html tmpfile.html diff --git a/src/doc/user/recoll.conf.xml b/src/doc/user/recoll.conf.xml new file mode 100644 index 00000000..81bd8b02 --- /dev/null +++ b/src/doc/user/recoll.conf.xml @@ -0,0 +1,589 @@ + + +Recoll main configuration file, recoll.conf + +Parameters affecting what documents we index + +topdirs +Space-separated list of files or +directories to recursively index. Default to ~ (indexes +$HOME). You can use symbolic links in the list, they will be followed, +independantly of the value of the followLinks variable. + +skippedNames +Files and directories which should be ignored. +White space separated list of wildcard patterns (simple ones, not paths, +must contain no / ), which will be tested against file and directory +names. The list in the default configuration does not exclude hidden +directories (names beginning with a dot), which means that it may index +quite a few things that you do not want. On the other hand, email user +agents like Thunderbird usually store messages in hidden directories, and +you probably want this indexed. One possible solution is to have ".*" in +"skippedNames", and add things like "~/.thunderbird" "~/.evolution" to +"topdirs". Not even the file names are indexed for patterns in this +list, see the "noContentSuffixes" variable for an alternative approach +which indexes the file names. Can be redefined for any +subtree. + +noContentSuffixes +List of name endings (not necessarily dot-separated suffixes) for +which we don't try MIME type identification, and don't uncompress or +index content. Only the names will be indexed. This +complements the now obsoleted recoll_noindex list from the mimemap file, +which will go away in a future release (the move from mimemap to +recoll.conf allows editing the list through the GUI). This is different +from skippedNames because these are name ending matches only (not +wildcard patterns), and the file name itself gets indexed normally. This +can be redefined for subdirectories. + +skippedPaths +Paths we should not go into. Space-separated list of +wildcard expressions for filesystem paths. Can contain files and +directories. The database and configuration directories will +automatically be added. The expressions are matched using 'fnmatch(3)' +with the FNM_PATHNAME flag set by default. This means that '/' characters +must be matched explicitely. You can set 'skippedPathsFnmPathname' to 0 +to disable the use of FNM_PATHNAME (meaning that '/*/dir3' will match +'/dir1/dir2/dir3'). The default value contains the usual mount point for +removable media to remind you that it is a bad idea to have Recoll work +on these (esp. with the monitor: media gets indexed on mount, all data +gets erased on unmount). Explicitely adding '/media/xxx' to the topdirs +will override this. + +skippedPathsFnmPathname +Set to 0 to +override use of FNM_PATHNAME for matching skipped +paths. + +daemSkippedPaths +skippedPaths equivalent specific to +real time indexing. This enables having parts of the tree +which are initially indexed but not monitored. If daemSkippedPaths is +not set, the daemon uses skippedPaths. + +zipSkippedNames +Space-separated list of wildcard expressions for names that should +be ignored inside zip archives. This is used directly by +the zip handler, and has a function similar to skippedNames, but works +independantly. Can be redefined for subdirectories. Supported by recoll +1.20 and newer. See +https://bitbucket.org/medoc/recoll/wiki/Filtering%20out%20Zip%20archive%20members + + +followLinks +Follow symbolic links during +indexing. The default is to ignore symbolic links to avoid +multiple indexing of linked files. No effort is made to avoid duplication +when this option is set to true. This option can be set individually for +each of the 'topdirs' members by using sections. It can not be changed +below the 'topdirs' level. Links in the 'topdirs' list itself are always +followed. + +indexedmimetypes +Restrictive list of +indexed mime types. Normally not set (in which case all +supported types are indexed). If it is set, +only the types from the list will have their contents indexed. The names +will be indexed anyway if indexallfilenames is set (default). MIME +type names should be taken from the mimemap file. Can be redefined for +subtrees. + +excludedmimetypes +List of excluded MIME +types. Lets you exclude some types from indexing. Can be +redefined for subtrees. + +compressedfilemaxkbs +Size limit for compressed +files. We need to decompress these in a +temporary directory for identification, which can be wasteful in some +cases. Limit the waste. Negative means no limit. 0 results in no +processing of any compressed file. Default 50 MB. + +textfilemaxmbs +Size limit for text +files. Mostly for skipping monster +logs. Default 20 MB. + +indexallfilenames +Index the file names of +unprocessed files Index the names of files the contents of +which we don't index because of an excluded or unsupported MIME +type. + +usesystemfilecommand +Use a system command +for file MIME type guessing as a final step in file type +identification This is generally useful, but will usually +cause the indexing of many bogus 'text' files. See 'systemfilecommand' +for the command used. + +systemfilecommand +Command used to guess +MIME types if the internal methods fails This should be a +"file -i" workalike. The file path will be added as a last parameter to +the command line. 'xdg-mime' works better than the traditional 'file' +command, and is now the configured default (with a hard-coded fallback to +'file') + +processwebqueue +Decide if we process the +Web queue. The queue is a directory where the Recoll Web +browser plugins create the copies of visited pages. + +textfilepagekbs +Page size for text +files. If this is set, text/plain files will be divided +into documents of approximately this size. Will reduce memory usage at +index time and help with loading data in the preview window at query +time. Particularly useful with very big files, such as application or +system logs. Also see textfilemaxmbs and +compressedfilemaxkbs. + +membermaxkbs +Size limit for archive +members. This is passed to the filters in the environment +as RECOLL_FILTER_MAXMEMBERKB. + + +Parameters affecting how we generate terms + +indexStripChars +Decide if we store +character case and diacritics in the index. If we do, +searches sensitive to case and diacritics can be performed, but the index +will be bigger, and some marginal weirdness may sometimes occur. The +default is a stripped index. When using multiple indexes for a search, +this parameter must be defined identically for all. Changing the value +implies an index reset. + +nonumbers +Decides if terms will be +generated for numbers. For example "123", "1.5e6", +192.168.1.4, would not be indexed if nonumbers is set ("value123" would +still be). Numbers are often quite interesting to search for, and this +should probably not be set except for special situations, ie, scientific +documents with huge amounts of numbers in them, where setting nonumbers +will reduce the index size. This can only be set for a whole index, not +for a subtree. + +dehyphenate +Determines if we index +'coworker' also when the input is 'co-worker'. This is new +in version 1.22, and on by default. Setting the variable to off allows +restoring the previous behaviour. + +nocjk +Decides if specific East Asian +(Chinese Korean Japanese) characters/word splitting is turned +off. This will save a small amount of CPU if you have no CJK +documents. If your document base does include such text but you are not +interested in searching it, setting nocjk may be a +significant time and space saver. + +cjkngramlen +This lets you adjust the size of +n-grams used for indexing CJK text. The default value of 2 is +probably appropriate in most cases. A value of 3 would allow more precision +and efficiency on longer words, but the index will be approximately twice +as large. + +indexstemminglanguages +Languages for which to create stemming expansion +data. Stemmer names can be found by executing 'recollindex +-l', or this can also be set from a list in the GUI. + +defaultcharset +Default character +set. This is used for files which do not contain a +character set definition (e.g.: text/plain). Values found inside files, +e.g. a 'charset' tag in HTML documents, will override it. If this is not +set, the default character set is the one defined by the NLS environment +($LC_ALL, $LC_CTYPE, $LANG), or ultimately iso-8859-1 (cp-1252 in fact). +If for some reason you want a general default which does not match your +LANG and is not 8859-1, use this variable. This can be redefined for any +sub-directory. + +unac_except_trans +A list of characters, +encoded in UTF-8, which should be handled specially +when converting text to unaccented lowercase. For +example, in Swedish, the letter a with diaeresis has full alphabet +citizenship and should not be turned into an a. +Each element in the space-separated list has the special character as +first element and the translation following. The handling of both the +lowercase and upper-case versions of a character should be specified, as +appartenance to the list will turn-off both standard accent and case +processing. The value is global and affects both indexing and querying. +Examples: +Swedish: +unac_except_trans = ää Ää öö Öö üü Üü ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl åå Ã…Ã¥ +. German: +unac_except_trans = ää Ää öö Öö üü Üü ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl +In French, you probably want to decompose oe and ae and nobody would type +a German ß +unac_except_trans = ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl +. The default for all until someone protests follows. These decompositions +are not performed by unac, but it is unlikely that someone would type the +composed forms in a search. +unac_except_trans = ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl + +maildefcharset +Overrides the default +character set for email messages which don't specify +one. This is mainly useful for readpst (libpst) dumps, +which are utf-8 but do not say so. + +localfields +Set fields on all files +(usually of a specific fs area). Syntax is the usual: +name = value ; attr1 = val1 ; [...] +value is empty so this needs an initial semi-colon. This is useful, e.g., +for setting the rclaptg field for application selection inside +mimeview. + +testmodifusemtime +Use mtime instead of +ctime to test if a file has been modified. The time is used +in addition to the size, which is always used. +Setting this can reduce re-indexing on systems where extended attributes +are used (by some other application), but not indexed, because changing +extended attributes only affects ctime. +Notes: +- This may prevent detection of change in some marginal file rename cases +(the target would need to have the same size and mtime). +- You should probably also set noxattrfields to 1 in this case, except if +you still prefer to perform xattr indexing, for example if the local +file update pattern makes it of value (as in general, there is a risk +for pure extended attributes updates without file modification to go +undetected). Perform a full index reset after changing this. + + +noxattrfields +Disable extended attributes +conversion to metadata fields. This probably needs to be +set if testmodifusemtime is set. + +metadatacmds +Define commands to +gather external metadata, e.g. tmsu tags. +There can be several entries, separated by semi-colons, each defining +which field name the data goes into and the command to use. Don't forget the +initial semi-colon. All the field names must be different. You can use +aliases in the "field" file if necessary. +As a not too pretty hack conceded to convenience, any field name +beginning with "rclmulti" will be taken as an indication that the command +returns multiple field values inside a text blob formatted as a recoll +configuration file ("fieldname = fieldvalue" lines). The rclmultixx name +will be ignored, and field names and values will be parsed from the data. +Example: metadatacmds = ; tags = tmsu tags %f; rclmulti1 = cmdOutputsConf %f + + + +Parameters affecting where and how we store things + +cachedir +Top directory for Recoll data. Recoll data +directories are normally located relative to the configuration directory +(e.g. ~/.recoll/xapiandb, ~/.recoll/mboxcache). If 'cachedir' is set, the +directories are stored under the specified value instead (e.g. if +cachedir is ~/.cache/recoll, the default dbdir would be +~/.cache/recoll/xapiandb). This affects dbdir, webcachedir, +mboxcachedir, aspellDicDir, which can still be individually specified to +override cachedir. Note that if you have multiple configurations, each +must have a different cachedir, there is no automatic computation of a +subpath under cachedir. + +maxfsoccuppc +Maximum file system occupation +over which we stop indexing. The value is a percentage, +corresponding to what the "Capacity" df output column shows. The default +value is 0, meaning no checking. + +xapiandb +Xapian database directory +location. This will be created on first indexing. If the +value is not an absolute path, it will be interpreted as relative to +cachedir if set, or the configuration directory (-c argument or +$RECOLL_CONFDIR). If nothing is specified, the default is then +~/.recoll/xapiandb/ + +idxstatusfile +Name of the scratch file where the indexer process updates its +status. Default: idxstatus.txt inside the configuration +directory. + +mboxcachedir +Directory location for storing mbox message offsets cache +files. This is normally 'mboxcache' under cachedir if set, +or else under the configuration directory, but it may be useful to share +a directory between different configurations. + +mboxcacheminmbs +Minimum mbox file size over which we cache the offsets. There is really no sense in caching offsets for small files. The +default is 5 MB. + +webcachedir +Directory where we store the archived web pages. This is only used by the web history indexing code +Default: cachedir/webcache if cachedir is set, else +$RECOLL_CONFDIR/webcache + +webcachemaxmbs +Maximum size in MB of the Web archive. This is only used by the web history indexing code. +Default: 40 MB. +Reducing the size will not physically truncate the file. + +webqueuedir +The path to the Web indexing queue. This is +hard-coded in the plugin as ~/.recollweb/ToIndex so there should be no +need or possibility to change it. + +aspellDicDir +Aspell dictionary storage directory location. The +aspell dictionary (aspdict.(lang).rws) is normally stored in the +directory specified by cachedir if set, or under the configuration +directory. + +filtersdir +Directory location for executable input handlers. If +RECOLL_FILTERSDIR is set in the environment, we use it instead. Defaults +to $prefix/share/recoll/filters. Can be redefined for +subdirectories. + +iconsdir +Directory location for icons. The only reason to +change this would be if you want to change the icons displayed in the +result list. Defaults to $prefix/share/recoll/images + + +Parameters affecting indexing performance and resource usage + +idxflushmb +Threshold (megabytes of new data) where we flush from memory to +disk index. Setting this allows some control over memory +usage by the indexer process. A value of 0 means no explicit flushing, +which lets Xapian perform its own thing, meaning flushing every +$XAPIAN_FLUSH_THRESHOLD documents created, modified or deleted: as memory +usage depends on average document size, not only document count, the +Xapian approach is is not very useful, and you should let Recoll manage +the flushes. The program compiled value is 0. The configured default +value (from this file) is 10 MB, and will be too low in many cases (it is +chosen to conserve memory). If you are looking +for maximum speed, you may want to experiment with values between 20 and +200. In my experience, values beyond this are always counterproductive. If +you find otherwise, please drop me a note. + +filtermaxseconds +Maximum external filter execution time in +seconds. Default 1200 (20mn). Set to 0 for no limit. This +is mainly to avoid infinite loops in postscript files +(loop.ps) + +filtermaxmbytes +Maximum virtual memory space for filter processes +(setrlimit(RLIMIT_AS)), in megabytes. Note that this +includes any mapped libs (there is no reliable Linux way to limit the +data space only), so we need to be a bit generous here. Anything over +2000 will be ignored on 32 bits machines. + +thrQSizes +Stage input queues configuration. There are three +internal queues in the indexing pipeline stages (file data extraction, +terms generation, index update). This parameter defines the queue depths +for each stage (three integer values). If a value of -1 is given for a +given stage, no queue is used, and the thread will go on performing the +next stage. In practise, deep queues have not been shown to increase +performance. Default: a value of 0 for the first queue tells Recoll to +perform autoconfiguration based on the detected number of CPUs (no need +for the two other values in this case). Use thrQSizes = -1 -1 -1 to +disable multithreading entirely. + +thrTCounts +Number of threads used for each indexing stage. The +three stages are: file data extraction, terms generation, index +update). The use of the counts is also controlled by some special values +in thrQSizes: if the first queue depth is 0, all counts are ignored +(autoconfigured); if a value of -1 is used for a queue depth, the +corresponding thread count is ignored. It makes no sense to use a value +other than 1 for the last stage because updating the Xapian index is +necessarily single-threaded (and protected by a mutex). + + +Miscellaneous parameters + +loglevel +Log file verbosity 1-6. A value of 2 will print +only errors and warnings. 3 will print information like document updates, +4 is quite verbose and 6 very verbose. + +logfilename +Log file destination. Use 'stderr' (default) to write to the +console. + +idxloglevel +Override loglevel for the indexer. + +idxlogfilename +Override logfilename for the indexer. + +daemloglevel +Override loglevel for the indexer in real time +mode. The default is to use the idx... values if set, else +the log... values. + +daemlogfilename +Override logfilename for the indexer in real time +mode. The default is to use the idx... values if set, else +the log... values. + +idxrundir +Indexing process current directory. The input +handlers sometimes leave temporary files in the current directory, so it +makes sense to have recollindex chdir to some temporary directory. If the +value is empty, the current directory is not changed. If the +value is (literal) tmp, we use the temporary directory as set by the +environment (RECOLL_TMPDIR else TMPDIR else /tmp). If the value is an +absolute path to a directory, we go there. + +checkneedretryindexscript +Script used to heuristically check if we need to retry indexing +files which previously failed. The default script checks +the modified dates on /usr/bin and /usr/local/bin. A relative path will +be looked up in the filters dirs, then in the path. Use an absolute path +to do otherwise. + +recollhelperpath +Additional places to search for helper executables. This is only used on Windows for now. + +idxabsmlen +Length of abstracts we store while indexing. Recoll stores an abstract for each indexed file. +The text can come from an actual 'abstract' section in the +document or will just be the beginning of the document. It is stored in +the index so that it can be displayed inside the result lists without +decoding the original file. The idxabsmlen parameter +defines the size of the stored abstract. The default value is 250 +bytes. The search interface gives you the choice to display this stored +text or a synthetic abstract built by extracting text around the search +terms. If you always prefer the synthetic abstract, you can reduce this +value and save a little space. + +idxmetastoredlen +Truncation length of stored metadata fields. This +does not affect indexing (the whole field is processed anyway), just the +amount of data stored in the index for the purpose of displaying fields +inside result lists or previews. The default value is 150 bytes which +may be too low if you have custom fields. + +aspellLanguage +Language definitions to use when creating the aspell +dictionary. The value must match a set of aspell language +definition files. You can type "aspell dicts" to see a list The default +if this is not set is to use the NLS environment to guess the +value. + +aspellAddCreateParam +Additional option and parameter to aspell dictionary creation +command. Some aspell packages may need an additional option +(e.g. on Debian Jessie: --local-data-dir=/usr/lib/aspell). See Debian bug +772415. + +aspellKeepStderr +Set this to have a look at aspell dictionary creation +errors. There are always many, so this is mostly for +debugging. + +noaspell +Disable aspell use. The aspell dictionary generation +takes time, and some combinations of aspell version, language, and local +terms, result in aspell crashing, so it sometimes makes sense to just +disable the thing. + +monauxinterval +Auxiliary database update interval. The real time +indexer only updates the auxiliary databases (stemdb, aspell) +periodically, because it would be too costly to do it for every document +change. The default period is one hour. + +monixinterval +Minimum interval (seconds) between processings of the indexing +queue. The real time indexer does not process each event +when it comes in, but lets the queue accumulate, to diminish overhead and +to aggregate multiple events affecting the same file. Default 30 +S. + +mondelaypatterns +Timing parameters for the real time indexing. Definitions for files which get a longer delay before reindexing +is allowed. This is for fast-changing files, that should only be +reindexed once in a while. A list of wildcardPattern:seconds pairs. The +patterns are matched with fnmatch(pattern, path, 0) You can quote entries +containing white space with double quotes (quote the whole entry, not the +pattern). The default is empty. +Example: mondelaypatterns = *.log:20 "*with spaces.*:30" + +monioniceclass +ionice class for the real time indexing process On platforms where this is supported. The default value is +3. + +monioniceclassdata +ionice class parameter for the real time indexing process. On platforms where this is supported. The default is +empty. + + +Query-time parameters (no impact on the index) + +autodiacsens +auto-trigger diacritics sensitivity (raw index only). IF the index is not stripped, decide if we automatically trigger +diacritics sensitivity if the search term has accented characters (not in +unac_except_trans). Else you need to use the query language and the "D" +modifier to specify diacritics sensitivity. Default is no. + +autocasesens +auto-trigger case sensitivity (raw index only). IF +the index is not stripped (see indexStripChars), decide if we +automatically trigger character case sensitivity if the search term has +upper-case characters in any but the first position. Else you need to use +the query language and the "C" modifier to specify character-case +sensitivity. Default is yes. + +maxTermExpand +Maximum query expansion count +for a single term (e.g.: when using wildcards). This only +affects queries, not indexing. We used to not limit this at all (except +for filenames where the limit was too low at 1000), but it is +unreasonable with a big index. Default 10000. + +maxXapianClauses +Maximum number of clauses +we add to a single Xapian query. This only affects queries, +not indexing. In some cases, the result of term expansion can be +multiplicative, and we want to avoid eating all the memory. Default +50000. + +snippetMaxPosWalk +Maximum number of positions we walk while populating a snippet for +the result list. The default of 1,000,000 may be +insufficient for very big documents, the consequence would be snippets +with possibly meaning-altering missing words. + + +Parameters for the PDF input script + +pdfocr +Attempt OCR of PDF files with no text content if both tesseract and +pdftoppm are installed. The default is off because OCR is so +very slow. + +pdfattach +Enable PDF attachment extraction by executing pdftk (if +available). This is +normally disabled, because it does slow down PDF indexing a bit even if +not one attachment is ever found. + + +Parameters set for specific locations + +mhmboxquirks +Enable thunderbird/mozilla-seamonkey mbox format quirks Set this for the directory where the email mbox files are +stored. + + diff --git a/src/doc/user/usermanual.html b/src/doc/user/usermanual.html new file mode 100644 index 00000000..c0c11462 --- /dev/null +++ b/src/doc/user/usermanual.html @@ -0,0 +1,10355 @@ + + + + + + + + Recoll user manual + + + + + + +
+
+
+
+

Recoll user manual

+
+ +
+
+

Jean-Francois Dockes

+ +
+ +
+
+
+ +
+ +
+ +
+
+

Permission is granted to copy, + distribute and/or modify this document under the terms + of the GNU Free Documentation License, Version 1.3 or + any later version published by the Free Software + Foundation; with no Invariant Sections, no Front-Cover + Texts, and no Back-Cover Texts. A copy of the license + can be found at the following location: GNU web site.

+ +

This document introduces full text search notions + and describes the installation and use of the + Recoll application. + This version describes Recoll 1.22.

+
+
+
+
+
+ +
+

Table of Contents

+ +
+
1. Introduction
+ +
+
+
1.1. Giving it a + try
+ +
1.2. Full text + search
+ +
1.3. Recoll + overview
+
+
+ +
2. Indexing
+ +
+
+
2.1. Introduction
+ +
+
+
2.1.1. Indexing + modes
+ +
2.1.2. Configurations, + multiple indexes
+ +
2.1.3. Document types
+ +
2.1.4. Indexing failures
+ +
2.1.5. Recovery
+
+
+ +
2.2. Index storage
+ +
+
+
2.2.1. Xapian index + formats
+ +
2.2.2. Security + aspects
+
+
+ +
2.3. Index + configuration
+ +
+
+
2.3.1. Multiple + indexes
+ +
2.3.2. Index case and + diacritics sensitivity
+ +
2.3.3. Indexing threads + configuration
+ +
2.3.4. The index configuration + GUI
+
+
+ +
2.4. Indexing WEB pages you + wisit
+ +
2.5. Extended attributes + data
+ +
2.6. Importing external + tags
+ +
2.7. Periodic + indexing
+ +
+
+
2.7.1. Running + indexing
+ +
2.7.2. Using cron to automate + indexing
+
+
+ +
2.8. Real time + indexing
+ +
+
+
2.8.1. Slowing down the + reindexing rate for fast changing + files
+
+
+
+
+ +
3. Searching
+ +
+
+
3.1. Searching with the Qt graphical user + interface
+ +
+
+
3.1.1. Simple + search
+ +
3.1.2. The default result + list
+ +
3.1.3. The result + table
+ +
3.1.4. Running arbitrary + commands on result files (1.20 and + later)
+ +
3.1.5. Displaying + thumbnails
+ +
3.1.6. The preview + window
+ +
3.1.7. The Query Fragments + window
+ +
3.1.8. Complex/advanced + search
+ +
3.1.9. The term explorer + tool
+ +
3.1.10. Multiple + indexes
+ +
3.1.11. Document + history
+ +
3.1.12. Sorting search results and + collapsing duplicates
+ +
3.1.13. Search tips, + shortcuts
+ +
3.1.14. Saving and restoring queries + (1.21 and later)
+ +
3.1.15. Customizing the search + interface
+
+
+ +
3.2. Searching with the KDE KIO + slave
+ +
+
+
3.2.1. What's this
+ +
3.2.2. Searchable + documents
+
+
+ +
3.3. Searching on the command + line
+ +
3.4. Using Synonyms + (1.22)
+ +
3.5. Path translations
+ +
3.6. The query language
+ +
+
+
3.6.1. Modifiers
+
+
+ +
3.7. Search case and diacritics + sensitivity
+ +
3.8. Anchored searches and + wildcards
+ +
+
+
3.8.1. More about + wildcards
+ +
3.8.2. Anchored + searches
+
+
+ +
3.9. Desktop + integration
+ +
+
+
3.9.1. Hotkeying + recoll
+ +
3.9.2. The KDE Kicker Recoll + applet
+
+
+
+
+ +
4. Programming interface
+ +
+
+
4.1. Writing a document input + handler
+ +
+
+
4.1.1. Simple input + handlers
+ +
4.1.2. "Multiple" + handlers
+ +
4.1.3. Telling + Recoll about the + handler
+ +
4.1.4. Input handler HTML + output
+ +
4.1.5. Page + numbers
+
+
+ +
4.2. Field data + processing
+ +
4.3. Python API
+ +
+
+
4.3.1. Introduction
+ +
4.3.2. Interface + elements
+ +
4.3.3. Python search + interface
+ +
4.3.4. Creating Python + external indexers
+ +
4.3.5. Package + compatibility with the previous + version
+
+
+
+
+ +
5. Installation and + configuration
+ +
+
+
5.1. Installing a binary + copy
+ +
5.2. Supporting + packages
+ +
5.3. Building from + source
+ +
+
+
5.3.1. Prerequisites
+ +
5.3.2. Building
+ +
5.3.3. Installation
+
+
+ +
5.4. Configuration + overview
+ +
+
+
5.4.1. Environment + variables
+ +
5.4.2. Recoll main + configuration file, recoll.conf
+ +
5.4.3. The fields + file
+ +
5.4.4. The mimemap + file
+ +
5.4.5. The mimeconf + file
+ +
5.4.6. The mimeview + file
+ +
5.4.7. The ptrans file
+ +
5.4.8. Examples of + configuration adjustments
+
+
+
+
+
+
+ +
+
+
+
+

Chapter 1. Introduction

+
+
+
+ +

This document introduces full text search notions and + describes the installation and use of the Recoll application. This version + describes Recoll 1.22.

+ +

Recoll was for a long + time dedicated to Unix-like systems. It was only lately + (2015) ported to MS-Windows. + Many references in this manual, especially file locations, + are specific to Unix, and not valid on Windows. Some described features are + also not available on Windows. The manual will be + progressively updated. Until this happens, most references to + shared files can be translated by looking under the Recoll + installation directory (esp. the Share subdirectory). The user configuration + is stored by default under AppData/Local/Recoll inside the user + directory, along with the index itself.

+ +
+
+
+
+

1.1. Giving it a + try

+
+
+
+ +

If you do not like reading manuals (who does?) but wish + to give Recoll a try, just + install the + application and start the recoll graphical user + interface (GUI), which will ask permission to index your + home directory by default, allowing you to search + immediately after indexing completes.

+ +

Do not do this if your home directory contains a huge + number of documents and you do not want to wait or are very + short on disk space. In this case, you may first want to + customize the configuration to + restrict the indexed area (for the very impatient with a + completed package install, from the recoll GUI: PreferencesIndexing configuration, then adjust + the Top directories + section).

+ +

Also be aware that, on Unix/Linux, you may need to + install the appropriate supporting applications + for document types that need them (for example antiword for Microsoft Word files).

+ +

The Recoll installation + for Windows is + self-contained and includes most useful auxiliary programs. + You will just need to install Python 2.7.

+
+ +
+
+
+
+

1.2. Full text + search

+
+
+
+ +

Recoll is a full text + search application, which means that it finds your data by + content rather than by external attributes (like the file + name). You specify words (terms) which should or should not + appear in the text you are looking for, and receive in + return a list of matching documents, ordered so that the + most relevant + documents will appear first.

+ +

You do not need to remember in what file or email + message you stored a given piece of information. You just + ask for related terms, and the tool will return a list of + documents where these terms are prominent, in a similar way + to Internet search engines.

+ +

Full text search applications try to determine which + documents are most relevant to the search terms you + provide. Computer algorithms for determining relevance can + be very complex, and in general are inferior to the power + of the human mind to rapidly determine relevance. The + quality of relevance guessing is probably the most + important aspect when evaluating a search application.

+ +

In many cases, you are looking for all the forms of a + word, including plurals, different tenses for a verb, or + terms derived from the same root or stem (example: floor, floors, floored, + flooring...). Queries are usually automatically + expanded to all such related terms (words that reduce to + the same stem). This can be prevented for searching for a + specific form.

+ +

Stemming, by itself, does not accommodate for + misspellings or phonetic searches. A full text search + application may also support this form of approximation. + For example, a search for aliterattion returning no + result may propose, depending on index contents, alliteration alteration alterations + altercation as possible replacement terms.

+
+ +
+
+
+
+

1.3. Recoll + overview

+
+
+
+ +

Recoll uses the + Xapian + information retrieval library as its storage and retrieval + engine. Xapian is a very + mature package using a + sophisticated probabilistic ranking model.

+ +

The Xapian library + manages an index database which describes where terms + appear in your document files. It efficiently processes the + complex queries which are produced by the Recoll query expansion mechanism, and + is in charge of the all-important relevance computation + task.

+ +

Recoll provides the + mechanisms and interface to get data into and out of the + index. This includes translating the many possible document + formats into pure text, handling term variations (using + Xapian stemmers), and + spelling approximations (using the aspell speller), interpreting user + queries and presenting results.

+ +

In a shorter way, Recoll does the dirty footwork, + Xapian deals with the + intelligent parts of the process.

+ +

The Xapian index can be + big (roughly the size of the original document set), but it + is not a document archive. Recoll can only display documents that + still exist at the place from which they were indexed. + (Actually, there is a way to reconstruct a document from + the information in the index, but the result is not nice, + as all formatting, punctuation and capitalization are + lost).

+ +

Recoll stores all + internal data in Unicode + UTF-8 format, and it can index files of many types + with different character sets, encodings, and languages + into the same index. It can process documents embedded + inside other documents (for example a pdf document stored + inside a Zip archive sent as an email attachment...), down + to an arbitrary depth.

+ +

Stemming is the process by which Recoll reduces words to their radicals + so that searching does not depend, for example, on a word + being singular or plural (floor, floors), or on a verb + tense (flooring, floored). Because the mechanisms used for + stemming depend on the specific grammatical rules for each + language, there is a separate Xapian stemmer module for most common + languages where stemming makes sense.

+ +

Recoll stores the + unstemmed versions of terms in the main index and uses + auxiliary databases for term expansion (one for each + stemming language), which means that you can switch + stemming languages between searches, or add a language + without needing a full reindex.

+ +

Storing documents written in different languages in the + same index is possible, and commonly done. In this + situation, you can specify several stemming languages for + the index.

+ +

Recoll currently makes + no attempt at automatic language recognition, which means + that the stemmer will sometimes be applied to terms from + other languages with potentially strange results. In + practise, even if this introduces possibilities of + confusion, this approach has been proven quite useful, and + it is much less cumbersome than separating your documents + according to what language they are written in.

+ +

By default, Recoll + strips most accents and diacritics from terms, and converts + them to lower case before either storing them in the index + or searching for them. As a consequence, it is impossible + to search for a particular capitalization of a term + (US / us), or to discriminate two terms based on + diacritics (sake / + saké, mate / maté).

+ +

Recoll versions 1.18 + and newer can optionally store the raw terms, without + accent stripping or case conversion. In this configuration, + default searches will behave as before, but it is possible + to perform searches sensitive to case and diacritics. This + is described in more detail in the section + about index case and diacritics sensitivity.

+ +

Recoll has many + parameters which define exactly what to index, and how to + classify and decode the source documents. These are kept in + configuration files. A + default configuration is copied into a standard location + (usually something like /usr/share/recoll/examples) during + installation. The default values set by the configuration + files in this directory may be overridden by values set + inside your personal configuration, found by default in the + .recoll sub-directory of your + home directory. The default configuration will index your + home directory with default parameters and should be + sufficient for giving Recoll a try, but you may want to + adjust it later, which can be done either by editing the + text files or by using configuration menus in the + recoll GUI. + Some other parameters affecting only the recoll GUI are stored in + the standard location defined by Qt.

+ +

The indexing process + is started automatically the first time you execute the + recoll GUI. + Indexing can also be performed by executing the + recollindex + command. Recoll indexing + is multithreaded by default when appropriate hardware + resources are available, and can perform in parallel + multiple tasks among text extraction, segmentation and + index updates.

+ +

Searches are usually + performed inside the recoll GUI, which has + many options to help you find what you are looking for. + However, there are other ways to perform Recoll searches: mostly a command line + interface, a Python programming interface, a + KDE KIO slave module, and Ubuntu + Unity Lens (for older versions) or Scope (for current versions) modules.

+
+
+ +
+
+
+
+

Chapter 2. Indexing

+
+
+
+ +
+
+
+
+

2.1. Introduction

+
+
+
+ +

Indexing is the process by which the set of documents is + analyzed and the data entered into the database. + Recoll indexing is + normally incremental: documents will only be processed if + they have been modified since the last run. On the first + execution, all documents will need processing. A full index + build can be forced later by specifying an option to the + indexing command (recollindex -z or -Z).

+ +

recollindex skips files + which caused an error during a previous pass. This is a + performance optimization, and a new behaviour in version + 1.21 (failed files were always retried by previous + versions). The command line option -k can be set to retry failed files, for + example after updating a filter.

+ +

The following sections give an overview of different + aspects of the indexing processes and configuration, with + links to detailed sections.

+ +

Depending on your data, temporary files may be needed + during indexing, some of them possibly quite big. You can + use the RECOLL_TMPDIR or + TMPDIR environment variables to + determine where they are created (the default is to use + /tmp). Using TMPDIR has the nice property that it may + also be taken into account by auxiliary commands executed + by recollindex.

+ +
+
+
+
+

2.1.1. Indexing + modes

+
+
+
+ +

Recoll indexing can + be performed along two different modes:

+ +
+
    +
  • +

    Periodic (or + batch) indexing: indexing takes place + at discrete times, by executing the recollindex + command. The typical usage is to have a nightly + indexing run programmed + into your cron file.

    +
  • + +
  • +

    Real time + indexing: indexing takes place as soon + as a file is created or changed. recollindex runs + as a daemon and uses a file system alteration + monitor such as inotify, Fam or Gamin to detect file + changes.

    +
  • +
+
+ +

The choice between the two methods is mostly a matter + of preference, and they can be combined by setting up + multiple indexes (ie: use periodic indexing on a big + documentation directory, and real time indexing on a + small home directory). Monitoring a big file system tree + can consume significant system resources.

+ +

The choice of method and the parameters used can be + configured from the recoll GUI: + Preferences → + Indexing schedule

+ +

The File menu also has + entries to start or stop the current indexing operation. + Stopping indexing is performed by killing the + recollindex + process, which will checkpoint its state and exit. A + later restart of indexing will mostly resume from where + things stopped (the file tree walk has to be restarted + from the beginning).

+ +

When the real time indexer is running, only a stop + operation is available from the menu. When no indexing is + running, you have a choice of updating the index or + rebuilding it (the first choice only processes changed + files, the second one zeroes the index before starting so + that all files are processed).

+
+ +
+
+
+
+

2.1.2. Configurations, + multiple indexes

+
+
+
+ +

The parameters describing what is to be indexed and + local preferences are defined in text files contained in + a configuration + directory.

+ +

All parameters have defaults, defined in system-wide + files.

+ +

Without further configuration, Recoll will index all appropriate + files from your home directory, with a reasonable set of + defaults.

+ +

A default personal configuration directory + ($HOME/.recoll/) is created + when a Recoll program is + first executed. It is possible to create other + configuration directories, and use them by setting the + RECOLL_CONFDIR environment + variable, or giving the -c + option to any of the Recoll commands.

+ +

In some cases, it may be interesting to index + different areas of the file system to separate databases. + You can do this by using multiple configuration + directories, each indexing a file system area to a + specific database. Typically, this would be done to + separate personal and shared indexes, or to take + advantage of the organization of your data to improve + search precision.

+ +

The generated indexes can be queried concurrently in a + transparent manner.

+ +

For index generation, multiple configurations are + totally independant from each other. When multiple + indexes need to be used for a single search, some parameters should be + consistent among the configurations.

+
+ +
+
+
+
+

2.1.3. Document types

+
+
+
+ +

Recoll knows about + quite a few different document types. The parameters for + document types recognition and processing are set in + configuration + files.

+ +

Most file types, like HTML or word processing files, + only hold one document. Some file types, like email + folders or zip archives, can hold many individually + indexed documents, which may themselves be compound ones. + Such hierarchies can go quite deep, and Recoll can process, for example, a + LibreOffice document + stored as an attachment to an email message inside an + email folder archived in a zip file...

+ +

Recoll indexing + processes plain text, HTML, OpenDocument + (Open/LibreOffice), email formats, and a few others + internally.

+ +

Other file types (ie: postscript, pdf, ms-word, rtf + ...) need external applications for preprocessing. The + list is in the installation section. + After every indexing operation, Recoll updates a list of commands + that would be needed for indexing existing files types. + This list can be displayed by selecting the menu option + FileShow Missing Helpers in the + recoll GUI. + It is stored in the missing + text file inside the configuration directory.

+ +

By default, Recoll + will try to index any file type that it has a way to + read. This is sometimes not desirable, and there are ways + to either exclude some types, or on the contrary to + define a positive list of types to be indexed. In the + latter case, any type not in the list will be + ignored.

+ +

Excluding types can be done by adding wildcard name + patterns to the skippedNames + list, which can be done from the GUI Index configuration + menu. For versions 1.20 and later, you can alternatively + set the excludedmimetypes + list in the configuration file. This can be redefined for + subdirectories.

+ +

You can also define an exclusive list of MIME types to + be indexed (no others will be indexed), by settting the + indexedmimetypes + configuration variable. Example:

+
+indexedmimetypes = text/html application/pdf
+          
+
+ +

It is possible to redefine this parameter for + subdirectories. Example:

+
+[/path/to/my/dir]
+indexedmimetypes = application/pdf
+          
+
+ +

(When using sections like this, don't forget that they + remain in effect until the end of the file or another + section indicator).

+ +

excludedmimetypes or + indexedmimetypes, can be set + either by editing the + main configuration file (recoll.conf), or from the GUI index + configuration tool.

+ +
+

Note

+ +

When editing the indexedmimetypes or excludedmimetypes lists, you should + use the MIME values listed in the mimemap file or in Recoll result + lists in preference to file + -i output: there are a number of + differences.

+
+
+ +
+
+
+
+

2.1.4. Indexing + failures

+
+
+
+ +

Indexing may fail for some documents, for a number of + reasons: a helper program may be missing, the document + may be corrupt, we may fail to uncompress a file because + no file system space is available, etc.

+ +

Recoll versions prior + to 1.21 always retried to index files which had + previously caused an error. This guaranteed that anything + that may have become indexable (for example because a + helper had been installed) would be indexed. However this + was bad for performance because some indexing failures + may be quite costly (for example failing to uncompress a + big file because of insufficient disk space).

+ +

The indexer in Recoll + versions 1.21 and later does not retry failed file by + default. Retrying will only occur if an explicit option + (-k) is set on the + recollindex + command line, or if a script executed when recollindex starts up + says so. The script is defined by a configuration + variable (checkneedretryindexscript), and makes a + rather lame attempt at deciding if a helper command may + have been installed, by checking if any of the common + bin directories have + changed.

+
+ +
+
+
+
+

2.1.5. Recovery

+
+
+
+ +

In the rare case where the index becomes corrupted + (which can signal itself by weird search results or + crashes), the index files need to be erased before + restarting a clean indexing pass. Just delete the + xapiandb directory (see + next section), or, + alternatively, start the next recollindex with the + -z option, which will reset + the database before indexing.

+
+
+ +
+
+
+
+

2.2. Index + storage

+
+
+
+ +

The default location for the index data is the + xapiandb subdirectory of the + Recoll configuration + directory, typically $HOME/.recoll/xapiandb/. This can be + changed via two different methods (with different + purposes):

+ +
+
    +
  • +

    You can specify a different configuration + directory by setting the RECOLL_CONFDIR environment variable, + or using the -c option to + the Recoll commands. + This method would typically be used to index + different areas of the file system to different + indexes. For example, if you were to issue the + following command:

    +
    +recoll -c ~/.indexes-email
    +
    + +

    Then Recoll would + use configuration files stored in ~/.indexes-email/ and, (unless + specified otherwise in recoll.conf) would look for the + index in ~/.indexes-email/xapiandb/.

    + +

    Using multiple configuration directories and + + configuration options allows you to tailor + multiple configurations and indexes to handle + whatever subset of the available data you wish to + make searchable.

    +
  • + +
  • +

    For a given configuration directory, you can + specify a non-default storage location for the index + by setting the dbdir + parameter in the configuration file (see the + + configuration section). This method would mainly + be of use if you wanted to keep the configuration + directory in its default location, but desired + another location for the index, typically out of disk + occupation concerns.

    +
  • +
+
+ +

The size of the index is determined by the size of the + set of documents, but the ratio can vary a lot. For a + typical mixed set of documents, the index size will often + be close to the data set size. In specific cases (a set of + compressed mbox files for example), the index can become + much bigger than the documents. It may also be much smaller + if the documents contain a lot of images or other + non-indexed data (an extreme example being a set of mp3 + files where only the tags would be indexed).

+ +

Of course, images, sound and video do not increase the + index size, which means that nowadays (2012), typically, + even a big index will be negligible against the total + amount of data on the computer.

+ +

The index data directory (xapiandb) only contains data that can be + completely rebuilt by an index run (as long as the original + documents exist), and it can always be destroyed + safely.

+ +
+
+
+
+

2.2.1. Xapian + index formats

+
+
+
+ +

Xapian versions + usually support several formats for index storage. A + given major Xapian + version will have a current format, used to create new + indexes, and will also support the format from the + previous major version.

+ +

Xapian will not + convert automatically an existing index from the older + format to the newer one. If you want to upgrade to the + new format, or if a very old index needs to be converted + because its format is not supported any more, you will + have to explicitly delete the old index, then run a + normal indexing process.

+ +

Using the -z option to + recollindex + is not sufficient to change the format, you will have to + delete all files inside the index directory (typically + ~/.recoll/xapiandb) before + starting the indexing.

+
+ +
+
+
+
+

2.2.2. Security + aspects

+
+
+
+ +

The Recoll index does + not hold copies of the indexed documents. But it does + hold enough data to allow for an almost complete + reconstruction. If confidential data is indexed, access + to the database directory should be restricted.

+ +

Recoll will create + the configuration directory with a mode of 0700 (access + by owner only). As the index data directory is by default + a sub-directory of the configuration directory, this + should result in appropriate protection.

+ +

If you use another setup, you should think of the kind + of protection you need for your index, set the directory + and files access modes appropriately, and also maybe + adjust the umask used during + index updates.

+
+
+ +
+
+
+
+

2.3. Index + configuration

+
+
+
+ +

Variables set inside the Recoll configuration files control + which areas of the file system are indexed, and how files + are processed. These variables can be set either by editing + the text files or by using the dialogs in the + recoll + GUI.

+ +

The first time you start recoll, you will be asked + whether or not you would like it to build the index. If you + want to adjust the configuration before indexing, just + click Cancel at this point, + which will get you into the configuration interface. If you + exit at this point, recoll + will have created a ~/.recoll + directory containing empty configuration files, which you + can edit by hand.

+ +

The configuration is documented inside the installation chapter + of this document, or in the recoll.conf(5) man page, but + the most current information will most likely be the + comments inside the sample file. The most immediately + useful variable you may interested in is probably topdirs, which determines what + subtrees get indexed.

+ +

The applications needed to index file types other than + text, HTML or email (ie: pdf, postscript, ms-word...) are + described in the external packages + section.

+ +

As of Recoll 1.18 there are two incompatible types of + Recoll indexes, depending on the treatment of character + case and diacritics. The next section describes the two + types in more detail.

+ +
+
+
+
+

2.3.1. Multiple + indexes

+
+
+
+ +

Multiple Recoll + indexes can be created by using several configuration + directories which are usually set to index different + areas of the file system. A specific index can be + selected for updating or searching, using the + RECOLL_CONFDIR environment + variable or the -c option to + recoll and + recollindex.

+ +

When working with the recoll index + configuration GUI, the configuration directory for which + parameters are modified is the one which was selected by + RECOLL_CONFDIR or the + -c parameter, and there is no + way to switch configurations within the GUI.

+ +

Additional configuration directory (beyond + ~/.recoll) must be created + by hand (mkdir or such), the GUI + will not do it. This is to avoid mistakenly creating + additional directories when an argument is mistyped.

+ +

A typical usage scenario for the multiple index + feature would be for a system administrator to set up a + central index for shared data, that you choose to search + or not in addition to your personal data. Of course, + there are other possibilities. There are many cases where + you know the subset of files that should be searched, and + where narrowing the search can improve the results. You + can achieve approximately the same effect with the + directory filter in advanced search, but multiple indexes + will have much better performance and may be worth the + trouble.

+ +

A recollindex program + instance can only update one specific index.

+ +

The main index (defined by RECOLL_CONFDIR or -c) is always active. If this is + undesirable, you can set up your base configuration to + index an empty directory.

+ +

The different search interfaces (GUI, command line, + ...) have different methods to define the set of indexes + to be used, see the appropriate section.

+ +

If a set of multiple indexes are to be used together + for searches, some configuration parameters must be + consistent among the set. These are parameters which need + to be the same when indexing and searching. As the + parameters come from the main configuration when + searching, they need to be compatible with what was set + when creating the other indexes (which came from their + respective configuration directories).

+ +

Most importantly, all indexes to be queried + concurrently must have the same option concerning + character case and diacritics stripping, but there are + other constraints. Most of the relevant parameters are + described in the + linked section.

+
+ +
+
+
+
+

2.3.2. Index + case and diacritics sensitivity

+
+
+
+ +

As of Recoll version + 1.18 you have a choice of building an index with terms + stripped of character case and diacritics, or one with + raw terms. For a source term of Résumé, the former will + store resume, the latter + Résumé.

+ +

Each type of index allows performing searches + insensitive to case and diacritics: with a raw index, the + user entry will be expanded to match all case and + diacritics variations present in the index. With a + stripped index, the search term will be stripped before + searching.

+ +

A raw index allows for another possibility which a + stripped index cannot offer: using case and diacritics to + discriminate between terms, returning different results + when searching for US and + us or resume and résumé. Read the section + about search case and diacritics sensitivity for more + details.

+ +

The type of index to be created is controlled by the + indexStripChars + configuration variable which can only be changed by + editing the configuration file. Any change implies an + index reset (not automated by Recoll), and all indexes in a search + must be set in the same way (again, not checked by + Recoll).

+ +

If the indexStripChars is + not set, Recoll 1.18 + creates a stripped index by default, for compatibility + with previous versions.

+ +

As a cost for added capability, a raw index will be + slightly bigger than a stripped one (around 10%). Also, + searches will be more complex, so probably slightly + slower, and the feature is still young, so that a certain + amount of weirdness cannot be excluded.

+ +

One of the most adverse consequence of using a raw + index is that some phrase and proximity searches may + become impossible: because each term needs to be + expanded, and all combinations searched for, the + multiplicative expansion may become unmanageable.

+
+ +
+
+
+
+

2.3.3. Indexing + threads configuration

+
+
+
+ +

The Recoll indexing + process recollindex can use + multiple threads to speed up indexing on multiprocessor + systems. The work done to index files is divided in + several stages and some of the stages can be executed by + multiple threads. The stages are:

+ +
+
    +
  1. File system walking: this is + always performed by the main thread.
  2. + +
  3. File conversion and data + extraction.
  4. + +
  5. Text processing (splitting, + stemming, etc.)
  6. + +
  7. Xapian index update.
  8. +
+
+ +

You can also read a longer document about the + transformation of Recoll + indexing to multithreading.

+ +

The threads configuration is controlled by two + configuration file parameters.

+ +
+
+
thrQSizes
+ +
+

This variable defines the job input queues + configuration. There are three possible queues for + stages 2, 3 and 4, and this parameter should give + the queue depth for each stage (three integer + values). If a value of -1 is used for a given + stage, no queue is used, and the thread will go on + performing the next stage. In practise, deep queues + have not been shown to increase performance. A + value of 0 for the first queue tells Recoll to perform + autoconfiguration (no need for anything else in + this case, thrTCounts is not used) - this is the + default configuration.

+
+ +
thrTCounts
+ +
+

This defines the number of threads used for each + stage. If a value of -1 is used for one of the + queue depths, the corresponding thread count is + ignored. It makes no sense to use a value other + than 1 for the last stage because updating the + Xapian index is + necessarily single-threaded (and protected by a + mutex).

+
+
+
+ +
+

Note

+ +

If the first value in thrQSizes is 0, thrTCounts is ignored.

+
+ +

The following example would use three queues (of depth + 2), and 4 threads for converting source documents, 2 for + processing their text, and one to update the index. This + was tested to be the best configuration on the test + system (quadri-processor with multiple disks).

+
+thrQSizes = 2 2 2
+thrTCounts =  4 2 1
+
+ +

The following example would use a single queue, and + the complete processing for each document would be + performed by a single thread (several documents will + still be processed in parallel in most cases). The + threads will use mutual exclusion when entering the index + update stage. In practise the performance would be close + to the precedent case in general, but worse in certain + cases (e.g. a Zip archive would be performed purely + sequentially), so the previous approach is preferred. + YMMV... The 2 last values for thrTCounts are ignored.

+
+thrQSizes = 2 -1 -1
+thrTCounts =  6 1 1
+
+ +

The following example would disable multithreading. + Indexing will be performed by a single thread.

+
+thrQSizes = -1 -1 -1
+
+
+ +
+
+
+
+

2.3.4. The + index configuration GUI

+
+
+
+ +

Most parameters for a given index configuration can be + set from a recoll GUI running on + this configuration (either as default, or by setting + RECOLL_CONFDIR or the + -c option.)

+ +

The interface is started from the PreferencesIndex Configuration menu entry. It + is divided in four tabs, Global + parameters, Local + parameters, Web + history (which is explained in the next section) + and Search parameters.

+ +

The Global parameters + tab allows setting global variables, like the lists of + top directories, skipped paths, or stemming + languages.

+ +

The Local parameters tab + allows setting variables that can be redefined for + subdirectories. This second tab has an initially empty + list of customisation directories, to which you can add. + The variables are then set for the currently selected + directory (or at the top level if the empty line is + selected).

+ +

The Search parameters + section defines parameters which are used at query time, + but are global to an index and affect all search tools, + not only the GUI.

+ +

The meaning for most entries in the interface is + self-evident and documented by a ToolTip popup on the text label. For + more detail, you will need to refer to the configuration + section of this guide.

+ +

The configuration tool normally respects the comments + and most of the formatting inside the configuration file, + so that it is quite possible to use it on hand-edited + files, which you might nevertheless want to backup + first...

+
+
+ +
+
+
+
+

2.4. Indexing WEB + pages you wisit

+
+
+
+ +

With the help of a Firefox extension, Recoll can index the Internet pages + that you visit. The extension was initially designed for + the Beagle indexer, but it + has recently be renamed and better adapted to Recoll.

+ +

The extension works by copying visited WEB pages to an + indexing queue directory, which Recoll then processes, indexing the + data, storing it into a local cache, then removing the file + from the queue.

+ +

This feature can be enabled in the GUI Index configuration panel, or by editing + the configuration file (set processwebqueue to 1).

+ +

A current pointer to the extension can be found, along + with up-to-date instructions, on the Recoll wiki.

+ +

A copy of the indexed WEB pages is retained by Recoll in + a local cache (from which previews can be fetched). The + cache size can be adjusted from the Index configuration / Web history panel. Once the maximum size + is reached, old pages are purged - both from the cache and + the index - to make room for new ones, so you need to + explicitly archive in some other place the pages that you + want to keep indefinitely.

+
+ +
+
+
+
+

2.5. Extended + attributes data

+
+
+
+ +

User extended attributes are named pieces of information + that most modern file systems can attach to any file.

+ +

Recoll versions 1.19 + and later process extended attributes as document fields by + default. For older versions, this has to be activated at + build time.

+ +

A freedesktop standard defines a few + special attributes, which are handled as such by + Recoll:

+ +
+
+
mime_type
+ +
+

If set, this overrides any other determination of + the file MIME type.

+
+ +
charset
+ +
If set, this defines the file character set (mostly + useful for plain text files).
+
+
+ +

By default, other attributes are handled as Recoll fields. On Linux, the + user prefix is removed from + the name. This can be configured more precisely inside the + fields configuration file.

+
+ +
+
+
+
+

2.6. Importing + external tags

+
+
+
+ +

During indexing, it is possible to import metadata for + each file by executing commands. For example, this could + extract user tag data for the file and store it in a field + for indexing.

+ +

See the section about + the metadatacmds field in + the main configuration chapter for a description of the + configuration syntax.

+ +

As an example, if you would want Recoll to use tags managed by + tmsu, you would add the + following to the configuration file:

+
+[/some/area/of/the/fs]
+metadatacmds = ; tags = tmsu tags %f
+      
+
+ +
+

Note

+ +

Depending on the tmsu + version, you may need/want to add options like + --database=/some/db.

+
+ +

You may want to restrict this processing to a subset of + the directory tree, because it may slow down indexing a bit + ([some/area/of/the/fs]).

+ +

Note the initial semi-colon after the equal sign.

+ +

In the example above, the output of tmsu is used to set a + field named tags. The field + name is arbitrary and could be tmsu or myfield just the same, but tags is an alias for the standard + Recoll keywords field, and the tmsu output will just + augment its contents. This will avoid the need to extend + the field + configuration.

+ +

Once re-indexing is performed (you'll need to force the + file reindexing, Recoll + will not detect the need by itself), you will be able to + search from the query language, through any of its aliases: + tags:some/alternate/values or + tags:all,these,values (the + compact field search syntax is supported for recoll 1.20 + and later. For older versions, you would need to repeat the + tags: specifier for each term, + e.g. tags:some OR + tags:alternate).

+ +

You should be aware that tags changes will not be + detected by the indexer if the file itself did not change. + One possible workaround would be to update the file + ctime when you modify the + tags, which would be consistent with how extended + attributes function. A pair of chmod commands could + accomplish this, or a touch -a + . Alternatively, just couple the tag update with a + recollindex -e -i + filename.

+
+ +
+
+
+
+

2.7. Periodic + indexing

+
+
+
+ +
+
+
+
+

2.7.1. Running + indexing

+
+
+
+ +

Indexing is always performed by the recollindex program, + which can be started either from the command line or from + the File menu in the + recoll GUI + program. When started from the GUI, the indexing will run + on the same configuration recoll was started on. + When started from the command line, recollindex will use + the RECOLL_CONFDIR variable or + accept a -c confdir option to specify + a non-default configuration directory.

+ +

If the recoll program finds no + index when it starts, it will automatically start + indexing (except if canceled).

+ +

The recollindex indexing + process can be interrupted by sending an interrupt + (Ctrl-C, SIGINT) or terminate + (SIGTERM) signal. Some time may elapse before the process + exits, because it needs to properly flush and close the + index. This can also be done from the recoll GUI FileStop Indexing menu entry.

+ +

After such an interruption, the index will be somewhat + inconsistent because some operations which are normally + performed at the end of the indexing pass will have been + skipped (for example, the stemming and spelling databases + will be inexistant or out of date). You just need to + restart indexing at a later time to restore consistency. + The indexing will restart at the interruption point (the + full file tree will be traversed, but files that were + indexed up to the interruption and for which the index is + still up to date will not need to be reindexed).

+ +

recollindex has a + number of other options which are described in its man + page. Only a few will be described here.

+ +

Option -z will reset the + index when starting. This is almost the same as + destroying the index files (the nuance is that the + Xapian format version + will not be changed).

+ +

Option -Z will force the + update of all documents without resetting the index + first. This will not have the "clean start" aspect of + -z, but the advantage is that + the index will remain available for querying while it is + rebuilt, which can be a significant advantage if it is + very big (some installations need days for a full index + rebuild).

+ +

Option -k will force + retrying files which previously failed to be indexed, for + example because of a missing helper program.

+ +

Of special interest also, maybe, are the -i and -f + options. -i allows indexing + an explicit list of files (given as command line + parameters or read on stdin). -f + tells recollindex to ignore + file selection parameters from the configuration. + Together, these options allow building a custom file + selection process for some area of the file system, by + adding the top directory to the skippedPaths list and using an + appropriate file selection method to build the file list + to be fed to recollindex + -if. Trivial example:

+
+            find . -name indexable.txt -print | recollindex -if
+          
+
+ +

recollindex + -i will not descend into + subdirectories specified as parameters, but just add them + as index entries. It is up to the external file selection + method to build the complete file list.

+
+ +
+
+
+
+

2.7.2. Using + cron + to automate indexing

+
+
+
+ +

The most common way to set up indexing is to have a + cron task execute it every night. For example the + following crontab entry + would do it every day at 3:30AM (supposing recollindex is in your + PATH):

+
+30 3 * * * recollindex > /some/tmp/dir/recolltrace 2>&1
+
+ +

Or, using anacron:

+
+1  15  su mylogin -c "recollindex recollindex > /tmp/rcltraceme 2>&1"
+
+ +

As of version 1.17 the Recoll GUI has dialogs to manage + crontab entries for + recollindex. You can + reach them from the PreferencesIndexing Schedule menu. They only + work with the good old cron, and do not give + access to all features of cron scheduling.

+ +

The usual command to edit your crontab is crontab -e (which will usually start the + vi editor + to edit the file). You may have more sophisticated tools + available on your system.

+ +

Please be aware that there may be differences between + your usual interactive command line environment and the + one seen by crontab commands. Especially the PATH + variable may be of concern. Please check the crontab + manual pages about possible issues.

+
+
+ +
+
+
+
+

2.8. Real time + indexing

+
+
+
+ +

Real time monitoring/indexing is performed by starting + the recollindex -m command. With this option, recollindex will detach + from the terminal and become a daemon, permanently + monitoring file changes and updating the index.

+ +

Under KDE, Gnome and some other desktop + environments, the daemon can automatically started when you + log in, by creating a desktop file inside the ~/.config/autostart directory. This can + be done for you by the Recoll GUI. Use the Preferences->Indexing Schedule + menu.

+ +

With older X11 setups, + starting the daemon is normally performed as part of the + user session script.

+ +

The rclmon.sh script can + be used to easily start and stop the daemon. It can be + found in the examples + directory (typically /usr/local/[share/]recoll/examples).

+ +

For example, my out of fashion xdm-based session has a .xsession script with the following lines + at the end:

+
+recollconf=$HOME/.recoll-home
+recolldata=/usr/local/share/recoll
+RECOLL_CONFDIR=$recollconf $recolldata/examples/rclmon.sh start
+
+fvwm 
+
+
+ +

The indexing daemon gets started, then the window + manager, for which the session waits.

+ +

By default the indexing daemon will monitor the state of + the X11 session, and exit when it finishes, it is not + necessary to kill it explicitly. (The X11 server monitoring can be disabled + with option -x to recollindex).

+ +

If you use the daemon completely out of an X11 session, you need to add option + -x to disable X11 session monitoring (else the + daemon will not start).

+ +

By default, the messages from the indexing daemon will + be setn to the same file as those from the interactive + commands (logfilename). You + may want to change this by setting the daemlogfilename and daemloglevel configuration parameters. + Also the log file will only be truncated when the daemon + starts. If the daemon runs permanently, the log file may + grow quite big, depending on the log level.

+ +

When building Recoll, + the real time indexing support can be customised during + package configuration with the + --with[out]-fam or --with[out]-inotify options. The default is + currently to include inotify monitoring on systems that + support it, and, as of Recoll 1.17, gamin support on FreeBSD.

+ +

While it is convenient that data is indexed in real + time, repeated indexing can generate a significant load on + the system when files such as email folders change. Also, + monitoring large file trees by itself significantly taxes + system resources. You probably do not want to enable it if + your system is short on resources. Periodic indexing is + adequate in most cases.

+ +
+

Increasing resources for inotify

+ +

On Linux systems, monitoring a big tree may need + increasing the resources available to inotify, which are + normally defined in /etc/sysctl.conf.

+
+### inotify
+#
+# cat  /proc/sys/fs/inotify/max_queued_events   - 16384
+# cat  /proc/sys/fs/inotify/max_user_instances  - 128
+# cat  /proc/sys/fs/inotify/max_user_watches    - 16384
+#
+# -- Change to:
+#
+fs.inotify.max_queued_events=32768
+fs.inotify.max_user_instances=256
+fs.inotify.max_user_watches=32768
+          
+
+ +

Especially, you will need to trim your tree or adjust + the max_user_watches value + if indexing exits with a message about errno ENOSPC (28) from inotify_add_watch.

+
+ +
+
+
+
+

2.8.1. Slowing + down the reindexing rate for fast changing + files

+
+
+
+ +

When using the real time monitor, it may happen that + some files need to be indexed, but change so often that + they impose an excessive load for the system.

+ +

Recoll provides a + configuration option to specify the minimum time before + which a file, specified by a wildcard pattern, cannot be + reindexed. See the mondelaypatterns parameter in the + configuration + section.

+
+
+
+ +
+
+
+
+

Chapter 3. Searching

+
+
+
+ +
+
+
+
+

3.1. Searching with the Qt + graphical user interface

+
+
+
+ +

The recoll + program provides the main user interface for searching. It + is based on the Qt + library.

+ +

recoll has + two search modes:

+ +
+
    +
  • +

    Simple search (the default, on the main screen) + has a single entry field where you can enter multiple + words.

    +
  • + +
  • +

    Advanced search (a panel accessed through the + Tools menu or the + toolbox bar icon) has multiple entry fields, which + you may use to build a logical condition, with + additional filtering on file type, location in the + file system, modification date, and size.

    +
  • +
+
+ +

In most cases, you can enter the terms as you think + them, even if they contain embedded punctuation or other + non-textual characters. For example, Recoll can handle things like email + addresses, or arbitrary cut and paste from another text + window, punctation and all.

+ +

The main case where you should enter text differently + from how it is printed is for east-asian languages + (Chinese, Japanese, Korean). Words composed of single or + multiple characters should be entered separated by white + space in this case (they would typically be printed without + white space).

+ +

Some searches can be quite complex, and you may want to + re-use them later, perhaps with some tweaking. Recoll versions 1.21 and later can + save and restore searches, using XML files. See + Saving and restoring queries.

+ +
+
+
+
+

3.1.1. Simple + search

+
+
+
+ +
+
    +
  1. +

    Start the recoll + program.

    +
  2. + +
  3. +

    Possibly choose a search mode: Any term, All terms, File name or Query language.

    +
  4. + +
  5. +

    Enter search term(s) in the text field at the + top of the window.

    +
  6. + +
  7. +

    Click the Search + button or hit the Enter key to start + the search.

    +
  8. +
+
+ +

The initial default search mode is Query language. Without special + directives, this will look for documents containing all + of the search terms (the ones with more terms will get + better scores), just like the All + terms mode which will ignore such directives. + Any term will search for + documents where at least one of the terms appear.

+ +

The Query Language + features are described in a + separate section.

+ +

All search modes allow wildcards inside terms + (*, ?, []). You + may want to have a look at the section about + wildcards for more information about this.

+ +

File name will + specifically look for file names. The point of having a + separate file name search is that wild card expansion can + be performed more efficiently on a small subset of the + index (allowing wild cards on the left of terms without + excessive penality). Things to know:

+ +
+
    +
  • +

    White space in the entry should match white + space in the file name, and is not treated + specially.

    +
  • + +
  • +

    The search is insensitive to character case and + accents, independantly of the type of index.

    +
  • + +
  • +

    An entry without any wild card character and not + capitalized will be prepended and appended with '*' + (ie: etc + -> *etc*, but + Etc -> + etc).

    +
  • + +
  • +

    If you have a big index (many files), + excessively generic fragments may result in + inefficient searches.

    +
  • +
+
+ +

You can search for exact phrases (adjacent words in a + given order) by enclosing the input inside double quotes. + Ex: "virtual reality".

+ +

When using a stripped index, character case has no + influence on search, except that you can disable stem + expansion for any term by capitalizing it. Ie: a search + for floor will also normally + look for flooring, + floored, etc., but a search + for Floor will only look for + floor, in any character + case. Stemming can also be disabled globally in the + preferences. When using a raw index, the + rules are a bit more complicated.

+ +

Recoll remembers the + last few searches that you performed. You can use the + simple search text entry widget (a combobox) to recall + them (click on the thing at the right of the text field). + Please note, however, that only the search texts are + remembered, not the mode (all/any/file name).

+ +

Typing Esc Space while entering a + word in the simple search entry will open a window with + possible completions for the word. The completions are + extracted from the database.

+ +

Double-clicking on a word in the result list or a + preview window will insert it into the simple search + entry field.

+ +

You can cut and paste any text into an All terms or Any + term search field, punctuation, newlines and all - + except for wildcard characters (single ? characters are ok). Recoll will process it and produce a + meaningful search. This is what most differentiates this + mode from the Query + Language mode, where you have to care about the + syntax.

+ +

You can use the ToolsAdvanced search dialog for more + complex searches.

+
+ +
+
+
+
+

3.1.2. The + default result list

+
+
+
+ +

After starting a search, a list of results will + instantly be displayed in the main list window.

+ +

By default, the document list is presented in order of + relevance (how well the system estimates that the + document matches the query). You can sort the result by + ascending or descending date by using the vertical arrows + in the toolbar.

+ +

Clicking on the Preview + link for an entry will open an internal preview window + for the document. Further Preview clicks for the same search will + open tabs in the existing preview window. You can use + Shift+Click + to force the creation of another preview window, which + may be useful to view the documents side by side. (You + can also browse successive results in a single preview + window by typing Shift+ArrowUp/Down in the + window).

+ +

Clicking the Open link + will start an external viewer for the document. By + default, Recoll lets the + desktop choose the appropriate application for most + document types (there is a short list of exceptions, see + further). If you prefer to completely customize the + choice of applications, you can uncheck the Use desktop preferences option in the + GUI preferences dialog, and click the Choose editor applications button to + adjust the predefined Recoll choices. The tool accepts + multiple selections of MIME types (e.g. to set up the + editor for the dozens of office file types).

+ +

Even when Use desktop + preferences is checked, there is a small list of + exceptions, for MIME types where the Recoll choice should override the + desktop one. These are applications which are well + integrated with Recoll, + especially evince for + viewing PDF and Postscript files because of its support + for opening the document at a specific page and passing a + search string as an argument. Of course, you can edit the + list (in the GUI preferences) if you would prefer to lose + the functionality and use the standard desktop tool.

+ +

You may also change the choice of applications by + editing the mimeview configuration file if you + find this more convenient.

+ +

Each result entry also has a right-click menu with an + Open With entry. This lets + you choose an application from the list of those which + registered with the desktop for the document MIME + type.

+ +

The Preview and + Open edit links may not be + present for all entries, meaning that Recoll has no configured way to + preview a given file type (which was indexed by name + only), or no configured external editor for the file + type. This can sometimes be adjusted simply by tweaking + the mimemap and mimeview configuration files (the + latter can be modified with the user preferences + dialog).

+ +

The format of the result list entries is entirely + configurable by using the preference dialog to edit an HTML + fragment.

+ +

You can click on the Query + details link at the top of the results page to see + the query actually performed, after stem expansion and + other processing.

+ +

Double-clicking on any word inside the result list or + a preview window will insert it into the simple search + text.

+ +

The result list is divided into pages (the size of + which you can change in the preferences). Use the arrow + buttons in the toolbar or the links at the bottom of the + page to browse the results.

+ +
+
+
+
+

3.1.2.1. No + results: the spelling suggestions

+
+
+
+ +

When a search yields no result, and if the + aspell dictionary is + configured, Recoll + will try to check for misspellings among the query + terms, and will propose lists of replacements. Clicking + on one of the suggestions will replace the word and + restart the search. You can hold any of the modifier + keys (Ctrl, Shift, etc.) while clicking if you would + rather stay on the suggestion screen because several + terms need replacement.

+
+ +
+
+
+
+

3.1.2.2. The + result list right-click menu

+
+
+
+ +

Apart from the preview and edit links, you can + display a pop-up menu by right-clicking over a + paragraph in the result list. This menu has the + following entries:

+ +
+
    +
  • +

    Preview

    +
  • + +
  • +

    Open

    +
  • + +
  • +

    Open With

    +
  • + +
  • +

    Run Script

    +
  • + +
  • +

    Copy File + Name

    +
  • + +
  • +

    Copy Url

    +
  • + +
  • +

    Save to File

    +
  • + +
  • +

    Find similar

    +
  • + +
  • +

    Preview Parent + document

    +
  • + +
  • +

    Open Parent + document

    +
  • + +
  • +

    Open Snippets + Window

    +
  • +
+
+ +

The Preview and + Open entries do the same + thing as the corresponding links.

+ +

Open With lets you + open the document with one of the applications claiming + to be able to handle its MIME type (the information + comes from the .desktop + files in /usr/share/applications).

+ +

Run Script allows + starting an arbitrary command on the result file. It + will only appear for results which are top-level files. + See + further for a more detailed description.

+ +

The Copy File Name and + Copy Url copy the + relevant data to the clipboard, for later pasting.

+ +

Save to File allows + saving the contents of a result document to a chosen + file. This entry will only appear if the document does + not correspond to an existing file, but is a + subdocument inside such a file (ie: an email + attachment). It is especially useful to extract + attachments with no associated editor.

+ +

The Open/Preview Parent + document entries allow working with the higher + level document (e.g. the email message an attachment + comes from). Recoll is + sometimes not totally accurate as to what it can or + can't do in this area. For example the Parent entry will also appear for an + email which is part of an mbox folder file, but you + can't actually visualize the mbox (there will be an + error dialog if you try).

+ +

If the document is a top-level file, Open Parent will start the default + file manager on the enclosing filesystem directory.

+ +

The Find similar entry + will select a number of relevant term from the current + document and enter them into the simple search field. + You can then start a simple search, with a good chance + of finding documents related to the current result. I + can't remember a single instance where this function + was actually useful to me...

+ +

The + Open Snippets Window + entry will only appear for documents which support page + breaks (typically PDF, Postscript, DVI). The snippets + window lists extracts from the document, taken around + search terms occurrences, along with the corresponding + page number, as links which can be used to start the + native viewer on the appropriate page. If the viewer + supports it, its search function will also be primed + with one of the search terms.

+
+
+ +
+
+
+
+

3.1.3. The + result table

+
+
+
+ +

In Recoll 1.15 and + newer, the results can be displayed in spreadsheet-like + fashion. You can switch to this presentation by clicking + the table-like icon in the toolbar (this is a toggle, + click again to restore the list).

+ +

Clicking on the column headers will allow sorting by + the values in the column. You can click again to invert + the order, and use the header right-click menu to reset + sorting to the default relevance order (you can also use + the sort-by-date arrows to do this).

+ +

Both the list and the table display the same + underlying results. The sort order set from the table is + still active if you switch back to the list mode. You can + click twice on a date sort arrow to reset it from + there.

+ +

The header right-click menu allows adding or deleting + columns. The columns can be resized, and their order can + be changed (by dragging). All the changes are recorded + when you quit recoll

+ +

Hovering over a table row will update the detail area + at the bottom of the window with the corresponding + values. You can click the row to freeze the display. The + bottom area is equivalent to a result list paragraph, + with links for starting a preview or a native + application, and an equivalent right-click menu. Typing + Esc (the + Escape key) will unfreeze the display.

+
+ +
+
+
+
+

3.1.4. Running + arbitrary commands on result files (1.20 and + later)

+
+
+
+ +

Apart from the Open and + Open With operations, which + allow starting an application on a result document (or a + temporary copy), based on its MIME type, it is also + possible to run arbitrary commands on results which are + top-level files, using the Run + Script entry in the results pop-up menu.

+ +

The commands which will appear in the Run Script submenu must be defined by + .desktop files inside the + scripts subdirectory of the + current configuration directory.

+ +

Here follows an example of a .desktop file, which could be named for + example, ~/.recoll/scripts/myscript.desktop (the + exact file name inside the directory is irrelevant):

+
+[Desktop Entry]
+Type=Application
+Name=MyFirstScript
+Exec=/home/me/bin/tryscript %F
+MimeType=*/*
+      
+
+ +

The Name attribute + defines the label which will appear inside the + Run Script menu. The + Exec attribute defines the + program to be run, which does not need to actually be a + script, of course. The MimeType attribute is not used, but + needs to exist.

+ +

The commands defined this way can also be used from + links inside the result paragraph.

+ +

As an example, it might make sense to write a script + which would move the document to the trash and purge it + from the Recoll + index.

+
+ +
+
+
+
+

3.1.5. Displaying + thumbnails

+
+
+
+ +

The default format for the result list entries and the + detail area of the result table display an icon for each + result document. The icon is either a generic one + determined from the MIME type, or a thumbnail of the + document appearance. Thumbnails are only displayed if + found in the standard freedesktop location, where they + would typically have been created by a file manager.

+ +

Recoll has no capability to create thumbnails. A + relatively simple trick is to use the Open parent document/folder entry in + the result list popup menu. This should open a file + manager window on the containing directory, which should + in turn create the thumbnails (depending on your + settings). Restarting the search should then display the + thumbnails.

+ +

There are also some pointers about thumbnail + generation on the Recoll wiki.

+
+ +
+
+
+
+

3.1.6. The + preview window

+
+
+
+ +

The preview window opens when you first click a + Preview link inside the + result list.

+ +

Subsequent preview requests for a given search open + new tabs in the existing window (except if you hold the + Shift key + while clicking which will open a new window for side by + side viewing).

+ +

Starting another search and requesting a preview will + create a new preview window. The old one stays open until + you close it.

+ +

You can close a preview tab by typing Ctrl-W (Ctrl + W) in the window. + Closing the last tab for a window will also close the + window.

+ +

Of course you can also close a preview window by using + the window manager button in the top of the frame.

+ +

You can display successive or previous documents from + the result list inside a preview tab by typing + Shift+Down or Shift+Up (Down and Up are the arrow + keys).

+ +

A right-click menu in the text area allows switching + between displaying the main text or the contents of + fields associated to the document (ie: author, abtract, + etc.). This is especially useful in cases where the term + match did not occur in the main text but in one of the + fields. In the case of images, you can switch between + three displays: the image itself, the image metadata as + extracted by exiftool and the + fields, which is the metadata stored in the index.

+ +

You can print the current preview window contents by + typing Ctrl-P (Ctrl + P) in the window + text.

+ +
+
+
+
+

3.1.6.1. Searching + inside the preview

+
+
+
+ +

The preview window has an internal search + capability, mostly controlled by the panel at the + bottom of the window, which works in two modes: as a + classical editor incremental search, where we look for + the text entered in the entry zone, or as a way to walk + the matches between the document and the Recoll query that found it.

+ +
+
+
Incremental text + search
+ +
+

The preview tabs have an internal incremental + search function. You initiate the search either + by typing a / (slash) or + CTL-F inside the + text area or by clicking into the Search for: text field and + entering the search string. You can then use the + Next and + Previous buttons to + find the next/previous occurrence. You can also + type F3 inside the + text area to get to the next occurrence.

+ +

If you have a search string entered and you + use Ctrl-Up/Ctrl-Down to browse the results, the + search is initiated for each successive document. + If the string is found, the cursor will be + positioned at the first occurrence of the search + string.

+
+ +
Walking the match + lists
+ +
+

If the entry area is empty when you click the + Next or + Previous buttons, + the editor will be scrolled to show the next + match to any search term (the next highlighted + zone). If you select a search group from the + dropdown list and click Next or Previous, the match list for + this group will be walked. This is not the same + as a text search, because the occurences will + include non-exact matches (as caused by stemming + or wildcards). The search will revert to the text + mode as soon as you edit the entry area.

+
+
+
+
+
+ +
+
+
+
+

3.1.7. The + Query Fragments window

+
+
+
+ +

Selecting the Tools + → Query Fragments + menu entry will open a window with radio- and + check-buttons which can be used to activate query + language fragments for filtering the current query. This + can be useful if you have frequent reusable selectors, + for example, filtering on alternate directories, or + searching just one category of files, not covered by the + standard category selectors.

+ +

The contents of the window are entirely customizable, + and defined by the contents of the fragbuts.xml file inside the + configuration directory. The sample file distributed with + Recoll (which you should + be able to find under /usr/share/recoll/examples/fragbuts.xml), + contains an example which filters the results from the + WEB history.

+ +

Here follows an example:

+
+<?xml version="1.0" encoding="UTF-8"?>
+
+<fragbuts version="1.0">
+
+  <radiobuttons>
+
+    <fragbut>
+      <label>Include Web Results</label>
+      <frag></frag>
+    </fragbut>
+
+    <fragbut>
+      <label>Exclude Web Results</label>
+      <frag>-rclbes:BGL</frag>
+    </fragbut>
+
+    <fragbut>
+      <label>Only Web Results</label>
+      <frag>rclbes:BGL</frag>
+    </fragbut>
+
+  </radiobuttons>
+
+  <buttons>
+
+    <fragbut>
+      <label>Year 2010</label>
+      <frag>date:2010-01-01/2010-12-31</frag>
+    </fragbut>
+
+    <fragbut>
+      <label>My Great Directory Only</label>
+      <frag>dir:/my/great/directory</frag>
+    </fragbut>
+
+  </buttons>
+</fragbuts>
+
+ +

Each radiobuttons or + buttons section defines a + line of checkbuttons or radiobuttons inside the window. + Any number of buttons can be selected, but the + radiobuttons in a line are exclusive.

+ +

Each fragbut section + defines the label for a button, and the Query Language + fragment which will be added (as an AND filter) before + performing the query if the button is active.

+ +

This feature is new in Recoll 1.20, and will probably be + refined depending on user feedback.

+
+ +
+
+
+
+

3.1.8. Complex/advanced + search

+
+
+
+ +

The advanced search dialog helps you build more + complex queries without memorizing the search language + constructs. It can be opened through the Tools menu or through the main + toolbar.

+ +

Recoll keeps a + history of searches. See Advanced search + history.

+ +

The dialog has two tabs:

+ +
+
    +
  1. +

    The first tab lets you specify terms to search + for, and permits specifying multiple clauses which + are combined to build the search.

    +
  2. + +
  3. +

    The second tab lets filter the results according + to file size, date of modification, MIME type, or + location.

    +
  4. +
+
+ +

Click on the Start + Search button in the advanced search dialog, or + type Enter + in any text field to start the search. The button in the + main window always performs a simple search.

+ +

Click on the Show query + details link at the top of the result page to see + the query expansion.

+ +
+
+
+
+

3.1.8.1. Avanced + search: the "find" tab

+
+
+
+ +

This part of the dialog lets you constructc a query + by combining multiple clauses of different types. Each + entry field is configurable for the following + modes:

+ +
+
    +
  • +

    All terms.

    +
  • + +
  • +

    Any term.

    +
  • + +
  • +

    None of the terms.

    +
  • + +
  • +

    Phrase (exact terms in order within an + adjustable window).

    +
  • + +
  • +

    Proximity (terms in any order within an + adjustable window).

    +
  • + +
  • +

    Filename search.

    +
  • +
+
+ +

Additional entry fields can be created by clicking + the Add clause + button.

+ +

When searching, the non-empty clauses will be + combined either with an AND or an OR conjunction, + depending on the choice made on the left (All clauses or Any clause).

+ +

Entries of all types except "Phrase" and "Near" + accept a mix of single words and phrases enclosed in + double quotes. Stemming and wildcard expansion will be + performed as for simple search.

+ +

Phrases and Proximity searches. These + two clauses work in similar ways, with the difference + that proximity searches do not impose an order on the + words. In both cases, an adjustable number (slack) of + non-matched words may be accepted between the searched + ones (use the counter on the left to adjust this + count). For phrases, the default count is zero (exact + match). For proximity it is ten (meaning that two + search terms, would be matched if found within a window + of twelve words). Examples: a phrase search for + quick fox with a slack of + 0 will match quick fox but + not quick brown fox. With + a slack of 1 it will match the latter, but not + fox quick. A proximity + search for quick fox with + the default slack will match the latter, and also + a fox is a cunning and quick + animal.

+
+ +
+
+
+
+

3.1.8.2. Avanced + search: the "filter" tab

+
+
+
+ +

This part of the dialog has several sections which + allow filtering the results of a search according to a + number of criteria

+ +
+
    +
  • +

    The first section allows filtering by dates of + last modification. You can specify both a minimum + and a maximum date. The initial values are set + according to the oldest and newest documents + found in the index.

    +
  • + +
  • +

    The next section allows filtering the results + by file size. There are two entries for minimum + and maximum size. Enter decimal numbers. You can + use suffix multipliers: k/K, m/M, g/G, t/T for 1E3, 1E6, 1E9, 1E12 + respectively.

    +
  • + +
  • +

    The next section allows filtering the results + by their MIME types, or MIME categories (ie: + media/text/message/etc.).

    + +

    You can transfer the types between two boxes, + to define which will be included or excluded by + the search.

    + +

    The state of the file type selection can be + saved as the default (the file type filter will + not be activated at program start-up, but the + lists will be in the restored state).

    +
  • + +
  • +

    The bottom section allows restricting the + search results to a sub-tree of the indexed area. + You can use the Invert checkbox to search for + files not in the sub-tree instead. If you use + directory filtering often and on big subsets of + the file system, you may think of setting up + multiple indexes instead, as the performance may + be better.

    + +

    You can use relative/partial paths for + filtering. Ie, entering dirA/dirB would match either + /dir1/dirA/dirB/myfile1 or + /dir2/dirA/dirB/someother/myfile2.

    +
  • +
+
+
+ +
+
+
+
+

3.1.8.3. Avanced + search history

+
+
+
+ +

The advanced search tool memorizes the last 100 + searches performed. You can walk the saved searches by + using the up and down arrow keys while the keyboard + focus belongs to the advanced search dialog.

+ +

The complex search history can be erased, along with + the one for simple search, by selecting the + FileErase Search History menu + entry.

+
+
+ +
+
+
+
+

3.1.9. The + term explorer tool

+
+
+
+ +

Recoll automatically + manages the expansion of search terms to their + derivatives (ie: plural/singular, verb inflections). But + there are other cases where the exact search term is not + known. For example, you may not remember the exact + spelling, or only know the beginning of the name.

+ +

The search will only propose replacement terms with + spelling variations when no matching document were found. + In some cases, both proper spellings and mispellings are + present in the index, and it may be interesting to look + for them explicitely.

+ +

The term explorer tool (started from the toolbar icon + or from the Term explorer + entry of the Tools menu) + can be used to search the full index terms list. It has + three modes of operations:

+ +
+
+
Wildcard
+ +
+

In this mode of operation, you can enter a + search string with shell-like wildcards (*, ?, []). + ie: xapi* + would display all index terms beginning with + xapi. + (More about wildcards here).

+
+ +
Regular expression
+ +
+

This mode will accept a regular expression as + input. Example: word[0-9]+. The + expression is implicitely anchored at the + beginning. Ie: press will match + pression + but not expression. You can + use .*press to match + the latter, but be aware that this will cause a + full index term list scan, which can be quite + long.

+
+ +
Stem expansion
+ +
+

This mode will perform the usual stem expansion + normally done as part user input processing. As + such it is probably mostly useful to demonstrate + the process.

+
+ +
Spelling/Phonetic
+ +
+

In this mode, you enter the term as you think it + is spelled, and Recoll will do its best to + find index terms that sound like your entry. This + mode uses the Aspell spelling application, + which must be installed on your system for things + to work (if your documents contain non-ascii + characters, Recoll + needs an aspell version newer than 0.60 for UTF-8 + support). The language which is used to build the + dictionary out of the index terms (which is done at + the end of an indexing pass) is the one defined by + your NLS environment. Weird things will probably + happen if languages are mixed up.

+
+
+
+ +

Note that in cases where Recoll does not know the beginning + of the string to search for (ie a wildcard expression + like *coll), + the expansion can take quite a long time because the full + index term list will have to be processed. The expansion + is currently limited at 10000 results for wildcards and + regular expressions. It is possible to change the limit + in the configuration file.

+ +

Double-clicking on a term in the result list will + insert it into the simple search entry field. You can + also cut/paste between the result list and any entry + field (the end of lines will be taken care of).

+
+ +
+
+
+
+

3.1.10. Multiple + indexes

+
+
+
+ +

See the section describing the use + of multiple indexes for generalities. Only the + aspects concerning the recoll GUI are + described here.

+ +

A recoll + program instance is always associated with a specific + index, which is the one to be updated when requested from + the File menu, but it can + use any number of Recoll + indexes for searching. The external indexes can be + selected through the external + indexes tab in the preferences dialog.

+ +

Index selection is performed in two phases. A set of + all usable indexes must first be defined, and then the + subset of indexes to be used for searching. These + parameters are retained across program executions (there + are kept separately for each Recoll configuration). The set of + all indexes is usually quite stable, while the active + ones might typically be adjusted quite frequently.

+ +

The main index (defined by RECOLL_CONFDIR) is always active. If this + is undesirable, you can set up your base configuration to + index an empty directory.

+ +

When adding a new index to the set, you can select + either a Recoll + configuration directory, or directly a Xapian index directory. In the first + case, the Xapian index + directory will be obtained from the selected + configuration.

+ +

As building the set of all indexes can be a little + tedious when done through the user interface, you can use + the RECOLL_EXTRA_DBS + environment variable to provide an initial set. This + might typically be set up by a system administrator so + that every user does not have to do it. The variable + should define a colon-separated list of index + directories, ie:

+
+export RECOLL_EXTRA_DBS=/some/place/xapiandb:/some/other/db
+
+ +

Another environment variable, RECOLL_ACTIVE_EXTRA_DBS allows adding to + the active list of indexes. This variable was suggested + and implemented by a Recoll user. It is mostly useful if + you use scripts to mount external volumes with + Recoll indexes. By using + RECOLL_EXTRA_DBS and + RECOLL_ACTIVE_EXTRA_DBS, you + can add and activate the index for the mounted volume + when starting recoll.

+ +

RECOLL_ACTIVE_EXTRA_DBS is + available for Recoll + versions 1.17.2 and later. A change was made in the same + update so that recoll will + automatically deactivate unreachable indexes when + starting up.

+
+ +
+
+
+
+

3.1.11. Document + history

+
+
+
+ +

Documents that you actually view (with the internal + preview or an external tool) are entered into the + document history, which is remembered.

+ +

You can display the history list by using the + Tools/Doc History menu entry.

+ +

You can erase the document history by using the + Erase document history + entry in the File menu.

+
+ +
+
+
+
+

3.1.12. Sorting + search results and collapsing duplicates

+
+
+
+ +

The documents in a result list are normally sorted in + order of relevance. It is possible to specify a different + sort order, either by using the vertical arrows in the + GUI toolbox to sort by date, or switching to the result + table display and clicking on any header. The sort order + chosen inside the result table remains active if you + switch back to the result list, until you click one of + the vertical arrows, until both are unchecked (you are + back to sort by relevance).

+ +

Sort parameters are remembered between program + invocations, but result sorting is normally always + inactive when the program starts. It is possible to keep + the sorting activation state between program invocations + by checking the Remember sort + activation state option in the preferences.

+ +

It is also possible to hide duplicate entries inside + the result list (documents with the exact same contents + as the displayed one). The test of identity is based on + an MD5 hash of the document container, not only of the + text contents (so that ie, a text document with an image + added will not be a duplicate of the text only). + Duplicates hiding is controlled by an entry in the + GUI configuration dialog, + and is off by default.

+ +

As of release 1.19, when a result document does have + undisplayed duplicates, a Dups link will be shown with the result + list entry. Clicking the link will display the paths + (URLs + ipaths) for the duplicate entries.

+
+ +
+
+
+
+

3.1.13. Search tips, + shortcuts

+
+
+
+ +
+
+
+
+

3.1.13.1. Terms + and search expansion

+
+
+
+ +

Term completion. Typing Esc Space in the simple + search entry field while entering a word will either + complete the current word if its beginning matches a + unique term in the index, or open a window to propose a + list of completions.

+ +

Picking up new terms from result or preview + text. Double-clicking on a word in the result + list or in a preview window will copy it to the simple + search entry field.

+ +

Wildcards. Wildcards can be used inside + search terms in all forms of searches. More about + wildcards.

+ +

Automatic suffixes. Words like + odt or ods can be automatically turned into + query language ext:xxx + clauses. This can be enabled in the Search preferences panel in the + GUI.

+ +

Disabling stem expansion. Entering a + capitalized word in any search field will prevent stem + expansion (no search for gardening if you enter Garden instead of garden). This is the only case where + character case should make a difference for a + Recoll search. You can + also disable stem expansion or change the stemming + language in the preferences.

+ +

Finding related documents. Selecting the + Find similar documents + entry in the result list paragraph right-click menu + will select a set of "interesting" terms from the + current result, and insert them into the simple search + entry field. You can then possibly edit the list and + start a search to find documents which may be + apparented to the current result.

+ +

File names. File names are added as + terms during indexing, and you can specify them as + ordinary terms in normal search fields (Recoll used to index all + directories in the file path as terms. This has been + abandoned as it did not seem really useful). + Alternatively, you can use the specific file name + search which will only look for file names, + and may be faster than the generic search especially + when using wildcards.

+
+ +
+
+
+
+

3.1.13.2. Working + with phrases and proximity

+
+
+
+ +

Phrases and Proximity searches. A phrase + can be looked for by enclosing it in double quotes. + Example: "user manual" + will look only for occurrences of user immediately followed by + manual. You can use the + This phrase field of the + advanced search dialog to the same effect. Phrases can + be entered along simple terms in all simple or advanced + search entry fields (except This + exact phrase).

+ +

AutoPhrases. This option can be set in + the preferences dialog. If it is set, a phrase will be + automatically built and added to simple searches when + looking for Any terms. + This will not change radically the results, but will + give a relevance boost to the results where the search + terms appear as a phrase. Ie: searching for + virtual reality will still + find all documents where either virtual or reality or both appear, but those + which contain virtual + reality should appear sooner in the list.

+ +

Phrase searches can strongly slow down a query if + most of the terms in the phrase are common. This is why + the autophrase option is + off by default for Recoll versions before 1.17. As of + version 1.17, autophrase + is on by default, but very common terms will be removed + from the constructed phrase. The removal threshold can + be adjusted from the search preferences.

+ +

Phrases and abbreviations. As of + Recoll version 1.17, + dotted abbreviations like I.B.M. are also automatically indexed + as a word without the dots: IBM. Searching for the word inside a + phrase (ie: "the IBM + company") will only match the dotted + abrreviation if you increase the phrase slack (using + the advanced search panel control, or the o query language modifier). Literal + occurences of the word will be matched normally.

+
+ +
+
+
+
+

3.1.13.3. Others

+
+
+
+ +

Using fields. You can use the query language and + field specifications to only search certain parts of + documents. This can be especially helpful with email, + for example only searching emails from a specific + originator: search tips + from:helpfulgui

+ +

Ajusting the result table columns. When + displaying results in table mode, you can use a right + click on the table headers to activate a pop-up menu + which will let you adjust what columns are displayed. + You can drag the column headers to adjust their order. + You can click them to sort by the field displayed in + the column. You can also save the result list in CSV + format.

+ +

Changing the GUI geometry. It is + possible to configure the GUI in wide form factor by + dragging the toolbars to one of the sides (their + location is remembered between sessions), and moving + the category filters to a menu (can be set in the + Preferences → + GUI configuration + → User interface + panel).

+ +

Query explanation. You can get an exact + description of what the query looked for, including + stem expansion, and Boolean operators used, by clicking + on the result list header.

+ +

Advanced search history. As of + Recoll 1.18, you can + display any of the last 100 complex searches performed + by using the up and down arrow keys while the advanced + search panel is active.

+ +

Browsing the result list inside a preview + window. Entering Shift-Down or + Shift-Up + (Shift + + an arrow key) in a preview window will display the next + or the previous document from the result list. Any + secondary search currently active will be executed on + the new document.

+ +

Scrolling the result list from the + keyboard. You can use PageUp and + PageDown + to scroll the result list, Shift+Home to go back + to the first page. These work even while the focus is + in the search entry.

+ +

Result table: moving the focus to the + table. You can use Ctrl-r to move the + focus from the search entry to the table, and then use + the arrow keys to change the current row. Ctrl-Shift-s returns + to the search.

+ +

Result table: open / preview. With the + focus in the result table, you can use Ctrl-o to open the + document from the current row, Ctrl-Shift-o to open + the document and close recoll, Ctrl-d to preview the + document.

+ +

Editing a new search while the focus is not in + the search entry. You can use the Ctrl-Shift-S shortcut + to return the cursor to the search entry (and select + the current search text), while the focus is anywhere + in the main window.

+ +

Forced opening of a preview window. You + can use Shift+Click on a + result list Preview link + to force the creation of a preview window instead of a + new tab in the existing one.

+ +

Closing previews. Entering Ctrl-W in a tab will + close it (and, for the last tab, close the preview + window). Entering Esc will close the + preview window and all its tabs.

+ +

Printing previews. Entering Ctrl-P in a preview + window will print the currently displayed text.

+ +

Quitting. Entering Ctrl-Q almost anywhere + will close the application.

+
+
+ +
+
+
+
+

3.1.14. Saving and + restoring queries (1.21 and later)

+
+
+
+ +

Both simple and advanced query dialogs save recent + history, but the amount is limited: old queries will + eventually be forgotten. Also, important queries may be + difficult to find among others. This is why both types of + queries can also be explicitely saved to files, from the + GUI menus: File → + Save last query / Load last + query

+ +

The default location for saved queries is a + subdirectory of the current configuration directory, but + saved queries are ordinary files and can be written or + moved anywhere.

+ +

Some of the saved query parameters are part of the + preferences (e.g. autophrase + or the active external indexes), and may differ when the + query is loaded from the time it was saved. In this case, + Recoll will warn of the + differences, but will not change the user + preferences.

+
+ +
+
+
+
+

3.1.15. Customizing + the search interface

+
+
+
+ +

You can customize some aspects of the search interface + by using the GUI + configuration entry in the Preferences menu.

+ +

There are several tabs in the dialog, dealing with the + interface itself, the parameters used for searching and + returning results, and what indexes are searched.

+ +

User interface + parameters: 

+ +
+
    +
  • +

    Highlight color for query + terms: Terms from the user query are + highlighted in the result list samples and the + preview window. The color can be chosen here. Any + Qt color string should work (ie red, #ff0000). The default is + blue.

    +
  • + +
  • +

    Style sheet: The + name of a Qt style + sheet text file which is applied to the whole + Recoll application on startup. The default value is + empty, but there is a skeleton style sheet + (recoll.qss) inside + the /usr/share/recoll/examples + directory. Using a style sheet, you can change most + recoll graphical + parameters: colors, fonts, etc. See the sample file + for a few simple examples.

    + +

    You should be aware that parameters (e.g.: the + background color) set inside the Recoll GUI style sheet will + override global system preferences, with possible + strange side effects: for example if you set the + foreground to a light color and the background to a + dark one in the desktop preferences, but only the + background is set inside the Recoll style sheet, and it is + light too, then text will appear light-on-light + inside the Recoll + GUI.

    +
  • + +
  • +

    Maximum text size + highlighted for preview Inserting highlights + on search term inside the text before inserting it + in the preview window involves quite a lot of + processing, and can be disabled over the given text + size to speed up loading.

    +
  • + +
  • +

    Prefer HTML to plain text + for preview if set, Recoll will display HTML + as such inside the preview window. If this causes + problems with the Qt HTML display, you can uncheck + it to display the plain text version instead.

    +
  • + +
  • +

    Plain text to HTML line + style: when displaying plain text inside the + preview window, Recoll tries to preserve some + of the original text line breaks and indentation. + It can either use PRE HTML tags, which will well + preserve the indentation but will force horizontal + scrolling for long lines, or use BR tags to break + at the original line breaks, which will let the + editor introduce other line breaks according to the + window width, but will lose some of the original + indentation. The third option has been available in + recent releases and is probably now the best one: + use PRE tags with line wrapping.

    +
  • + +
  • +

    Choose editor + applicationsr: this opens a dialog which + allows you to select the application to be used to + open each MIME type. The default is nornally to use + the xdg-open utility, + but you can override it.

    +
  • + +
  • +

    Exceptions: even + wen xdg-open is used + by default for opening documents, you can set + exceptions for MIME types that will still be opened + according to Recoll preferences. This is + useful for passing parameters like page numbers or + search strings to applications that support them + (e.g. evince). + This cannot be done with xdg-open which + only supports passing one parameter.

    +
  • + +
  • +

    Document filter choice + style: this will let you choose if the + document categories are displayed as a list or a + set of buttons, or a menu.

    +
  • + +
  • +

    Start with simple search + mode: this lets you choose the value of the + simple search type on program startup. Either a + fixed value (e.g. Query + Language, or the value in use when the + program last exited.

    +
  • + +
  • +

    Auto-start simple search + on white space entry: if this is checked, a + search will be executed each time you enter a space + in the simple search input field. This lets you + look at the result list as you enter new terms. + This is off by default, you may like it or + not...

    +
  • + +
  • +

    Start with advanced + search dialog open : If you use this dialog + frequently, checking the entries will get it to + open when recoll starts.

    +
  • + +
  • +

    Remember sort activation + state if set, Recoll will remember the sort + tool stat between invocations. It normally starts + with sorting disabled.

    +
  • +
+
+ +

Result list + parameters: 

+ +
+
    +
  • +

    Number of results in a + result page

    +
  • + +
  • +

    Result list font: + There is quite a lot of information shown in the + result list, and you may want to customize the font + and/or font size. The rest of the fonts used by + Recoll are + determined by your generic Qt config (try the + qtconfig + command).

    +
  • + +
  • +

    Edit result list paragraph format + string: allows you to change the + presentation of each result list entry. See the + result list + customisation section.

    +
  • + +
  • +

    Edit result page HTML header + insert: allows you to define text inserted + at the end of the result page HTML header. More + detail in the result list + customisation section.

    +
  • + +
  • +

    Date format: + allows specifying the format used for displaying + dates inside the result list. This should be + specified as an strftime() string (man + strftime).

    +
  • + +
  • +

    Abstract snippet separator: for + synthetic abstracts built from index data, which + are usually made of several snippets from different + parts of the document, this defines the snippet + separator, an ellipsis by default.

    +
  • +
+
+ +

Search + parameters: 

+ +
+
    +
  • +

    Hide duplicate + results: decides if result list entries are + shown for identical documents found in different + places.

    +
  • + +
  • +

    Stemming language: + stemming obviously depends on the document's + language. This listbox will let you chose among the + stemming databases which were built during indexing + (this is set in the + main configuration file), or later added with + recollindex + -s (See the recollindex manual). + Stemming languages which are dynamically added will + be deleted at the next indexing pass unless they + are also added in the configuration file.

    +
  • + +
  • +

    Automatically add phrase + to simple searches: a phrase will be + automatically built and added to simple searches + when looking for Any + terms. This will give a relevance boost to + the results where the search terms appear as a + phrase (consecutive and in order).

    +
  • + +
  • +

    Autophrase term frequency + threshold percentage: very frequent terms + should not be included in automatic phrase searches + for performance reasons. The parameter defines the + cutoff percentage (percentage of the documents + where the term appears).

    +
  • + +
  • +

    Replace abstracts from + documents: this decides if we should + synthesize and display an abstract in place of an + explicit abstract found within the document + itself.

    +
  • + +
  • +

    Dynamically build + abstracts: this decides if Recoll tries to build document + abstracts (lists of snippets) when + displaying the result list. Abstracts are + constructed by taking context from the document + information, around the search terms.

    +
  • + +
  • +

    Synthetic abstract + size: adjust to taste...

    +
  • + +
  • +

    Synthetic abstract + context words: how many words should be + displayed around each term occurrence.

    +
  • + +
  • +

    Query language magic file + name suffixes: a list of words which + automatically get turned into ext:xxx file name suffix clauses + when starting a query language query (ie: + doc xls xlsx...). This + will save some typing for people who use file types + a lot when querying.

    +
  • +
+
+ +

External + indexes: This panel will let you browse for + additional indexes that you may want to search. External + indexes are designated by their database directory (ie: + /home/someothergui/.recoll/xapiandb, + /usr/local/recollglobal/xapiandb).

+ +

Once entered, the indexes will appear in the + External indexes list, and + you can chose which ones you want to use at any moment by + checking or unchecking their entries.

+ +

Your main database (the one the current configuration + indexes to), is always implicitly active. If this is not + desirable, you can set up your configuration so that it + indexes, for example, an empty directory. An alternative + indexer may also need to implement a way of purging the + index from stale data,

+ +
+
+
+
+

3.1.15.1. The + result list format

+
+
+
+ +

Newer versions of Recoll (from 1.17) normally use + WebKit HTML widgets for the result list and the + snippets + window (this may be disabled at build time). Total + customisation is possible with full support for CSS and + Javascript. Conversely, there are limits to what you + can do with the older Qt QTextBrowser, but still, it is + possible to decide what data each result will contain, + and how it will be displayed.

+ +

The result list presentation can be exhaustively + customized by adjusting two elements:

+ +
+
    +
  • +

    The paragraph format

    +
  • + +
  • +

    HTML code inside the header section. For + versions 1.21 and later, this is also used for + the snippets + window

    +
  • +
+
+ +

The paragraph format and the header fragment can be + edited from the Result + list tab of the GUI + configuration.

+ +

The header fragment is used both for the result list + and the snippets window. The snippets list is a table + and has a snippets class + attribute. Each paragraph in the result list is a + table, with class respar, + but this can be changed by editing the paragraph + format.

+ +

There are a few examples on the page about customising the result list on + the Recoll web + site.

+ +
+
+
+
+
The + paragraph format
+
+
+
+ +

This is an arbitrary HTML string where the + following printf-like % + substitutions will be performed:

+ +
+
    +
  • +

    %A. Abstract

    +
  • + +
  • +

    %D. Date

    +
  • + +
  • +

    %I. Icon image name. This is + normally determined from the MIME type. The + associations are defined inside the mimeconf configuration + file. If a thumbnail for the file is found + at the standard Freedesktop location, this will + be displayed instead.

    +
  • + +
  • +

    %K. Keywords (if any)

    +
  • + +
  • +

    %L. Precooked Preview, Edit, and + possibly Snippets links

    +
  • + +
  • +

    %M. MIME type

    +
  • + +
  • +

    %N. result Number inside the + result page

    +
  • + +
  • +

    %P. Parent folder Url. In the + case of an embedded document, this is the + parent folder for the top level container + file.

    +
  • + +
  • +

    %R. Relevance percentage

    +
  • + +
  • +

    %S. Size information

    +
  • + +
  • +

    %T. Title or Filename if not + set.

    +
  • + +
  • +

    %t. Title or Filename if not + set.

    +
  • + +
  • +

    %U. Url

    +
  • +
+
+ +

The format of the Preview, Edit, and Snippets + links is <a + href="P%N">, <a + href="E%N"> and <a + href="A%N"> where docnum (%N) expands + to the document number inside the result page).

+ +

A link target defined as "F%N" will open the document + corresponding to the %P + parent folder expansion, usually creating a file + manager window on the folder where the container file + resides. E.g.:

+
+<a href="F%N">%P</a>
+
+ +

A link target defined as R%N|scriptname + will run the corresponding script on the result file + (if the document is embedded, the script will be + started on the top-level parent). See the + section about defining scripts.

+ +

In addition to the predefined values above, all + strings like %(fieldname) will be replaced by the + value of the field named fieldname for this document. Only + stored fields can be accessed in this way, the value + of indexed but not stored fields is not known at this + point in the search process (see field + configuration). There are currently very few + fields stored by default, apart from the values above + (only author and + filename), so this + feature will need some custom local configuration to + be useful. An example candidate would be the + recipient field which is + generated by the message input handlers.

+ +

The default value for the paragraph format string + is:

+
+    "<table class=\"respar\">\n"
+    "<tr>\n"
+    "<td><a href='%U'><img src='%I' width='64'></a></td>\n"
+    "<td>%L &nbsp;<i>%S</i> &nbsp;&nbsp;<b>%T</b><br>\n"
+    "<span style='white-space:nowrap'><i>%M</i>&nbsp;%D</span>&nbsp;&nbsp;&nbsp; <i>%U</i>&nbsp;%i<br>\n"
+    "%A %K</td>\n"
+    "</tr></table>\n"
+
+ +

You may, for example, try the following for a more + web-like experience:

+
+<u><b><a href="P%N">%T</a></b></u><br>
+%A<font color=#008000>%U - %S</font> - %L
+
+ +

Note that the P%N link in the above paragraph + makes the title a preview link. Or the clean + looking:

+
+<img src="%I" align="left">%L <font color="#900000">%R</font>
+&nbsp;&nbsp;<b>%T&</b><br>%S&nbsp;
+<font color="#808080"><i>%U</i></font>
+<table bgcolor="#e0e0e0">
+<tr><td><div>%A</div></td></tr>
+</table>%K
+
+ +

These samples, and some others are on the web site, with pictures to show + how they look.

+ +

It is also possible to define the value of + the snippet separator inside the abstract + section.

+
+
+
+
+ +
+
+
+
+

3.2. Searching with the KDE + KIO slave

+
+
+
+ +
+
+
+
+

3.2.1. What's + this

+
+
+
+ +

The Recoll KIO slave + allows performing a Recoll search by entering an + appropriate URL in a KDE open dialog, or with an + HTML-based interface displayed in Konqueror.

+ +

The HTML-based interface is similar to the Qt-based + interface, but slightly less powerful for now. Its + advantage is that you can perform your search while + staying fully within the KDE framework: drag and drop + from the result list works normally and you have your + normal choice of applications for opening files.

+ +

The alternative interface uses a directory view of + search results. Due to limitations in the current KIO + slave interface, it is currently not obviously useful (to + me).

+ +

The interface is described in more detail inside a + help file which you can access by entering recoll:/ inside the konqueror URL line + (this works only if the recoll KIO slave has been + previously installed).

+ +

The instructions for building this module are located + in the source tree. See: kde/kio/recoll/00README.txt. Some Linux + distributions do package the kio-recoll module, so check + before diving into the build process, maybe it's already + out there ready for one-click installation.

+
+ +
+
+
+
+

3.2.2. Searchable + documents

+
+
+
+ +

As a sample application, the Recoll KIO slave could allow + preparing a set of HTML documents (for example a manual) + so that they become their own search interface inside + konqueror.

+ +

This can be done by either explicitly inserting + <a + href="recoll://..."> links around some document + areas, or automatically by adding a very small + javascript program to + the documents, like the following example, which would + initiate a search by double-clicking any term:

+
+<script language="JavaScript">
+    function recollsearch() {
+        var t = document.getSelection();
+        window.location.href = 'recoll://search/query?qtp=a&p=0&q=' +
+            encodeURIComponent(t);
+    }
+</script>
+ ....
+<body ondblclick="recollsearch()">
+
+
+
+
+ +
+
+
+
+

3.3. Searching on + the command line

+
+
+
+ +

There are several ways to obtain search results as a + text stream, without a graphical interface:

+ +
+
    +
  • +

    By passing option -t + to the recoll program, or + by calling it as recollq (through a + link).

    +
  • + +
  • +

    By using the recollq + program.

    +
  • + +
  • +

    By writing a custom Python program, using the + Recoll Python API.

    +
  • +
+
+ +

The first two methods work in the same way and + accept/need the same arguments (except for the additional + -t to recoll). The query to be + executed is specified as command line arguments.

+ +

recollq is + not built by default. You can use the Makefile in the query directory to build it. This is a + very simple program, and if you can program a little c++, + you may find it useful to taylor its output format to your + needs. Not that recollq is only really useful on systems + where the Qt libraries (or even the X11 ones) are not + available. Otherwise, just use recoll + -t, which takes the exact same parameters and + options which are described for recollq

+ +

recollq + has a man page (not installed by default, look in the + doc/man directory). The Usage + string is as follows:

+
+recollq: usage:
+ -P: Show the date span for all the documents present in the index
+ [-o|-a|-f] [-q] <query string>
+ Runs a recoll query and displays result lines. 
+  Default: will interpret the argument(s) as a xesam query string
+    query may be like: 
+    implicit AND, Exclusion, field spec:    t1 -t2 title:t3
+    OR has priority: t1 OR t2 t3 OR t4 means (t1 OR t2) AND (t3 OR t4)
+    Phrase: "t1 t2" (needs additional quoting on cmd line)
+  -o Emulate the GUI simple search in ANY TERM mode
+  -a Emulate the GUI simple search in ALL TERMS mode
+  -f Emulate the GUI simple search in filename mode
+  -q is just ignored (compatibility with the recoll GUI command line)
+Common options:
+    -c <configdir> : specify config directory, overriding $RECOLL_CONFDIR
+    -d also dump file contents
+    -n [first-]<cnt> define the result slice. The default value for [first]
+       is 0. Without the option, the default max count is 2000.
+       Use n=0 for no limit
+    -b : basic. Just output urls, no mime types or titles
+    -Q : no result lines, just the processed query and result count
+    -m : dump the whole document meta[] array for each result
+    -A : output the document abstracts
+    -S fld : sort by field <fld>
+    -s stemlang : set stemming language to use (must exist in index...)
+       Use -s "" to turn off stem expansion
+    -D : sort descending
+    -i <dbdir> : additional index, several can be given
+    -e use url encoding (%xx) for urls
+    -F <field name list> : output exactly these fields for each result.
+       The field values are encoded in base64, output in one line and 
+       separated by one space character. This is the recommended format 
+       for use by other programs. Use a normal query with option -m to 
+       see the field names.
+
+ +

Sample execution:

+
+recollq 'ilur -nautique mime:text/html'
+Recoll query: ((((ilur:(wqf=11) OR ilurs) AND_NOT (nautique:(wqf=11)
+  OR nautiques OR nautiqu OR nautiquement)) FILTER Ttext/html))
+4 results
+text/html       [file:///Users/uncrypted-dockes/projets/bateaux/ilur/comptes.html]      [comptes.html]  18593   bytes   
+text/html       [file:///Users/uncrypted-dockes/projets/nautique/webnautique/articles/ilur1/index.html] [Constructio...
+text/html       [file:///Users/uncrypted-dockes/projets/pagepers/index.html]    [psxtcl/writemime/recoll]...
+text/html       [file:///Users/uncrypted-dockes/projets/bateaux/ilur/factEtCie/recu-chasse-maree....
+
+
+ +
+
+
+
+

3.4. Using Synonyms + (1.22)

+
+
+
+ +

Term synonyms: there are a number of ways to + use term synonyms for searching text:

+ +
+
    +
  • +

    At index creation time, they can be used to alter + the indexed terms, either increasing or decreasing + their number, by expanding the original terms to all + synonyms, or by reducing all synonym terms to a + canonical one.

    +
  • + +
  • +

    At query time, they can be used to match texts + containing terms which are synonyms of the ones + specified by the user, either by expanding the query + for all synonyms, or by reducing the user entry to + canonical terms (the latter only works if the + corresponding processing has been performed while + creating the index).

    +
  • +
+
+ +

Recoll only uses + synonyms at query time. A user query term which part of a + synonym group will be optionally expanded into an + OR query for all terms in the + group.

+ +

Synonym groups are defined inside ordinary text files. + Each line in the file defines a group.

+ +

Example:

+
+hi hello "good morning"
+
+# not sure about "au revoir" though. Is this english ?
+bye goodbye "see you" \
+  "au revoir" 
+    
+
+ +

As usual, lines beginning with a # are comments, empty lines are ignored, + and lines can be continued by ending them with a + backslash.

+ +

Multi-word synonyms are supported, but be aware that + these will generate phrase queries, which may degrade + performance and will disable stemming expansion for the + phrase terms.

+ +

The synonyms file can be specified in the Search parameters tab of the GUI configuration Preferences menu entry, or as an option + for command-line searches.

+ +

Once the file is defined, the use of synonyms can be + enabled or disabled directly from the Preferences menu.

+ +

The synonyms are searched for matches with user terms + after the latter are stem-expanded, but the contents of the + synonyms file itself is not subjected to stem expansion. + This means that a match will not be found if the form + present in the synonyms file is not present anywhere in the + document set.

+ +

The synonyms function is probably not going to help you + find your letters to Mr. Smith. It is best used for + domain-specific searches. For example, it was initially + suggested by a user performing searches among historical + documents: the synonyms file would contains nicknames and + aliases for each of the persons of interest.

+
+ +
+
+
+
+

3.5. Path + translations

+
+
+
+ +

In some cases, the document paths stored inside the + index do not match the actual ones, so that document + previews and accesses will fail. This can occur in a number + of circumstances:

+ +
+
    +
  • +

    When using multiple indexes it is a relatively + common occurrence that some will actually reside on a + remote volume, for exemple mounted via NFS. In this + case, the paths used to access the documents on the + local machine are not necessarily the same than the + ones used while indexing on the remote machine. For + example, /home/me may + have been used as a topdirs elements while indexing, but + the directory might be mounted as /net/server/home/me on the local + machine.

    +
  • + +
  • +

    The case may also occur with removable disks. It + is perfectly possible to configure an index to live + with the documents on the removable disk, but it may + happen that the disk is not mounted at the same place + so that the documents paths from the index are + invalid.

    +
  • + +
  • +

    As a last exemple, one could imagine that a big + directory has been moved, but that it is currently + inconvenient to run the indexer.

    +
  • +
+
+ +

Recoll has a facility + for rewriting access paths when extracting the data from + the index. The translations can be defined for the main + index and for any additional query index.

+ +

The path translation facility will be useful whenever + the documents paths seen by the indexer are not the same as + the ones which should be used at query time.

+ +

In the above NFS example, Recoll could be instructed to rewrite + any file:///home/me URL from + the index to file:///net/server/home/me, allowing + accesses from the client.

+ +

The translations are defined in the ptrans configuration file, which can + be edited by hand or from the GUI external indexes + configuration dialog: PreferencesExternal index dialog, then click the + Paths translations button on + the right below the index list.

+ +
+

Note

+ +

Due to a current bug, the GUI must be restarted after + changing the ptrans values + (even when they were changed from the GUI).

+
+
+ +
+
+
+
+

3.6. The query + language

+
+
+
+ +

The query language processor is activated in the GUI + simple search entry when the search mode selector is set to + Query Language. It can also + be used with the KIO slave or the command line search. It + broadly has the same capabilities as the complex search + interface in the GUI.

+ +

The language was based on the now defunct Xesam user search language + specification.

+ +

If the results of a query language search puzzle you and + you doubt what has been actually searched for, you can use + the GUI Show Query link at the + top of the result list to check the exact query which was + finally executed by Xapian.

+ +

Here follows a sample request that we are going to + explain:

+
+          author:"john doe" Beatles OR Lennon Live OR Unplugged -potatoes
+      
+
+ +

This would search for all documents with John Doe appearing as a + phrase in the author field (exactly what this is would + depend on the document type, ie: the From: header, for an email message), and + containing either beatles or lennon and either + live or + unplugged but not + potatoes (in any + part of the document).

+ +

An element is composed of an optional field + specification, and a value, separated by a colon (the field + separator is the last colon in the element). Examples: + Eugenie, + author:balzac, + dc:title:grandet + dc:title:"eugenie + grandet"

+ +

The colon, if present, means "contains". Xesam defines + other relations, which are mostly unsupported for now + (except in special cases, described further down).

+ +

All elements in the search entry are normally combined + with an implicit AND. It is possible to specify that + elements be OR'ed instead, as in Beatles OR Lennon. The OR must be entered literally (capitals), + and it has priority over the AND associations: word1 word2 OR word3 means word1 AND (word2 OR word3) not (word1 AND word2) OR word3.

+ +

Recoll versions 1.21 + and later, allow using parentheses to group elements, which + will sometimes make things clearer, and may allow + expressing combinations which would have been difficult + otherwise.

+ +

An element preceded by a - + specifies a term that should not appear.

+ +

As usual, words inside quotes define a phrase (the order + of words is significant), so that title:"prejudice pride" is + not the same as title:prejudice + title:pride, and is unlikely to find a + result.

+ +

Words inside phrases and capitalized words are not + stem-expanded. Wildcards may be used anywhere inside a + term. Specifying a wild-card on the left of a term can + produce a very slow search (or even an incorrect one if the + expansion is truncated because of excessive size). Also see + More about + wildcards.

+ +

To save you some typing, recent Recoll versions (1.20 and later) + interpret a comma-separated list of terms as an AND list + inside the field. Use slash characters ('/') for an OR + list. No white space is allowed. So

+
+author:john,lennon
+
+ +

will search for documents with john and lennon inside the author field (in any order), and

+
+author:john/ringo
+
+ +

would search for john or + ringo.

+ +

Modifiers can be set on a double-quote value, for + example to specify a proximity search (unordered). See + the modifier section. No space + must separate the final double-quote and the modifiers + value, e.g. "two + one"po10

+ +

Recoll currently + manages the following default fields:

+ +
+
    +
  • +

    title, subject or caption are synonyms which specify + data to be searched for in the document title or + subject.

    +
  • + +
  • +

    author or + from for searching the + documents originators.

    +
  • + +
  • +

    recipient or + to for searching the + documents recipients.

    +
  • + +
  • +

    keyword for searching + the document-specified keywords (few documents + actually have any).

    +
  • + +
  • +

    filename for the + document's file name. This is not necessarily set for + all documents: internal documents contained inside a + compound one (for example an EPUB section) do not + inherit the container file name any more, this was + replaced by an explicit field (see next). + Sub-documents can still have a specific filename, if it is implied by the + document format, for example the attachment file name + for an email attachment.

    +
  • + +
  • +

    containerfilename. + This is set for all documents, both top-level and + contained sub-documents, and is always the name of + the filesystem directory entry which contains the + data. The terms from this field can only be matched + by an explicit field specification (as opposed to + terms from filename + which are also indexed as general document content). + This avoids getting matches for all the sub-documents + when searching for the container file name.

    +
  • + +
  • +

    ext specifies the + file name extension (Ex: ext:html)

    +
  • +
+
+ +

Recoll 1.20 and later + have a way to specify aliases for the field names, which + will save typing, for example by aliasing filename to fn or containerfilename to cfn. See the section about the + fields file

+ +

The document input handlers used while indexing have the + possibility to create other fields with arbitrary names, + and aliases may be defined in the configuration, so that + the exact field search possibilities may be different for + you if someone took care of the customisation.

+ +

The field syntax also supports a few field-like, but + special, criteria:

+ +
+
    +
  • +

    dir for filtering the + results on file location (Ex: dir:/home/me/somedir). -dir also works to find results not + in the specified directory (release >= 1.15.8). + Tilde expansion will be performed as usual (except + for a bug in versions 1.19 to 1.19.11p1). Wildcards + will be expanded, but please have a + look at an important limitation of wildcards in + path filters.

    + +

    Relative paths also make sense, for example, + dir:share/doc would + match either /usr/share/doc or /usr/local/share/doc

    + +

    Several dir clauses + can be specified, both positive and negative. For + example the following makes sense:

    +
    +dir:recoll dir:src -dir:utils -dir:common
    +            
    +
    + +

    This would select results which have both + recoll and src in the path (in any order), and + which have not either utils or common.

    + +

    You can also use OR + conjunctions with dir: + clauses.

    + +

    A special aspect of dir clauses is that the values in + the index are not transcoded to UTF-8, and never + lower-cased or unaccented, but stored as binary. This + means that you need to enter the values in the exact + lower or upper case, and that searches for names with + diacritics may sometimes be impossible because of + character set conversion issues. Non-ASCII UNIX file + paths are an unending source of trouble and are best + avoided.

    + +

    You need to use double-quotes around the path + value if it contains space characters.

    +
  • + +
  • +

    size for filtering + the results on file size. Example: size<10000. You can use + <, > or = as operators. You can specify a + range like the following: size>100 size<1000. The usual + k/K, m/M, g/G, t/T can + be used as (decimal) multipliers. Ex: size>1k to search for files + bigger than 1000 bytes.

    +
  • + +
  • +

    date for searching or + filtering on dates. The syntax for the argument is + based on the ISO8601 standard for dates and time + intervals. Only dates are supported, no times. The + general syntax is 2 elements separated by a + / character. Each + element can be a date or a period of time. Periods + are specified as PnYnMnD. The n numbers are the + respective numbers of years, months or days, any of + which may be missing. Dates are specified as + YYYY-MM-DD. The days and + months parts may be missing. If the / is present but an element is + missing, the missing element is interpreted as the + lowest or highest date in the index. Examples:

    + +
    +
      +
    • +

      2001-03-01/2002-05-01 the + basic syntax for an interval of dates.

      +
    • + +
    • +

      2001-03-01/P1Y2M the same + specified with a period.

      +
    • + +
    • +

      2001/ from the + beginning of 2001 to the latest date in the + index.

      +
    • + +
    • +

      2001 the whole + year of 2001

      +
    • + +
    • +

      P2D/ means 2 + days ago up to now if there are no documents + with dates in the future.

      +
    • + +
    • +

      /2003 all + documents from 2003 or older.

      +
    • +
    +
    + +

    Periods can also be specified with small letters + (ie: p2y).

    +
  • + +
  • +

    mime or format for specifying the MIME type. + These clauses are processed besides the normal + Boolean logic of the search. Multiple values will be + OR'ed (instead of the normal AND). You can specify + types to be excluded, with the usual -, and use wildcards. Example: + mime:text/* + -mime:text/plain Specifying an explicit + boolean operator before a mime specification is not supported + and will produce strange results.

    +
  • + +
  • +

    type or rclcat for specifying the category + (as in text/media/presentation/etc.). The + classification of MIME types in categories is defined + in the Recoll + configuration (mimeconf), and can be modified or + extended. The default category names are those which + permit filtering results in the main GUI screen. + Categories are OR'ed like MIME types above, and can + be negated with -.

    +
  • +
+
+ +
+

Note

+ +

mime, rclcat, size and date criteria always affect the whole + query (they are applied as a final filter), even if set + with other terms inside a parenthese.

+
+ +
+

Note

+ +

mime (or the equivalent + rclcat) is the only field with an + OR default. You do need to + use OR with ext terms for example.

+
+ +
+
+
+
+

3.6.1. Modifiers

+
+
+
+ +

Some characters are recognized as search modifiers + when found immediately after the closing double quote of + a phrase, as in "some + term"modifierchars. The actual "phrase" can be a + single term of course. Supported modifiers:

+ +
+
    +
  • +

    l can be used to + turn off stemming (mostly makes sense with + p because stemming is + off by default for phrases).

    +
  • + +
  • +

    s can be used to + turn off synonym expansion, if a synonyms file is + in place (only for Recoll 1.22 and later).

    +
  • + +
  • +

    o can be used to + specify a "slack" for phrase and proximity + searches: the number of additional terms that may + be found between the specified ones. If + o is followed by an + integer number, this is the slack, else the default + is 10.

    +
  • + +
  • +

    p can be used to + turn the default phrase search into a proximity one + (unordered). Example: "order + any in"p

    +
  • + +
  • +

    C will turn on case + sensitivity (if the index supports it).

    +
  • + +
  • +

    D will turn on + diacritics sensitivity (if the index supports + it).

    +
  • + +
  • +

    A weight can be specified for a query element by + specifying a decimal value at the start of the + modifiers. Example: "Important"2.5.

    +
  • +
+
+
+
+ +
+
+
+
+

3.7. Search case and + diacritics sensitivity

+
+
+
+ +

For Recoll versions + 1.18 and later, and when working + with a raw index (not the default), searches + can be sensitive to character case and diacritics. How this + happens is controlled by configuration variables and what + search data is entered.

+ +

The general default is that searches entered without + upper-case or accented characters are insensitive to case + and diacritics. An entry of resume will match any of Resume, RESUME, résumé, Résumé etc.

+ +

Two configuration variables can automate switching on + sensitivity (they were documented but actually did nothing + until Recoll 1.22):

+ +
+
+
autodiacsens
+ +
+

If this is set, search sensitivity to diacritics + will be turned on as soon as an accented character + exists in a search term. When the variable is set to + true, resume will start + a diacritics-unsensitive search, but résumé will be matched + exactly. The default value is false.

+
+ +
autocasesens
+ +
+

If this is set, search sensitivity to character + case will be turned on as soon as an upper-case + character exists in a search term except for the first one. + When the variable is set to true, us or Us will start a + diacritics-unsensitive search, but US will be matched exactly. The + default value is true (contrary to + autodiacsens).

+
+
+
+ +

As in the past, capitalizing the first letter of a word + will turn off its stem expansion and have no effect on + case-sensitivity.

+ +

You can also explicitely activate case and diacritics + sensitivity by using modifiers with the query language. + C will make the term + case-sensitive, and D will + make it diacritics-sensitive. Examples:

+
+        "us"C
+   
+
+ +

will search for the term us + exactly (Us will not be a + match).

+
+        "resume"D
+      
+
+ +

will search for the term resume exactly (résumé will not be a + match).

+ +

When either case or diacritics sensitivity is activated, + stem expansion is turned off. Having both does not make + much sense.

+
+ +
+
+
+
+

3.8. Anchored + searches and wildcards

+
+
+
+ +

Some special characters are interpreted by Recoll in search strings to expand or + specialize the search. Wildcards expand a root term in + controlled ways. Anchor characters can restrict a search to + succeed only if the match is found at or near the beginning + of the document or one of its fields.

+ +
+
+
+
+

3.8.1. More + about wildcards

+
+
+
+ +

All words entered in Recoll search fields will be + processed for wildcard expansion before the request is + finally executed.

+ +

The wildcard characters are:

+ +
+
    +
  • +

    * which matches 0 + or more characters.

    +
  • + +
  • +

    ? which matches a + single character.

    +
  • + +
  • +

    [] which allow + defining sets of characters to be matched (ex: + [abc] matches a single character which + may be 'a' or 'b' or 'c', [0-9] matches any number.

    +
  • +
+
+ +

You should be aware of a few things when using + wildcards.

+ +
+
    +
  • +

    Using a wildcard character at the beginning of a + word can make for a slow search because + Recoll will have + to scan the whole index term list to find the + matches. However, this is much less a problem for + field searches, and queries like author:*@domain.com + can sometimes be very useful.

    +
  • + +
  • +

    For Recoll + version 18 only, when working with a raw index + (preserving character case and diacritics), the + literal part of a wildcard expression will be + matched exactly for case and diacritics. This is + not true any more for versions 19 and later.

    +
  • + +
  • +

    Using a * at the + end of a word can produce more matches than you + would think, and strange search results. You can + use the term + explorer tool to check what completions exist + for a given term. You can also see exactly what + search was performed by clicking on the link at the + top of the result list. In general, for natural + language terms, stem expansion will produce better + results than an ending * (stem expansion is turned off + when any wildcard character appears in the + term).

    +
  • +
+
+ +
+
+
+
+

3.8.1.1. Wildcards + and path filtering

+
+
+
+ +

Due to the way that Recoll processes wildcards inside + dir path filtering + clauses, they will have a multiplicative effect on the + query size. A clause containg wildcards in several + paths elements, like, for example, dir:/home/me/*/*/docdir, + will almost certainly fail if your indexed tree is of + any realistic size.

+ +

Depending on the case, you may be able to work + around the issue by specifying the paths elements more + narrowly, with a constant prefix, or by using 2 + separate dir: clauses + instead of multiple wildcards, as in dir:/home/me dir:docdir. The latter + query is not equivalent to the initial one because it + does not specify a number of directory levels, but + that's the best we can do (and it may be actually more + useful in some cases).

+
+
+ +
+
+
+
+

3.8.2. Anchored + searches

+
+
+
+ +

Two characters are used to specify that a search hit + should occur at the beginning or at the end of the text. + ^ at the beginning of a term + or phrase constrains the search to happen at the start, + $ at the end force it to + happen at the end.

+ +

As this function is implemented as a phrase search it + is possible to specify a maximum distance at which the + hit should occur, either through the controls of the + advanced search panel, or using the query language, for + example, as in:

+
+"^someterm"o10
+
+ +

which would force someterm to be found within 10 terms of + the start of the text. This can be combined with a field + search as in somefield:"^someterm"o10 or somefield:someterm$.

+ +

This feature can also be used with an actual phrase + search, but in this case, the distance applies to the + whole phrase and anchor, so that, for example, + bla bla my unexpected term + at the beginning of the text would be a match for + "^my term"o5.

+ +

Anchored searches can be very useful for searches + inside somewhat structured documents like scientific + articles, in case explicit metadata has not been supplied + (a most frequent case), for example for looking for + matches inside the abstract or the list of authors (which + occur at the top of the document).

+
+
+ +
+
+
+
+

3.9. Desktop + integration

+
+
+
+ +

Being independant of the desktop type has its drawbacks: + Recoll desktop integration + is minimal. However there are a few tools available:

+ +
+ +
+ +

Here follow a few other things that may help.

+ +
+
+
+
+

3.9.1. Hotkeying + recoll

+
+
+
+ +

It is surprisingly convenient to be able to show or + hide the Recoll GUI with + a single keystroke. Recoll comes with a small Python + script, based on the libwnck window manager interface + library, which will allow you to do just this. The + detailed instructions are on this wiki page.

+
+ +
+
+
+
+

3.9.2. The KDE Kicker + Recoll applet

+
+
+
+ +

This is probably obsolete now. Anyway:

+ +

The Recoll source + tree contains the source code to the recoll_applet, a small application + derived from the find_applet. This can be used to add + a small Recoll launcher + to the KDE panel.

+ +

The applet is not automatically built with the main + Recoll programs, nor is + it included with the main source distribution (because + the KDE build boilerplate makes it relatively big). You + can download its source from the recoll.org download + page. Use the omnipotent configure;make;make + install incantation to build and + install.

+ +

You can then add the applet to the panel by + right-clicking the panel and choosing the Add applet entry.

+ +

The recoll_applet has + a small text window where you can type a Recoll query (in query language + form), and an icon which can be used to restrict the + search to certain types of files. It is quite primitive, + and launches a new recoll GUI instance every time (even + if it is already running). You may find it useful + anyway.

+
+
+
+ +
+
+
+
+

Chapter 4. Programming + interface

+
+
+
+ +

Recoll has an Application + Programming Interface, usable both for indexing and + searching, currently accessible from the Python language.

+ +

Another less radical way to extend the application is to + write input handlers for new types of documents.

+ +

The processing of metadata attributes for documents + (fields) is highly + configurable.

+ +
+
+
+
+

4.1. Writing a + document input handler

+
+
+
+ +
+

Terminology

+ +

The small programs or pieces of code which handle the + processing of the different document types for + Recoll used to be called + filters, which is still + reflected in the name of the directory which holds them + and many configuration variables. They were named this + way because one of their primary functions is to filter + out the formatting directives and keep the text content. + However these modules may have other behaviours, and the + term input handler is now + progressively substituted in the documentation. + filter is still used in many + places though.

+
+ +

Recoll input handlers + cooperate to translate from the multitude of input document + formats, simple ones as opendocument, acrobat), or compound ones such as + Zip or Email, into the final Recoll indexing input format, which is + plain text. Most input handlers are executable programs or + scripts. A few handlers are coded in C++ and live inside + recollindex. + This latter kind will not be described here.

+ +

There are currently (since version 1.13) two kinds of + external executable input handlers:

+ +
+
    +
  • +

    Simple exec handlers + run once and exit. They can be bare programs like + antiword, or + scripts using other programs. They are very simple to + write, because they just need to print the converted + document to the standard output. Their output can be + plain text or HTML. HTML is usually preferred because + it can store metadata fields and it allows preserving + some of the formatting for the GUI preview.

    +
  • + +
  • +

    Multiple execm + handlers can process multiple files (sparing the + process startup time which can be very significant), + or multiple documents per file (e.g.: for + zip or chm files). They communicate + with the indexer through a simple protocol, but are + nevertheless a bit more complicated than the older + kind. Most of new handlers are written in + Python, using a + common module to handle the protocol. There is an + exception, rclimg which is + written in Perl. The subdocuments output by these + handlers can be directly indexable (text or HTML), or + they can be other simple or compound documents that + will need to be processed by another handler.

    +
  • +
+
+ +

In both cases, handlers deal with regular file system + files, and can process either a single document, or a + linear list of documents in each file. Recoll is responsible for performing + up to date checks, deal with more complex embedding and + other upper level issues.

+ +

A simple handler returning a document in text/plain format, can transfer no + metadata to the indexer. Generic metadata, like document + size or modification date, will be gathered and stored by + the indexer.

+ +

Handlers that produce text/html format can return an arbitrary + amount of metadata inside HTML meta tags. These will be processed + according to the directives found in the fields configuration file.

+ +

The handlers that can handle multiple documents per file + return a single piece of data to identify each document + inside the file. This piece of data, called an ipath element will be sent back by + Recoll to extract the + document at query time, for previewing, or for creating a + temporary file to be opened by a viewer.

+ +

The following section describes the simple handlers, and + the next one gives a few explanations about the + execm ones. You could + conceivably write a simple handler with only the elements + in the manual. This will not be the case for the other + ones, for which you will have to look at the code.

+ +
+
+
+
+

4.1.1. Simple + input handlers

+
+
+
+ +

Recoll simple + handlers are usually shell-scripts, but this is in no way + necessary. Extracting the text from the native format is + the difficult part. Outputting the format expected by + Recoll is trivial. + Happily enough, most document formats have translators or + text extractors which can be called from the handler. In + some cases the output of the translating program is + completely appropriate, and no intermediate shell-script + is needed.

+ +

Input handlers are called with a single argument which + is the source file name. They should output the result to + stdout.

+ +

When writing a handler, you should decide if it will + output plain text or HTML. Plain text is simpler, but you + will not be able to add metadata or vary the output + character encoding (this will be defined in a + configuration file). Additionally, some formatting may be + easier to preserve when previewing HTML. Actually the + deciding factor is metadata: Recoll has a way to extract metadata + from the HTML header and use it for field + searches..

+ +

The RECOLL_FILTER_FORPREVIEW environment + variable (values yes, + no) tells the handler if the + operation is for indexing or previewing. Some handlers + use this to output a slightly different format, for + example stripping uninteresting repeated keywords (ie: + Subject: for email) when + indexing. This is not essential.

+ +

You should look at one of the simple handlers, for + example rclps for a starting + point.

+ +

Don't forget to make your handler executable before + testing !

+
+ +
+
+
+
+

4.1.2. "Multiple" + handlers

+
+
+
+ +

If you can program and want to write an execm handler, it should not be too + difficult to make sense of one of the existing modules. + There is a sample one with many comments, not actually + used by Recoll, which + would index a text file as one document per line. Look + for rcltxtlines.py in the + src/filters directory in + the Recoll BitBucket repository (the sample not in + the distributed release at the moment).

+ +

You can also have a look at the slightly more complex + rclzip + which uses Zip file paths as identifiers (ipath).

+ +

execm handlers sometimes + need to make a choice for the nature of the ipath elements that they use in + communication with the indexer. Here are a few + guidelines:

+ +
+
    +
  • +

    Use ASCII or UTF-8 (if the identifier is an + integer print it, for example, like printf %d would + do).

    +
  • + +
  • +

    If at all possible, the data should make some + kind of sense when printed to a log file to help + with debugging.

    +
  • + +
  • +

    Recoll uses a + colon (:) as a + separator to store a complex path internally (for + deeper embedding). Colons inside the ipath elements output by a handler + will be escaped, but would be a bad choice as a + handler-specific separator (mostly, again, for + debugging issues).

    +
  • +
+
+ +

In any case, the main goal is that it should be easy + for the handler to extract the target document, given the + file name and the ipath + element.

+ +

execm handlers will also + produce a document with a null ipath element. Depending on the type of + document, this may have some associated data (e.g. the + body of an email message), or none (typical for an + archive file). If it is empty, this document will be + useful anyway for some operations, as the parent of the + actual data documents.

+
+ +
+
+
+
+

4.1.3. Telling + Recoll about the + handler

+
+
+
+ +

There are two elements that link a file to the handler + which should process it: the association of file to MIME + type and the association of a MIME type with a + handler.

+ +

The association of files to MIME types is mostly based + on name suffixes. The types are defined inside the + mimemap file. Example:

+
+
+.doc = application/msword
+
+ +

If no suffix association is found for the file name, + Recoll will try to + execute a system command (typically file -i or xdg-mime) to determine + a MIME type.

+ +

The second element is the association of MIME types to + handlers in the mimeconf file. A sample will + probably be better than a long explanation:

+
+
+[index]
+application/msword = exec antiword -t -i 1 -m UTF-8;\
+     mimetype = text/plain ; charset=utf-8
+
+application/ogg = exec rclogg
+
+text/rtf = exec unrtf --nopict --html; charset=iso-8859-1; mimetype=text/html
+
+application/x-chm = execm rclchm
+
+ +

The fragment specifies that:

+ +
+
    +
  • +

    application/msword + files are processed by executing the antiword program, + which outputs text/plain encoded in utf-8.

    +
  • + +
  • +

    application/ogg + files are processed by the rclogg script, + with default output type (text/html, with encoding specified + in the header, or utf-8 by default).

    +
  • + +
  • +

    text/rtf is + processed by unrtf, which + outputs text/html. The + iso-8859-1 encoding is + specified because it is not the utf-8 default, and not output by + unrtf + in the HTML header section.

    +
  • + +
  • +

    application/x-chm + is processed by a persistant handler. This is + determined by the execm keyword.

    +
  • +
+
+
+ +
+
+
+
+

4.1.4. Input + handler HTML output

+
+
+
+ +

The output HTML could be very minimal like the + following example:

+
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
+  </head>
+  <body>
+   Some text content
+  </body>
+</html>
+          
+
+ +

You should take care to escape some characters inside + the text by transforming them into appropriate entities. + At the very minimum, "&" + should be transformed into "&amp;", "<" should be transformed into + "&lt;". This is not + always properly done by translating programs which output + HTML, and of course never by those which output plain + text.

+ +

When encapsulating plain text in an HTML body, the + display of a preview may be improved by enclosing the + text inside <pre> + tags.

+ +

The character set needs to be specified in the header. + It does not need to be UTF-8 (Recoll will take care of translating + it), but it must be accurate for good results.

+ +

Recoll will process + meta tags inside the header + as possible document fields candidates. Documents fields + can be processed by the indexer in different ways, for + searching or displaying inside query results. This is + described in a following + section.

+ +

By default, the indexer will process the standard + header fields if they are present: title, meta/description, and meta/keywords are both indexed and + stored for query-time display.

+ +

A predefined non-standard meta tag will also be processed by + Recoll without further + configuration: if a date tag + is present and has the right format, it will be used as + the document date (for display and sorting), in + preference to the file modification date. The date format + should be as follows:

+
+<meta name="date" content="YYYY-mm-dd HH:MM:SS">
+or
+<meta name="date" content="YYYY-mm-ddTHH:MM:SS">
+          
+
+ +

Example:

+
+<meta name="date" content="2013-02-24 17:50:00">
+          
+
+ +

Input handlers also have the possibility to "invent" + field names. This should also be output as meta tags:

+
+<meta name="somefield" content="Some textual data" />
+
+ +

You can embed HTML markup inside the content of custom + fields, for improving the display inside result lists. In + this case, add a (wildly non-standard) markup attribute to tell Recoll that the value is HTML and + should not be escaped for display.

+
+<meta name="somefield" markup="html" content="Some <i>textual</i> data" />
+
+ +

As written above, the processing of fields is + described in a further + section.

+
+ +
+
+
+
+

4.1.5. Page + numbers

+
+
+
+ +

The indexer will interpret ^L characters in the handler output as + indicating page breaks, and will record them. At query + time, this allows starting a viewer on the right page for + a hit or a snippet. Currently, only the PDF, Postscript + and DVI handlers generate page breaks.

+
+
+ +
+
+
+
+

4.2. Field data + processing

+
+
+
+ +

Fields are named pieces of + information in or about documents, like title, author, abstract.

+ +

The field values for documents can appear in several + ways during indexing: either output by input handlers as + meta fields in the HTML header + section, or extracted from file extended attributes, or + added as attributes of the Doc + object when using the API, or again synthetized internally + by Recoll.

+ +

The Recoll query + language allows searching for text in a specific field.

+ +

Recoll defines a number + of default fields. Additional ones can be output by + handlers, and described in the fields configuration file.

+ +

Fields can be:

+ +
+
    +
  • +

    indexed, meaning that + their terms are separately stored in inverted lists + (with a specific prefix), and that a field-specific + search is possible.

    +
  • + +
  • +

    stored, meaning that + their value is recorded in the index data record for + the document, and can be returned and displayed with + search results.

    +
  • +
+
+ +

A field can be either or both indexed and stored. This + and other aspects of fields handling is defined inside the + fields configuration + file.

+ +

The sequence of events for field processing is as + follows:

+ +
+
    +
  • +

    During indexing, recollindex scans + all meta fields in HTML + documents (most document types are transformed into + HTML at some point). It compares the name for each + element to the configuration defining what should be + done with fields (the fields file)

    +
  • + +
  • +

    If the name for the meta element matches one for a field + that should be indexed, the contents are processed + and the terms are entered into the index with the + prefix defined in the fields file.

    +
  • + +
  • +

    If the name for the meta element matches one for a field + that should be stored, the content of the element is + stored with the document data record, from which it + can be extracted and displayed at query time.

    +
  • + +
  • +

    At query time, if a field search is performed, the + index prefix is computed and the match is only + performed against appropriately prefixed terms in the + index.

    +
  • + +
  • +

    At query time, the field can be displayed inside + the result list by using the appropriate directive in + the definition of the result list + paragraph format. All fields are displayed on the + fields screen of the preview window (which you can + reach through the right-click menu). This is + independant of the fact that the search which + produced the results used the field or not.

    +
  • +
+
+ +

You can find more information in the section about the + fields file, or in + comments inside the file.

+ +

You can also have a look at the example on the Wiki, detailing how one + could add a page + count field to pdf documents for displaying + inside result lists.

+
+ +
+
+
+
+

4.3. Python API

+
+
+
+ +
+
+
+
+

4.3.1. Introduction

+
+
+
+ +

Recoll versions after + 1.11 define a Python programming interface, both for + searching and creating/updating an index.

+ +

The search interface is used in the Recoll Ubuntu Unity Lens and the + Recoll Web UI. It can + run queries on any Recoll configuration.

+ +

The index update section of the API may be used to + create and update Recoll + indexes on specific configurations (separate from the + ones created by recollindex). The + resulting databases can be queried alone, or in + conjunction with regular ones, through the GUI or any of + the query interfaces.

+ +

The search API is modeled along the Python database + API specification. There were two major changes along + Recoll versions:

+ +
+
    +
  • +

    The basis for the Recoll API changed from Python + database API version 1.0 (Recoll versions up to 1.18.1), + to version 2.0 (Recoll 1.18.2 and later).

    +
  • + +
  • +

    The recoll module + became a package (with an internal recoll module) as of Recoll version 1.19, in order + to add more functions. For existing code, this only + changes the way the interface must be imported.

    +
  • +
+
+ +

We will describe the new API and package structure + here. A paragraph at the end of this section will explain + a few differences and ways to write code compatible with + both versions.

+ +

The Python interface can be found in the source + package, under python/recoll.

+ +

The python/recoll/ + directory contains the usual setup.py. After configuring the main + Recoll code, you can use + the script to build and install the Python module:

+
+            cd recoll-xxx/python/recoll
+            python setup.py build
+            python setup.py install
+          
+
+ +

As of Recoll 1.19, + the module can be compiled for Python3.

+ +

The normal Recoll + installer installs the Python2 API along with the main + code. The Python3 version must be explicitely built and + installed.

+ +

When installing from a repository, and depending on + the distribution, the Python API can sometimes be found + in a separate package.

+ +

As an introduction, the following small sample will + run a query and list the title and url for each of the + results. It would work with Recoll 1.19 and later. The + python/samples source + directory contains several examples of Python programming + with Recoll, exercising + the extension more completely, and especially its data + extraction features.

+
+#!/usr/bin/env python
+
+from recoll import recoll
+
+db = recoll.connect()
+query = db.query()
+nres = query.execute("some query")
+results = query.fetchmany(20)
+for doc in results:
+    print(doc.url, doc.title)
+
+
+ +
+
+
+
+

4.3.2. Interface + elements

+
+
+
+ +

A few elements in the interface are specific and and + need an explanation.

+ +
+
+
ipath
+ +
+

This data value (set as a field in the Doc + object) is stored, along with the URL, but not + indexed by Recoll. + Its contents are not interpreted by the index + layer, and its use is up to the application. For + example, the Recoll file system indexer + uses the ipath to + store the part of the document access path internal + to (possibly imbricated) container documents. + ipath in this case is + a vector of access elements (e.g, the first part + could be a path inside a zip file to an archive + member which happens to be an mbox file, the second + element would be the message sequential number + inside the mbox etc.). url and ipath are returned in every search + result and define the access to the original + document. ipath is + empty for top-level document/files (e.g. a PDF + document which is a filesystem file). The + Recoll GUI knows + about the structure of the ipath values used by the + filesystem indexer, and uses it for such functions + as opening the parent of a given document.

+
+ +
udi
+ +
+

An udi (unique + document identifier) identifies a document. Because + of limitations inside the index engine, it is + restricted in length (to 200 bytes), which is why a + regular URI cannot be used. The structure and + contents of the udi is + defined by the application and opaque to the index + engine. For example, the internal file system + indexer uses the complete document path (file path + + internal path), truncated to length, the + suppressed part being replaced by a hash value. The + udi is not explicit in + the query interface (it is used "under the hood" by + the rclextract + module), but it is an explicit element of the + update interface.

+
+ +
parent_udi
+ +
+

If this attribute is set on a document when + entering it in the index, it designates its + physical container document. In a multilevel + hierarchy, this may not be the immediate parent. + parent_udi is + optional, but its use by an indexer may simplify + index maintenance, as Recoll will automatically + delete all children defined by parent_udi == udi when the + document designated by udi is destroyed. e.g. if a + Zip archive contains + entries which are themselves containers, like + mbox files, all the + subdocuments inside the Zip file (mbox, messages, message + attachments, etc.) would have the same parent_udi, matching the + udi for the + Zip file, and all + would be destroyed when the Zip file (identified by its + udi) is removed from + the index. The standard filesystem indexer uses + parent_udi.

+
+ +
Stored and indexed + fields
+ +
+

The fields file + inside the Recoll + configuration defines which document fields are + either "indexed" (searchable), "stored" + (retrievable with search results), or both.

+
+
+
+
+ +
+
+
+
+

4.3.3. Python + search interface

+
+
+
+ +
+
+
+
+

4.3.3.1. Recoll + package

+
+
+
+ +

The recoll package + contains two modules:

+ +
+
    +
  • +

    The recoll module + contains functions and classes used to query (or + update) the index. This section will only + describe the query part, see further for the + update part.

    +
  • + +
  • +

    The rclextract + module contains functions and classes used to + access document data.

    +
  • +
+
+
+ +
+
+
+
+

4.3.3.2. The + recoll module

+
+
+
+ +
+
+
+
+
Functions
+
+
+
+ +
+
+
connect(confdir=None, + extra_dbs=None, writable = False)
+ +
+

The connect() + function connects to one or several + Recoll + index(es) and returns a Db object.

+ +
+
    +
  • +

    confdir + may specify a configuration directory. + The usual defaults apply.

    +
  • + +
  • +

    extra_dbs + is a list of additional indexes (Xapian + directories).

    +
  • + +
  • +

    writable + decides if we can index new data through + this connection.

    +
  • +
+
+ +

This call initializes the recoll module, and + it should always be performed before any other + call or object creation.

+
+
+
+
+ +
+
+
+
+
Classes
+
+
+
+ +
+
+
+
+
The + Db class
+
+
+
+ +

A Db object is created by a connect() call and holds a + connection to a Recoll index.

+ +
+
+
Db.close()
+ +
+

Closes the connection. You can't do + anything with the Db object after this.

+
+ +
Db.query(), + Db.cursor()
+ +
+

These aliases return a blank Query object for this + index.

+
+ +
Db.setAbstractParams(maxchars, + contextwords)
+ +
+

Set the parameters used to build snippets + (sets of keywords in context text fragments). + maxchars defines + the maximum total size of the abstract. + contextwords + defines how many terms are shown around the + keyword.

+
+ +
Db.termMatch(match_type, + expr, field='', maxlen=-1, casesens=False, + diacsens=False, lang='english')
+ +
+

Expand an expression against the index + term list. Performs the basic function from + the GUI term explorer tool. match_type can be either of + wildcard, + regexp or + stem. Returns a + list of terms expanded from the input + expression.

+
+
+
+
+ +
+
+
+
+
The + Query class
+
+
+
+ +

A Query object + (equivalent to a cursor in the Python DB API) is + created by a Db.query() call. It is used to + execute index searches.

+ +
+
+
Query.sortby(fieldname, + ascending=True)
+ +
+

Sort results by fieldname, in + ascending or descending order. Must be called + before executing the search.

+
+ +
Query.execute(query_string, stemming=1, + stemlang="english")
+ +
+

Starts a search for query_string, + a Recoll + search language string.

+
+ +
Query.executesd(SearchData)
+ +
+

Starts a search for the query defined by + the SearchData object.

+
+ +
Query.fetchmany(size=query.arraysize)
+ +
+

Fetches the next Doc objects in the current + search results, and returns them as an array + of the required size, which is by default the + value of the arraysize data member.

+
+ +
Query.fetchone()
+ +
+

Fetches the next Doc object from the current + search results.

+
+ +
Query.close()
+ +
+

Closes the query. The object is unusable + after the call.

+
+ +
Query.scroll(value, + mode='relative')
+ +
+

Adjusts the position in the current result + set. mode can be + relative or + absolute.

+
+ +
Query.getgroups()
+ +
+

Retrieves the expanded query terms as a + list of pairs. Meaningful only after + executexx In each pair, the first entry is a + list of user terms (of size one for simple + terms, or more for group and phrase clauses), + the second a list of query terms as derived + from the user terms and used in the Xapian + Query.

+
+ +
Query.getxquery()
+ +
+

Return the Xapian query description as a + Unicode string. Meaningful only after + executexx.

+
+ +
Query.highlight(text, + ishtml = 0, methods = object)
+ +
+

Will insert <span "class=rclmatch">, + </span> tags around the match areas in + the input text and return the modified text. + ishtml can be + set to indicate that the input text is HTML + and that HTML special characters should not + be escaped. methods if set should be an + object with methods startMatch(i) and + endMatch() which will be called for each + match and should return a begin and end + tag

+
+ +
Query.makedocabstract(doc, methods = + object))
+ +
+

Create a snippets abstract for + doc (a + Doc object) by + selecting text around the match terms. If + methods is set, will also perform + highlighting. See the highlight method.

+
+ +
Query.__iter__() and + Query.next()
+ +
+

So that things like for doc in query: will + work.

+
+
+
+ +
+
+
Query.arraysize
+ +
+

Default number of records processed by + fetchmany (r/w).

+
+ +
Query.rowcount
+ +
+

Number of records returned by the last + execute.

+
+ +
Query.rownumber
+ +
+

Next index to be fetched from results. + Normally increments after each fetchone() + call, but can be set/reset before the call to + effect seeking (equivalent to using + scroll()). + Starts at 0.

+
+
+
+
+ +
+
+
+
+
+ The Doc class
+
+
+
+ +

A Doc object + contains index data for a given document. The data + is extracted from the index when searching, or set + by the indexer program when updating. The Doc + object has many attributes to be read or set by its + user. It matches exactly the Rcl::Doc C++ object. + Some of the attributes are predefined, but, + especially when indexing, others can be set, the + name of which will be processed as field names by + the indexing configuration. Inputs can be specified + as Unicode or strings. Outputs are Unicode objects. + All dates are specified as Unix timestamps, printed + as strings. Please refer to the rcldb/rcldoc.h C++ file for a + description of the predefined attributes.

+ +

At query time, only the fields that are defined + as stored either by + default or in the fields configuration file will be + meaningful in the Doc + object. Especially this will not be the case for + the document text. See the rclextract module for accessing + document contents.

+ +
+
+
get(key), [] + operator
+ +
+

Retrieve the named doc attribute. You can + also use getattr(doc, + key) or doc.key.

+
+ +
doc.key = + value
+ +
+

Set the the named doc attribute. You can + also use setattr(doc, + key, value).

+
+ +
getbinurl()
+ +
+

Retrieve the URL in byte array format (no + transcoding), for use as parameter to a + system call.

+
+ +
setbinurl(url)
+ +
+

Set the URL in byte array format (no + transcoding).

+
+ +
items()
+ +
+

Return a dictionary of doc object + keys/values

+
+ +
keys()
+ +
+

list of doc object keys (attribute + names).

+
+
+
+
+ +
+
+
+
+
+ The SearchData class
+
+
+
+ +

A SearchData object + allows building a query by combining clauses, for + execution by Query.executesd(). It can be used + in replacement of the query language approach. The + interface is going to change a little, so no + detailed doc for now...

+ +
+
+
addclause(type='and'|'or'|'excl'|'phrase'|'near'|'sub', + qstring=string, slack=0, field='', stemming=1, + subSearch=SearchData)
+
+
+
+
+
+ +
+
+
+
+

4.3.3.3. The + rclextract module

+
+
+
+ +

Index queries do not provide document content (only + a partial and unprecise reconstruction is performed to + show the snippets text). In order to access the actual + document data, the data extraction part of the indexing + process must be performed (subdocument access and + format translation). This is not trivial in general. + The rclextract module + currently provides a single class which can be used to + access the data content for result documents.

+ +
+
+
+
+
Classes
+
+
+
+ +
+
+
+
+
+ The Extractor class
+
+
+
+ +
+
+
Extractor(doc)
+ +
+

An Extractor + object is built from a Doc object, output from a + query.

+
+ +
Extractor.textextract(ipath)
+ +
+

Extract document defined by ipath and + return a Doc + object. The doc.text field has the document + text converted to either text/plain or + text/html according to doc.mimetype. The + typical use would be as follows:

+
+qdoc = query.fetchone()
+extractor = recoll.Extractor(qdoc)
+doc = extractor.textextract(qdoc.ipath)
+# use doc.text, e.g. for previewing
+
+
+ +
Extractor.idoctofile(ipath, targetmtype, + outfile='')
+ +
+

Extracts document into an output file, + which can be given explicitly or will be + created as a temporary file to be deleted by + the caller. Typical use:

+
+qdoc = query.fetchone()
+extractor = recoll.Extractor(qdoc)
+filename = extractor.idoctofile(qdoc.ipath, qdoc.mimetype)
+
+
+
+
+
+
+
+ +
+
+
+
+

4.3.3.4. Search + API usage example

+
+
+
+ +

The following sample would query the index with a + user language string. See the python/samples directory inside the + Recoll source for + other examples. The recollgui subdirectory has a very + embryonic GUI which demonstrates the highlighting and + data extraction functions.

+
+#!/usr/bin/env python
+
+from recoll import recoll
+
+db = recoll.connect()
+db.setAbstractParams(maxchars=80, contextwords=4)
+
+query = db.query()
+nres = query.execute("some user question")
+print "Result count: ", nres
+if nres > 5:
+    nres = 5
+for i in range(nres):
+    doc = query.fetchone()
+    print "Result #%d" % (query.rownumber,)
+    for k in ("title", "size"):
+        print k, ":", getattr(doc, k).encode('utf-8')
+    abs = db.makeDocAbstract(doc, query).encode('utf-8')
+    print abs
+    print
+
+
+
+
+
+ +
+
+
+
+

4.3.4. Creating + Python external indexers

+
+
+
+ +

The update API can be used to create an index from + data which is not accessible to the regular Recoll indexer, or structured to + present difficulties to the Recoll input handlers.

+ +

An indexer created using this API will be have + equivalent work to do as the the Recoll file system + indexer: look for modified documents, extract their text, + call the API for indexing it, take care of purging the + index out of data from documents which do not exist in + the document store any more.

+ +

The data for such an external indexer should be stored + in an index separate from any used by the Recoll internal file system indexer. + The reason is that the main document indexer purge pass + (removal of deleted documents) would also remove all the + documents belonging to the external indexer, as they were + not seen during the filesystem walk. The main indexer + documents would also probably be a problem for the + external indexer own purge operation.

+ +

While there would be ways to enable multiple foreign + indexers to cooperate on a single index, it is just + simpler to use separate ones, and use the multiple index + access capabilities of the query interface, if + needed.

+ +

There are two parts in the update interface:

+ +
+
    +
  • +

    Methods inside the recoll module allow inserting + data into the index, to make it accessible by the + normal query interface.

    +
  • + +
  • +

    An interface based on scripts execution is + defined to allow either the GUI or the rclextract module to access + original document data for previewing or + editing.

    +
  • +
+
+ +
+
+
+
+

4.3.4.1. Python + update interface

+
+
+
+ +

The update methods are part of the recoll module described above. The + connect() method is used with a writable=true parameter to obtain a + writable Db object. The + following Db object + methods are then available.

+ +
+
+
addOrUpdate(udi, doc, + parent_udi=None)
+ +
+

Add or update index data for a given document + The udi + string must define a unique id for the document. + It is an opaque interface element and not + interpreted inside Recoll. doc is a Doc object, + created from the data to be indexed (the main + text should be in doc.text). If parent_udi + is set, this is a unique identifier for the + top-level container (e.g. for the filesystem + indexer, this would be the one which is an actual + file).

+
+ +
delete(udi)
+ +
+

Purge index from all data for udi, and all documents (if any) + which have a matrching parent_udi.

+
+ +
needUpdate(udi, + sig)
+ +
+

Test if the index needs to be updated for the + document identified by udi. If this call is to be used, + the doc.sig field + should contain a signature value when calling + addOrUpdate(). The + needUpdate() call + then compares its parameter value with the stored + sig for udi. sig is an opaque value, compared + as a string.

+ +

The filesystem indexer uses a concatenation of + the decimal string values for file size and + update time, but a hash of the contents could + also be used.

+ +

As a side effect, if the return value is false + (the index is up to date), the call will set the + existence flag for the document (and any + subdocument defined by its parent_udi), so that a later + purge() call will + preserve them).

+ +

The use of needUpdate() and purge() is optional, and the + indexer may use another method for checking the + need to reindex or to delete stale entries.

+
+ +
purge()
+ +
+

Delete all documents that were not touched + during the just finished indexing pass (since + open-for-write). These are the documents for the + needUpdate() call was not performed, indicating + that they no longer exist in the primary storage + system.

+
+
+
+
+ +
+
+
+
+

4.3.4.2. Query + data access for external indexers (1.23)

+
+
+
+ +

Recoll has internal + methods to access document data for its internal + (filesystem) indexer. An external indexer needs to + provide data access methods if it needs integration + with the GUI (e.g. preview function), or support for + the rclextract + module.

+ +

The index data and the access method are linked by + the rclbes (recoll backend + storage) Doc field. You + should set this to a short string value identifying + your indexer (e.g. the filesystem indexer uses either + "FS" or an empty value, the Web history indexer uses + "BGL").

+ +

The link is actually performed inside a backends configuration file (stored + in the configuration directory). This defines commands + to execute to access data from the specified indexer. + Example, for the mbox indexing sample found in the + Recoll source (which sets rclbes="MBOX"):

+
+[MBOX]
+fetch = /path/to/recoll/src/python/samples/rclmbox.py fetch
+makesig = path/to/recoll/src/python/samples/rclmbox.py makesig
+        
+
+ +

fetch and makesig define two commands to execute + to respectively retrieve the document text and compute + the document signature (the example implementation uses + the same script with different first parameters to + perform both operations).

+ +

The scripts are called with three additional + arguments: udi, + url, ipath, stored with the document when + it was indexed, and may use any or all to perform the + requested operation. The caller expects the result data + on stdout.

+
+ +
+
+
+
+

4.3.4.3. External + indexer samples

+
+
+
+ +

The Recoll source tree has two samples of external + indexers in the src/python/samples directory. The + more interesting one is rclmbox.py which indexes a directory + containing mbox folder + files. It exercises most features in the update + interface, and has a data access interface.

+ +

See the comments inside the file for more + information.

+
+
+ +
+
+
+
+

4.3.5. Package + compatibility with the previous version

+
+
+
+ +

The following code fragments can be used to ensure + that code can run with both the old and the new API (as + long as it does not use the new abilities of the new API + of course).

+ +

Adapting to the new package structure:

+
+
+try:
+    from recoll import recoll
+    from recoll import rclextract
+    hasextract = True
+except:
+    import recoll
+    hasextract = False
+
+      
+
+ +

Adapting to the change of nature of the next Query + member. The same test can be used to choose to use the + scroll() method (new) or set + the next value (old).

+
+
+       rownum = query.next if type(query.next) == int else \
+                 query.rownumber
+
+      
+
+
+
+
+ +
+
+
+
+

Chapter 5. Installation and + configuration

+
+
+
+ +
+
+
+
+

5.1. Installing a + binary copy

+
+
+
+ +

Recoll binary copies + are always distributed as regular packages for your system. + They can be obtained either through the system's normal + software distribution framework (e.g. Debian/Ubuntu apt, FreeBSD ports, etc.), or from some + type of "backports" repository providing versions newer + than the standard ones, or found on the Recoll WEB site in some cases. The + most up-to-date information about Recoll packages can + usually be found on the Recoll WEB site + downloads page

+ +

There used to exist another form of binary install, as + pre-compiled source trees, but these are just less + convenient than the packages and don't exist any more.

+ +

The package management tools will usually automatically + deal with hard dependancies for packages obtained from a + proper package repository. You will have to deal with them + by hand for downloaded packages (for example, when + dpkg + complains about missing dependancies).

+ +

In all cases, you will have to check or install + supporting applications + for the file types that you want to index beyond those that + are natively processed by Recoll (text, HTML, email files, and a + few others).

+ +

You should also maybe have a look at the configuration + section (but this may not be necessary for a quick test + with default parameters). Most parameters can be more + conveniently set from the GUI interface.

+
+ +
+
+
+
+

5.2. Supporting + packages

+
+
+
+ +
+

Note

+ +

The Windows + installation of Recoll + is self-contained, and only needs Python 2.7 to be + externally installed. Windows users can skip this + section.

+
+ +

Recoll uses external + applications to index some file types. You need to install + them for the file types that you wish to have indexed + (these are run-time optional dependencies. None is needed + for building or running Recoll except for indexing their + specific file type).

+ +

After an indexing pass, the commands that were found + missing can be displayed from the recoll File menu. The list is stored in the + missing text file inside the + configuration directory.

+ +

A list of common file types which need external commands + follows. Many of the handlers need the iconv command, which is + not always listed as a dependancy.

+ +

Please note that, due to the relatively dynamic nature + of this information, the most up to date version is now + kept on http://www.recoll.org/features.html along with + links to the home pages or best source/patches pages, and + misc tips. The list below is not updated often and may be + quite stale.

+ +

For many Linux distributions, most of the commands + listed can be installed from the package repositories. + However, the packages are sometimes outdated, or not the + best version for Recoll, + so you should take a look at http://www.recoll.org/features.html if a file + type is important to you.

+ +

As of Recoll release + 1.14, a number of XML-based formats that were handled by ad + hoc handler code now use the xsltproc command, which + usually comes with libxslt. These are: abiword, fb2 + (ebooks), kword, openoffice, svg.

+ +

Now for the list:

+ +
+
    +
  • +

    Openoffice files need unzip and + xsltproc.

    +
  • + +
  • +

    PDF files need pdftotext which is + part of Poppler + (usually comes with the poppler-utils package). Avoid the + original one from Xpdf.

    +
  • + +
  • +

    Postscript files need pstotext. The + original version has an issue with shell character in + file names, which is corrected in recent packages. + See http://www.recoll.org/features.html + for more detail.

    +
  • + +
  • +

    MS Word needs antiword. It is + also useful to have wvWare installed as + it may be be used as a fallback for some files which + antiword does not + handle.

    +
  • + +
  • +

    MS Excel and PowerPoint are processed by internal + Python + handlers.

    +
  • + +
  • +

    MS Open XML (docx) needs xsltproc.

    +
  • + +
  • +

    Wordperfect files need wpd2html from the + libwpd (or + libwpd-tools on + Ubuntu) package.

    +
  • + +
  • +

    RTF files need unrtf, which, in + its older versions, has much trouble with non-western + character sets. Many Linux distributions carry + outdated unrtf versions. + Check http://www.recoll.org/features.html + for details.

    +
  • + +
  • +

    TeX files need untex or + detex. + Check http://www.recoll.org/features.html + for sources if it's not packaged for your + distribution.

    +
  • + +
  • +

    dvi files need dvips.

    +
  • + +
  • +

    djvu files need djvutxt and + djvused + from the DjVuLibre + package.

    +
  • + +
  • +

    Audio files: Recoll releases 1.14 and later + use a single Python + handler based on mutagen for all audio file + types.

    +
  • + +
  • +

    Pictures: Recoll + uses the Exiftool + Perl package to + extract tag information. Most image file formats are + supported. Note that there may not be much interest + in indexing the technical tags (image size, aperture, + etc.). This is only of interest if you store personal + tags or textual descriptions inside the image + files.

    +
  • + +
  • +

    chm: files in Microsoft help format need Python + and the pychm module + (which needs chmlib).

    +
  • + +
  • +

    ICS: up to Recoll + 1.13, iCalendar files need Python and the icalendar module. icalendar is not needed for + newer versions, which use internal code.

    +
  • + +
  • +

    Zip archives need Python (and the standard zipfile + module).

    +
  • + +
  • +

    Rar archives need Python, the rarfile Python module and the + unrar + utility.

    +
  • + +
  • +

    Midi karaoke files need Python and the Midi + module

    +
  • + +
  • +

    Konqueror webarchive format with Python (uses the + Tarfile module).

    +
  • + +
  • +

    Mimehtml web archive format (support based on the + email handler, which introduces some mild weirdness, + but still usable).

    +
  • +
+
+ +

Text, HTML, email folders, and Scribus files are + processed internally. Lyx + is used to index Lyx files. Many handlers need iconv and the standard + sed and + awk.

+
+ +
+
+
+
+

5.3. Building from + source

+
+
+
+ +
+
+
+
+

5.3.1. Prerequisites

+
+
+
+ +

If you can install any or all of the following through + the package manager for your system, all the better. + Especially Qt is a very + big piece of software, but you will most probably be able + to find a binary package.

+ +

You may have to compile Xapian but this is easy.

+ +

The shopping list:

+ +
+
    +
  • +

    The autoconf, + automake and + libtool triad. Only + autoconf is needed up + to Recoll + 1.21.

    +
  • + +
  • +

    C++ compiler. Up to Recoll version 1.13.04, its + absence can manifest itself by strange messages + about a missing iconv_open.

    +
  • + +
  • +

    bison command + (for Recoll 1.21 + and later).

    +
  • + +
  • +

    xsltproc command. + For building the documentation (for Recoll 1.21 and later). This + sometimes comes with the libxslt package. And also the + Docbook XML and style sheet files.

    +
  • + +
  • +

    Development files for Xapian core.

    + +
    +

    Important

    + +

    If you are building Xapian for an older CPU + (before Pentium 4 or Athlon 64), you need to add + the --disable-sse + flag to the configure command. Else all Xapian + application will crash with an illegal instruction error.

    +
    +
  • + +
  • +

    Development files for Qt 4 . + Recoll has not + been tested with Qt + 5 yet. Recoll 1.15.9 was the last + version to support Qt + 3. If you do not want to install or build + the Qt Webkit + module, Recoll has + a configuration option to disable its use (see + further).

    +
  • + +
  • +

    Development files for X11 and zlib.

    +
  • + +
  • +

    Development files for Python (or use --disable-python-module).

    +
  • + +
  • +

    You may also need libiconv. On Linux systems, the iconv + interface is part of libc and you should not need + to do anything special.

    +
  • +
+
+ +

Check the Recoll download + page for up to date version information.

+
+ +
+
+
+
+

5.3.2. Building

+
+
+
+ +

Recoll has been built + on Linux, FreeBSD, Mac OS X, and Solaris, most versions + after 2005 should be ok, maybe some older ones too + (Solaris 8 is ok). If you build on another system, and + need to modify things, I would very much + welcome patches.

+ +

Configure options: 

+ +
+
    +
  • +

    --without-aspell + will disable the code for phonetic matching of + search terms.

    +
  • + +
  • +

    --with-fam or + --with-inotify will + enable the code for real time indexing. Inotify + support is enabled by default on recent Linux + systems.

    +
  • + +
  • +

    --with-qzeitgeist + will enable sending Zeitgeist events about the + visited search results, and needs the qzeitgeist package.

    +
  • + +
  • +

    --disable-webkit is + available from version 1.17 to implement the result + list with a Qt + QTextBrowser instead of a WebKit widget if you do + not or can't depend on the latter.

    +
  • + +
  • +

    --disable-idxthreads + is available from version 1.19 to suppress + multithreading inside the indexing process. You can + also use the run-time configuration to restrict + recollindex to + using a single thread, but the compile-time option + may disable a few more unused locks. This only + applies to the use of multithreading for the core + index processing (data input). The Recoll monitor mode always + uses at least two threads of execution.

    +
  • + +
  • +

    --disable-python-module will avoid + building the Python module.

    +
  • + +
  • +

    --disable-xattr will + prevent fetching data from file extended + attributes. Beyond a few standard attributes, + fetching extended attributes data can only be + useful is some application stores data in there, + and also needs some simple configuration (see + comments in the fields configuration file).

    +
  • + +
  • +

    --enable-camelcase + will enable splitting camelCase words. + This is not enabled by default as it has the + unfortunate side-effect of making some phrase + searches quite confusing: ie, "MySQL manual" would be matched by + "MySQL manual" and + "my sql manual" but + not "mysql manual" + (only inside phrase searches).

    +
  • + +
  • +

    --with-file-command + Specify the version of the 'file' command to use + (ie: --with-file-command=/usr/local/bin/file). Can + be useful to enable the gnu version on systems + where the native one is bad.

    +
  • + +
  • +

    --disable-qtgui + Disable the Qt interface. Will allow building the + indexer and the command line search program in + absence of a Qt environment.

    +
  • + +
  • +

    --disable-x11mon + Disable X11 + connection monitoring inside recollindex. Together + with --disable-qtgui, this allows building recoll + without Qt and + X11.

    +
  • + +
  • +

    --disable-userdoc + will avoid building the user manual. This avoids + having to install the Docbook XML/XSL files and the + TeX toolchain used for translating the manual to + PDF.

    +
  • + +
  • +

    --disable-pic + (Recoll versions + up to 1.21 only) will compile Recoll with position-dependant + code. This is incompatible with building the KIO or + the Python or + PHP extensions, + but might yield very marginally faster code.

    +
  • + +
  • +

    Of course the usual autoconf configure + options, like --prefix + apply.

    +
  • +
+
+ +

Normal procedure (for source extracted from a tar + distribution):

+
+        cd recoll-xxx
+        ./configure
+        make
+        (practices usual hardship-repelling invocations)
+      
+
+ +

When building from source cloned from the BitBucket + repository, you also need to install autoconf, automake, and libtool and you must execute + sh autogen.sh in the top + source directory before running configure.

+ +
+
+
+
+

5.3.2.1. Building + on Solaris

+
+
+
+ +

We did not test building the GUI on Solaris for + recent versions. You will need at least Qt 4.4. There + are some hints on an old web site page, they may still be + valid.

+ +

Someone did test the 1.19 indexer and Python module + build, they do work, with a few minor glitches. Be sure + to use GNU make and install.

+
+
+ +
+
+
+
+

5.3.3. Installation

+
+
+
+ +

Either type make + install or execute recollinstall prefix, + in the root of the source tree. This will copy the + commands to prefix/bin and the + sample configuration files, scripts and other shared data + to prefix/share/recoll.

+ +

If the installation prefix given to recollinstall is + different from either the system default or the value + which was specified when executing configure (as in + configure --prefix + /some/path), you will have to set the + RECOLL_DATADIR environment + variable to indicate where the shared data is to be found + (ie for (ba)sh: export + RECOLL_DATADIR=/some/path/share/recoll).

+ +

You can then proceed to configuration.

+
+
+ +
+
+
+
+

5.4. Configuration + overview

+
+
+
+ +

Most of the parameters specific to the recoll GUI are set + through the Preferences menu + and stored in the standard Qt place ($HOME/.config/Recoll.org/recoll.conf). + You probably do not want to edit this by hand.

+ +

Recoll indexing options + are set inside text configuration files located in a + configuration directory. There can be several such + directories, each of which defines the parameters for one + index.

+ +

The configuration files can be edited by hand or through + the Index configuration + dialog (Preferences menu). + The GUI tool will try to respect your formatting and + comments as much as possible, so it is quite possible to + use both approaches on the same configuration.

+ +

The most accurate documentation for the configuration + parameters is given by comments inside the default files, + and we will just give a general overview here.

+ +

For each index, there are at least two sets of + configuration files. System-wide configuration files are + kept in a directory named like /usr/share/recoll/examples, and define + default values, shared by all indexes. For each index, a + parallel set of files defines the customized + parameters.

+ +

The default location of the customized configuration is + the .recoll directory in your + home. Most people will only use this directory.

+ +

This location can be changed, or others can be added + with the RECOLL_CONFDIR + environment variable or the -c + option parameter to recoll and recollindex.

+ +

In addition (as of Recoll version 1.19.7), it is possible + to specify two additional configuration directories which + will be stacked before and after the user configuration + directory. These are defined by the RECOLL_CONFTOP and RECOLL_CONFMID environment variables. Values + from configuration files inside the top directory will + override user ones, values from configuration files inside + the middle directory will override system ones and be + overriden by user ones. These two variables may be of use + to applications which augment Recoll functionality, and need to add + configuration data without disturbing the user's files. + Please note that the two, currently single, values will + probably be interpreted as colon-separated lists in the + future: do not use colon characters inside the directory + paths.

+ +

If the .recoll directory + does not exist when recoll or recollindex are started, + it will be created with a set of empty configuration files. + recoll will + give you a chance to edit the configuration file before + starting indexing. recollindex will proceed + immediately. To avoid mistakes, the automatic directory + creation will only occur for the default location, not if + -c or RECOLL_CONFDIR were used (in the latter + cases, you will have to create the directory).

+ +

All configuration files share the same format. For + example, a short extract of the main configuration file + might look as follows:

+
+        # Space-separated list of directories to index.
+        topdirs =  ~/docs /usr/share/doc
+
+        [~/somedirectory-with-utf8-txt-files]
+        defaultcharset = utf-8
+        
+
+ +

There are three kinds of lines:

+ +
+
    +
  • +

    Comment (starts with #) or empty.

    +
  • + +
  • +

    Parameter affectation (name = value).

    +
  • + +
  • +

    Section definition ([somedirname]).

    +
  • +
+
+ +

Long lines can be broken by ending each incomplete part + with a backslash (\).

+ +

Depending on the type of configuration file, section + definitions either separate groups of parameters or allow + redefining some parameters for a directory sub-tree. They + stay in effect until another section definition, or the end + of file, is encountered. Some of the parameters used for + indexing are looked up hierarchically from the current + directory location upwards. Not all parameters can be + meaningfully redefined, this is specified for each in the + next section.

+ +

When found at the beginning of a file path, the tilde + character (~) is expanded to the name of the user's home + directory, as a shell would do.

+ +

Some parameters are lists of strings. White space is + used for separation. List elements with embedded spaces can + be quoted using double-quotes. Double quotes inside these + elements can be escaped with a backslash.

+ +

No value inside a configuration file can contain a + newline character. Long lines can be continued by escaping + the physical newline with backslash, even inside quoted + strings.

+
+astringlist =  "some string \
+with spaces"
+thesame = "some string with spaces"        
+        
+
+ +

Parameters which are not part of string lists can't be + quoted, and leading and trailing space characters are + stripped before the value is used.

+ +

Encoding issues. Most of the configuration + parameters are plain ASCII. Two particular sets of values + may cause encoding issues:

+ +
+
    +
  • +

    File path parameters may contain non-ascii + characters and should use the exact same byte values + as found in the file system directory. Usually, this + means that the configuration file should use the + system default locale encoding.

    +
  • + +
  • +

    The unac_except_trans + parameter should be encoded in UTF-8. If your system + locale is not UTF-8, and you need to also specify + non-ascii file paths, this poses a difficulty because + common text editors cannot handle multiple encodings + in a single file. In this relatively unlikely case, + you can edit the configuration file as two separate + text files with appropriate encodings, and + concatenate them to create the complete + configuration.

    +
  • +
+
+ +
+
+
+
+

5.4.1. Environment + variables

+
+
+
+ +
+
+
RECOLL_CONFDIR
+ +
+

Defines the main configuration directory.

+
+ +
RECOLL_TMPDIR, TMPDIR
+ +
+

Locations for temporary files, in this order of + priority. The default if none of these is set is to + use /tmp. Big + temporary files may be created during indexing, + mostly for decompressing, and also for processing, + e.g. email attachments.

+
+ +
RECOLL_CONFTOP, + RECOLL_CONFMID
+ +
+

Allow adding configuration directories with + priorities below and above the user directory (see + above the Configuration overview section for + details).

+
+ +
RECOLL_EXTRA_DBS, + RECOLL_ACTIVE_EXTRA_DBS
+ +
+

Help for setting up external indexes. See + this + paragraph for explanations.

+
+ +
RECOLL_DATADIR
+ +
+

Defines replacement for the default location of + Recoll data files, normally found in, e.g., + /usr/share/recoll).

+
+ +
RECOLL_FILTERSDIR
+ +
+

Defines replacement for the default location of + Recoll filters, normally found in, e.g., + /usr/share/recoll/filters).

+
+ +
ASPELL_PROG
+ +
+

aspell program to + use for creating the spelling dictionary. The + result has to be compatible with the libaspell which Recoll is using.

+
+ +
VARNAME
+ +
+

Blabla

+
+
+
+
+ +
+
+
+
+

5.4.2. Recoll + main configuration file, recoll.conf

+
+
+
+ +
+
+
+
+

5.4.2.1. Parameters + affecting what documents we index

+
+
+
+ +
+
topdirs
+ +
+

Space-separated list of files or directories to + recursively index. Default to ~ (indexes $HOME). + You can use symbolic links in the list, they will + be followed, independantly of the value of the + followLinks variable.

+
+ +
skippedNames
+ +
+

Files and directories which should be ignored. + White space separated list of wildcard patterns + (simple ones, not paths, must contain no / ), which + will be tested against file and directory names. + The list in the default configuration does not + exclude hidden directories (names beginning with a + dot), which means that it may index quite a few + things that you do not want. On the other hand, + email user agents like Thunderbird usually store + messages in hidden directories, and you probably + want this indexed. One possible solution is to have + ".*" in "skippedNames", and add things like + "~/.thunderbird" "~/.evolution" to "topdirs". Not + even the file names are indexed for patterns in + this list, see the "noContentSuffixes" variable for + an alternative approach which indexes the file + names. Can be redefined for any subtree.

+
+ +
noContentSuffixes
+ +
+

List of name endings (not necessarily + dot-separated suffixes) for which we don't try MIME + type identification, and don't uncompress or index + content. Only the names will be indexed. This + complements the now obsoleted recoll_noindex list + from the mimemap file, which will go away in a + future release (the move from mimemap to + recoll.conf allows editing the list through the + GUI). This is different from skippedNames because + these are name ending matches only (not wildcard + patterns), and the file name itself gets indexed + normally. This can be redefined for + subdirectories.

+
+ +
skippedPaths
+ +
+

Paths we should not go into. Space-separated + list of wildcard expressions for filesystem paths. + Can contain files and directories. The database and + configuration directories will automatically be + added. The expressions are matched using + 'fnmatch(3)' with the FNM_PATHNAME flag set by + default. This means that '/' characters must be + matched explicitely. You can set + 'skippedPathsFnmPathname' to 0 to disable the use + of FNM_PATHNAME (meaning that '/*/dir3' will match + '/dir1/dir2/dir3'). The default value contains the + usual mount point for removable media to remind you + that it is a bad idea to have Recoll work on these + (esp. with the monitor: media gets indexed on + mount, all data gets erased on unmount). + Explicitely adding '/media/xxx' to the topdirs will + override this.

+
+ +
+ skippedPathsFnmPathname
+ +
+

Set to 0 to override use of FNM_PATHNAME for + matching skipped paths.

+
+ +
daemSkippedPaths
+ +
+

skippedPaths equivalent specific to real time + indexing. This enables having parts of the tree + which are initially indexed but not monitored. If + daemSkippedPaths is not set, the daemon uses + skippedPaths.

+
+ +
zipSkippedNames
+ +
+

Space-separated list of wildcard expressions for + names that should be ignored inside zip archives. + This is used directly by the zip handler, and has a + function similar to skippedNames, but works + independantly. Can be redefined for subdirectories. + Supported by recoll 1.20 and newer. See + https://bitbucket.org/medoc/recoll/wiki/Filtering%20out%20Zip%20archive%20members

+
+ +
followLinks
+ +
+

Follow symbolic links during indexing. The + default is to ignore symbolic links to avoid + multiple indexing of linked files. No effort is + made to avoid duplication when this option is set + to true. This option can be set individually for + each of the 'topdirs' members by using sections. It + can not be changed below the 'topdirs' level. Links + in the 'topdirs' list itself are always + followed.

+
+ +
indexedmimetypes
+ +
+

Restrictive list of indexed mime types. Normally + not set (in which case all supported types are + indexed). If it is set, only the types from the + list will have their contents indexed. The names + will be indexed anyway if indexallfilenames is set + (default). MIME type names should be taken from the + mimemap file. Can be redefined for subtrees.

+
+ +
excludedmimetypes
+ +
+

List of excluded MIME types. Lets you exclude + some types from indexing. Can be redefined for + subtrees.

+
+ +
compressedfilemaxkbs
+ +
+

Size limit for compressed files. We need to + decompress these in a temporary directory for + identification, which can be wasteful in some + cases. Limit the waste. Negative means no limit. 0 + results in no processing of any compressed file. + Default 50 MB.

+
+ +
textfilemaxmbs
+ +
+

Size limit for text files. Mostly for skipping + monster logs. Default 20 MB.

+
+ +
indexallfilenames
+ +
+

Index the file names of unprocessed files Index + the names of files the contents of which we don't + index because of an excluded or unsupported MIME + type.

+
+ +
usesystemfilecommand
+ +
+

Use a system command for file MIME type guessing + as a final step in file type identification This is + generally useful, but will usually cause the + indexing of many bogus 'text' files. See + 'systemfilecommand' for the command used.

+
+ +
systemfilecommand
+ +
+

Command used to guess MIME types if the internal + methods fails This should be a "file -i" workalike. + The file path will be added as a last parameter to + the command line. 'xdg-mime' works better than the + traditional 'file' command, and is now the + configured default (with a hard-coded fallback to + 'file')

+
+ +
processwebqueue
+ +
+

Decide if we process the Web queue. The queue is + a directory where the Recoll Web browser plugins + create the copies of visited pages.

+
+ +
textfilepagekbs
+ +
+

Page size for text files. If this is set, + text/plain files will be divided into documents of + approximately this size. Will reduce memory usage + at index time and help with loading data in the + preview window at query time. Particularly useful + with very big files, such as application or system + logs. Also see textfilemaxmbs and + compressedfilemaxkbs.

+
+ +
membermaxkbs
+ +
+

Size limit for archive members. This is passed + to the filters in the environment as + RECOLL_FILTER_MAXMEMBERKB.

+
+
+
+ +
+
+
+
+

5.4.2.2. Parameters + affecting how we generate terms

+
+
+
+ +
+
indexStripChars
+ +
+

Decide if we store character case and diacritics + in the index. If we do, searches sensitive to case + and diacritics can be performed, but the index will + be bigger, and some marginal weirdness may + sometimes occur. The default is a stripped index. + When using multiple indexes for a search, this + parameter must be defined identically for all. + Changing the value implies an index reset.

+
+ +
nonumbers
+ +
+

Decides if terms will be generated for numbers. + For example "123", "1.5e6", 192.168.1.4, would not + be indexed if nonumbers is set ("value123" would + still be). Numbers are often quite interesting to + search for, and this should probably not be set + except for special situations, ie, scientific + documents with huge amounts of numbers in them, + where setting nonumbers will reduce the index size. + This can only be set for a whole index, not for a + subtree.

+
+ +
dehyphenate
+ +
+

Determines if we index 'coworker' also when the + input is 'co-worker'. This is new in version 1.22, + and on by default. Setting the variable to off + allows restoring the previous behaviour.

+
+ +
nocjk
+ +
+

Decides if specific East Asian (Chinese Korean + Japanese) characters/word splitting is turned off. + This will save a small amount of CPU if you have no + CJK documents. If your document base does include + such text but you are not interested in searching + it, setting nocjk may be a significant time and + space saver.

+
+ +
cjkngramlen
+ +
+

This lets you adjust the size of n-grams used + for indexing CJK text. The default value of 2 is + probably appropriate in most cases. A value of 3 + would allow more precision and efficiency on longer + words, but the index will be approximately twice as + large.

+
+ +
+ indexstemminglanguages
+ +
+

Languages for which to create stemming expansion + data. Stemmer names can be found by executing + 'recollindex -l', or this can also be set from a + list in the GUI.

+
+ +
defaultcharset
+ +
+

Default character set. This is used for files + which do not contain a character set definition + (e.g.: text/plain). Values found inside files, e.g. + a 'charset' tag in HTML documents, will override + it. If this is not set, the default character set + is the one defined by the NLS environment ($LC_ALL, + $LC_CTYPE, $LANG), or ultimately iso-8859-1 + (cp-1252 in fact). If for some reason you want a + general default which does not match your LANG and + is not 8859-1, use this variable. This can be + redefined for any sub-directory.

+
+ +
unac_except_trans
+ +
+

A list of characters, encoded in UTF-8, which + should be handled specially when converting text to + unaccented lowercase. For example, in Swedish, the + letter a with diaeresis has full alphabet + citizenship and should not be turned into an a. + Each element in the space-separated list has the + special character as first element and the + translation following. The handling of both the + lowercase and upper-case versions of a character + should be specified, as appartenance to the list + will turn-off both standard accent and case + processing. The value is global and affects both + indexing and querying. Examples: Swedish: + unac_except_trans = ää Ää + öö Öö üü Üü + ßss œoe Œoe æae Æae + ffff fifi flfl åå + Åå . German: unac_except_trans = + ää Ää öö Öö + üü Üü ßss œoe + Œoe æae Æae ffff fifi + flfl In French, you probably want to + decompose oe and ae and nobody would type a German + ß unac_except_trans = ßss œoe + Œoe æae Æae ffff fifi + flfl . The default for all until someone + protests follows. These decompositions are not + performed by unac, but it is unlikely that someone + would type the composed forms in a search. + unac_except_trans = ßss œoe Œoe + æae Æae ffff fifi + flfl

+
+ +
maildefcharset
+ +
+

Overrides the default character set for email + messages which don't specify one. This is mainly + useful for readpst (libpst) dumps, which are utf-8 + but do not say so.

+
+ +
localfields
+ +
+

Set fields on all files (usually of a specific + fs area). Syntax is the usual: name = value ; attr1 + = val1 ; [...] value is empty so this needs an + initial semi-colon. This is useful, e.g., for + setting the rclaptg field for application selection + inside mimeview.

+
+ +
testmodifusemtime
+ +
+

Use mtime instead of ctime to test if a file has + been modified. The time is used in addition to the + size, which is always used. Setting this can reduce + re-indexing on systems where extended attributes + are used (by some other application), but not + indexed, because changing extended attributes only + affects ctime. Notes: - This may prevent detection + of change in some marginal file rename cases (the + target would need to have the same size and mtime). + - You should probably also set noxattrfields to 1 + in this case, except if you still prefer to perform + xattr indexing, for example if the local file + update pattern makes it of value (as in general, + there is a risk for pure extended attributes + updates without file modification to go + undetected). Perform a full index reset after + changing this.

+
+ +
noxattrfields
+ +
+

Disable extended attributes conversion to + metadata fields. This probably needs to be set if + testmodifusemtime is set.

+
+ +
metadatacmds
+ +
+

Define commands to gather external metadata, + e.g. tmsu tags. There can be several entries, + separated by semi-colons, each defining which field + name the data goes into and the command to use. + Don't forget the initial semi-colon. All the field + names must be different. You can use aliases in the + "field" file if necessary. As a not too pretty hack + conceded to convenience, any field name beginning + with "rclmulti" will be taken as an indication that + the command returns multiple field values inside a + text blob formatted as a recoll configuration file + ("fieldname = fieldvalue" lines). The rclmultixx + name will be ignored, and field names and values + will be parsed from the data. Example: metadatacmds + = ; tags = tmsu tags %f; rclmulti1 = cmdOutputsConf + %f

+
+
+
+ +
+
+
+
+

5.4.2.3. Parameters + affecting where and how we store things

+
+
+
+ +
+
cachedir
+ +
+

Top directory for Recoll data. Recoll data + directories are normally located relative to the + configuration directory (e.g. ~/.recoll/xapiandb, + ~/.recoll/mboxcache). If 'cachedir' is set, the + directories are stored under the specified value + instead (e.g. if cachedir is ~/.cache/recoll, the + default dbdir would be ~/.cache/recoll/xapiandb). + This affects dbdir, webcachedir, mboxcachedir, + aspellDicDir, which can still be individually + specified to override cachedir. Note that if you + have multiple configurations, each must have a + different cachedir, there is no automatic + computation of a subpath under cachedir.

+
+ +
maxfsoccuppc
+ +
+

Maximum file system occupation over which we + stop indexing. The value is a percentage, + corresponding to what the "Capacity" df output + column shows. The default value is 0, meaning no + checking.

+
+ +
xapiandb
+ +
+

Xapian database directory location. This will be + created on first indexing. If the value is not an + absolute path, it will be interpreted as relative + to cachedir if set, or the configuration directory + (-c argument or $RECOLL_CONFDIR). If nothing is + specified, the default is then + ~/.recoll/xapiandb/

+
+ +
idxstatusfile
+ +
+

Name of the scratch file where the indexer + process updates its status. Default: idxstatus.txt + inside the configuration directory.

+
+ +
mboxcachedir
+ +
+

Directory location for storing mbox message + offsets cache files. This is normally 'mboxcache' + under cachedir if set, or else under the + configuration directory, but it may be useful to + share a directory between different + configurations.

+
+ +
mboxcacheminmbs
+ +
+

Minimum mbox file size over which we cache the + offsets. There is really no sense in caching + offsets for small files. The default is 5 MB.

+
+ +
webcachedir
+ +
+

Directory where we store the archived web pages. + This is only used by the web history indexing code + Default: cachedir/webcache if cachedir is set, else + $RECOLL_CONFDIR/webcache

+
+ +
webcachemaxmbs
+ +
+

Maximum size in MB of the Web archive. This is + only used by the web history indexing code. + Default: 40 MB. Reducing the size will not + physically truncate the file.

+
+ +
webqueuedir
+ +
+

The path to the Web indexing queue. This is + hard-coded in the plugin as ~/.recollweb/ToIndex so + there should be no need or possibility to change + it.

+
+ +
aspellDicDir
+ +
+

Aspell dictionary storage directory location. + The aspell dictionary (aspdict.(lang).rws) is + normally stored in the directory specified by + cachedir if set, or under the configuration + directory.

+
+ +
filtersdir
+ +
+

Directory location for executable input + handlers. If RECOLL_FILTERSDIR is set in the + environment, we use it instead. Defaults to + $prefix/share/recoll/filters. Can be redefined for + subdirectories.

+
+ +
iconsdir
+ +
+

Directory location for icons. The only reason to + change this would be if you want to change the + icons displayed in the result list. Defaults to + $prefix/share/recoll/images

+
+
+
+ +
+
+
+
+

5.4.2.4. Parameters + affecting indexing performance and resource + usage

+
+
+
+ +
+
idxflushmb
+ +
+

Threshold (megabytes of new data) where we flush + from memory to disk index. Setting this allows some + control over memory usage by the indexer process. A + value of 0 means no explicit flushing, which lets + Xapian perform its own thing, meaning flushing + every $XAPIAN_FLUSH_THRESHOLD documents created, + modified or deleted: as memory usage depends on + average document size, not only document count, the + Xapian approach is is not very useful, and you + should let Recoll manage the flushes. The program + compiled value is 0. The configured default value + (from this file) is 10 MB, and will be too low in + many cases (it is chosen to conserve memory). If + you are looking for maximum speed, you may want to + experiment with values between 20 and 200. In my + experience, values beyond this are always + counterproductive. If you find otherwise, please + drop me a note.

+
+ +
filtermaxseconds
+ +
+

Maximum external filter execution time in + seconds. Default 1200 (20mn). Set to 0 for no + limit. This is mainly to avoid infinite loops in + postscript files (loop.ps)

+
+ +
filtermaxmbytes
+ +
+

Maximum virtual memory space for filter + processes (setrlimit(RLIMIT_AS)), in megabytes. + Note that this includes any mapped libs (there is + no reliable Linux way to limit the data space + only), so we need to be a bit generous here. + Anything over 2000 will be ignored on 32 bits + machines.

+
+ +
thrQSizes
+ +
+

Stage input queues configuration. There are + three internal queues in the indexing pipeline + stages (file data extraction, terms generation, + index update). This parameter defines the queue + depths for each stage (three integer values). If a + value of -1 is given for a given stage, no queue is + used, and the thread will go on performing the next + stage. In practise, deep queues have not been shown + to increase performance. Default: a value of 0 for + the first queue tells Recoll to perform + autoconfiguration based on the detected number of + CPUs (no need for the two other values in this + case). Use thrQSizes = -1 -1 -1 to disable + multithreading entirely.

+
+ +
thrTCounts
+ +
+

Number of threads used for each indexing stage. + The three stages are: file data extraction, terms + generation, index update). The use of the counts is + also controlled by some special values in + thrQSizes: if the first queue depth is 0, all + counts are ignored (autoconfigured); if a value of + -1 is used for a queue depth, the corresponding + thread count is ignored. It makes no sense to use a + value other than 1 for the last stage because + updating the Xapian index is necessarily + single-threaded (and protected by a mutex).

+
+
+
+ +
+
+
+
+

5.4.2.5. Miscellaneous + parameters

+
+
+
+ +
+
loglevel
+ +
+

Log file verbosity 1-6. A value of 2 will print + only errors and warnings. 3 will print information + like document updates, 4 is quite verbose and 6 + very verbose.

+
+ +
logfilename
+ +
+

Log file destination. Use 'stderr' (default) to + write to the console.

+
+ +
idxloglevel
+ +
+

Override loglevel for the indexer.

+
+ +
idxlogfilename
+ +
+

Override logfilename for the indexer.

+
+ +
daemloglevel
+ +
+

Override loglevel for the indexer in real time + mode. The default is to use the idx... values if + set, else the log... values.

+
+ +
daemlogfilename
+ +
+

Override logfilename for the indexer in real + time mode. The default is to use the idx... values + if set, else the log... values.

+
+ +
idxrundir
+ +
+

Indexing process current directory. The input + handlers sometimes leave temporary files in the + current directory, so it makes sense to have + recollindex chdir to some temporary directory. If + the value is empty, the current directory is not + changed. If the value is (literal) tmp, we use the + temporary directory as set by the environment + (RECOLL_TMPDIR else TMPDIR else /tmp). If the value + is an absolute path to a directory, we go + there.

+
+ +
+ checkneedretryindexscript
+ +
+

Script used to heuristically check if we need to + retry indexing files which previously failed. The + default script checks the modified dates on + /usr/bin and /usr/local/bin. A relative path will + be looked up in the filters dirs, then in the path. + Use an absolute path to do otherwise.

+
+ +
recollhelperpath
+ +
+

Additional places to search for helper + executables. This is only used on Windows for + now.

+
+ +
idxabsmlen
+ +
+

Length of abstracts we store while indexing. + Recoll stores an abstract for each indexed file. + The text can come from an actual 'abstract' section + in the document or will just be the beginning of + the document. It is stored in the index so that it + can be displayed inside the result lists without + decoding the original file. The idxabsmlen + parameter defines the size of the stored abstract. + The default value is 250 bytes. The search + interface gives you the choice to display this + stored text or a synthetic abstract built by + extracting text around the search terms. If you + always prefer the synthetic abstract, you can + reduce this value and save a little space.

+
+ +
idxmetastoredlen
+ +
+

Truncation length of stored metadata fields. + This does not affect indexing (the whole field is + processed anyway), just the amount of data stored + in the index for the purpose of displaying fields + inside result lists or previews. The default value + is 150 bytes which may be too low if you have + custom fields.

+
+ +
aspellLanguage
+ +
+

Language definitions to use when creating the + aspell dictionary. The value must match a set of + aspell language definition files. You can type + "aspell dicts" to see a list The default if this is + not set is to use the NLS environment to guess the + value.

+
+ +
aspellAddCreateParam
+ +
+

Additional option and parameter to aspell + dictionary creation command. Some aspell packages + may need an additional option (e.g. on Debian + Jessie: --local-data-dir=/usr/lib/aspell). See + Debian bug 772415.

+
+ +
aspellKeepStderr
+ +
+

Set this to have a look at aspell dictionary + creation errors. There are always many, so this is + mostly for debugging.

+
+ +
noaspell
+ +
+

Disable aspell use. The aspell dictionary + generation takes time, and some combinations of + aspell version, language, and local terms, result + in aspell crashing, so it sometimes makes sense to + just disable the thing.

+
+ +
monauxinterval
+ +
+

Auxiliary database update interval. The real + time indexer only updates the auxiliary databases + (stemdb, aspell) periodically, because it would be + too costly to do it for every document change. The + default period is one hour.

+
+ +
monixinterval
+ +
+

Minimum interval (seconds) between processings + of the indexing queue. The real time indexer does + not process each event when it comes in, but lets + the queue accumulate, to diminish overhead and to + aggregate multiple events affecting the same file. + Default 30 S.

+
+ +
mondelaypatterns
+ +
+

Timing parameters for the real time indexing. + Definitions for files which get a longer delay + before reindexing is allowed. This is for + fast-changing files, that should only be reindexed + once in a while. A list of wildcardPattern:seconds + pairs. The patterns are matched with + fnmatch(pattern, path, 0) You can quote entries + containing white space with double quotes (quote + the whole entry, not the pattern). The default is + empty. Example: mondelaypatterns = *.log:20 "*with + spaces.*:30"

+
+ +
monioniceclass
+ +
+

ionice class for the real time indexing process + On platforms where this is supported. The default + value is 3.

+
+ +
+ monioniceclassdata
+ +
+

ionice class parameter for the real time + indexing process. On platforms where this is + supported. The default is empty.

+
+
+
+ +
+
+
+
+

5.4.2.6. Query-time + parameters (no impact on the index)

+
+
+
+ +
+
autodiacsens
+ +
+

auto-trigger diacritics sensitivity (raw index + only). IF the index is not stripped, decide if we + automatically trigger diacritics sensitivity if the + search term has accented characters (not in + unac_except_trans). Else you need to use the query + language and the "D" modifier to specify diacritics + sensitivity. Default is no.

+
+ +
autocasesens
+ +
+

auto-trigger case sensitivity (raw index only). + IF the index is not stripped (see indexStripChars), + decide if we automatically trigger character case + sensitivity if the search term has upper-case + characters in any but the first position. Else you + need to use the query language and the "C" modifier + to specify character-case sensitivity. Default is + yes.

+
+ +
maxTermExpand
+ +
+

Maximum query expansion count for a single term + (e.g.: when using wildcards). This only affects + queries, not indexing. We used to not limit this at + all (except for filenames where the limit was too + low at 1000), but it is unreasonable with a big + index. Default 10000.

+
+ +
maxXapianClauses
+ +
+

Maximum number of clauses we add to a single + Xapian query. This only affects queries, not + indexing. In some cases, the result of term + expansion can be multiplicative, and we want to + avoid eating all the memory. Default 50000.

+
+ +
snippetMaxPosWalk
+ +
+

Maximum number of positions we walk while + populating a snippet for the result list. The + default of 1,000,000 may be insufficient for very + big documents, the consequence would be snippets + with possibly meaning-altering missing words.

+
+
+
+ +
+
+
+
+

5.4.2.7. Parameters + for the PDF input script

+
+
+
+ +
+
pdfocr
+ +
+

Attempt OCR of PDF files with no text content if + both tesseract and pdftoppm are installed. The + default is off because OCR is so very slow.

+
+ +
pdfattach
+ +
+

Enable PDF attachment extraction by executing + pdftk (if available). This is normally disabled, + because it does slow down PDF indexing a bit even + if not one attachment is ever found.

+
+
+
+ +
+
+
+
+

5.4.2.8. Parameters + set for specific locations

+
+
+
+ +
+
mhmboxquirks
+ +
+

Enable thunderbird/mozilla-seamonkey mbox format + quirks Set this for the directory where the email + mbox files are stored.

+
+
+
+
+ +
+
+
+
+

5.4.3. The + fields file

+
+
+
+ +

This file contains information about dynamic fields + handling in Recoll. Some + very basic fields have hard-wired behaviour, and, mostly, + you should not change the original data inside the + fields file. But you can + create custom fields fitting your data and handle them + just like they were native ones.

+ +

The fields file has + several sections, which each define an aspect of fields + processing. Quite often, you'll have to modify several + sections to obtain the desired behaviour.

+ +

We will only give a short description here, you should + refer to the comments inside the default file for more + detailed information.

+ +

Field names should be lowercase alphabetic ASCII.

+ +
+
+
[prefixes]
+ +
+

A field becomes indexed (searchable) by having a + prefix defined in this section.

+
+ +
[stored]
+ +
+

A field becomes stored (displayable inside + results) by having its name listed in this section + (typically with an empty value).

+
+ +
[aliases]
+ +
+

This section defines lists of synonyms for the + canonical names used inside the [prefixes] and [stored] sections

+
+ +
[queryaliases]
+ +
+

This section also defines aliases for the + canonic field names, with the difference that the + substitution will only be used at query time, + avoiding any possibility that the value would + pick-up random metadata from documents.

+
+ +
handler-specific + sections
+ +
+

Some input handlers may need specific + configuration for handling fields. Only the email + message handler currently has such a section (named + [mail]). It allows + indexing arbitrary email headers in addition to the + ones indexed by default. Other such sections may + appear in the future.

+
+
+
+ +

Here follows a small example of a personal + fields file. This would + extract a specific email header and use it as a + searchable field, with data displayable inside result + lists. (Side note: as the email handler does no decoding + on the values, only plain ascii headers can be indexed, + and only the first occurrence will be used for headers + that occur several times).

+
+[prefixes]
+# Index mailmytag contents (with the given prefix)
+mailmytag = XMTAG
+
+[stored]
+# Store mailmytag inside the document data record (so that it can be
+# displayed - as %(mailmytag) - in result lists).
+mailmytag = 
+
+[queryaliases]
+filename = fn
+containerfilename = cfn
+
+[mail]
+# Extract the X-My-Tag mail header, and use it internally with the
+# mailmytag field name
+x-my-tag = mailmytag
+
+ +
+
+
+
+

5.4.3.1. Extended + attributes in the fields file

+
+
+
+ +

Recoll versions + 1.19 and later process user extended file attributes as + documents fields by default.

+ +

Attributes are processed as fields of the same name, + after removing the user + prefix on Linux.

+ +

The [xattrtofields] + section of the fields + file allows specifying translations from extended + attributes names to Recoll field names. An empty + translation disables use of the corresponding attribute + data.

+
+
+ +
+
+
+
+

5.4.4. The + mimemap file

+
+
+
+ +

mimemap specifies the + file name extension to MIME type mappings.

+ +

For file names without an extension, or with an + unknown one, a system command (file -i, or xdg-mime) will be + executed to determine the MIME type (this can be switched + off, or the command changed inside the main configuration + file).

+ +

The mappings can be specified on a per-subtree basis, + which may be useful in some cases. Example: okular notes have a .xml extension but should be handled + specially, which is possible because they are usually all + located in one place. Example:

+
+[~/.kde/share/apps/okular/docdata]
+.xml = application/x-okular-notes
+
+ +

The recoll_noindex + mimemap variable has been + moved to recoll.conf and + renamed to noContentSuffixes, while keeping the + same function, as of Recoll version 1.21. For older + Recoll versions, see the + documentation for noContentSuffixes but use recoll_noindex in mimemap.

+
+ +
+
+
+
+

5.4.5. The + mimeconf file

+
+
+
+ +

mimeconf specifies how + the different MIME types are handled for indexing, and + which icons are displayed in the recoll result + lists.

+ +

Changing the parameters in the [index] section is + probably not a good idea except if you are a Recoll developer.

+ +

The [icons] section allows you to change the icons + which are displayed by recoll in the result + lists (the values are the basenames of the png images + inside the iconsdir + directory (specified in recoll.conf).

+
+ +
+
+
+
+

5.4.6. The + mimeview file

+
+
+
+ +

mimeview specifies which + programs are started when you click on an Open link in a result list. Ie: HTML is + normally displayed using firefox, but you may prefer + Konqueror, your + openoffice.org program + might be named oofice instead of + openoffice + etc.

+ +

Changes to this file can be done by direct editing, or + through the recoll GUI preferences + dialog.

+ +

If Use desktop preferences to + choose document editor is checked in the + Recoll GUI preferences, + all mimeview entries will + be ignored except the one labelled application/x-all (which is set to use + xdg-open by + default).

+ +

In this case, the xallexcepts top level variable defines a + list of MIME type exceptions which will be processed + according to the local entries instead of being passed to + the desktop. This is so that specific Recoll options such as a page number + or a search string can be passed to applications that + support them, such as the evince viewer.

+ +

As for the other configuration files, the normal usage + is to have a mimeview + inside your own configuration directory, with just the + non-default entries, which will override those from the + central configuration file.

+ +

All viewer definition entries must be placed under a + [view] section.

+ +

The keys in the file are normally MIME types. You can + add an application tag to specialize the choice for an + area of the filesystem (using a localfields specification in + mimeconf). The syntax for + the key is mimetype|tag

+ +

The nouncompforviewmts + entry, (placed at the top level, outside of the + [view] section), holds a + list of MIME types that should not be uncompressed before + starting the viewer (if they are found compressed, ie: + mydoc.doc.gz).

+ +

The right side of each assignment holds a command to + be executed for opening the file. The following + substitutions are performed:

+ +
+
    +
  • +

    %D. Document date

    +
  • + +
  • +

    %f. File name. This may be the name + of a temporary file if it was necessary to create + one (ie: to extract a subdocument from a + container).

    +
  • + +
  • +

    %i. Internal path, for subdocuments + of containers. The format depends on the container + type. If this appears in the command line, + Recoll will not + create a temporary file to extract the subdocument, + expecting the called application (possibly a + script) to be able to handle it.

    +
  • + +
  • +

    %M. MIME type

    +
  • + +
  • +

    %p. Page index. Only significant for + a subset of document types, currently only PDF, + Postscript and DVI files. Can be used to start the + editor at the right page for a match or + snippet.

    +
  • + +
  • +

    %s. Search term. The value will only + be set for documents with indexed page numbers (ie: + PDF). The value will be one of the matched search + terms. It would allow pre-setting the value in the + "Find" entry inside Evince for example, for easy + highlighting of the term.

    +
  • + +
  • +

    %u. Url.

    +
  • +
+
+ +

In addition to the predefined values above, all + strings like %(fieldname) + will be replaced by the value of the field named + fieldname for the document. + This could be used in combination with field + customisation to help with opening the document.

+
+ +
+
+
+
+

5.4.7. The + ptrans file

+
+
+
+ +

ptrans specifies + query-time path translations. These can be useful in + multiple cases.

+ +

The file has a section for any index which needs + translations, either the main one or additional query + indexes. The sections are named with the Xapian index directory names. No + slash character should exist at the end of the paths (all + comparisons are textual). An exemple should make things + sufficiently clear

+
+          [/home/me/.recoll/xapiandb]
+          /this/directory/moved = /to/this/place
+
+          [/path/to/additional/xapiandb]
+          /server/volume1/docdir = /net/server/volume1/docdir
+          /server/volume2/docdir = /net/server/volume2/docdir
+        
+
+
+ +
+
+
+
+

5.4.8. Examples + of configuration adjustments

+
+
+
+ +
+
+
+
+

5.4.8.1. Adding + an external viewer for an non-indexed type

+
+
+
+ +

Imagine that you have some kind of file which does + not have indexable content, but for which you would + like to have a functional Open link in the result list (when + found by file name). The file names end in .blob and can be + displayed by application blobviewer.

+ +

You need two entries in the configuration files for + this to work:

+ +
+
    +
  • +

    In $RECOLL_CONFDIR/mimemap + (typically ~/.recoll/mimemap), add the + following line:

    +
    +.blob = application/x-blobapp
    +
    + +

    Note that the MIME type is made up here, and + you could call it diesel/oil just + the same.

    +
  • + +
  • +

    In $RECOLL_CONFDIR/mimeview under + the [view] section, + add:

    +
    +application/x-blobapp = blobviewer %f
    +
    + +

    We are supposing that blobviewer wants + a file name parameter here, you would use + %u if it liked URLs + better.

    +
  • +
+
+ +

If you just wanted to change the application used by + Recoll to display a + MIME type which it already knows, you would just need + to edit mimeview. The + entries you add in your personal file override those in + the central configuration, which you do not need to + alter. mimeview can also + be modified from the Gui.

+
+ +
+
+
+
+

5.4.8.2. Adding + indexing support for a new file type

+
+
+
+ +

Let us now imagine that the above .blob files actually + contain indexable text and that you know how to extract + it with a command line program. Getting Recoll to index the files is easy. + You need to perform the above alteration, and also to + add data to the mimeconf + file (typically in ~/.recoll/mimeconf):

+ +
+
    +
  • +

    Under the [index] + section, add the following line (more about the + rclblob + indexing script later):

    +
    +application/x-blobapp = exec rclblob
    +
    +
  • + +
  • +

    Under the [icons] + section, you should choose an icon to be + displayed for the files inside the result lists. + Icons are normally 64x64 pixels PNG files which + live in /usr/share/recoll/images.

    +
  • + +
  • +

    Under the [categories] section, you should + add the MIME type where it makes sense (you can + also create a category). Categories may be used + for filtering in advanced search.

    +
  • +
+
+ +

The rclblob handler should + be an executable program or script which exists inside + /usr/share/recoll/filters. It will be + given a file name as argument and should output the + text or html contents on the standard output.

+ +

The filter + programming section describes in more detail how to + write an input handler.

+
+
+
+
+
+ + diff --git a/src/doc/user/usermanual.xml b/src/doc/user/usermanual.xml index cbe8c549..8fa9472c 100644 --- a/src/doc/user/usermanual.xml +++ b/src/doc/user/usermanual.xml @@ -2,9 +2,10 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ Recoll"> -http://www.recoll.org/features.html"> - +http://www.recoll.org/features.html"> + Xapian"> +Windows"> ]> @@ -25,8 +26,8 @@ 2005-2015 Jean-Francois Dockes - + Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free @@ -47,7 +48,23 @@ Introduction - + This document introduces full text search notions + and describes the installation and use of the &RCL; + application. This version describes &RCL; &RCLVERSION;. + + &RCL; was for a long time dedicated to Unix-like systems. It + was only lately (2015) ported to + MS-Windows. Many references in this + manual, especially file locations, are specific to Unix, and not + valid on &WIN;. Some described features are also not available on + &WIN;. The manual will be progressively updated. Until this happens, + most references to shared files can be translated by looking under + the Recoll installation directory (esp. the + Share subdirectory). The user configuration is + stored by default under AppData/Local/Recoll + inside the user directory, along with the index itself. + + Giving it a try If you do not like reading manuals (who does?) but @@ -68,19 +85,24 @@ , then adjust the Top directories section). - Also be aware that you may need to install the + Also be aware that, on Unix/Linux, you may need to install the appropriate supporting applications for document types that need them (for example antiword for - Microsoft Word files). + Microsoft Word files). + + The &RCL; installation for &WIN; is self-contained and includes + most useful auxiliary programs. You will just need to install Python + 2.7. + Full text search - &RCL; is a full text search application. Full text search + &RCL; is a full text search application, which means that it finds your data by content rather than by external attributes - (like a file name). You specify words + (like the file name). You specify words (terms) which should or should not appear in the text you are looking for, and receive in return a list of matching documents, ordered so that the most @@ -188,23 +210,22 @@ cumbersome than separating your documents according to what language they are written in. - Before version 1.18, &RCL; stripped most accents and - diacritics from terms, and converted them to lower case before + By default, &RCL; strips most accents and + diacritics from terms, and converts them to lower case before either storing them in the index or searching for them. As a - consequence, it was impossible to search for a particular + consequence, it is impossible to search for a particular capitalization of a term (US / us), or to discriminate two terms based on diacritics (sake / saké, mate / maté). - As of version 1.18, &RCL; can optionally store the raw terms, - without accent stripping or case conversion. In this configuration, - it is still possible (and most common) for a query to be - insensitive to case and/or diacritics. Appropriate term expansions - are performed before actually accessing the main index. This is - described in more detail in the section about index case and - diacritics sensitivity. + &RCL; versions 1.18 and newer can optionally store the raw + terms, without accent stripping or case conversion. In this + configuration, default searches will behave as before, but it is + possible to perform searches sensitive to case and + diacritics. This is described in more detail + in the section about index + case and diacritics sensitivity. &RCL; has many parameters which define exactly what to index, and how to classify and decode the source @@ -212,12 +233,12 @@ linkend="RCL.INDEXING.CONFIG">configuration files. A default configuration is copied into a standard location (usually something like - /usr/[local/]share/recoll/examples) + /usr/share/recoll/examples) during installation. The default values set by the configuration files in this directory may be overridden by - values that you set inside your personal configuration, found + values set inside your personal configuration, found by default in the .recoll sub-directory - of your home directory. The default configuration will index + of your home directory. The default configuration will index your home directory with default parameters and should be sufficient for giving &RCL; a try, but you may want to adjust it later, which can be done either by editing the text files @@ -241,7 +262,7 @@ are other ways to perform &RCL; searches: mostly a command line interface, a - + Python programming interface, a KDE KIO slave module, and @@ -334,6 +355,21 @@ Indexing schedule + + The File + menu also has entries to start or stop + the current indexing operation. Stopping indexing is performed by + killing the recollindex process, which will + checkpoint its state and exit. A later restart of indexing will + mostly resume from where things stopped (the file tree walk has to + be restarted from the beginning). + + When the real time indexer is running, only a stop operation + is available from the menu. When no indexing is running, you have + a choice of updating the index or rebuilding it (the first choice + only processes changed files, the second one zeroes the index + before starting so that all files are processed). + @@ -438,7 +474,8 @@ indexedmimetypes = application/pdf (When using sections like this, don't forget that they remain in effect until the end of the file or another section - indicator). + indicator). + excludedmimetypes or indexedmimetypes, can be set either by @@ -447,6 +484,11 @@ indexedmimetypes = application/pdf (recoll.conf), or from the GUI index configuration tool. + When editing the indexedmimetypes + or excludedmimetypes lists, you should use the + MIME values listed in the mimemap file + or in Recoll result lists in preference to file -i + output: there are a number of differences. @@ -466,7 +508,7 @@ indexedmimetypes = application/pdf may be quite costly (for example failing to uncompress a big file because of insufficient disk space). - The indexer in &RCL; versions 1.21 and later do not + The indexer in &RCL; versions 1.21 and later does not retry failed file by default. Retrying will only occur if an explicit option () is set on the recollindex command line, or if a script @@ -508,11 +550,9 @@ indexedmimetypes = application/pdf option to the &RCL; commands. This method would typically be used to index different areas of the file system to different indexes. For example, if you were to issue the - following commands: - -export RECOLL_CONFDIR=~/.indexes-email -recoll - Then &RCL; would use configuration files + following command: + recoll -c ~/.indexes-email Then + &RCL; would use configuration files stored in ~/.indexes-email/ and, (unless specified otherwise in recoll.conf) would look for @@ -592,11 +632,10 @@ recoll complete reconstruction. If confidential data is indexed, access to the database directory should be restricted. - &RCL; (since version 1.4) will create the configuration - directory with a mode of 0700 (access by owner only). As the - index data directory is by default a sub-directory of the - configuration directory, this should result in appropriate - protection. + &RCL; will create the configuration directory with a mode of + 0700 (access by owner only). As the index data directory is by + default a sub-directory of the configuration directory, this should + result in appropriate protection. If you use another setup, you should think of the kind of protection you need for your index, set the directory @@ -664,6 +703,18 @@ recoll option to recoll and recollindex. + When working with the recoll index + configuration GUI, the configuration directory for which parameters + are modified is the one which was selected by + RECOLL_CONFDIR or the parameter, + and there is no way to switch configurations within the GUI. + + Additional configuration directory (beyond + ~/.recoll) must be created by hand + (mkdir or such), the GUI will not do it. This is + to avoid mistakenly creating additional directories when an + argument is mistyped. + A typical usage scenario for the multiple index feature would be for a system administrator to set up a central index for shared data, that you choose to search or not in addition to @@ -755,6 +806,106 @@ recoll + + + + + Indexing threads configuration + + The &RCL; indexing process + recollindex can use multiple threads to + speed up indexing on multiprocessor systems. The work done + to index files is divided in several stages and some of the + stages can be executed by multiple threads. The stages are: + + File system walking: this is always performed by + the main thread. + File conversion and data extraction. + Text processing (splitting, stemming, + etc.) + &XAP; index update. + + + You can also read a + + longer document about the transformation of + &RCL; indexing to multithreading. + + The threads configuration is controlled by two + configuration file parameters. + + + + thrQSizes + This variable defines the job input queues + configuration. There are three possible queues for stages + 2, 3 and 4, and this parameter should give the queue depth + for each stage (three integer values). If a value of -1 is + used for a given stage, no queue is used, and the thread + will go on performing the next stage. In practise, deep + queues have not been shown to increase performance. A value + of 0 for the first queue tells &RCL; to perform + autoconfiguration (no need for anything else in this case, + thrTCounts is not used) - this is the default + configuration. + + + + thrTCounts + This defines the number of threads used + for each stage. If a value of -1 is used for one of + the queue depths, the corresponding thread count is + ignored. It makes no sense to use a value other than 1 + for the last stage because updating the &XAP; index is + necessarily single-threaded (and protected by a + mutex). + + + + + + If the first value in thrQSizes is + 0, thrTCounts is ignored. + + The following example would use three queues (of depth 2), + and 4 threads for converting source documents, 2 for + processing their text, and one to update the index. This was + tested to be the best configuration on the test system + (quadri-processor with multiple disks). + +thrQSizes = 2 2 2 +thrTCounts = 4 2 1 + + + + The following example would use a single queue, and the + complete processing for each document would be performed by + a single thread (several documents will still be processed + in parallel in most cases). The threads will use mutual + exclusion when entering the index update stage. In practise + the performance would be close to the precedent case in + general, but worse in certain cases (e.g. a Zip archive + would be performed purely sequentially), so the previous + approach is preferred. YMMV... The 2 last values for + thrTCounts are ignored. + +thrQSizes = 2 -1 -1 +thrTCounts = 6 1 1 + + + + The following example would disable + multithreading. Indexing will be performed by a single + thread. + +thrQSizes = -1 -1 -1 + + + + + + + The index configuration GUI @@ -880,16 +1031,64 @@ recoll Importing external tags During indexing, it is possible to import metadata for - each file by executing commands. For example, this could - extract user tag data for the file and store it in a field for - indexing. + each file by executing commands. For example, this could + extract user tag data for the file and store it in a field for + indexing. See the - section - about the metadatacmds field in - the main configuration chapter for more detail. + section + about the metadatacmds field in + the main configuration chapter for a description of the + configuration syntax. + + As an example, if you would want &RCL; to use tags managed by + tmsu, you would add the following to the + configuration file: + + [/some/area/of/the/fs] +metadatacmds = ; tags = tmsu tags %f + + + Depending on the tmsu version, + you may need/want to add options like + --database=/some/db. + + You may want to restrict this processing to a subset of + the directory tree, because it may slow down indexing a bit + ([some/area/of/the/fs]). + Note the initial semi-colon after the equal sign. + + In the example above, the output of tmsu is + used to set a field named tags. The field name is + arbitrary and could be tmsu or + myfield just the same, but tags + is an alias for the standard &RCL; keywords field, + and the tmsu output will just augment its + contents. This will avoid the need to extend the field configuration. + + Once re-indexing is performed (you'll need to force the file + reindexing, &RCL; will not detect the need by itself), you will + be able to search from the query language, through any of its + aliases: tags:some/alternate/values or + tags:all,these,values (the compact field search + syntax is supported for recoll 1.20 and later. For + older versions, you would need to repeat the tags: + specifier for each term, e.g. tags:some OR + tags:alternate). + + You should be aware that tags changes will not be detected by + the indexer if the file itself did not change. One possible + workaround would be to update the file ctime when + you modify the tags, which + would be consistent with how extended attributes function. A pair of + chmod commands could accomplish this, or a + touch -a . Alternatively, just + couple the tag update with a recollindex -e -i + filename. + + - Periodic indexing @@ -1119,7 +1318,7 @@ fvwm # -- Change to: # fs.inotify.max_queued_events=32768 -fs.notify.max_user_instances=256 +fs.inotify.max_user_instances=256 fs.inotify.max_user_watches=32768 @@ -2898,13 +3097,14 @@ MimeType=*/* stream, without a graphical interface: By passing option to the - recoll program. + recoll program, or by calling it as + recollq (through a link). By using the recollq program. By writing a custom Python program, using the - Recoll Python API. + Recoll Python API. @@ -2975,6 +3175,75 @@ text/html [file:///Users/uncrypted-dockes/projets/bateaux/ilur/factEtCie/r + + Using Synonyms (1.22) + + Term synonyms: + there are a number of ways to use term synonyms for searching text: + + At index creation time, they can be used to alter the + indexed terms, either increasing or decreasing their number, by + expanding the original terms to all synonyms, or by + reducing all synonym terms to a canonical one. + At query time, they can be used to match texts + containing terms which are synonyms of the ones specified by the user, + either by expanding the query for all synonyms, or by reducing the user + entry to canonical terms (the latter only works if the corresponding + processing has been performed while creating the index). + + + + + &RCL; only uses synonyms at query time. A user query term which + part of a synonym group will be optionally expanded into an + OR query for all terms in the group. + + Synonym groups are defined inside ordinary text files. Each line + in the file defines a group. + + Example: + +hi hello "good morning" + +# not sure about "au revoir" though. Is this english ? +bye goodbye "see you" \ + "au revoir" + + + + As usual, lines beginning with a # are comments, + empty lines are ignored, and lines can be continued by ending them with + a backslash. + + + Multi-word synonyms are supported, but be aware that these will + generate phrase queries, which may degrade performance and will disable + stemming expansion for the phrase terms. + + The synonyms file can be specified in the Search + parameters tab of the GUI configuration + Preferences menu entry, or as an option for + command-line searches. + + Once the file is defined, the use of synonyms can be enabled or + disabled directly from the Preferences + menu. + + The synonyms are searched for matches with user terms after the + latter are stem-expanded, but the contents of the synonyms file itself + is not subjected to stem expansion. This means that a match will not be + found if the form present in the synonyms file is not present anywhere + in the document set. + + The synonyms function is probably not going to help you find your + letters to Mr. Smith. It is best used for domain-specific searches. For + example, it was initially suggested by a user performing searches among + historical documents: the synonyms file would contains nicknames and + aliases for each of the persons of interest. + + + + Path translations @@ -3006,15 +3275,15 @@ text/html [file:///Users/uncrypted-dockes/projets/bateaux/ilur/factEtCie/r inconvenient to run the indexer. - More generally, the path translation facility may be useful - whenever the documents paths seen by the indexer are not the same - as the ones which should be used at query time. - &RCL; has a facility for rewriting access paths when extracting the data from the index. The translations can be defined for the main index and for any additional query index. + The path translation facility will be useful + whenever the documents paths seen by the indexer are not the same + as the ones which should be used at query time. + In the above NFS example, &RCL; could be instructed to rewrite any file:///home/me URL from the index to file:///net/server/home/me, @@ -3024,7 +3293,16 @@ text/html [file:///Users/uncrypted-dockes/projets/bateaux/ilur/factEtCie/r ptrans configuration file, which can be edited by hand or from the GUI external indexes - configuration dialog. + configuration dialog: + Preferences + External index dialog + , then click the Paths + translations button on the right below the index + list. + + Due to a current bug, the GUI must be restarted + after changing the ptrans values (even when they + were changed from the GUI). @@ -3039,7 +3317,7 @@ text/html [file:///Users/uncrypted-dockes/projets/bateaux/ilur/factEtCie/r capabilities as the complex search interface in the GUI. - The language is based on the (seemingly defunct) + The language was based on the now defunct Xesam user search language specification. @@ -3095,11 +3373,10 @@ text/html [file:///Users/uncrypted-dockes/projets/bateaux/ilur/factEtCie/r not (word1 AND word2) OR - word3. Explicit - parenthesis are not supported. + word3. - As of &RCL; 1.21, you can use parentheses to group - elements, which will sometimes make things clearer, and may + &RCL; versions 1.21 and later, allow using parentheses to + group elements, which will sometimes make things clearer, and may allow expressing combinations which would have been difficult otherwise. @@ -3195,8 +3472,14 @@ text/html [file:///Users/uncrypted-dockes/projets/bateaux/ilur/factEtCie/r linkend="RCL.INSTALL.CONFIG.FIELDS">section about the fields file + The document input handlers used while indexing have the + possibility to create other fields with arbitrary names, and + aliases may be defined in the configuration, so that the exact + field search possibilities may be different for you if someone + took care of the customisation. + The field syntax also supports a few field-like, but - special, criteria: + special, criteria: @@ -3226,7 +3509,7 @@ dir:recoll dir:src -dir:utils -dir:common path (in any order), and which have not either utils or common. - + You can also use OR conjunctions with dir: clauses. @@ -3296,19 +3579,15 @@ dir:recoll dir:src -dir:utils -dir:common mime or format for specifying the - MIME type. This one is quite special because you can specify - several values which will be OR'ed (the normal default for the - language is AND). Ex: mime:text/plain - mime:text/html. Specifying an explicit boolean - operator before a - mime specification is not supported and - will produce strange results. You can filter out certain types - by using negation (-mime:some/type), and you can - use wildcards in the value (mime:text/*). - Note that mime is - the ONLY field with an OR default. You do need to use - OR with ext terms for - example. + MIME type. These clauses are processed besides the normal + Boolean logic of the search. Multiple values will be OR'ed + (instead of the normal AND). You can specify types to be + excluded, with the usual -, and use + wildcards. Example: mime:text/* + -mime:text/plain + Specifying an explicit boolean + operator before a mime specification is not + supported and will produce strange results. type or @@ -3318,17 +3597,25 @@ dir:recoll dir:src -dir:utils -dir:common (mimeconf), and can be modified or extended. The default category names are those which permit filtering results in the main GUI screen. Categories are OR'ed - like MIME types above. This can't be negated with - - either. + like MIME types above, and can be negated with + -. - The document input handlers used while indexing have the - possibility to create other fields with arbitrary names, and - aliases may be defined in the configuration, so that the exact - field search possibilities may be different for you if someone - took care of the customisation. + + mime, rclcat, + size and date criteria + always affect the whole query (they are applied as a final + filter), even if set with other terms inside a parenthese. + + + + mime (or the equivalent + rclcat) is the only + field with an OR default. You do need to use + OR with ext terms for + example. Modifiers @@ -3344,6 +3631,11 @@ dir:recoll dir:src -dir:utils -dir:common stemming is off by default for phrases). + s can be used to turn off + synonym expansion, if a synonyms file is in place (only for + &RCL; 1.22 and later). + + o can be used to specify a "slack" for phrase and proximity searches: the number of additional terms that may be found between the specified @@ -3353,7 +3645,7 @@ dir:recoll dir:src -dir:utils -dir:common p can be used to turn the default phrase search into a proximity one - (unordered). Example:"order any in"p + (unordered). Example: "order any in"p C will turn on case @@ -3380,17 +3672,19 @@ dir:recoll dir:src -dir:utils -dir:common For &RCL; versions 1.18 and later, and when working with a raw index (not the default), searches can be - made sensitive - to character case and diacritics. How this happens is controlled by - configuration variables and what search data is entered. + sensitive to character case and diacritics. How this happens + is controlled by configuration variables and what search data is + entered. - The general default is that searches are insensitive to case - and diacritics. An entry of resume will match any - of Resume, RESUME, + The general default is that searches entered without upper-case + or accented characters are insensitive to case and diacritics. An + entry of resume will match any of + Resume, RESUME, résumé, Résumé etc. Two configuration variables can automate switching on - sensitivity: + sensitivity (they were documented but actually did nothing until + &RCL; 1.22): @@ -3666,7 +3960,7 @@ dir:recoll dir:src -dir:utils -dir:common Writing a document input handler - TerminologyThe small programs or pieces + TerminologyThe small programs or pieces of code which handle the processing of the different document types for &RCL; used to be called filters, which is still reflected in the name of the directory which @@ -3676,7 +3970,7 @@ dir:recoll dir:src -dir:utils -dir:common content. However these modules may have other behaviours, and the term input handler is now progressively substituted in the documentation. filter is - still used in many places though. + still used in many places though. &RCL; input handlers cooperate to translate from the multitude of input document formats, simple ones @@ -3690,7 +3984,7 @@ dir:recoll dir:src -dir:utils -dir:common inside recollindex. This latter kind will not be described here. - There are currently (1.18 and since 1.13) two kinds of + There are currently (since version 1.13) two kinds of external executable input handlers: Simple exec handlers @@ -3801,13 +4095,18 @@ dir:recoll dir:src -dir:utils -dir:common If you can program and want to write an execm handler, it should not be too - difficult to make sense of one of the existing modules. For - example, look at rclzip which uses Zip - file paths as identifiers (ipath), - and rclics, which uses an integer - index. Also have a look at the comments inside - the internfile/mh_execm.h file and - possibly at the corresponding module. + difficult to make sense of one of the existing modules. There is + a sample one with many comments, not actually used by &RCL;, + which would index a text file as one document per line. Look for + rcltxtlines.py in the + src/filters directory in the &RCL; BitBucket + repository (the sample + not in the distributed release at the moment). + + You can also have a look at the slightly more complex + rclzip which uses Zip + file paths as identifiers (ipath). execm handlers sometimes need to make a choice for the nature of the ipath @@ -3858,13 +4157,13 @@ dir:recoll dir:src -dir:utils -dir:common .doc = application/msword If no suffix association is found for the file name, &RCL; will try - to execute the file -i command to determine a - MIME type. + to execute a system command (typically file -i or + xdg-mime) to determine a MIME type. - The association of file types to handlers is performed in - the + The second element is the association of MIME types to handlers + in the mimeconf file. A sample will probably be - of better help than a long explanation: + better than a long explanation: [index] @@ -4103,97 +4402,43 @@ or - - API + + Python API - - Interface elements - - A few elements in the interface are specific and and need - an explanation. - - - - - udi An udi (unique document - identifier) identifies a document. Because of limitations - inside the index engine, it is restricted in length (to - 200 bytes), which is why a regular URI cannot be used. The - structure and contents of the udi is defined by the - application and opaque to the index engine. For example, - the internal file system indexer uses the complete - document path (file path + internal path), truncated to - length, the suppressed part being replaced by a hash - value. - - - - ipath - - This data value (set as a field in the Doc - object) is stored, along with the URL, but not indexed by - &RCL;. Its contents are not interpreted, and its use is up - to the application. For example, the &RCL; internal file - system indexer stores the part of the document access path - internal to the container file (ipath in - this case is a list of subdocument sequential numbers). url - and ipath are returned in every search result and permit - access to the original document. - - - - - Stored and indexed fields - - The fields file inside - the &RCL; configuration defines which document fields are - either "indexed" (searchable), "stored" (retrievable with - search results), or both. - - - - - - Data for an external indexer, should be stored in a - separate index, not the one for the &RCL; internal file system - indexer, except if the latter is not used at all). The reason - is that the main document indexer purge pass would remove all - the other indexer's documents, as they were not seen during - indexing. The main indexer documents would also probably be a - problem for the external indexer purge operation. - - - - - Python interface - - + Introduction &RCL; versions after 1.11 define a Python programming - interface, both for searching and indexing. The indexing - portion has seen little use, but the searching one is used - in the Recoll Ubuntu Unity Lens and Recoll Web UI. + interface, both for searching and creating/updating an + index. - The API is inspired by the Python database API - specification. There were two major changes in recent &RCL; - versions: - - The basis for the &RCL; API changed from Python - database API version 1.0 (&RCL; versions up to 1.18.1), - to version 2.0 (&RCL; 1.18.2 and later). - The recoll module became a - package (with an internal recoll - module) as of &RCL; version 1.19, in order to add more - functions. For existing code, this only changes the way - the interface must be imported. - + The search interface is used in the &RCL; Ubuntu Unity Lens + and the &RCL; Web UI. It can run queries on any &RCL; + configuration. + + The index update section of the API may be used to create and + update &RCL; indexes on specific configurations (separate from the + ones created by recollindex). The resulting + databases can be queried alone, or in conjunction with regular + ones, through the GUI or any of the query interfaces. + + The search API is modeled along the Python database API + specification. There were two major changes along &RCL; versions: + + The basis for the &RCL; API changed from Python + database API version 1.0 (&RCL; versions up to 1.18.1), + to version 2.0 (&RCL; 1.18.2 and later). + The recoll module became a + package (with an internal recoll + module) as of &RCL; version 1.19, in order to add more + functions. For existing code, this only changes the way + the interface must be imported. + - We will mostly describe the new API and package - structure here. A paragraph at the end of this section will - explain a few differences and ways to write code - compatible with both versions. + We will describe the new API and package structure here. A + paragraph at the end of this section will explain a few differences + and ways to write code compatible with both versions. The Python interface can be found in the source package, under python/recoll. @@ -4209,35 +4454,151 @@ or - The normal &RCL; installer installs the Python - API along with the main code. + As of &RCL; 1.19, the module can be compiled for + Python3. + + The normal &RCL; installer installs the Python2 + API along with the main code. The Python3 version must be + explicitely built and installed. When installing from a repository, and depending on the - distribution, the Python API can sometimes be found in a - separate package. + distribution, the Python API can sometimes be found in a + separate package. - + As an introduction, the following small sample will run a + query and list the title and url for each of the results. It would + work with &RCL; 1.19 and later. The + python/samples source directory contains + several examples of Python programming with &RCL;, exercising the + extension more completely, and especially its data extraction + features. - + + + + + + Interface elements + + A few elements in the interface are specific and and need + an explanation. + + + + > + ipath + + This data value (set as a field in the Doc + object) is stored, along with the URL, but not indexed by + &RCL;. Its contents are not interpreted by the index layer, and + its use is up to the application. For example, the &RCL; file + system indexer uses the ipath to store the + part of the document access path internal to (possibly + imbricated) container documents. ipath in + this case is a vector of access elements (e.g, the first part + could be a path inside a zip file to an archive member which + happens to be an mbox file, the second element would be the + message sequential number inside the mbox + etc.). url and ipath are + returned in every search result and define the access to the + original document. ipath is empty for + top-level document/files (e.g. a PDF document which is a + filesystem file). The &RCL; GUI knows about the structure of the + ipath values used by the filesystem indexer, + and uses it for such functions as opening the parent of a given + document. + + + + + udi + + An udi (unique document + identifier) identifies a document. Because of limitations inside + the index engine, it is restricted in length (to 200 bytes), + which is why a regular URI cannot be used. The structure and + contents of the udi is defined by the + application and opaque to the index engine. For example, the + internal file system indexer uses the complete document path + (file path + internal path), truncated to length, the suppressed + part being replaced by a hash value. The udi + is not explicit in the query interface (it is used "under the + hood" by the rclextract module), but it is + an explicit element of the update interface. + + + + parent_udi + + If this attribute is set on a document when + entering it in the index, it designates its physical container + document. In a multilevel hierarchy, this may not be the + immediate parent. parent_udi is optional, but + its use by an indexer may simplify index maintenance, as &RCL; + will automatically delete all children defined by + parent_udi == udi when the document designated + by udi is destroyed. e.g. if a + Zip archive contains entries which are + themselves containers, like mbox files, all + the subdocuments inside the Zip file (mbox, + messages, message attachments, etc.) would have the same + parent_udi, matching the + udi for the Zip file, and + all would be destroyed when the Zip file + (identified by its udi) is removed from the + index. The standard filesystem indexer uses + parent_udi. + + + + Stored and indexed fields + + The fields file inside + the &RCL; configuration defines which document fields are + either "indexed" (searchable), "stored" (retrievable with + search results), or both. + + + + + + + + + Python search interface + + Recoll package The recoll package contains two modules: The recoll module contains - functions and classes used to query (or update) the - index. + functions and classes used to query (or update) the + index. This section will only describe the query part, see + further for the update part. The rclextract module contains - functions and classes used to access document - data. + functions and classes used to access document + data. - + The recoll module - + Functions @@ -4245,73 +4606,72 @@ or connect(confdir=None, extra_dbs=None, writable = False) - The connect() function connects to + The connect() function connects to one or several &RCL; index(es) and returns - a Db object. + a Db object. - confdir may specify + confdir may specify a configuration directory. The usual defaults - apply. - extra_dbs is a list of - additional indexes (Xapian directories). - writable decides if + apply. + extra_dbs is a list of + additional indexes (Xapian directories). + writable decides if we can index new data through this - connection. + connection. - This call initializes the recoll module, and it should - always be performed before any other call or object creation. + This call initializes the recoll module, and it should + always be performed before any other call or object + creation. - - + Classes - + The Db class A Db object is created by a connect() call and holds a connection to a Recoll index. - Methods Db.close() - Closes the connection. You can't do anything + Closes the connection. You can't do anything with the Db object after - this. + this. - Db.query(), Db.cursor() These + Db.query(), Db.cursor() These aliases return a blank Query object - for this index. + for this index. Db.setAbstractParams(maxchars, - contextwords) Set the parameters used + contextwords) Set the parameters used to build snippets (sets of keywords in context text fragments). maxchars defines the maximum total size of the abstract. contextwords defines how many - terms are shown around the keyword. + terms are shown around the keyword. Db.termMatch(match_type, expr, field='', maxlen=-1, casesens=False, diacsens=False, lang='english') - Expand an expression against the + Expand an expression against the index term list. Performs the basic function from the GUI term explorer tool. match_type can be either of wildcard, regexp or stem. Returns a list of terms expanded from the input expression. - + @@ -4319,7 +4679,7 @@ or - + The Query class A Query object (equivalent to a @@ -4328,80 +4688,80 @@ or execute index searches. - Methods Query.sortby(fieldname, ascending=True) - Sort results + Sort results by fieldname, in ascending or descending order. Must be called before executing - the search. + the search. Query.execute(query_string, stemming=1, stemlang="english") - Starts a search + Starts a search for query_string, a &RCL; - search language string. + search language string. Query.executesd(SearchData) - Starts a search for the query defined by the - SearchData object. + Starts a search for the query defined by the + SearchData object. Query.fetchmany(size=query.arraysize) - Fetches + Fetches the next Doc objects in the current search results, and returns them as an array of the required size, which is by default the value of - the arraysize data member. + the arraysize data member. Query.fetchone() - Fetches the next Doc object - from the current search results. + Fetches the next Doc object + from the current search results. Query.close() - Closes the query. The object is unusable - after the call. + Closes the query. The object is unusable + after the call. Query.scroll(value, mode='relative') - Adjusts the position in the current result + Adjusts the position in the current result set. mode can be relative - or absolute. + or absolute. Query.getgroups() - Retrieves the expanded query terms as a list + Retrieves the expanded query terms as a list of pairs. Meaningful only after executexx In each pair, the first entry is a list of user terms (of size one for simple terms, or more for group and phrase clauses), the second a list of query terms as derived from the user terms and used in the Xapian - Query. + Query. Query.getxquery() - Return the Xapian query description as a Unicode string. - Meaningful only after executexx. + Return the Xapian query description as a + Unicode string. + Meaningful only after executexx. Query.highlight(text, ishtml = 0, methods = object) - Will insert <span "class=rclmatch">, + Will insert <span "class=rclmatch">, </span> tags around the match areas in the input text and return the modified text. ishtml can be set to indicate that the input text is HTML and @@ -4409,40 +4769,41 @@ or methods if set should be an object with methods startMatch(i) and endMatch() which will be called for each match and should return a begin and end - tag + tag Query.makedocabstract(doc, methods = object)) - Create a snippets abstract + Create a snippets abstract for doc (a Doc object) by selecting text around the match terms. If methods is set, will also perform highlighting. See the highlight method. - + Query.__iter__() and Query.next() - So that things like for doc in - query: will work. + So that things like for doc in + query: will work. - Data descriptors - Query.arraysize Default - number of records processed by fetchmany (r/w). + Query.arraysize + Default number of records processed by fetchmany + (r/w). - Query.rowcountNumber of - records returned by the last execute. - Query.rownumberNext index - to be fetched from results. Normally increments after - each fetchone() call, but can be set/reset before the - call to effect seeking (equivalent to - using scroll()). Starts at - 0. + Query.rowcountNumber + of records returned by the last + execute. + Query.rownumberNext index + to be fetched from results. Normally increments after + each fetchone() call, but can be set/reset before the + call to effect seeking (equivalent to + using scroll()). Starts at + 0. @@ -4450,7 +4811,7 @@ or - + The Doc class A Doc object contains index data @@ -4476,31 +4837,55 @@ or module for accessing document contents. - Methods get(key), [] operator - Retrieve the named doc attribute + + Retrieve the named doc + attribute. You can also use + getattr(doc, key) or + doc.key. - getbinurl()Retrieve - the URL in byte array format (no transcoding), for use as - parameter to a system call. + + + doc.key = value + + Set the the named doc + attribute. You can also use + setattr(doc, key, value). + + + getbinurl() + + Retrieve the URL in byte array format (no + transcoding), for use as parameter to a system + call. + + + + setbinurl(url) + + Set the URL in byte array format (no + transcoding). + + items() - Return a dictionary of doc object - keys/values + Return a dictionary of doc object + keys/values + keys() - list of doc object keys (attribute - names). + list of doc object keys (attribute + names). - + The SearchData class A SearchData object allows building @@ -4511,13 +4896,12 @@ or for now... - Methods addclause(type='and'|'or'|'excl'|'phrase'|'near'|'sub', qstring=string, slack=0, field='', stemming=1, subSearch=SearchData) - + @@ -4526,7 +4910,7 @@ or - + The rclextract module Index queries do not provide document content (only a @@ -4539,24 +4923,23 @@ or provides a single class which can be used to access the data content for result documents. - + Classes - + The Extractor class - Methods Extractor(doc) - An Extractor object is + An Extractor object is built from a Doc object, output - from a query. + from a query. Extractor.textextract(ipath) - Extract document defined + Extract document defined by ipath and return a Doc object. The doc.text field has the document text converted to either text/plain or @@ -4568,11 +4951,11 @@ extractor = recoll.Extractor(qdoc) doc = extractor.textextract(qdoc.ipath) # use doc.text, e.g. for previewing - + Extractor.idoctofile(ipath, targetmtype, outfile='') - Extracts document into an output file, + Extracts document into an output file, which can be given explicitly or will be created as a temporary file to be deleted by the caller. Typical use: @@ -4580,7 +4963,7 @@ qdoc = query.fetchone() extractor = recoll.Extractor(qdoc) filename = extractor.idoctofile(qdoc.ipath, qdoc.mimetype) - + @@ -4589,10 +4972,8 @@ filename = extractor.idoctofile(qdoc.ipath, qdoc.mimetype) - - - - Example code + + Search API usage example The following sample would query the index with a user language string. See the python/samples @@ -4627,17 +5008,189 @@ for i in range(nres): + - - Compatibility with the previous version - The following code fragments can be used to ensure that - code can run with both the old and the new API (as long as it - does not use the new abilities of the new API of - course). + + Creating Python external indexers - Adapting to the new package structure: - + The update API can be used to create an index from data which + is not accessible to the regular &RCL; indexer, or structured to + present difficulties to the &RCL; input handlers. + + An indexer created using this API will be have equivalent work + to do as the the Recoll file system indexer: look for modified + documents, extract their text, call the API for indexing it, take + care of purging the index out of data from documents which do not + exist in the document store any more. + + The data for such an external indexer should be stored in an + index separate from any used by the &RCL; internal file system + indexer. The reason is that the main document indexer purge pass + (removal of deleted documents) would also remove all the documents + belonging to the external indexer, as they were not seen during the + filesystem walk. The main indexer documents would also probably be a + problem for the external indexer own purge operation. + + While there would be ways to enable multiple foreign indexers + to cooperate on a single index, it is just simpler to use separate + ones, and use the multiple index access capabilities of the query + interface, if needed. + + There are two parts in the update interface: + + + Methods inside the recoll + module allow inserting data into the index, to make it accessible by + the normal query interface. + An interface based on scripts execution is defined + to allow either the GUI or the rclextract + module to access original document data for previewing or + editing. + + + + Python update interface + + The update methods are part of the + recoll module described above. The connect() + method is used with a writable=true parameter to + obtain a writable Db object. The following + Db object methods are then available. + + + + + addOrUpdate(udi, doc, parent_udi=None) + Add or update index data for a given document + The + + udi string must define a unique id for + the document. It is an opaque interface element and not + interpreted inside Recoll. doc is a + + + Doc object, created from the data to be + indexed (the main text should be in + doc.text). If + + parent_udi is set, this is a unique + identifier for the top-level container (e.g. for the + filesystem indexer, this would be the one which is an actual + file). + + + + + delete(udi) + Purge index from all data for + udi, and all documents (if any) which have a + matrching parent_udi. + + + + needUpdate(udi, sig) + Test if the index needs to be updated for the + document identified by udi. If this call is + to be used, the doc.sig field should contain + a signature value when calling + addOrUpdate(). The + needUpdate() call then compares its + parameter value with the stored sig for + udi. sig is an opaque + value, compared as a string. + The filesystem indexer uses a + concatenation of the decimal string values for file size and + update time, but a hash of the contents could also be + used. + As a side effect, if the return value is false (the index + is up to date), the call will set the existence flag for the + document (and any subdocument defined by its + parent_udi), so that a later + purge() call will preserve them). + The use of needUpdate() and + purge() is optional, and the indexer may use + another method for checking the need to reindex or to delete + stale entries. + + + + purge() + Delete all documents that were not touched + during the just finished indexing pass (since + open-for-write). These are the documents for the needUpdate() + call was not performed, indicating that they no longer exist in + the primary storage system. + + + + + + + + Query data access for external indexers (1.23) + + &RCL; has internal methods to access document data for its + internal (filesystem) indexer. An external indexer needs to provide + data access methods if it needs integration with the GUI + (e.g. preview function), or support for the + rclextract module. + + The index data and the access method are linked by the + rclbes (recoll backend storage) + Doc field. You should set this to a short string + value identifying your indexer (e.g. the filesystem indexer uses either + "FS" or an empty value, the Web history indexer uses "BGL"). + + The link is actually performed inside a + backends configuration file (stored in the + configuration directory). This defines commands to execute to + access data from the specified indexer. Example, for the mbox + indexing sample found in the Recoll source (which sets + rclbes="MBOX"): + [MBOX] +fetch = /path/to/recoll/src/python/samples/rclmbox.py fetch +makesig = path/to/recoll/src/python/samples/rclmbox.py makesig + + fetch and makesig + define two commands to execute to respectively retrieve the + document text and compute the document signature (the example + implementation uses the same script with different first parameters + to perform both operations). + + The scripts are called with three additional arguments: + udi, url, + ipath, stored with the document when it was + indexed, and may use any or all to perform the requested + operation. The caller expects the result data on + stdout. + + + + + External indexer samples + + The Recoll source tree has two samples of external indexers + in the src/python/samples directory. The more + interesting one is rclmbox.py which indexes a + directory containing mbox folder files. It + exercises most features in the update interface, and has a data + access interface. + + See the comments inside the file for more information. + + + + + Package compatibility with the previous version + + The following code fragments can be used to ensure that + code can run with both the old and the new API (as long as it + does not use the new abilities of the new API of + course). + + Adapting to the new package structure: + - + - Adapting to the change of nature of - the next Query - member. The same test can be used to choose to use - the scroll() method (new) or set - the next value (old). + Adapting to the change of nature of + the next Query + member. The same test can be used to choose to use + the scroll() method (new) or set + the next value (old). - + - + - - + + + @@ -4682,7 +5236,11 @@ except: FreeBSD ports, etc.), or from some type of "backports" repository providing versions newer than the standard ones, or found on the &RCL; WEB site in some - cases. + cases. The most up-to-date information about Recoll packages can + usually be found on the + + Recoll WEB site downloads + page There used to exist another form of binary install, as pre-compiled source trees, but these are just less convenient than @@ -4711,6 +5269,10 @@ except: Supporting packages + The &WIN; installation of &RCL; is self-contained, and + only needs Python 2.7 to be externally installed. &WIN; users can + skip this section. + &RCL; uses external applications to index some file types. You need to install them for the file types that you wish to have indexed (these are run-time optional dependencies. None is @@ -4872,11 +5434,28 @@ except: The shopping list: + The autoconf, + automake and libtool + triad. Only autoconf is needed up to &RCL; + 1.21. + C++ compiler. Up to &RCL; version 1.13.04, its absence can manifest itself by strange messages about a missing iconv_open. + bison command (for &RCL; 1.21 + and later). + + + xsltproc command. For building + the documentation (for &RCL; 1.21 + and later). This sometimes comes with the + libxslt package. And also the Docbook XML and + style sheet files. + + + Development files for Xapian core. @@ -4907,6 +5486,11 @@ except: zlib. + + Development files for Python + (or use --disable-python-module). + + You may also need @@ -5009,11 +5593,17 @@ except: Qt and X11. - will compile - &RCL; with position-dependant code. This is incompatible with - building the KIO or the Python - or PHP extensions, but might - yield very marginally faster code. + + will avoid building the user manual. This avoids having to + install the Docbook XML/XSL files and the TeX toolchain used for + translating the manual to PDF. + + (&RCL; versions up + to 1.21 only) will compile + &RCL; with position-dependant code. This is incompatible with + building the KIO or the Python + or PHP extensions, but might + yield very marginally faster code. Of course the usual autoconf configure @@ -5023,7 +5613,8 @@ except: - Normal procedure: + Normal procedure (for source extracted from a tar + distribution): cd recoll-xxx ./configure @@ -5031,15 +5622,12 @@ except: (practices usual hardship-repelling invocations) - - There is little auto-configuration. The - configure script will mainly link one of - the system-specific files in the mk - directory to mk/sysconf. If your system - is not known yet, it will tell you as much, and you may want - to manually copy and modify one of the existing files (the new - file name should be the output of uname - ). + When building from source cloned from the BitBucket repository, + you also need to install autoconf, + automake, and + libtool and you must execute sh + autogen.sh in the top source directory before running + configure. Building on Solaris @@ -5102,20 +5690,31 @@ except: the Index configuration dialog (Preferences menu). The GUI tool will try to respect your formatting and comments as much as possible, - so it is quite possible to use both ways. + so it is quite possible to use both approaches on the same + configuration. The most accurate documentation for the configuration parameters is given by comments inside the default files, and we will just give a general overview here. - By default, for each index, there are two sets of + For each index, there are at least two sets of configuration files. System-wide configuration files are kept in a directory named - like /usr/[local/]share/recoll/examples, + like /usr/share/recoll/examples, and define default values, shared by all indexes. For each index, a parallel set of files defines the customized parameters. + The default location of the customized configuration is the + .recoll + directory in your home. Most people will only use this + directory. + + This location can be changed, or others can be added with the + RECOLL_CONFDIR environment variable or the + option parameter to recoll and + recollindex. + In addition (as of &RCL; version 1.19.7), it is possible to specify two additional configuration directories which will be stacked before and after the user configuration @@ -5133,17 +5732,7 @@ except: future: do not use colon characters inside the directory paths. - The default location of the configuration is the - .recoll - directory in your home. Most people will only use this - directory. - - This location can be changed, or others can be added with the - RECOLL_CONFDIR environment variable or the - option parameter to recoll and - recollindex. - - If the .recoll directory does not + If the .recoll directory does not exist when recoll or recollindex are started, it will be created with a set of empty configuration files. @@ -5155,7 +5744,6 @@ except: default location, not if or RECOLL_CONFDIR were used (in the latter cases, you will have to create the directory). - All configuration files share the same format. For example, a short extract of the main configuration file might @@ -5181,6 +5769,9 @@ except: + Long lines can be broken by ending each incomplete part with + a backslash (\). + Depending on the type of configuration file, section definitions either separate groups of parameters or allow redefining some parameters for a directory sub-tree. They stay @@ -5195,15 +5786,31 @@ except: character (~) is expanded to the name of the user's home directory, as a shell would do. - White space is used for separation inside lists. - List elements with embedded spaces can be quoted using - double-quotes. + Some parameters are lists of strings. White space is used for + separation. List elements with embedded spaces can be quoted using + double-quotes. Double quotes inside these elements can be escaped + with a backslash. + + No value inside a configuration file can contain a newline + character. Long lines can be continued by escaping the + physical newline with backslash, even inside quoted strings. + +astringlist = "some string \ +with spaces" +thesame = "some string with spaces" + + + Parameters which are not part of string lists can't be + quoted, and leading and trailing space characters are + stripped before the value is used. + + + Encoding issues + Most of the configuration parameters are plain ASCII. Two + particular sets of values may cause encoding issues: + + - - Encoding issues - Most of the configuration parameters are plain ASCII. Two - particular sets of values may cause encoding issues: - File path parameters may contain non-ascii @@ -5291,841 +5898,10 @@ except: - - The main configuration file, recoll.conf + + - recoll.conf is the main - configuration file. It defines things like - what to index (top directories and things to ignore), and the - default character set to use for document types which do not - specify it internally. - - The default configuration will index your home - directory. If this is not appropriate, start - recoll to create a blank - configuration, click Cancel, and edit - the configuration file before restarting the command. This - will start the initial indexing, which may take some time. - - Most of the following parameters can be changed from the - Index Configuration menu in the - recoll interface. Some can only be set by - editing the configuration file. - - - Parameters affecting what documents we index: - - - - - topdirs - Specifies the list of directories or files to - index (recursively for directories). You can use symbolic links - as elements of this list. See the - followLinks option about following symbolic links - found under the top elements (not followed by default). - - - - skippedNames - - A space-separated list of wilcard patterns for - names of files or directories that should be completely - ignored. The list defined in the default file is: - -skippedNames = #* bin CVS Cache cache* caughtspam tmp .thumbnails .svn \ - *~ .beagle .git .hg .bzr loop.ps .xsession-errors \ - .recoll* xapiandb recollrc recoll.conf - - The list can be redefined at any sub-directory in the - indexed area. - The top-level directories are not affected by this - list (that is, a directory in topdirs - might match and would still be indexed). - The list in the default configuration does not - exclude hidden directories (names beginning with a - dot), which means that it may index quite a few things - that you do not want. On the other hand, email user - agents like thunderbird - usually store messages in hidden directories, and you - probably want this indexed. One possible solution is to - have .* in - skippedNames, and add things like - ~/.thunderbird or - ~/.evolution in - topdirs. - - Not even the file names are indexed for patterns - in this list. See the - noContentSuffixes variable for an alternative - approach which indexes the file names. - - - - noContentSuffixes - This is a list of file name endings (not - wildcard expressions, nor dot-delimited suffixes). Only the - names of matching files will be indexed (no attempt at MIME - type identification, no decompression, no content - indexing). This can be redefined for - subdirectories, and edited from the GUI. The default value is: - -noContentSuffixes = .md5 .map \ - .o .lib .dll .a .sys .exe .com \ - .mpp .mpt .vsd \ - .img .img.gz .img.bz2 .img.xz .image .image.gz .image.bz2 .image.xz \ - .dat .bak .rdf .log.gz .log .db .msf .pid \ - ,v ~ # - - - - - skippedPaths and - daemSkippedPaths - - A space-separated list of patterns for - paths of files or directories that should be skipped. - There is no default in the sample configuration file, - but the code always adds the configuration and database - directories in there. - skippedPaths is used both by - batch and real time - indexing. daemSkippedPaths can be - used to specify things that should be indexed at - startup, but not monitored. - Example of use for skipping text files only in a - specific directory: - -skippedPaths = ~/somedir/*.txt - - - - - - skippedPathsFnmPathname - The values in the - *skippedPaths variables are matched by - default with fnmatch(3), with the - FNM_PATHNAME flag. This means that '/' - characters must be matched explicitely. You can set - skippedPathsFnmPathname to 0 to disable - the use of FNM_PATHNAME (meaning that /*/dir3 will match - /dir1/dir2/dir3). - - - - - - zipSkippedNames - A space-separated list of patterns for - names of files or directories that should be ignored - inside zip archives. This is used directly by the zip - handler, and has a function similar to skippedNames, but - works independantly. Can be redefined for filesystem - subdirectories. For versions up to 1.19, you will need - to update the Zip handler and install a supplementary - Python module. The details are - described on - the &RCL; wiki. - - - - - followLinks - Specifies if the indexer should follow - symbolic links while walking the file tree. The default is - to ignore symbolic links to avoid multiple indexing of - linked files. No effort is made to avoid duplication when - this option is set to true. This option can be set - individually for each of the topdirs - members by using sections. It can not be changed below the - topdirs level. - - - - indexedmimetypes - &RCL; normally indexes any file which it - knows how to read. This list lets you restrict the indexed - MIME types to what you specify. If the variable is - unspecified or the list empty (the default), all supported - types are processed. Can be redefined for subdirectories. - - - - excludedmimetypes - This list lets you exclude some MIME types from - indexing. Can be redefined for subdirectories. - - - - compressedfilemaxkbs - Size limit for compressed (.gz or .bz2) - files. These need to be decompressed in a temporary - directory for identification, which can be very wasteful - if 'uninteresting' big compressed files are present. - Negative means no limit, 0 means no processing of any - compressed file. Defaults to -1. - - - - textfilemaxmbs - Maximum size for text files. Very big text - files are often uninteresting logs. Set to -1 to disable - (default 20MB). - - - - textfilepagekbs - If set to other than -1, text files will be - indexed as multiple documents of the given page size. This may - be useful if you do want to index very big text files as it - will both reduce memory usage at index time and help with - loading data to the preview window. A size of a few megabytes - would seem reasonable (default: 1MB). - - - - membermaxkbs - This defines the maximum size in kilobytes for - an archive member (zip, tar or rar at the moment). Bigger - entries will be skipped. - - - - indexallfilenames - &RCL; indexes file names in a special - section of the database to allow specific file names - searches using wild cards. This parameter decides if - file name indexing is performed only for files with MIME - types that would qualify them for full text indexing, or - for all files inside the selected subtrees, independently of - MIME type. - - - - usesystemfilecommand - Decide if we execute a system command - (file by default) - as a final step for determining the MIME type for a file - (the main procedure uses suffix associations as defined in - the mimemap file). This can be useful - for files with suffix-less names, but it will also cause - the indexing of many bogus "text" files. - - - - systemfilecommand - Command to use for mime for mime type - determination if usesystefilecommand is - set. Recent versions of xdg-mime sometimes - work better than file. - - - - processwebqueue - If this is set, process the directory where - Web browser plugins copy visited pages for indexing. - - - - webqueuedir - The path to the web indexing queue. This is - hard-coded in the Firefox plugin as - ~/.recollweb/ToIndex so there should be no - need to change it. - - - - - - - - Parameters affecting how we generate terms: - - Changing some of these parameters will imply a full - reindex. Also, when using multiple indexes, it may not make sense - to search indexes that don't share the values for these parameters, - because they usually affect both search and index operations. - - - - indexStripChars - Decide if we strip characters of diacritics and - convert them to lower-case before terms are indexed. If we - don't, searches sensitive to case and diacritics can be - performed, but the index will be bigger, and some marginal - weirdness may sometimes occur. The default is a stripped - index (indexStripChars = 1) for - now. When using multiple indexes for a search, - this parameter must be defined identically for - all. Changing the value implies an index reset. - - - - maxTermExpand - Maximum expansion count for a single term (e.g.: - when using wildcards). The default of 10000 is reasonable and - will avoid queries that appear frozen while the engine is - walking the term list. - - - - maxXapianClauses - Maximum number of elementary clauses we can add - to a single Xapian query. In some cases, the result of term - expansion can be multiplicative, and we want to avoid using - excessive memory. The default of 100 000 should be both - high enough in most cases and compatible with current - typical hardware configurations. - - - - nonumbers - If this set to true, no terms will be generated - for numbers. For example "123", "1.5e6", 192.168.1.4, would not - be indexed ("value123" would still be). Numbers are often quite - interesting to search for, and this should probably not be set - except for special situations, ie, scientific documents with huge - amounts of numbers in them. This can only be set for a whole - index, not for a subtree. - - - - nocjk - If this set to true, specific east asian - (Chinese Korean Japanese) characters/word splitting is - turned off. This will save a small amount of cpu if you - have no CJK documents. If your document base does include - such text but you are not interested in searching it, - setting nocjk may be a significant time - and space saver. - - - - cjkngramlen - This lets you adjust the size of n-grams - used for indexing CJK text. The default value of 2 is - probably appropriate in most cases. A value of 3 would - allow more precision and efficiency on longer words, but - the index will be approximately twice as large. - - - - indexstemminglanguages - A list of languages for which the stem - expansion databases will be built. See - recollindex - 1 or use the - recollindex command - for possible values. You can add a stem expansion database - for a different language by using - recollindex , but it - will be deleted during the next indexing. Only languages - listed in the configuration file are permanent. - - - - defaultcharset - The name of the character set used for - files that do not contain a character set definition (ie: - plain text files). This can be redefined for any - sub-directory. If it is not set at all, the character set - used is the one defined by the nls environment ( - LC_ALL, LC_CTYPE, - LANG), or iso8859-1 - if nothing is set. - - - - unac_except_trans - This is a list of characters, encoded in UTF-8, - which should be handled specially when converting text to - unaccented lowercase. For example, in Swedish, the letter - a with diaeresis has full alphabet - citizenship and should not be turned into an - a. Each element in the space-separated list - has the special character as first element and the translation - following. The handling of both the lowercase and upper-case - versions of a character should be specified, as appartenance to - the list will turn-off both standard accent and case - processing. Example for Swedish: - -unac_except_trans = åå Åå ää Ää öö Öö - - - Note that the translation is not limited to a single - character, you could very well have something like - üue in the list. - - The default value set for - unac_except_trans can't be listed here - because I have trouble with SGML and UTF-8, but it only - contains ligature decompositions: german ss, oe, ae, fi, - fl. - - This parameter can't be defined for subdirectories, it - is global, because there is no way to do otherwise when - querying. If you have document sets which would need different - values, you will have to index and query them separately. - - - - maildefcharset - This can be used to define the default - character set specifically for email messages which don't - specify it. This is mainly useful for readpst (libpst) dumps, - which are utf-8 but do not say so. - - - - localfields - This allows setting fields for all documents - under a given directory. Typical usage would be to set an - "rclaptg" field, to be used in mimeview to - select a specific viewer. If several fields are to be set, they - should be separated with a semi-colon (';') character, which there - is currently no way to escape. Also note the initial semi-colon. - Example: - localfields= ;rclaptg=gnus;other = val, then - select specifier viewer with - mimetype|tag=... in - mimeview. - - - - testmodifusemtime - If true, use mtime instead of default ctime to - determine if a file has been modified (in addition to - size, which is always used). Setting this can reduce - re-indexing on systems where extended attributes are - modified (by some other application), but not indexed - (changing extended attributes only affects - ctime). Notes: - - This may prevent detection of change - in some marginal file rename cases (the target would - need to have the same size and - mtime). - You should probably also set - noxattrfields to 1 in this case, except if you still - prefer to perform xattr indexing, for example if the - local file update pattern makes it of value (as in - general, there is a risk for pure extended attributes - updates without file modification to go - undetected). - - Perform a full index reset after changing the value of - this parameter. - - - - noxattrfields - Recoll versions 1.19 and later - automatically translate file extended attributes into - document fields (to be processed according to the - parameters from the fields - file). Setting this variable to 1 will disable the - behaviour. - - - - metadatacmds - This allows executing external commands - for each file and storing the output in &RCL; document - fields. This could be used for example to index - external tag data. The value is a list of field names - and commands, don't forget an initial - semi-colon. Example: - -[/some/area/of/the/fs] -metadatacmds = ; tags = tmsu tags %f; otherfield = somecmd -xx %f - - As a specially disgusting hack brought by - &RCL; 1.19.7, if a "field name" begins - with rclmulti, the data returned by - the command is expected to contain multiple field - values, in configuration file format. This allows - setting several fields by executing a single - command. Example: - -metadatacmds = ; rclmulti1 = somecmd %f - - If somecmd returns data in the form - of: - -field1 = value1 -field2 = value for field2 - - field1 - and field2 will be set inside the - document metadata. - - - - - - - - - - Parameters affecting where and how we store things: - - - dbdir - The name of the Xapian data directory. It - will be created if needed when the index is - initialized. If this is not an absolute path, it will be - interpreted relative to the configuration directory. The - value can have embedded spaces but starting or trailing - spaces will be trimmed. You cannot use quotes here. - - - - idxstatusfile - The name of the scratch file where the indexer - process updates its status. Default: - idxstatus.txt inside the configuration - directory. - - - - maxfsoccuppc - Maximum file system occupation before we - stop indexing. The value is a percentage, corresponding to - what the "Capacity" df output column shows. The default - value is 0, meaning no checking. - - - - mboxcachedir - The directory where mbox message offsets cache - files are held. This is normally $RECOLL_CONFDIR/mboxcache, but - it may be useful to share a directory between different - configurations. - - - - mboxcacheminmbs - The minimum mbox file size over which we - cache the offsets. There is really no sense in caching - offsets for small files. The default is 5 MB. - - - - webcachedir - This is only used by the web browser - plugin indexing code, and defines where the cache for visited - pages will live. Default: - $RECOLL_CONFDIR/webcache - - - - webcachemaxmbs - This is only used by the web browser - plugin indexing code, and defines the maximum size for the web - page cache. Default: 40 MB. Quite unfortunately, this is only - taken into account when creating the cache file. You need to - delete the file for a change to be taken into account. - - - - - idxflushmb - Threshold (megabytes of new text data) where we - flush from memory to disk index. Setting this can help control - memory usage. A value of 0 means no explicit flushing, letting - Xapian use its own default, which is flushing every 10000 (or - XAPIAN_FLUSH_THRESHOLD) documents, which gives little memory - usage control, as memory usage also depends on average document - size. The default value is 10, and it is probably a bit low. If - your system usually has free memory, you can try higher values - between 20 and 80. In my experience, values beyond 100 are - always counterproductive. - - - - - - - - Parameters affecting multithread processing - - The &RCL; indexing process - recollindex can use multiple threads to - speed up indexing on multiprocessor systems. The work done - to index files is divided in several stages and some of the - stages can be executed by multiple threads. The stages are: - - File system walking: this is always performed by - the main thread. - File conversion and data extraction. - Text processing (splitting, stemming, - etc.) - &XAP; index update. - - - You can also read a - - longer document about the transformation of - &RCL; indexing to multithreading. - - The threads configuration is controlled by two - configuration file parameters. - - - - thrQSizes - This variable defines the job input queues - configuration. There are three possible queues for - stages 2, 3 and 4, and this parameter should give the - queue depth for each stage (three integer values). If - a value of -1 is used for a given stage, no queue is - used, and the thread will go on performing the next - stage. In practise, deep queues have not been shown to - increase performance. A value of 0 for the first queue - tells &RCL; to perform autoconfiguration (no need for - the two other values in this case) - this is the - default configuration. - - - - thrTCounts - This defines the number of threads used - for each stage. If a value of -1 is used for one of - the queue depths, the corresponding thread count is - ignored. It makes no sense to use a value other than 1 - for the last stage because updating the &XAP; index is - necessarily single-threaded (and protected by a - mutex). - - - - - - The following example would use three queues (of depth 2), - and 4 threads for converting source documents, 2 for - processing their text, and one to update the index. This was - tested to be the best configuration on the test system - (quadri-processor with multiple disks). - -thrQSizes = 2 2 2 -thrTCounts = 4 2 1 - - - - The following example would use a single queue, and the - complete processing for each document would be performed by - a single thread (several documents will still be processed - in parallel in most cases). The threads will use mutual - exclusion when entering the index update stage. In practise - the performance would be close to the precedent case in - general, but worse in certain cases (e.g. a Zip archive - would be performed purely sequentially), so the previous - approach is preferred. YMMV... The 2 last values for - thrTCounts are ignored. - -thrQSizes = 2 -1 -1 -thrTCounts = 6 1 1 - - - - The following example would disable - multithreading. Indexing will be performed by a single - thread. - -thrQSizes = -1 -1 -1 - - - - - - - Miscellaneous parameters: - - - - autodiacsens - IF the index is not stripped, decide if we - automatically trigger diacritics sensitivity if the search - term has accented characters (not in - unac_except_trans). Else you need to use - the query language and the D modifier to - specify diacritics sensitivity. Default is no. - - - - autocasesens - IF the index is not stripped, decide if we - automatically trigger character case sensitivity if the - search term has upper-case characters in any but the first - position. Else you need to use the query language and the - C modifier to specify character-case - sensitivity. Default is yes. - - - - loglevel,daemloglevel - Verbosity level for recoll and - recollindex. A value of 4 lists quite a lot of - debug/information messages. 2 only lists errors. The - daemversion is specific to the indexing monitor - daemon. - - - - logfilename, - daemlogfilename - Where the messages should go. 'stderr' can - be used as a special value, and is the default. The - daemversion is specific to the indexing monitor - daemon. - - - - checkneedretryindexscript - This defines the name for a command - executed by recollindex when starting - indexing. If the exit status of the command is 0, - recollindex retries to index all files - which previously could not be indexed because of data - extraction errors. The default value is a script which - checks if any of the common bin - directories have changed (indicating that a helper program - may have been installed). - - - - mondelaypatterns - This allows specify wildcard path patterns - (processed with fnmatch(3) with 0 flag), to match files which - change too often and for which a delay should be observed before - re-indexing. This is a space-separated list, each entry being a - pattern and a time in seconds, separated by a colon. You can - use double quotes if a path entry contains white - space. Example: - -mondelaypatterns = *.log:20 "this one has spaces*:10" - - - - - monixinterval - Minimum interval (seconds) for processing the - indexing queue. The real time monitor does not process each - event when it comes in, but will wait this time for the queue - to accumulate to diminish overhead and in order to aggregate - multiple events to the same file. Default 30 S. - - - - monauxinterval - Period (in seconds) at which the real time - monitor will regenerate the auxiliary databases (spelling, - stemming) if needed. The default is one hour. - - - - monioniceclass, monioniceclassdata - These allow defining the - ionice class and data used by the - indexer (default class 3, no data). - - - - filtermaxseconds - Maximum handler execution time, after which it - is aborted. Some postscript programs just loop... - - - - filtermaxmbytes - &RCL; 1.20.7 and later. Maximum handler memory - utilisation. This uses setrlimit(RLIMIT_AS) on most systems - (total virtual memory space size limit). Some programs may start - with 500 MBytes of mapped shared libraries, so take this into - account when choosing a value. The default is a liberal - 2000MB. - - - - filtersdir - A directory to search for the external - input handler scripts used to index some types of files. The - value should not be changed, except if you want to modify - one of the default scripts. The value can be redefined for - any sub-directory. - - - - iconsdir - The name of the directory where - recoll result list icons are - stored. You can change this if you want different - images. - - - - idxabsmlen - &RCL; stores an abstract for each indexed - file inside the database. The text can come from an actual - 'abstract' section in the document or will just be the - beginning of the document. It is stored in the index so - that it can be displayed inside the result lists without - decoding the original - file. The idxabsmlen parameter defines - the size of the stored abstract. The default value is 250 bytes. - The search interface gives you the choice to display this - stored text or a synthetic abstract built by extracting - text around the search terms. If you always - prefer the synthetic abstract, you can reduce this value - and save a little space. - - - - - idxmetastoredlen - Maximum stored length for metadata - fields. This does not affect indexing (the whole field is - processed anyway), just the amount of data stored in the - index for the purpose of displaying fields inside result - lists or previews. The default value is 150 bytes which - may be too low if you have custom fields. - - - - aspellLanguage - Language definitions to use when creating - the aspell dictionary. The value must match a set of - aspell language definition files. You can type "aspell - config" to see where these are installed (look for - data-dir). The default if the variable is not set is to - use your desktop national language environment to guess - the value. - - - - noaspell - If this is set, the aspell dictionary - generation is turned off. Useful for cases where you don't - need the functionality or when it is unusable because - aspell crashes during dictionary generation. - - - - mhmboxquirks - This allows definining location-related quirks - for the mailbox handler. Currently only the - tbird flag is defined, and it should be set - for directories which hold - Thunderbird data, as their folder - format is weird. - - - - - - - The fields file @@ -6246,18 +6022,20 @@ x-my-tag = mailmytag mimemap specifies the file name extension to MIME type mappings. - For file names without an extension, or with an unknown - one, the system's file - command will be - executed to determine the MIME type (this can be switched off - inside the main configuration file). + For file names without an extension, or with an unknown one, + a system command (file , or + xdg-mime) will be executed to determine the MIME + type (this can be switched off, or the command changed inside the + main configuration file). The mappings can be specified on a per-subtree basis, which may be useful in some cases. Example: - gaim logs have a - .txt extension but + okular notes have a + .xml extension but should be handled specially, which is possible because they - are usually all located in one place. + are usually all located in one place. Example: + [~/.kde/share/apps/okular/docdata] +.xml = application/x-okular-notes The recoll_noindex mimemap variable has been moved to @@ -6491,7 +6269,7 @@ application/x-blobapp = exec rclblob section, you should choose an icon to be displayed for the files inside the result lists. Icons are normally 64x64 pixels PNG files which live in - /usr/[local/]share/recoll/images. + /usr/share/recoll/images. Under the [categories] section, you should add the MIME type where it makes sense @@ -6502,7 +6280,7 @@ application/x-blobapp = exec rclblob The rclblob handler should be an executable program or script which exists inside - /usr/[local/]share/recoll/filters. It + /usr/share/recoll/filters. It will be given a file name as argument and should output the text or html contents on the standard output. diff --git a/src/doc/user/webhelp/00README.txt b/src/doc/user/webhelp/00README.txt new file mode 100644 index 00000000..50f6609c --- /dev/null +++ b/src/doc/user/webhelp/00README.txt @@ -0,0 +1,13 @@ + +01-2016: +Webhelp is not packaged on Debian. To setup the build: + + - Get a docbook-xsl tar distribution (e.g. docbook-xsl-1.79.1.tar.bz2) + - Copy webhelp/xsl to the docbook dist: + /usr/share/xml/docbook/stylesheet/docbook-xsl/webhelp/xsl + - Possibly adjust + DOCBOOK_DIST := /usr/share/xml/docbook/stylesheet/docbook-xsl/ + in Makefile (if other system with docbook installed elsewhere). + +Did not try to get the search to work (needs lucene etc.) + diff --git a/src/doc/user/webhelp/Makefile b/src/doc/user/webhelp/Makefile new file mode 100644 index 00000000..50b210ef --- /dev/null +++ b/src/doc/user/webhelp/Makefile @@ -0,0 +1,114 @@ +# Configuration +# The name of the source DocBook xml file +INPUT_XML = ../usermanual.xml + +# The makefile assumes that you have a +# directory named images that contains +# your images. It copies this to the +# output directory +USER_IMAGES_PARENT_DIR=docsrc + +# Name of the desired output directory +# This will be created if it doesn't exist +OUTPUT_DIR = docs + +# A list of files to exclude from indexing +INDEXER_EXCLUDED_FILES = ix01.html + +# Profiling params. For more information on +# profiling (conditional text) and DocBook documents, see +# http://www.sagehill.net/docbookxsl/Profiling.html +PROFILE.ARCH = "" +PROFILE.AUDIENCE = "" +PROFILE.CONDITION = "" +PROFILE.CONFORMANCE = "" +PROFILE.LANG = "" +PROFILE.OS = "" +PROFILE.REVISION = "" +PROFILE.REVISIONFLAG = "" +PROFILE.ROLE = "" +PROFILE.SECURITY = "" +PROFILE.STATUS = "" +PROFILE.USERLEVEL = "" +PROFILE.VENDOR = "" +PROFILE.WORDSIZE = "" +PROFILE.ATTRIBUTE = "" +PROFILE.VALUE = "" + +# Use this variable to pass in other stringparams +# to the xsltproc pass that generates DocBook output. +# For example: +# OTHER_XSLTPROC_ARGS = --stringparam example.param "" +OTHER_XSLTPROC_ARGS = + +# Path to the DocBook Distribution that +# contains the xslts etc. +DOCBOOK_DIST := /usr/share/xml/docbook/stylesheet/docbook-xsl/ + +# ================================================= +# You probably don't need to change anything below +# unless you choose to add a validation step. +# ================================================ +DOCBOOK_EXTENSIONS_DIR = $(DOCBOOK_DIST)/extensions +INDEXER_JAR := $(DOCBOOK_EXTENSIONS_DIR)/webhelpindexer.jar +TAGSOUP_JAR := $(DOCBOOK_EXTENSIONS_DIR)/tagsoup-1.2.1.jar +LUCENE_ANALYZER_JAR := $(DOCBOOK_EXTENSIONS_DIR)/lucene-analyzers-3.0.0.jar +LUCENE_CORE_JAR := $(DOCBOOK_EXTENSIONS_DIR)/lucene-core-3.0.0.jar + +classpath := $(INDEXER_JAR):$(TAGSOUP_JAR):$(LUCENE_ANALYZER_JAR):$(LUCENE_CORE_JAR) + +all: copyfiles docs/index.html # index + +${OUTPUT_DIR}/favicon.ico: template/favicon.ico + cp -p template/favicon.ico ${OUTPUT_DIR}/favicon.ico +${OUTPUT_DIR}/common/main.js: template/common/main.js + cp -rp template/common ${OUTPUT_DIR} + +copyfiles: ${OUTPUT_DIR}/favicon.ico ${OUTPUT_DIR}/common/main.js + +# test ! -d $(USER_IMAGES_PARENT_DIR)/images/ || \ +# cp -rp $(USER_IMAGES_PARENT_DIR)/images ${OUTPUT_DIR}/images + +docs/index.html: ${INPUT_XML} + xsltproc --xinclude --output xincluded-profiled.xml \ + --stringparam profile.arch ${PROFILE.ARCH} \ + --stringparam profile.audience ${PROFILE.AUDIENCE} \ + --stringparam profile.condition ${PROFILE.CONDITION} \ + --stringparam profile.conformance ${PROFILE.CONFORMANCE} \ + --stringparam profile.lang ${PROFILE.LANG} \ + --stringparam profile.os ${PROFILE.OS} \ + --stringparam profile.revision ${PROFILE.REVISION} \ + --stringparam profile.revisionflag ${PROFILE.REVISIONFLAG} \ + --stringparam profile.role ${PROFILE.ROLE} \ + --stringparam profile.security ${PROFILE.SECURITY} \ + --stringparam profile.status ${PROFILE.STATUS} \ + --stringparam profile.userlevel ${PROFILE.USERLEVEL} \ + --stringparam profile.vendor ${PROFILE.VENDOR} \ + --stringparam profile.wordsize ${PROFILE.WORDSIZE} \ + --stringparam profile.attribute ${PROFILE.ATTRIBUTE} \ + --stringparam profile.value ${PROFILE.VALUE} \ + ${DOCBOOK_DIST}/profiling/profile.xsl \ + ${INPUT_XML} + xsltproc ${OTHER_XSLTPROC_ARGS} \ + ${DOCBOOK_DIST}/webhelp/xsl/webhelp.xsl \ + xincluded-profiled.xml + rm xincluded-profiled.xml + +index: + java \ + -DhtmlDir=$(OUTPUT_DIR) \ + -DindexerLanguage=en \ + -DhtmlExtension=html \ + -DdoStem=true \ + -DindexerExcludedFiles=$(INDEXER_EXCLUDED_FILES) \ + -Dorg.xml.sax.driver=org.ccil.cowan.tagsoup.Parser \ + -Djavax.xml.parsers.SAXParserFactory=org.ccil.cowan.tagsoup.jaxp.SAXFactoryImpl \ + -classpath $(classpath) \ + com.nexwave.nquindexer.IndexerMain + + cp -r template/search/* ${OUTPUT_DIR}/search + +clean: + $(RM) -r ${OUTPUT_DIR} + mkdir -p $(OUTPUT_DIR) + diff --git a/src/doc/user/webhelp/docs/favicon.ico b/src/doc/user/webhelp/docs/favicon.ico new file mode 100755 index 00000000..281a0368 Binary files /dev/null and b/src/doc/user/webhelp/docs/favicon.ico differ diff --git a/src/doc/user/webhelp/template/common/browserDetect.js b/src/doc/user/webhelp/template/common/browserDetect.js new file mode 100644 index 00000000..c6a2c73a --- /dev/null +++ b/src/doc/user/webhelp/template/common/browserDetect.js @@ -0,0 +1,116 @@ +var BrowserDetect = { + init: function () { + this.browser = this.searchString(this.dataBrowser) || "An unknown browser"; + this.version = this.searchVersion(navigator.userAgent) + || this.searchVersion(navigator.appVersion) + || "an unknown version"; + this.OS = this.searchString(this.dataOS) || "an unknown OS"; + }, + searchString: function (data) { + for (var i=0;ip{ font-weight: bold; } + +p.breadcrumbs { + display: inline; + margin-bottom: 0px; + margin-top: 33px; +} + +p.breadcrumbs a { + padding-right: 12px; + margin-right: 5px; + text-decoration: none; + color: #575757; + text-transform: uppercase; + font-size: 10px; +} + +p.breadcrumbs a:first-child {background: url(../images/breadcrumb-arrow-white.png) no-repeat right center;} + +p.breadcrumbs a:hover {text-decoration: underline;} + +#star ul.star { + LIST-STYLE: none; + MARGIN: 0; + PADDING: 0; + WIDTH: 85px; + /* was 100 */ + HEIGHT: 20px; + LEFT: 1px; + TOP: -5px; + POSITION: relative; + FLOAT: right; + BACKGROUND: url('../images/starsSmall.png') repeat-x 0 -25px; +} +#star li { + PADDING: 0; + MARGIN: 0; + FLOAT: right; + DISPLAY: block; + WIDTH: 85px; + /* was 100 */ + HEIGHT: 20px; + TEXT-DECORATION: none; + text-indent: -9000px; + Z-INDEX: 20; + POSITION: absolute; + PADDING: 0; +} +#star li.curr { + BACKGROUND: url('../images/starsSmall.png') left 25px; + FONT-SIZE: 1px; +} + +table.navLinks {margin-right: 20px;} + +table.navLinks td a { + text-decoration: none; + text-transform: uppercase; + color: black; + font-size: 11px; +} + +a.navLinkPrevious { + padding-left: 12px; + background: url(../images/previous-arrow.png) no-repeat left center; +} + +a.navLinkNext { + padding-right: 12px; + background: url(../images/next-arrow.png) no-repeat right center; +} + +a#showHideButton { + padding-left: 20px; + background: url(../images/sidebar.png) no-repeat left center; +} + + +.filetree li span a { color: #777; } + +#treediv { -webkit-box-shadow: #CCC 0px 1px 2px 0px inset; } + +.legal, .legal *{ + color: #555; + text-align: center; + padding-bottom: 10px; +} + +.internal { color : #0000CC;} + +.writeronly {color : red;} + +.remark, .remark .added, .remark .changed, .remark .deleted{ background: yellow;} + +tr th, tr th .internal, tr th .added, tr th .changed { + background: #00589E; + color: white; + font-weight: bold; + text-align: left; +} + +.statustext{ + position:fixed; + top:105px; + width: 0%; + height: 0%; + opacity: .3; + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -o-transform: rotate(90deg); + white-space: nowrap; + color: red; + font-weight: bold; + font-size: 2em; + margin-top: 30px; +} + +#toolbar { + width: 100%; + height: 33px; + position: fixed; + top: 93px; + z-index: 99; + left: 280px; + color: #333; + line-height: 28px; + padding-left: 10px; +} + +#toolbar-left { + position: relative; + left: 0px; +} + +body p.breadcrumbs { + margin: 0px; + padding: 0px; + line-height: 28px; +} + +/*body #content { + position: static; + margin-top: 126px; + top: 0px; +}*/ + +body.sidebar #toolbar{left: 0px;} + +body.sidebar #toolbar-left{left: 0px;} + +div#toolbar-left img {vertical-align: text-top;} + +div.note *, div.caution *, div.important *, div.tip *, div.warning * { + background: inherit !important; + color: inherit !important; + border: inherit !important; +} + +#content table thead, #content table th{ + background: gray; + color: white; + font-weight: bold; +} + +#content table caption{font-weight: bold;} + +#content table td, #content table {border: 1px solid black;} + +#content table td, #content table th { padding: 5px;} + +#content table {margin-bottom: 20px;} + +*[align = 'center']{ text-align: center;} + +#content .qandaset>table, #content .qandaset>table td, #content .calloutlist table, #content .calloutlist table td, #content .navfooter table, #content .navfooter table td { + border: 0px solid; +} + +#sidebar { display: none } + +@media print { + + body * { + visibility: hidden; + } + + #content, #content * { + visibility: visible; + } + + #sidebar, .navfooter { + display: none; + } + + #content { + margin: 0 0 0 0; + } + +} + diff --git a/src/doc/user/webhelp/template/common/images/admon/caution.png b/src/doc/user/webhelp/template/common/images/admon/caution.png new file mode 100644 index 00000000..5b7809ca Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/admon/caution.png differ diff --git a/src/doc/user/webhelp/template/common/images/admon/important.png b/src/doc/user/webhelp/template/common/images/admon/important.png new file mode 100644 index 00000000..12c90f60 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/admon/important.png differ diff --git a/src/doc/user/webhelp/template/common/images/admon/note.png b/src/doc/user/webhelp/template/common/images/admon/note.png new file mode 100644 index 00000000..d0c3c645 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/admon/note.png differ diff --git a/src/doc/user/webhelp/template/common/images/admon/tip.png b/src/doc/user/webhelp/template/common/images/admon/tip.png new file mode 100644 index 00000000..5c4aab3b Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/admon/tip.png differ diff --git a/src/doc/user/webhelp/template/common/images/admon/warning.png b/src/doc/user/webhelp/template/common/images/admon/warning.png new file mode 100644 index 00000000..1c33db8f Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/admon/warning.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/1.png b/src/doc/user/webhelp/template/common/images/callouts/1.png new file mode 100755 index 00000000..de682c62 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/1.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/10.png b/src/doc/user/webhelp/template/common/images/callouts/10.png new file mode 100755 index 00000000..96c6ce45 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/10.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/11.png b/src/doc/user/webhelp/template/common/images/callouts/11.png new file mode 100755 index 00000000..4550cb09 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/11.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/12.png b/src/doc/user/webhelp/template/common/images/callouts/12.png new file mode 100755 index 00000000..ef0f6350 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/12.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/13.png b/src/doc/user/webhelp/template/common/images/callouts/13.png new file mode 100755 index 00000000..b4878f1a Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/13.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/14.png b/src/doc/user/webhelp/template/common/images/callouts/14.png new file mode 100755 index 00000000..a222d7bf Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/14.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/15.png b/src/doc/user/webhelp/template/common/images/callouts/15.png new file mode 100755 index 00000000..f6a76d51 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/15.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/16.png b/src/doc/user/webhelp/template/common/images/callouts/16.png new file mode 100755 index 00000000..c5ef6359 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/16.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/17.png b/src/doc/user/webhelp/template/common/images/callouts/17.png new file mode 100755 index 00000000..85a2101e Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/17.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/18.png b/src/doc/user/webhelp/template/common/images/callouts/18.png new file mode 100755 index 00000000..7744d257 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/18.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/19.png b/src/doc/user/webhelp/template/common/images/callouts/19.png new file mode 100755 index 00000000..44bacf8a Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/19.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/2.png b/src/doc/user/webhelp/template/common/images/callouts/2.png new file mode 100755 index 00000000..24ec0f65 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/2.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/20.png b/src/doc/user/webhelp/template/common/images/callouts/20.png new file mode 100755 index 00000000..5e100fe5 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/20.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/21.png b/src/doc/user/webhelp/template/common/images/callouts/21.png new file mode 100755 index 00000000..c87e80a9 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/21.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/22.png b/src/doc/user/webhelp/template/common/images/callouts/22.png new file mode 100755 index 00000000..20593a4e Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/22.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/23.png b/src/doc/user/webhelp/template/common/images/callouts/23.png new file mode 100755 index 00000000..3909b9cd Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/23.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/24.png b/src/doc/user/webhelp/template/common/images/callouts/24.png new file mode 100755 index 00000000..963a9e77 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/24.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/25.png b/src/doc/user/webhelp/template/common/images/callouts/25.png new file mode 100755 index 00000000..458a9199 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/25.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/26.png b/src/doc/user/webhelp/template/common/images/callouts/26.png new file mode 100755 index 00000000..74b25073 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/26.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/27.png b/src/doc/user/webhelp/template/common/images/callouts/27.png new file mode 100755 index 00000000..611b8ce8 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/27.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/28.png b/src/doc/user/webhelp/template/common/images/callouts/28.png new file mode 100755 index 00000000..6aa21af6 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/28.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/29.png b/src/doc/user/webhelp/template/common/images/callouts/29.png new file mode 100755 index 00000000..6009b520 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/29.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/3.png b/src/doc/user/webhelp/template/common/images/callouts/3.png new file mode 100755 index 00000000..01cdff1d Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/3.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/30.png b/src/doc/user/webhelp/template/common/images/callouts/30.png new file mode 100755 index 00000000..c4dc404b Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/30.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/4.png b/src/doc/user/webhelp/template/common/images/callouts/4.png new file mode 100755 index 00000000..1e42fb37 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/4.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/5.png b/src/doc/user/webhelp/template/common/images/callouts/5.png new file mode 100755 index 00000000..635e7f81 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/5.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/6.png b/src/doc/user/webhelp/template/common/images/callouts/6.png new file mode 100755 index 00000000..521aedde Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/6.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/7.png b/src/doc/user/webhelp/template/common/images/callouts/7.png new file mode 100755 index 00000000..0d4b876a Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/7.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/8.png b/src/doc/user/webhelp/template/common/images/callouts/8.png new file mode 100755 index 00000000..50fa94d1 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/8.png differ diff --git a/src/doc/user/webhelp/template/common/images/callouts/9.png b/src/doc/user/webhelp/template/common/images/callouts/9.png new file mode 100755 index 00000000..7190d5a9 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/callouts/9.png differ diff --git a/src/doc/user/webhelp/template/common/images/header-bg.gif b/src/doc/user/webhelp/template/common/images/header-bg.gif new file mode 100644 index 00000000..f9efa280 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/header-bg.gif differ diff --git a/src/doc/user/webhelp/template/common/images/header-bg.png b/src/doc/user/webhelp/template/common/images/header-bg.png new file mode 100755 index 00000000..75202f9b Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/header-bg.png differ diff --git a/src/doc/user/webhelp/template/common/images/highlight-blue.gif b/src/doc/user/webhelp/template/common/images/highlight-blue.gif new file mode 100644 index 00000000..4fdabde6 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/highlight-blue.gif differ diff --git a/src/doc/user/webhelp/template/common/images/highlight-yellow.gif b/src/doc/user/webhelp/template/common/images/highlight-yellow.gif new file mode 100644 index 00000000..3e847e7e Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/highlight-yellow.gif differ diff --git a/src/doc/user/webhelp/template/common/images/loading.gif b/src/doc/user/webhelp/template/common/images/loading.gif new file mode 100644 index 00000000..085ccaec Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/loading.gif differ diff --git a/src/doc/user/webhelp/template/common/images/logo.png b/src/doc/user/webhelp/template/common/images/logo.png new file mode 100644 index 00000000..42e78483 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/logo.png differ diff --git a/src/doc/user/webhelp/template/common/images/next-arrow.png b/src/doc/user/webhelp/template/common/images/next-arrow.png new file mode 100644 index 00000000..db595f46 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/next-arrow.png differ diff --git a/src/doc/user/webhelp/template/common/images/previous-arrow.png b/src/doc/user/webhelp/template/common/images/previous-arrow.png new file mode 100644 index 00000000..347bc534 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/previous-arrow.png differ diff --git a/src/doc/user/webhelp/template/common/images/search-icon.png b/src/doc/user/webhelp/template/common/images/search-icon.png new file mode 100644 index 00000000..715f62d0 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/search-icon.png differ diff --git a/src/doc/user/webhelp/template/common/images/showHideTreeIcons.png b/src/doc/user/webhelp/template/common/images/showHideTreeIcons.png new file mode 100644 index 00000000..c1ec1f96 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/showHideTreeIcons.png differ diff --git a/src/doc/user/webhelp/template/common/images/sidebar.png b/src/doc/user/webhelp/template/common/images/sidebar.png new file mode 100644 index 00000000..54926718 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/sidebar.png differ diff --git a/src/doc/user/webhelp/template/common/images/starsSmall.png b/src/doc/user/webhelp/template/common/images/starsSmall.png new file mode 100644 index 00000000..490a27b9 Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/starsSmall.png differ diff --git a/src/doc/user/webhelp/template/common/images/toc-icon.png b/src/doc/user/webhelp/template/common/images/toc-icon.png new file mode 100644 index 00000000..40b34bce Binary files /dev/null and b/src/doc/user/webhelp/template/common/images/toc-icon.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/jquery-1.7.2.min.js b/src/doc/user/webhelp/template/common/jquery/jquery-1.7.2.min.js new file mode 100644 index 00000000..93adea19 --- /dev/null +++ b/src/doc/user/webhelp/template/common/jquery/jquery-1.7.2.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.7.2 jquery.com | jquery.org/license */ +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;e=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
a",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="
"+""+"
",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="
t
",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="
",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( +a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&j.push({elem:this,matches:d.slice(e)});for(k=0;k0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
","
"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f +.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(;d1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]===""&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/src/doc/user/webhelp/template/common/jquery/jquery-ui-1.8.2.custom.min.js b/src/doc/user/webhelp/template/common/jquery/jquery-ui-1.8.2.custom.min.js new file mode 100644 index 00000000..fec53e8e --- /dev/null +++ b/src/doc/user/webhelp/template/common/jquery/jquery-ui-1.8.2.custom.min.js @@ -0,0 +1,321 @@ +/*! + * jQuery UI 1.8.2 + * + * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI + */ +(function(c){c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.2",plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a=0)&&c(a).is(":focusable")}})}})(jQuery); +;/*! + * jQuery UI Widget 1.8.2 + * + * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Widget + */ +(function(b){var j=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add(this).each(function(){b(this).triggerHandler("remove")});return j.call(b(this),a,c)})};b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]=function(h){return!!b.data(h,a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend({},c.options);b[e][a].prototype= +b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)):d;if(e&&d.substring(0,1)==="_")return h;e?this.each(function(){var g=b.data(this,a),i=g&&b.isFunction(g[d])?g[d].apply(g,f):g;if(i!==g&&i!==undefined){h=i;return false}}):this.each(function(){var g= +b.data(this,a);if(g){d&&g.option(d);g._init()}else b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){this.element=b(c).data(this.widgetName,this);this.options=b.extend(true,{},this.options,b.metadata&&b.metadata.get(c)[this.widgetName],a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create(); +this._init()},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(a,c){var d=a,e=this;if(arguments.length===0)return b.extend({},e.options);if(typeof a==="string"){if(c===undefined)return this.options[a];d={};d[a]=c}b.each(d,function(f, +h){e._setOption(f,h)});return e},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",c);return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a= +b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery); +;/*! + * jQuery UI Mouse 1.8.2 + * + * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Mouse + * + * Depends: + * jquery.ui.widget.js + */ +(function(c){c.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var a=this;this.element.bind("mousedown."+this.widgetName,function(b){return a._mouseDown(b)}).bind("click."+this.widgetName,function(b){if(a._preventClickEvent){a._preventClickEvent=false;b.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName)},_mouseDown:function(a){a.originalEvent=a.originalEvent||{};if(!a.originalEvent.mouseHandled){this._mouseStarted&& +this._mouseUp(a);this._mouseDownEvent=a;var b=this,e=a.which==1,f=typeof this.options.cancel=="string"?c(a.target).parents().add(a.target).filter(this.options.cancel).length:false;if(!e||f||!this._mouseCapture(a))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){b.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a)){this._mouseStarted=this._mouseStart(a)!==false;if(!this._mouseStarted){a.preventDefault(); +return true}}this._mouseMoveDelegate=function(d){return b._mouseMove(d)};this._mouseUpDelegate=function(d){return b._mouseUp(d)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);c.browser.safari||a.preventDefault();return a.originalEvent.mouseHandled=true}},_mouseMove:function(a){if(c.browser.msie&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&& +this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=a.target==this._mouseDownEvent.target;this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX- +a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery); +;/* + * jQuery UI Position 1.8.2 + * + * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Position + */ +(function(c){c.ui=c.ui||{};var m=/left|center|right/,n=/top|center|bottom/,p=c.fn.position,q=c.fn.offset;c.fn.position=function(a){if(!a||!a.of)return p.apply(this,arguments);a=c.extend({},a);var b=c(a.of),d=(a.collision||"flip").split(" "),e=a.offset?a.offset.split(" "):[0,0],g,h,i;if(a.of.nodeType===9){g=b.width();h=b.height();i={top:0,left:0}}else if(a.of.scrollTo&&a.of.document){g=b.width();h=b.height();i={top:b.scrollTop(),left:b.scrollLeft()}}else if(a.of.preventDefault){a.at="left top";g=h= +0;i={top:a.of.pageY,left:a.of.pageX}}else{g=b.outerWidth();h=b.outerHeight();i=b.offset()}c.each(["my","at"],function(){var f=(a[this]||"").split(" ");if(f.length===1)f=m.test(f[0])?f.concat(["center"]):n.test(f[0])?["center"].concat(f):["center","center"];f[0]=m.test(f[0])?f[0]:"center";f[1]=n.test(f[1])?f[1]:"center";a[this]=f});if(d.length===1)d[1]=d[0];e[0]=parseInt(e[0],10)||0;if(e.length===1)e[1]=e[0];e[1]=parseInt(e[1],10)||0;if(a.at[0]==="right")i.left+=g;else if(a.at[0]==="center")i.left+= +g/2;if(a.at[1]==="bottom")i.top+=h;else if(a.at[1]==="center")i.top+=h/2;i.left+=e[0];i.top+=e[1];return this.each(function(){var f=c(this),k=f.outerWidth(),l=f.outerHeight(),j=c.extend({},i);if(a.my[0]==="right")j.left-=k;else if(a.my[0]==="center")j.left-=k/2;if(a.my[1]==="bottom")j.top-=l;else if(a.my[1]==="center")j.top-=l/2;j.left=parseInt(j.left);j.top=parseInt(j.top);c.each(["left","top"],function(o,r){c.ui.position[d[o]]&&c.ui.position[d[o]][r](j,{targetWidth:g,targetHeight:h,elemWidth:k, +elemHeight:l,offset:e,my:a.my,at:a.at})});c.fn.bgiframe&&f.bgiframe();f.offset(c.extend(j,{using:a.using}))})};c.ui.position={fit:{left:function(a,b){var d=c(window);b=a.left+b.elemWidth-d.width()-d.scrollLeft();a.left=b>0?a.left-b:Math.max(0,a.left)},top:function(a,b){var d=c(window);b=a.top+b.elemHeight-d.height()-d.scrollTop();a.top=b>0?a.top-b:Math.max(0,a.top)}},flip:{left:function(a,b){if(b.at[0]!=="center"){var d=c(window);d=a.left+b.elemWidth-d.width()-d.scrollLeft();var e=b.my[0]==="left"? +-b.elemWidth:b.my[0]==="right"?b.elemWidth:0,g=-2*b.offset[0];a.left+=a.left<0?e+b.targetWidth+g:d>0?e-b.targetWidth+g:0}},top:function(a,b){if(b.at[1]!=="center"){var d=c(window);d=a.top+b.elemHeight-d.height()-d.scrollTop();var e=b.my[1]==="top"?-b.elemHeight:b.my[1]==="bottom"?b.elemHeight:0,g=b.at[1]==="top"?b.targetHeight:-b.targetHeight,h=-2*b.offset[1];a.top+=a.top<0?e+b.targetHeight+h:d>0?e+g+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(a,b){if(/static/.test(c.curCSS(a,"position")))a.style.position= +"relative";var d=c(a),e=d.offset(),g=parseInt(c.curCSS(a,"top",true),10)||0,h=parseInt(c.curCSS(a,"left",true),10)||0;e={top:b.top-e.top+g,left:b.left-e.left+h};"using"in b?b.using.call(a,e):d.css(e)};c.fn.offset=function(a){var b=this[0];if(!b||!b.ownerDocument)return null;if(a)return this.each(function(){c.offset.setOffset(this,a)});return q.call(this)}}})(jQuery); +;/* + * jQuery UI Resizable 1.8.2 + * + * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Resizables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function(d){d.widget("ui.resizable",d.ui.mouse,{widgetEventPrefix:"resize",options:{alsoResize:false,animate:false,animateDuration:"slow",animateEasing:"swing",aspectRatio:false,autoHide:false,containment:false,ghost:false,grid:false,handles:"e,s,se",helper:false,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1E3},_create:function(){var b=this,a=this.options;this.element.addClass("ui-resizable");d.extend(this,{_aspectRatio:!!a.aspectRatio,aspectRatio:a.aspectRatio,originalElement:this.element, +_proportionallyResizeElements:[],_helper:a.helper||a.ghost||a.animate?a.helper||"ui-resizable-helper":null});if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)){/relative/.test(this.element.css("position"))&&d.browser.opera&&this.element.css({position:"relative",top:"auto",left:"auto"});this.element.wrap(d('
').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(), +top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle= +this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=a.handles||(!d(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne", +nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all")this.handles="n,e,s,w,se,sw,ne,nw";var c=this.handles.split(",");this.handles={};for(var e=0;e
');/sw|se|ne|nw/.test(g)&&f.css({zIndex:++a.zIndex});"se"==g&&f.addClass("ui-icon ui-icon-gripsmall-diagonal-se");this.handles[g]=".ui-resizable-"+g;this.element.append(f)}}this._renderAxis=function(h){h=h||this.element;for(var i in this.handles){if(this.handles[i].constructor== +String)this.handles[i]=d(this.handles[i],this.element).show();if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var j=d(this.handles[i],this.element),l=0;l=/sw|ne|nw|se|n|s/.test(i)?j.outerHeight():j.outerWidth();j=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join("");h.css(j,l);this._proportionallyResize()}d(this.handles[i])}};this._renderAxis(this.element);this._handles=d(".ui-resizable-handle",this.element).disableSelection(); +this._handles.mouseover(function(){if(!b.resizing){if(this.className)var h=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=h&&h[1]?h[1]:"se"}});if(a.autoHide){this._handles.hide();d(this.element).addClass("ui-resizable-autohide").hover(function(){d(this).removeClass("ui-resizable-autohide");b._handles.show()},function(){if(!b.resizing){d(this).addClass("ui-resizable-autohide");b._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();var b=function(c){d(c).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()}; +if(this.elementIsWrapper){b(this.element);var a=this.element;a.after(this.originalElement.css({position:a.css("position"),width:a.outerWidth(),height:a.outerHeight(),top:a.css("top"),left:a.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);b(this.originalElement);return this},_mouseCapture:function(b){var a=false;for(var c in this.handles)if(d(this.handles[c])[0]==b.target)a=true;return!this.options.disabled&&a},_mouseStart:function(b){var a=this.options,c=this.element.position(), +e=this.element;this.resizing=true;this.documentScroll={top:d(document).scrollTop(),left:d(document).scrollLeft()};if(e.is(".ui-draggable")||/absolute/.test(e.css("position")))e.css({position:"absolute",top:c.top,left:c.left});d.browser.opera&&/relative/.test(e.css("position"))&&e.css({position:"relative",top:"auto",left:"auto"});this._renderProxy();c=m(this.helper.css("left"));var g=m(this.helper.css("top"));if(a.containment){c+=d(a.containment).scrollLeft()||0;g+=d(a.containment).scrollTop()||0}this.offset= +this.helper.offset();this.position={left:c,top:g};this.size=this._helper?{width:e.outerWidth(),height:e.outerHeight()}:{width:e.width(),height:e.height()};this.originalSize=this._helper?{width:e.outerWidth(),height:e.outerHeight()}:{width:e.width(),height:e.height()};this.originalPosition={left:c,top:g};this.sizeDiff={width:e.outerWidth()-e.width(),height:e.outerHeight()-e.height()};this.originalMousePosition={left:b.pageX,top:b.pageY};this.aspectRatio=typeof a.aspectRatio=="number"?a.aspectRatio: +this.originalSize.width/this.originalSize.height||1;a=d(".ui-resizable-"+this.axis).css("cursor");d("body").css("cursor",a=="auto"?this.axis+"-resize":a);e.addClass("ui-resizable-resizing");this._propagate("start",b);return true},_mouseDrag:function(b){var a=this.helper,c=this.originalMousePosition,e=this._change[this.axis];if(!e)return false;c=e.apply(this,[b,b.pageX-c.left||0,b.pageY-c.top||0]);if(this._aspectRatio||b.shiftKey)c=this._updateRatio(c,b);c=this._respectSize(c,b);this._propagate("resize", +b);a.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize();this._updateCache(c);this._trigger("resize",b,this.ui());return false},_mouseStop:function(b){this.resizing=false;var a=this.options,c=this;if(this._helper){var e=this._proportionallyResizeElements,g=e.length&&/textarea/i.test(e[0].nodeName);e=g&&d.ui.hasScroll(e[0],"left")?0:c.sizeDiff.height; +g={width:c.size.width-(g?0:c.sizeDiff.width),height:c.size.height-e};e=parseInt(c.element.css("left"),10)+(c.position.left-c.originalPosition.left)||null;var f=parseInt(c.element.css("top"),10)+(c.position.top-c.originalPosition.top)||null;a.animate||this.element.css(d.extend(g,{top:f,left:e}));c.helper.height(c.size.height);c.helper.width(c.size.width);this._helper&&!a.animate&&this._proportionallyResize()}d("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop", +b);this._helper&&this.helper.remove();return false},_updateCache:function(b){this.offset=this.helper.offset();if(k(b.left))this.position.left=b.left;if(k(b.top))this.position.top=b.top;if(k(b.height))this.size.height=b.height;if(k(b.width))this.size.width=b.width},_updateRatio:function(b){var a=this.position,c=this.size,e=this.axis;if(b.height)b.width=c.height*this.aspectRatio;else if(b.width)b.height=c.width/this.aspectRatio;if(e=="sw"){b.left=a.left+(c.width-b.width);b.top=null}if(e=="nw"){b.top= +a.top+(c.height-b.height);b.left=a.left+(c.width-b.width)}return b},_respectSize:function(b){var a=this.options,c=this.axis,e=k(b.width)&&a.maxWidth&&a.maxWidthb.width,h=k(b.height)&&a.minHeight&&a.minHeight>b.height;if(f)b.width=a.minWidth;if(h)b.height=a.minHeight;if(e)b.width=a.maxWidth;if(g)b.height=a.maxHeight;var i=this.originalPosition.left+this.originalSize.width,j=this.position.top+this.size.height, +l=/sw|nw|w/.test(c);c=/nw|ne|n/.test(c);if(f&&l)b.left=i-a.minWidth;if(e&&l)b.left=i-a.maxWidth;if(h&&c)b.top=j-a.minHeight;if(g&&c)b.top=j-a.maxHeight;if((a=!b.width&&!b.height)&&!b.left&&b.top)b.top=null;else if(a&&!b.top&&b.left)b.left=null;return b},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var b=this.helper||this.element,a=0;a');var a=d.browser.msie&&d.browser.version<7,c=a?1:0;a=a?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+a,height:this.element.outerHeight()+a,position:"absolute",left:this.elementOffset.left-c+"px",top:this.elementOffset.top-c+"px",zIndex:++b.zIndex});this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(b,a){return{width:this.originalSize.width+ +a}},w:function(b,a){return{left:this.originalPosition.left+a,width:this.originalSize.width-a}},n:function(b,a,c){return{top:this.originalPosition.top+c,height:this.originalSize.height-c}},s:function(b,a,c){return{height:this.originalSize.height+c}},se:function(b,a,c){return d.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[b,a,c]))},sw:function(b,a,c){return d.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,a,c]))},ne:function(b,a,c){return d.extend(this._change.n.apply(this, +arguments),this._change.e.apply(this,[b,a,c]))},nw:function(b,a,c){return d.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,a,c]))}},_propagate:function(b,a){d.ui.plugin.call(this,b,[a,this.ui()]);b!="resize"&&this._trigger(b,a,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}});d.extend(d.ui.resizable, +{version:"1.8.2"});d.ui.plugin.add("resizable","alsoResize",{start:function(){var b=d(this).data("resizable").options,a=function(c){d(c).each(function(){d(this).data("resizable-alsoresize",{width:parseInt(d(this).width(),10),height:parseInt(d(this).height(),10),left:parseInt(d(this).css("left"),10),top:parseInt(d(this).css("top"),10)})})};if(typeof b.alsoResize=="object"&&!b.alsoResize.parentNode)if(b.alsoResize.length){b.alsoResize=b.alsoResize[0];a(b.alsoResize)}else d.each(b.alsoResize,function(c){a(c)}); +else a(b.alsoResize)},resize:function(){var b=d(this).data("resizable"),a=b.options,c=b.originalSize,e=b.originalPosition,g={height:b.size.height-c.height||0,width:b.size.width-c.width||0,top:b.position.top-e.top||0,left:b.position.left-e.left||0},f=function(h,i){d(h).each(function(){var j=d(this),l=d(this).data("resizable-alsoresize"),p={};d.each((i&&i.length?i:["width","height","top","left"])||["width","height","top","left"],function(n,o){if((n=(l[o]||0)+(g[o]||0))&&n>=0)p[o]=n||null});if(/relative/.test(j.css("position"))&& +d.browser.opera){b._revertToRelativePosition=true;j.css({position:"absolute",top:"auto",left:"auto"})}j.css(p)})};typeof a.alsoResize=="object"&&!a.alsoResize.nodeType?d.each(a.alsoResize,function(h,i){f(h,i)}):f(a.alsoResize)},stop:function(){var b=d(this).data("resizable");if(b._revertToRelativePosition&&d.browser.opera){b._revertToRelativePosition=false;el.css({position:"relative"})}d(this).removeData("resizable-alsoresize-start")}});d.ui.plugin.add("resizable","animate",{stop:function(b){var a= +d(this).data("resizable"),c=a.options,e=a._proportionallyResizeElements,g=e.length&&/textarea/i.test(e[0].nodeName),f=g&&d.ui.hasScroll(e[0],"left")?0:a.sizeDiff.height;g={width:a.size.width-(g?0:a.sizeDiff.width),height:a.size.height-f};f=parseInt(a.element.css("left"),10)+(a.position.left-a.originalPosition.left)||null;var h=parseInt(a.element.css("top"),10)+(a.position.top-a.originalPosition.top)||null;a.element.animate(d.extend(g,h&&f?{top:h,left:f}:{}),{duration:c.animateDuration,easing:c.animateEasing, +step:function(){var i={width:parseInt(a.element.css("width"),10),height:parseInt(a.element.css("height"),10),top:parseInt(a.element.css("top"),10),left:parseInt(a.element.css("left"),10)};e&&e.length&&d(e[0]).css({width:i.width,height:i.height});a._updateCache(i);a._propagate("resize",b)}})}});d.ui.plugin.add("resizable","containment",{start:function(){var b=d(this).data("resizable"),a=b.element,c=b.options.containment;if(a=c instanceof d?c.get(0):/parent/.test(c)?a.parent().get(0):c){b.containerElement= +d(a);if(/document/.test(c)||c==document){b.containerOffset={left:0,top:0};b.containerPosition={left:0,top:0};b.parentData={element:d(document),left:0,top:0,width:d(document).width(),height:d(document).height()||document.body.parentNode.scrollHeight}}else{var e=d(a),g=[];d(["Top","Right","Left","Bottom"]).each(function(i,j){g[i]=m(e.css("padding"+j))});b.containerOffset=e.offset();b.containerPosition=e.position();b.containerSize={height:e.innerHeight()-g[3],width:e.innerWidth()-g[1]};c=b.containerOffset; +var f=b.containerSize.height,h=b.containerSize.width;h=d.ui.hasScroll(a,"left")?a.scrollWidth:h;f=d.ui.hasScroll(a)?a.scrollHeight:f;b.parentData={element:a,left:c.left,top:c.top,width:h,height:f}}}},resize:function(b){var a=d(this).data("resizable"),c=a.options,e=a.containerOffset,g=a.position;b=a._aspectRatio||b.shiftKey;var f={top:0,left:0},h=a.containerElement;if(h[0]!=document&&/static/.test(h.css("position")))f=e;if(g.left<(a._helper?e.left:0)){a.size.width+=a._helper?a.position.left-e.left: +a.position.left-f.left;if(b)a.size.height=a.size.width/c.aspectRatio;a.position.left=c.helper?e.left:0}if(g.top<(a._helper?e.top:0)){a.size.height+=a._helper?a.position.top-e.top:a.position.top;if(b)a.size.width=a.size.height*c.aspectRatio;a.position.top=a._helper?e.top:0}a.offset.left=a.parentData.left+a.position.left;a.offset.top=a.parentData.top+a.position.top;c=Math.abs((a._helper?a.offset.left-f.left:a.offset.left-f.left)+a.sizeDiff.width);e=Math.abs((a._helper?a.offset.top-f.top:a.offset.top- +e.top)+a.sizeDiff.height);g=a.containerElement.get(0)==a.element.parent().get(0);f=/relative|absolute/.test(a.containerElement.css("position"));if(g&&f)c-=a.parentData.left;if(c+a.size.width>=a.parentData.width){a.size.width=a.parentData.width-c;if(b)a.size.height=a.size.width/a.aspectRatio}if(e+a.size.height>=a.parentData.height){a.size.height=a.parentData.height-e;if(b)a.size.width=a.size.height*a.aspectRatio}},stop:function(){var b=d(this).data("resizable"),a=b.options,c=b.containerOffset,e=b.containerPosition, +g=b.containerElement,f=d(b.helper),h=f.offset(),i=f.outerWidth()-b.sizeDiff.width;f=f.outerHeight()-b.sizeDiff.height;b._helper&&!a.animate&&/relative/.test(g.css("position"))&&d(this).css({left:h.left-e.left-c.left,width:i,height:f});b._helper&&!a.animate&&/static/.test(g.css("position"))&&d(this).css({left:h.left-e.left-c.left,width:i,height:f})}});d.ui.plugin.add("resizable","ghost",{start:function(){var b=d(this).data("resizable"),a=b.options,c=b.size;b.ghost=b.originalElement.clone();b.ghost.css({opacity:0.25, +display:"block",position:"relative",height:c.height,width:c.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof a.ghost=="string"?a.ghost:"");b.ghost.appendTo(b.helper)},resize:function(){var b=d(this).data("resizable");b.ghost&&b.ghost.css({position:"relative",height:b.size.height,width:b.size.width})},stop:function(){var b=d(this).data("resizable");b.ghost&&b.helper&&b.helper.get(0).removeChild(b.ghost.get(0))}});d.ui.plugin.add("resizable","grid",{resize:function(){var b= +d(this).data("resizable"),a=b.options,c=b.size,e=b.originalSize,g=b.originalPosition,f=b.axis;a.grid=typeof a.grid=="number"?[a.grid,a.grid]:a.grid;var h=Math.round((c.width-e.width)/(a.grid[0]||1))*(a.grid[0]||1);a=Math.round((c.height-e.height)/(a.grid[1]||1))*(a.grid[1]||1);if(/^(se|s|e)$/.test(f)){b.size.width=e.width+h;b.size.height=e.height+a}else if(/^(ne)$/.test(f)){b.size.width=e.width+h;b.size.height=e.height+a;b.position.top=g.top-a}else{if(/^(sw)$/.test(f)){b.size.width=e.width+h;b.size.height= +e.height+a}else{b.size.width=e.width+h;b.size.height=e.height+a;b.position.top=g.top-a}b.position.left=g.left-h}}});var m=function(b){return parseInt(b,10)||0},k=function(b){return!isNaN(parseInt(b,10))}})(jQuery); +; +/* + * jQuery UI Selectable 1.8.2 + * + * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Selectables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function(e){e.widget("ui.selectable",e.ui.mouse,{options:{appendTo:"body",autoRefresh:true,distance:0,filter:"*",tolerance:"touch"},_create:function(){var c=this;this.element.addClass("ui-selectable");this.dragged=false;var f;this.refresh=function(){f=e(c.options.filter,c.element[0]);f.each(function(){var d=e(this),b=d.offset();e.data(this,"selectable-item",{element:this,$element:d,left:b.left,top:b.top,right:b.left+d.outerWidth(),bottom:b.top+d.outerHeight(),startselected:false,selected:d.hasClass("ui-selected"), +selecting:d.hasClass("ui-selecting"),unselecting:d.hasClass("ui-unselecting")})})};this.refresh();this.selectees=f.addClass("ui-selectee");this._mouseInit();this.helper=e("
")},destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item");this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable");this._mouseDestroy();return this},_mouseStart:function(c){var f=this;this.opos=[c.pageX, +c.pageY];if(!this.options.disabled){var d=this.options;this.selectees=e(d.filter,this.element[0]);this._trigger("start",c);e(d.appendTo).append(this.helper);this.helper.css({"z-index":100,position:"absolute",left:c.clientX,top:c.clientY,width:0,height:0});d.autoRefresh&&this.refresh();this.selectees.filter(".ui-selected").each(function(){var b=e.data(this,"selectable-item");b.startselected=true;if(!c.metaKey){b.$element.removeClass("ui-selected");b.selected=false;b.$element.addClass("ui-unselecting"); +b.unselecting=true;f._trigger("unselecting",c,{unselecting:b.element})}});e(c.target).parents().andSelf().each(function(){var b=e.data(this,"selectable-item");if(b){var g=!c.metaKey||!b.$element.hasClass("ui-selected");b.$element.removeClass(g?"ui-unselecting":"ui-selected").addClass(g?"ui-selecting":"ui-unselecting");b.unselecting=!g;b.selecting=g;(b.selected=g)?f._trigger("selecting",c,{selecting:b.element}):f._trigger("unselecting",c,{unselecting:b.element});return false}})}},_mouseDrag:function(c){var f= +this;this.dragged=true;if(!this.options.disabled){var d=this.options,b=this.opos[0],g=this.opos[1],h=c.pageX,i=c.pageY;if(b>h){var j=h;h=b;b=j}if(g>i){j=i;i=g;g=j}this.helper.css({left:b,top:g,width:h-b,height:i-g});this.selectees.each(function(){var a=e.data(this,"selectable-item");if(!(!a||a.element==f.element[0])){var k=false;if(d.tolerance=="touch")k=!(a.left>h||a.righti||a.bottomb&&a.rightg&&a.bottom")},destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item");this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable");this._mouseDestroy();return this},_mouseStart:function(c){var f=this;this.opos=[c.pageX, +c.pageY];if(!this.options.disabled){var d=this.options;this.selectees=e(d.filter,this.element[0]);this._trigger("start",c);e(d.appendTo).append(this.helper);this.helper.css({"z-index":100,position:"absolute",left:c.clientX,top:c.clientY,width:0,height:0});d.autoRefresh&&this.refresh();this.selectees.filter(".ui-selected").each(function(){var b=e.data(this,"selectable-item");b.startselected=true;if(!c.metaKey){b.$element.removeClass("ui-selected");b.selected=false;b.$element.addClass("ui-unselecting"); +b.unselecting=true;f._trigger("unselecting",c,{unselecting:b.element})}});e(c.target).parents().andSelf().each(function(){var b=e.data(this,"selectable-item");if(b){var g=!c.metaKey||!b.$element.hasClass("ui-selected");b.$element.removeClass(g?"ui-unselecting":"ui-selected").addClass(g?"ui-selecting":"ui-unselecting");b.unselecting=!g;b.selecting=g;(b.selected=g)?f._trigger("selecting",c,{selecting:b.element}):f._trigger("unselecting",c,{unselecting:b.element});return false}})}},_mouseDrag:function(c){var f= +this;this.dragged=true;if(!this.options.disabled){var d=this.options,b=this.opos[0],g=this.opos[1],h=c.pageX,i=c.pageY;if(b>h){var j=h;h=b;b=j}if(g>i){j=i;i=g;g=j}this.helper.css({left:b,top:g,width:h-b,height:i-g});this.selectees.each(function(){var a=e.data(this,"selectable-item");if(!(!a||a.element==f.element[0])){var k=false;if(d.tolerance=="touch")k=!(a.left>h||a.righti||a.bottomb&&a.rightg&&a.bottom").addClass("ui-autocomplete").appendTo("body",c).mousedown(function(){setTimeout(function(){clearTimeout(a.closing)},13)}).menu({focus:function(d,b){b=b.item.data("item.autocomplete"); +false!==a._trigger("focus",null,{item:b})&&/^key/.test(d.originalEvent.type)&&a.element.val(b.value)},selected:function(d,b){b=b.item.data("item.autocomplete");false!==a._trigger("select",d,{item:b})&&a.element.val(b.value);a.close(d);d=a.previous;if(a.element[0]!==c.activeElement){a.element.focus();a.previous=d}a.selectedItem=b},blur:function(){a.menu.element.is(":visible")&&a.element.val(a.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu");e.fn.bgiframe&&this.menu.element.bgiframe()}, +destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup");this.menu.element.remove();e.Widget.prototype.destroy.call(this)},_setOption:function(a){e.Widget.prototype._setOption.apply(this,arguments);a==="source"&&this._initSource()},_initSource:function(){var a,c;if(e.isArray(this.options.source)){a=this.options.source;this.source=function(d,b){b(e.ui.autocomplete.filter(a,d.term))}}else if(typeof this.options.source=== +"string"){c=this.options.source;this.source=function(d,b){e.getJSON(c,d,b)}}else this.source=this.options.source},search:function(a,c){a=a!=null?a:this.element.val();if(a.length").data("item.autocomplete", +c).append(""+c.label+"").appendTo(a)},_move:function(a,c){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term);this.menu.deactivate()}else this.menu[a](c);else this.search(null,c)},widget:function(){return this.menu.element}});e.extend(e.ui.autocomplete,{escapeRegex:function(a){return a.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")},filter:function(a,c){var d=new RegExp(e.ui.autocomplete.escapeRegex(c), +"i");return e.grep(a,function(b){return d.test(b.label||b.value||b)})}})})(jQuery); +(function(e){e.widget("ui.menu",{_create:function(){var a=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(c){if(e(c.target).closest(".ui-menu-item a").length){c.preventDefault();a.select(c)}});this.refresh()},refresh:function(){var a=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex", +-1).mouseenter(function(c){a.activate(c,e(this).parent())}).mouseleave(function(){a.deactivate()})},activate:function(a,c){this.deactivate();if(this.hasScroll()){var d=c.offset().top-this.element.offset().top,b=this.element.attr("scrollTop"),f=this.element.height();if(d<0)this.element.attr("scrollTop",b+d);else d>f&&this.element.attr("scrollTop",b+d-f+c.height())}this.active=c.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",a,{item:c})},deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id"); +this._trigger("blur");this.active=null}},next:function(a){this.move("next",".ui-menu-item:first",a)},previous:function(a){this.move("prev",".ui-menu-item:last",a)},first:function(){return this.active&&!this.active.prev().length},last:function(){return this.active&&!this.active.next().length},move:function(a,c,d){if(this.active){a=this.active[a+"All"](".ui-menu-item").eq(0);a.length?this.activate(d,a):this.activate(d,this.element.children(c))}else this.activate(d,this.element.children(c))},nextPage:function(a){if(this.hasScroll())if(!this.active|| +this.last())this.activate(a,this.element.children(":first"));else{var c=this.active.offset().top,d=this.element.height(),b=this.element.children("li").filter(function(){var f=e(this).offset().top-c-d+e(this).height();return f<10&&f>-10});b.length||(b=this.element.children(":last"));this.activate(a,b)}else this.activate(a,this.element.children(!this.active||this.last()?":first":":last"))},previousPage:function(a){if(this.hasScroll())if(!this.active||this.first())this.activate(a,this.element.children(":last")); +else{var c=this.active.offset().top,d=this.element.height();result=this.element.children("li").filter(function(){var b=e(this).offset().top-c+d-e(this).height();return b<10&&b>-10});result.length||(result=this.element.children(":first"));this.activate(a,result)}else this.activate(a,this.element.children(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()").addClass("ui-button-text").html(this.options.label).appendTo(b.empty()).text(),d=this.options.icons,e=d.primary&&d.secondary;if(d.primary||d.secondary){b.addClass("ui-button-text-icon"+(e?"s":""));d.primary&&b.prepend("");d.secondary&&b.append("");if(!this.options.text){b.addClass(e?"ui-button-icons-only":"ui-button-icon-only").removeClass("ui-button-text-icons ui-button-text-icon"); +this.hasTitle||b.attr("title",c)}}else b.addClass("ui-button-text-only")}}});a.widget("ui.buttonset",{_create:function(){this.element.addClass("ui-buttonset");this._init()},_init:function(){this.refresh()},_setOption:function(b,c){b==="disabled"&&this.buttons.button("option",b,c);a.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){this.buttons=this.element.find(":button, :submit, :reset, :checkbox, :radio, a, :data(button)").filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass("ui-corner-left").end().filter(":last").addClass("ui-corner-right").end().end()}, +destroy:function(){this.element.removeClass("ui-buttonset");this.buttons.map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy");a.Widget.prototype.destroy.call(this)}})})(jQuery); +;/* + * jQuery UI Dialog 1.8.2 + * + * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Dialog + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.button.js + * jquery.ui.draggable.js + * jquery.ui.mouse.js + * jquery.ui.position.js + * jquery.ui.resizable.js + */ +(function(c){c.widget("ui.dialog",{options:{autoOpen:true,buttons:{},closeOnEscape:true,closeText:"close",dialogClass:"",draggable:true,hide:null,height:"auto",maxHeight:false,maxWidth:false,minHeight:150,minWidth:150,modal:false,position:"center",resizable:true,show:null,stack:true,title:"",width:300,zIndex:1E3},_create:function(){this.originalTitle=this.element.attr("title");var a=this,b=a.options,d=b.title||a.originalTitle||" ",e=c.ui.dialog.getTitleId(a.element),g=(a.uiDialog=c("
")).appendTo(document.body).hide().addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+ +b.dialogClass).css({zIndex:b.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(i){if(b.closeOnEscape&&i.keyCode&&i.keyCode===c.ui.keyCode.ESCAPE){a.close(i);i.preventDefault()}}).attr({role:"dialog","aria-labelledby":e}).mousedown(function(i){a.moveToTop(false,i)});a.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g);var f=(a.uiDialogTitlebar=c("
")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g), +h=c('').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){h.addClass("ui-state-hover")},function(){h.removeClass("ui-state-hover")}).focus(function(){h.addClass("ui-state-focus")}).blur(function(){h.removeClass("ui-state-focus")}).click(function(i){a.close(i);return false}).appendTo(f);(a.uiDialogTitlebarCloseText=c("")).addClass("ui-icon ui-icon-closethick").text(b.closeText).appendTo(h);c("").addClass("ui-dialog-title").attr("id", +e).html(d).prependTo(f);if(c.isFunction(b.beforeclose)&&!c.isFunction(b.beforeClose))b.beforeClose=b.beforeclose;f.find("*").add(f).disableSelection();b.draggable&&c.fn.draggable&&a._makeDraggable();b.resizable&&c.fn.resizable&&a._makeResizable();a._createButtons(b.buttons);a._isOpen=false;c.fn.bgiframe&&g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;a.overlay&&a.overlay.destroy();a.uiDialog.hide();a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"); +a.uiDialog.remove();a.originalTitle&&a.element.attr("title",a.originalTitle);return a},widget:function(){return this.uiDialog},close:function(a){var b=this,d;if(false!==b._trigger("beforeClose",a)){b.overlay&&b.overlay.destroy();b.uiDialog.unbind("keypress.ui-dialog");b._isOpen=false;if(b.options.hide)b.uiDialog.hide(b.options.hide,function(){b._trigger("close",a)});else{b.uiDialog.hide();b._trigger("close",a)}c.ui.dialog.overlay.resize();if(b.options.modal){d=0;c(".ui-dialog").each(function(){if(this!== +b.uiDialog[0])d=Math.max(d,c(this).css("z-index"))});c.ui.dialog.maxZ=d}return b}},isOpen:function(){return this._isOpen},moveToTop:function(a,b){var d=this,e=d.options;if(e.modal&&!a||!e.stack&&!e.modal)return d._trigger("focus",b);if(e.zIndex>c.ui.dialog.maxZ)c.ui.dialog.maxZ=e.zIndex;if(d.overlay){c.ui.dialog.maxZ+=1;d.overlay.$el.css("z-index",c.ui.dialog.overlay.maxZ=c.ui.dialog.maxZ)}a={scrollTop:d.element.attr("scrollTop"),scrollLeft:d.element.attr("scrollLeft")};c.ui.dialog.maxZ+=1;d.uiDialog.css("z-index", +c.ui.dialog.maxZ);d.element.attr(a);d._trigger("focus",b);return d},open:function(){if(!this._isOpen){var a=this,b=a.options,d=a.uiDialog;a.overlay=b.modal?new c.ui.dialog.overlay(a):null;d.next().length&&d.appendTo("body");a._size();a._position(b.position);d.show(b.show);a.moveToTop(true);b.modal&&d.bind("keypress.ui-dialog",function(e){if(e.keyCode===c.ui.keyCode.TAB){var g=c(":tabbable",this),f=g.filter(":first");g=g.filter(":last");if(e.target===g[0]&&!e.shiftKey){f.focus(1);return false}else if(e.target=== +f[0]&&e.shiftKey){g.focus(1);return false}}});c([]).add(d.find(".ui-dialog-content :tabbable:first")).add(d.find(".ui-dialog-buttonpane :tabbable:first")).add(d).filter(":first").focus();a._trigger("open");a._isOpen=true;return a}},_createButtons:function(a){var b=this,d=false,e=c("
").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix");b.uiDialog.find(".ui-dialog-buttonpane").remove();typeof a==="object"&&a!==null&&c.each(a,function(){return!(d=true)});if(d){c.each(a, +function(g,f){g=c('').text(g).click(function(){f.apply(b.element[0],arguments)}).appendTo(e);c.fn.button&&g.button()});e.appendTo(b.uiDialog)}},_makeDraggable:function(){function a(f){return{position:f.position,offset:f.offset}}var b=this,d=b.options,e=c(document),g;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(f,h){g=d.height==="auto"?"auto":c(this).height();c(this).height(c(this).height()).addClass("ui-dialog-dragging"); +b._trigger("dragStart",f,a(h))},drag:function(f,h){b._trigger("drag",f,a(h))},stop:function(f,h){d.position=[h.position.left-e.scrollLeft(),h.position.top-e.scrollTop()];c(this).removeClass("ui-dialog-dragging").height(g);b._trigger("dragStop",f,a(h));c.ui.dialog.overlay.resize()}})},_makeResizable:function(a){function b(f){return{originalPosition:f.originalPosition,originalSize:f.originalSize,position:f.position,size:f.size}}a=a===undefined?this.options.resizable:a;var d=this,e=d.options,g=d.uiDialog.css("position"); +a=typeof a==="string"?a:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:e.maxWidth,maxHeight:e.maxHeight,minWidth:e.minWidth,minHeight:d._minHeight(),handles:a,start:function(f,h){c(this).addClass("ui-dialog-resizing");d._trigger("resizeStart",f,b(h))},resize:function(f,h){d._trigger("resize",f,b(h))},stop:function(f,h){c(this).removeClass("ui-dialog-resizing");e.height=c(this).height();e.width=c(this).width();d._trigger("resizeStop", +f,b(h));c.ui.dialog.overlay.resize()}}).css("position",g).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight,a.height)},_position:function(a){var b=[],d=[0,0];a=a||c.ui.dialog.prototype.options.position;if(typeof a==="string"||typeof a==="object"&&"0"in a){b=a.split?a.split(" "):[a[0],a[1]];if(b.length===1)b[1]=b[0];c.each(["left","top"],function(e,g){if(+b[e]===b[e]){d[e]=b[e];b[e]= +g}})}else if(typeof a==="object"){if("left"in a){b[0]="left";d[0]=a.left}else if("right"in a){b[0]="right";d[0]=-a.right}if("top"in a){b[1]="top";d[1]=a.top}else if("bottom"in a){b[1]="bottom";d[1]=-a.bottom}}(a=this.uiDialog.is(":visible"))||this.uiDialog.show();this.uiDialog.css({top:0,left:0}).position({my:b.join(" "),at:b.join(" "),offset:d.join(" "),of:window,collision:"fit",using:function(e){var g=c(this).css(e).offset().top;g<0&&c(this).css("top",e.top-g)}});a||this.uiDialog.hide()},_setOption:function(a, +b){var d=this,e=d.uiDialog,g=e.is(":data(resizable)"),f=false;switch(a){case "beforeclose":a="beforeClose";break;case "buttons":d._createButtons(b);break;case "closeText":d.uiDialogTitlebarCloseText.text(""+b);break;case "dialogClass":e.removeClass(d.options.dialogClass).addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+b);break;case "disabled":b?e.addClass("ui-dialog-disabled"):e.removeClass("ui-dialog-disabled");break;case "draggable":b?d._makeDraggable():e.draggable("destroy");break; +case "height":f=true;break;case "maxHeight":g&&e.resizable("option","maxHeight",b);f=true;break;case "maxWidth":g&&e.resizable("option","maxWidth",b);f=true;break;case "minHeight":g&&e.resizable("option","minHeight",b);f=true;break;case "minWidth":g&&e.resizable("option","minWidth",b);f=true;break;case "position":d._position(b);break;case "resizable":g&&!b&&e.resizable("destroy");g&&typeof b==="string"&&e.resizable("option","handles",b);!g&&b!==false&&d._makeResizable(b);break;case "title":c(".ui-dialog-title", +d.uiDialogTitlebar).html(""+(b||" "));break;case "width":f=true;break}c.Widget.prototype._setOption.apply(d,arguments);f&&d._size()},_size:function(){var a=this.options,b;this.element.css({width:"auto",minHeight:0,height:0});b=this.uiDialog.css({height:"auto",width:a.width}).height();this.element.css(a.height==="auto"?{minHeight:Math.max(a.minHeight-b,0),height:"auto"}:{minHeight:0,height:Math.max(a.height-b,0)}).show();this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight", +this._minHeight())}});c.extend(c.ui.dialog,{version:"1.8.2",uuid:0,maxZ:0,getTitleId:function(a){a=a.attr("id");if(!a){this.uuid+=1;a=this.uuid}return"ui-dialog-title-"+a},overlay:function(a){this.$el=c.ui.dialog.overlay.create(a)}});c.extend(c.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:c.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(a){return a+".dialog-overlay"}).join(" "),create:function(a){if(this.instances.length===0){setTimeout(function(){c.ui.dialog.overlay.instances.length&& +c(document).bind(c.ui.dialog.overlay.events,function(d){return c(d.target).zIndex()>=c.ui.dialog.overlay.maxZ})},1);c(document).bind("keydown.dialog-overlay",function(d){if(a.options.closeOnEscape&&d.keyCode&&d.keyCode===c.ui.keyCode.ESCAPE){a.close(d);d.preventDefault()}});c(window).bind("resize.dialog-overlay",c.ui.dialog.overlay.resize)}var b=(this.oldInstances.pop()||c("
").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(),height:this.height()});c.fn.bgiframe&& +b.bgiframe();this.instances.push(b);return b},destroy:function(a){this.oldInstances.push(this.instances.splice(c.inArray(a,this.instances),1)[0]);this.instances.length===0&&c([document,window]).unbind(".dialog-overlay");a.remove();var b=0;c.each(this.instances,function(){b=Math.max(b,this.css("z-index"))});this.maxZ=b},height:function(){var a,b;if(c.browser.msie&&c.browser.version<7){a=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight);b=Math.max(document.documentElement.offsetHeight, +document.body.offsetHeight);return a",remove:null,select:null,show:null,spinner:"Loading…",tabTemplate:'
  • #{label}
  • '},_create:function(){this._tabify(true)},_setOption:function(c,e){if(c=="selected")this.options.collapsible&& +e==this.options.selected||this.select(e);else{this.options[c]=e;this._tabify()}},_tabId:function(c){return c.title&&c.title.replace(/\s/g,"_").replace(/[^A-Za-z0-9\-_:\.]/g,"")||this.options.idPrefix+s()},_sanitizeSelector:function(c){return c.replace(/:/g,"\\:")},_cookie:function(){var c=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+v());return d.cookie.apply(null,[c].concat(d.makeArray(arguments)))},_ui:function(c,e){return{tab:c,panel:e,index:this.anchors.index(c)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var c= +d(this);c.html(c.data("label.tabs")).removeData("label.tabs")})},_tabify:function(c){function e(g,f){g.css({display:""});!d.support.opacity&&f.opacity&&g[0].style.removeAttribute("filter")}this.list=this.element.find("ol,ul").eq(0);this.lis=d("li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return d("a",this)[0]});this.panels=d([]);var a=this,b=this.options,h=/^#.+/;this.anchors.each(function(g,f){var j=d(f).attr("href"),l=j.split("#")[0],p;if(l&&(l===location.toString().split("#")[0]|| +(p=d("base")[0])&&l===p.href)){j=f.hash;f.href=j}if(h.test(j))a.panels=a.panels.add(a._sanitizeSelector(j));else if(j!="#"){d.data(f,"href.tabs",j);d.data(f,"load.tabs",j.replace(/#.*$/,""));j=a._tabId(f);f.href="#"+j;f=d("#"+j);if(!f.length){f=d(b.panelTemplate).attr("id",j).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(a.panels[g-1]||a.list);f.data("destroy.tabs",true)}a.panels=a.panels.add(f)}else b.disabled.push(g)});if(c){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"); +this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(b.selected===undefined){location.hash&&this.anchors.each(function(g,f){if(f.hash==location.hash){b.selected=g;return false}});if(typeof b.selected!="number"&&b.cookie)b.selected=parseInt(a._cookie(),10);if(typeof b.selected!="number"&&this.lis.filter(".ui-tabs-selected").length)b.selected= +this.lis.index(this.lis.filter(".ui-tabs-selected"));b.selected=b.selected||(this.lis.length?0:-1)}else if(b.selected===null)b.selected=-1;b.selected=b.selected>=0&&this.anchors[b.selected]||b.selected<0?b.selected:0;b.disabled=d.unique(b.disabled.concat(d.map(this.lis.filter(".ui-state-disabled"),function(g){return a.lis.index(g)}))).sort();d.inArray(b.selected,b.disabled)!=-1&&b.disabled.splice(d.inArray(b.selected,b.disabled),1);this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active"); +if(b.selected>=0&&this.anchors.length){this.panels.eq(b.selected).removeClass("ui-tabs-hide");this.lis.eq(b.selected).addClass("ui-tabs-selected ui-state-active");a.element.queue("tabs",function(){a._trigger("show",null,a._ui(a.anchors[b.selected],a.panels[b.selected]))});this.load(b.selected)}d(window).bind("unload",function(){a.lis.add(a.anchors).unbind(".tabs");a.lis=a.anchors=a.panels=null})}else b.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"));this.element[b.collapsible?"addClass": +"removeClass"]("ui-tabs-collapsible");b.cookie&&this._cookie(b.selected,b.cookie);c=0;for(var i;i=this.lis[c];c++)d(i)[d.inArray(c,b.disabled)!=-1&&!d(i).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");b.cache===false&&this.anchors.removeData("cache.tabs");this.lis.add(this.anchors).unbind(".tabs");if(b.event!="mouseover"){var k=function(g,f){f.is(":not(.ui-state-disabled)")&&f.addClass("ui-state-"+g)},n=function(g,f){f.removeClass("ui-state-"+g)};this.lis.bind("mouseover.tabs", +function(){k("hover",d(this))});this.lis.bind("mouseout.tabs",function(){n("hover",d(this))});this.anchors.bind("focus.tabs",function(){k("focus",d(this).closest("li"))});this.anchors.bind("blur.tabs",function(){n("focus",d(this).closest("li"))})}var m,o;if(b.fx)if(d.isArray(b.fx)){m=b.fx[0];o=b.fx[1]}else m=o=b.fx;var q=o?function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.hide().removeClass("ui-tabs-hide").animate(o,o.duration||"normal",function(){e(f,o);a._trigger("show", +null,a._ui(g,f[0]))})}:function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.removeClass("ui-tabs-hide");a._trigger("show",null,a._ui(g,f[0]))},r=m?function(g,f){f.animate(m,m.duration||"normal",function(){a.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");e(f,m);a.element.dequeue("tabs")})}:function(g,f){a.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");a.element.dequeue("tabs")};this.anchors.bind(b.event+".tabs", +function(){var g=this,f=d(this).closest("li"),j=a.panels.filter(":not(.ui-tabs-hide)"),l=d(a._sanitizeSelector(this.hash));if(f.hasClass("ui-tabs-selected")&&!b.collapsible||f.hasClass("ui-state-disabled")||f.hasClass("ui-state-processing")||a._trigger("select",null,a._ui(this,l[0]))===false){this.blur();return false}b.selected=a.anchors.index(this);a.abort();if(b.collapsible)if(f.hasClass("ui-tabs-selected")){b.selected=-1;b.cookie&&a._cookie(b.selected,b.cookie);a.element.queue("tabs",function(){r(g, +j)}).dequeue("tabs");this.blur();return false}else if(!j.length){b.cookie&&a._cookie(b.selected,b.cookie);a.element.queue("tabs",function(){q(g,l)});a.load(a.anchors.index(this));this.blur();return false}b.cookie&&a._cookie(b.selected,b.cookie);if(l.length){j.length&&a.element.queue("tabs",function(){r(g,j)});a.element.queue("tabs",function(){q(g,l)});a.load(a.anchors.index(this))}else throw"jQuery UI Tabs: Mismatching fragment identifier.";d.browser.msie&&this.blur()});this.anchors.bind("click.tabs", +function(){return false})},destroy:function(){var c=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var e=d.data(this,"href.tabs");if(e)this.href=e;var a=d(this).unbind(".tabs");d.each(["href","load","cache"],function(b,h){a.removeData(h+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){d.data(this, +"destroy.tabs")?d(this).remove():d(this).removeClass("ui-state-default ui-corner-top ui-tabs-selected ui-state-active ui-state-hover ui-state-focus ui-state-disabled ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide")});c.cookie&&this._cookie(null,c.cookie);return this},add:function(c,e,a){if(a===undefined)a=this.anchors.length;var b=this,h=this.options;e=d(h.tabTemplate.replace(/#\{href\}/g,c).replace(/#\{label\}/g,e));c=!c.indexOf("#")?c.replace("#",""):this._tabId(d("a",e)[0]);e.addClass("ui-state-default ui-corner-top").data("destroy.tabs", +true);var i=d("#"+c);i.length||(i=d(h.panelTemplate).attr("id",c).data("destroy.tabs",true));i.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(a>=this.lis.length){e.appendTo(this.list);i.appendTo(this.list[0].parentNode)}else{e.insertBefore(this.lis[a]);i.insertBefore(this.panels[a])}h.disabled=d.map(h.disabled,function(k){return k>=a?++k:k});this._tabify();if(this.anchors.length==1){h.selected=0;e.addClass("ui-tabs-selected ui-state-active");i.removeClass("ui-tabs-hide"); +this.element.queue("tabs",function(){b._trigger("show",null,b._ui(b.anchors[0],b.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[a],this.panels[a]));return this},remove:function(c){var e=this.options,a=this.lis.eq(c).remove(),b=this.panels.eq(c).remove();if(a.hasClass("ui-tabs-selected")&&this.anchors.length>1)this.select(c+(c+1=c?--h:h});this._tabify();this._trigger("remove", +null,this._ui(a.find("a")[0],b[0]));return this},enable:function(c){var e=this.options;if(d.inArray(c,e.disabled)!=-1){this.lis.eq(c).removeClass("ui-state-disabled");e.disabled=d.grep(e.disabled,function(a){return a!=c});this._trigger("enable",null,this._ui(this.anchors[c],this.panels[c]));return this}},disable:function(c){var e=this.options;if(c!=e.selected){this.lis.eq(c).addClass("ui-state-disabled");e.disabled.push(c);e.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[c],this.panels[c]))}return this}, +select:function(c){if(typeof c=="string")c=this.anchors.index(this.anchors.filter("[href$="+c+"]"));else if(c===null)c=-1;if(c==-1&&this.options.collapsible)c=this.options.selected;this.anchors.eq(c).trigger(this.options.event+".tabs");return this},load:function(c){var e=this,a=this.options,b=this.anchors.eq(c)[0],h=d.data(b,"load.tabs");this.abort();if(!h||this.element.queue("tabs").length!==0&&d.data(b,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(c).addClass("ui-state-processing"); +if(a.spinner){var i=d("span",b);i.data("label.tabs",i.html()).html(a.spinner)}this.xhr=d.ajax(d.extend({},a.ajaxOptions,{url:h,success:function(k,n){d(e._sanitizeSelector(b.hash)).html(k);e._cleanup();a.cache&&d.data(b,"cache.tabs",true);e._trigger("load",null,e._ui(e.anchors[c],e.panels[c]));try{a.ajaxOptions.success(k,n)}catch(m){}},error:function(k,n){e._cleanup();e._trigger("load",null,e._ui(e.anchors[c],e.panels[c]));try{a.ajaxOptions.error(k,n,c,b)}catch(m){}}}));e.element.dequeue("tabs");return this}}, +abort:function(){this.element.queue([]);this.panels.stop(false,true);this.element.queue("tabs",this.element.queue("tabs").splice(-2,2));if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup();return this},url:function(c,e){this.anchors.eq(c).removeData("cache.tabs").data("load.tabs",e);return this},length:function(){return this.anchors.length}});d.extend(d.ui.tabs,{version:"1.8.2"});d.extend(d.ui.tabs.prototype,{rotation:null,rotate:function(c,e){var a=this,b=this.options,h=a._rotate||(a._rotate= +function(i){clearTimeout(a.rotation);a.rotation=setTimeout(function(){var k=b.selected;a.select(++k").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0});c.wrap(b);b=c.parent();if(c.css("position")=="static"){b.css({position:"relative"});c.css({position:"relative"})}else{f.extend(a,{position:c.css("position"),zIndex:c.css("z-index")});f.each(["top","left","bottom","right"],function(d,e){a[e]=c.css(e);if(isNaN(parseInt(a[e],10)))a[e]="auto"}); +c.css({position:"relative",top:0,left:0})}return b.css(a).show()},removeWrapper:function(c){if(c.parent().is(".ui-effects-wrapper"))return c.parent().replaceWith(c);return c},setTransition:function(c,a,b,d){d=d||{};f.each(a,function(e,g){unit=c.cssUnit(g);if(unit[0]>0)d[g]=unit[0]*b+unit[1]});return d}});f.fn.extend({effect:function(c){var a=j.apply(this,arguments);a={options:a[1],duration:a[2],callback:a[3]};var b=f.effects[c];return b&&!f.fx.off?b.call(this,a):this},_show:f.fn.show,show:function(c){if(!c|| +typeof c=="number"||f.fx.speeds[c])return this._show.apply(this,arguments);else{var a=j.apply(this,arguments);a[1].mode="show";return this.effect.apply(this,a)}},_hide:f.fn.hide,hide:function(c){if(!c||typeof c=="number"||f.fx.speeds[c])return this._hide.apply(this,arguments);else{var a=j.apply(this,arguments);a[1].mode="hide";return this.effect.apply(this,a)}},__toggle:f.fn.toggle,toggle:function(c){if(!c||typeof c=="number"||f.fx.speeds[c]||typeof c=="boolean"||f.isFunction(c))return this.__toggle.apply(this, +arguments);else{var a=j.apply(this,arguments);a[1].mode="toggle";return this.effect.apply(this,a)}},cssUnit:function(c){var a=this.css(c),b=[];f.each(["em","px","%","pt"],function(d,e){if(a.indexOf(e)>0)b=[parseFloat(a),e]});return b}});f.easing.jswing=f.easing.swing;f.extend(f.easing,{def:"easeOutQuad",swing:function(c,a,b,d,e){return f.easing[f.easing.def](c,a,b,d,e)},easeInQuad:function(c,a,b,d,e){return d*(a/=e)*a+b},easeOutQuad:function(c,a,b,d,e){return-d*(a/=e)*(a-2)+b},easeInOutQuad:function(c, +a,b,d,e){if((a/=e/2)<1)return d/2*a*a+b;return-d/2*(--a*(a-2)-1)+b},easeInCubic:function(c,a,b,d,e){return d*(a/=e)*a*a+b},easeOutCubic:function(c,a,b,d,e){return d*((a=a/e-1)*a*a+1)+b},easeInOutCubic:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a+b;return d/2*((a-=2)*a*a+2)+b},easeInQuart:function(c,a,b,d,e){return d*(a/=e)*a*a*a+b},easeOutQuart:function(c,a,b,d,e){return-d*((a=a/e-1)*a*a*a-1)+b},easeInOutQuart:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a+b;return-d/2*((a-=2)*a*a*a-2)+ +b},easeInQuint:function(c,a,b,d,e){return d*(a/=e)*a*a*a*a+b},easeOutQuint:function(c,a,b,d,e){return d*((a=a/e-1)*a*a*a*a+1)+b},easeInOutQuint:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a*a+b;return d/2*((a-=2)*a*a*a*a+2)+b},easeInSine:function(c,a,b,d,e){return-d*Math.cos(a/e*(Math.PI/2))+d+b},easeOutSine:function(c,a,b,d,e){return d*Math.sin(a/e*(Math.PI/2))+b},easeInOutSine:function(c,a,b,d,e){return-d/2*(Math.cos(Math.PI*a/e)-1)+b},easeInExpo:function(c,a,b,d,e){return a==0?b:d*Math.pow(2, +10*(a/e-1))+b},easeOutExpo:function(c,a,b,d,e){return a==e?b+d:d*(-Math.pow(2,-10*a/e)+1)+b},easeInOutExpo:function(c,a,b,d,e){if(a==0)return b;if(a==e)return b+d;if((a/=e/2)<1)return d/2*Math.pow(2,10*(a-1))+b;return d/2*(-Math.pow(2,-10*--a)+2)+b},easeInCirc:function(c,a,b,d,e){return-d*(Math.sqrt(1-(a/=e)*a)-1)+b},easeOutCirc:function(c,a,b,d,e){return d*Math.sqrt(1-(a=a/e-1)*a)+b},easeInOutCirc:function(c,a,b,d,e){if((a/=e/2)<1)return-d/2*(Math.sqrt(1-a*a)-1)+b;return d/2*(Math.sqrt(1-(a-=2)* +a)+1)+b},easeInElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(b(this).offset()).appendTo("body")});return!0},_mouseStart:function(a){var c=this.options;this.helper=this._createHelper(a);this._cacheHelperProportions();b.ui.ddmanager&&(b.ui.ddmanager.current=this); +this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};b.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this.position=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY= +a.pageY;c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt);c.containment&&this._setContainment();if(!1===this._trigger("start",a))return this._clear(),!1;this._cacheHelperProportions();b.ui.ddmanager&&!c.dropBehaviour&&b.ui.ddmanager.prepareOffsets(this,a);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(a,!0);b.ui.ddmanager&&b.ui.ddmanager.dragStart(this,a);return!0},_mouseDrag:function(a,c){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute"); +if(!c){var d=this._uiHash();if(!1===this._trigger("drag",a,d))return this._mouseUp({}),!1;this.position=d.position}if(!this.options.axis||"y"!=this.options.axis)this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||"x"!=this.options.axis)this.helper[0].style.top=this.position.top+"px";b.ui.ddmanager&&b.ui.ddmanager.drag(this,a);return!1},_mouseStop:function(a){var c=!1;b.ui.ddmanager&&!this.options.dropBehaviour&&(c=b.ui.ddmanager.drop(this,a));this.dropped&&(c=this.dropped,this.dropped= +!1);if((!this.element[0]||!this.element[0].parentNode)&&"original"==this.options.helper)return!1;if("invalid"==this.options.revert&&!c||"valid"==this.options.revert&&c||!0===this.options.revert||b.isFunction(this.options.revert)&&this.options.revert.call(this.element,c)){var d=this;b(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){d._trigger("stop",a)!==false&&d._clear()})}else!1!==this._trigger("stop",a)&&this._clear();return!1},_mouseUp:function(a){!0=== +this.options.iframeFix&&b("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)});b.ui.ddmanager&&b.ui.ddmanager.dragStop(this,a);return b.ui.mouse.prototype._mouseUp.call(this,a)},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(a){var c=!this.options.handle||!b(this.options.handle,this.element).length?!0:!1;b(this.options.handle,this.element).find("*").andSelf().each(function(){this==a.target&&(c= +!0)});return c},_createHelper:function(a){var c=this.options,a=b.isFunction(c.helper)?b(c.helper.apply(this.element[0],[a])):"clone"==c.helper?this.element.clone().removeAttr("id"):this.element;a.parents("body").length||a.appendTo("parent"==c.appendTo?this.element[0].parentNode:c.appendTo);a[0]!=this.element[0]&&!/(fixed|absolute)/.test(a.css("position"))&&a.css("position","absolute");return a},_adjustOffsetFromHelper:function(a){"string"==typeof a&&(a=a.split(" "));b.isArray(a)&&(a={left:+a[0],top:+a[1]|| +0});"left"in a&&(this.offset.click.left=a.left+this.margins.left);"right"in a&&(this.offset.click.left=this.helperProportions.width-a.right+this.margins.left);"top"in a&&(this.offset.click.top=a.top+this.margins.top);"bottom"in a&&(this.offset.click.top=this.helperProportions.height-a.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var a=this.offsetParent.offset();"absolute"==this.cssPosition&&(this.scrollParent[0]!=document&&b.ui.contains(this.scrollParent[0], +this.offsetParent[0]))&&(a.left+=this.scrollParent.scrollLeft(),a.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&"html"==this.offsetParent[0].tagName.toLowerCase()&&b.browser.msie)a={top:0,left:0};return{top:a.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:a.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"==this.cssPosition){var b=this.element.position();return{top:b.top- +(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:b.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(), +height:this.helper.outerHeight()}},_setContainment:function(){var a=this.options;"parent"==a.containment&&(a.containment=this.helper[0].parentNode);if("document"==a.containment||"window"==a.containment)this.containment=["document"==a.containment?0:b(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,"document"==a.containment?0:b(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,("document"==a.containment?0:b(window).scrollLeft())+b("document"==a.containment?document: +window).width()-this.helperProportions.width-this.margins.left,("document"==a.containment?0:b(window).scrollTop())+(b("document"==a.containment?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(a.containment)&&a.containment.constructor!=Array){var a=b(a.containment),c=a[0];if(c){a.offset();var d="hidden"!=b(c).css("overflow");this.containment=[(parseInt(b(c).css("borderLeftWidth"),10)||0)+(parseInt(b(c).css("paddingLeft"), +10)||0),(parseInt(b(c).css("borderTopWidth"),10)||0)+(parseInt(b(c).css("paddingTop"),10)||0),(d?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(b(c).css("borderLeftWidth"),10)||0)-(parseInt(b(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(d?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(b(c).css("borderTopWidth"),10)||0)-(parseInt(b(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom]; +this.relative_container=a}}else a.containment.constructor==Array&&(this.containment=a.containment)},_convertPositionTo:function(a,c){c||(c=this.position);var d="absolute"==a?1:-1,g="absolute"==this.cssPosition&&!(this.scrollParent[0]!=document&&b.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,h=/(html|body)/i.test(g[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(b.browser.safari&&526>b.browser.version&&"fixed"==this.cssPosition? +0:("fixed"==this.cssPosition?-this.scrollParent.scrollTop():h?0:g.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(b.browser.safari&&526>b.browser.version&&"fixed"==this.cssPosition?0:("fixed"==this.cssPosition?-this.scrollParent.scrollLeft():h?0:g.scrollLeft())*d)}},_generatePosition:function(a){var c=this.options,d="absolute"==this.cssPosition&&!(this.scrollParent[0]!=document&&b.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent, +g=/(html|body)/i.test(d[0].tagName),h=a.pageX,e=a.pageY;if(this.originalPosition){var f;this.containment&&(this.relative_container?(f=this.relative_container.offset(),f=[this.containment[0]+f.left,this.containment[1]+f.top,this.containment[2]+f.left,this.containment[3]+f.top]):f=this.containment,a.pageX-this.offset.click.leftf[2]&&(h=f[2]+this.offset.click.left), +a.pageY-this.offset.click.top>f[3]&&(e=f[3]+this.offset.click.top));c.grid&&(e=c.grid[1]?this.originalPageY+Math.round((e-this.originalPageY)/c.grid[1])*c.grid[1]:this.originalPageY,e=f?!(e-this.offset.click.topf[3])?e:!(e-this.offset.click.topf[2])?h:!(h-this.offset.click.left< +f[0])?h-c.grid[0]:h+c.grid[0]:h)}return{top:e-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(b.browser.safari&&526>b.browser.version&&"fixed"==this.cssPosition?0:"fixed"==this.cssPosition?-this.scrollParent.scrollTop():g?0:d.scrollTop()),left:h-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(b.browser.safari&&526>b.browser.version&&"fixed"==this.cssPosition?0:"fixed"==this.cssPosition?-this.scrollParent.scrollLeft():g?0:d.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"); +this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove();this.helper=null;this.cancelHelperRemoval=!1},_trigger:function(a,c,d){d=d||this._uiHash();b.ui.plugin.call(this,a,[c,d]);"drag"==a&&(this.positionAbs=this._convertPositionTo("absolute"));return b.Widget.prototype._trigger.call(this,a,c,d)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}});b.extend(b.ui.draggable,{version:"1.8.14"}); +b.ui.plugin.add("draggable","connectToSortable",{start:function(a,c){var d=b(this).data("draggable"),g=d.options,h=b.extend({},c,{item:d.element});d.sortables=[];b(g.connectToSortable).each(function(){var c=b.data(this,"sortable");c&&!c.options.disabled&&(d.sortables.push({instance:c,shouldRevert:c.options.revert}),c.refreshPositions(),c._trigger("activate",a,h))})},stop:function(a,c){var d=b(this).data("draggable"),g=b.extend({},c,{item:d.element});b.each(d.sortables,function(){this.instance.isOver? +(this.instance.isOver=0,d.cancelHelperRemoval=!0,this.instance.cancelHelperRemoval=!1,this.shouldRevert&&(this.instance.options.revert=!0),this.instance._mouseStop(a),this.instance.options.helper=this.instance.options._helper,"original"==d.options.helper&&this.instance.currentItem.css({top:"auto",left:"auto"})):(this.instance.cancelHelperRemoval=!1,this.instance._trigger("deactivate",a,g))})},drag:function(a,c){var d=b(this).data("draggable"),g=this;b.each(d.sortables,function(){this.instance.positionAbs= +d.positionAbs;this.instance.helperProportions=d.helperProportions;this.instance.offset.click=d.offset.click;this.instance._intersectsWith(this.instance.containerCache)?(this.instance.isOver||(this.instance.isOver=1,this.instance.currentItem=b(g).clone().removeAttr("id").appendTo(this.instance.element).data("sortable-item",!0),this.instance.options._helper=this.instance.options.helper,this.instance.options.helper=function(){return c.helper[0]},a.target=this.instance.currentItem[0],this.instance._mouseCapture(a, +!0),this.instance._mouseStart(a,!0,!0),this.instance.offset.click.top=d.offset.click.top,this.instance.offset.click.left=d.offset.click.left,this.instance.offset.parent.left-=d.offset.parent.left-this.instance.offset.parent.left,this.instance.offset.parent.top-=d.offset.parent.top-this.instance.offset.parent.top,d._trigger("toSortable",a),d.dropped=this.instance.element,d.currentItem=d.element,this.instance.fromOutside=d),this.instance.currentItem&&this.instance._mouseDrag(a)):this.instance.isOver&& +(this.instance.isOver=0,this.instance.cancelHelperRemoval=!0,this.instance.options.revert=!1,this.instance._trigger("out",a,this.instance._uiHash(this.instance)),this.instance._mouseStop(a,!0),this.instance.options.helper=this.instance.options._helper,this.instance.currentItem.remove(),this.instance.placeholder&&this.instance.placeholder.remove(),d._trigger("fromSortable",a),d.dropped=!1)})}});b.ui.plugin.add("draggable","cursor",{start:function(){var a=b("body"),c=b(this).data("draggable").options; +a.css("cursor")&&(c._cursor=a.css("cursor"));a.css("cursor",c.cursor)},stop:function(){var a=b(this).data("draggable").options;a._cursor&&b("body").css("cursor",a._cursor)}});b.ui.plugin.add("draggable","opacity",{start:function(a,c){var d=b(c.helper),g=b(this).data("draggable").options;d.css("opacity")&&(g._opacity=d.css("opacity"));d.css("opacity",g.opacity)},stop:function(a,c){var d=b(this).data("draggable").options;d._opacity&&b(c.helper).css("opacity",d._opacity)}});b.ui.plugin.add("draggable", +"scroll",{start:function(){var a=b(this).data("draggable");a.scrollParent[0]!=document&&"HTML"!=a.scrollParent[0].tagName&&(a.overflowOffset=a.scrollParent.offset())},drag:function(a){var c=b(this).data("draggable"),d=c.options,g=!1;if(c.scrollParent[0]!=document&&"HTML"!=c.scrollParent[0].tagName){if(!d.axis||"x"!=d.axis)c.overflowOffset.top+c.scrollParent[0].offsetHeight-a.pageY=k&&e<=l||f>=k&&f<=l||el)&&(g>=i&&g<=j||h>=i&&h<=j||gj);default:return!1}}; +b.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(a,c){var d=b.ui.ddmanager.droppables[a.options.scope]||[],g=c?c.type:null,h=(a.currentItem||a.element).find(":data(droppable)").andSelf(),e=0;a:for(;e').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})), +this.element=this.element.parent().data("resizable",this.element.data("resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize", +"none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize());this.handles=a.handles||(!b(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"});if(this.handles.constructor==String){"all"== +this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw");var h=this.handles.split(",");this.handles={};for(var e=0;e');/sw|se|ne|nw/.test(f)&&i.css({zIndex:++a.zIndex});"se"==f&&i.addClass("ui-icon ui-icon-gripsmall-diagonal-se");this.handles[f]=".ui-resizable-"+f;this.element.append(i)}}this._renderAxis=function(f){var f=f||this.element,c;for(c in this.handles){this.handles[c].constructor==String&&(this.handles[c]= +b(this.handles[c],this.element).show());if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var a=b(this.handles[c],this.element),d=0,d=/sw|ne|nw|se|n|s/.test(c)?a.outerHeight():a.outerWidth(),a=["padding",/ne|nw|n/.test(c)?"Top":/se|sw|s/.test(c)?"Bottom":/^e$/.test(c)?"Right":"Left"].join("");f.css(a,d);this._proportionallyResize()}b(this.handles[c])}};this._renderAxis(this.element);this._handles=b(".ui-resizable-handle",this.element).disableSelection(); +this._handles.mouseover(function(){if(!c.resizing){if(this.className)var b=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);c.axis=b&&b[1]?b[1]:"se"}});a.autoHide&&(this._handles.hide(),b(this.element).addClass("ui-resizable-autohide").hover(function(){if(!a.disabled){b(this).removeClass("ui-resizable-autohide");c._handles.show()}},function(){if(!a.disabled&&!c.resizing){b(this).addClass("ui-resizable-autohide");c._handles.hide()}}));this._mouseInit()},destroy:function(){this._mouseDestroy(); +var c=function(c){b(c).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){c(this.element);var a=this.element;a.after(this.originalElement.css({position:a.css("position"),width:a.outerWidth(),height:a.outerHeight(),top:a.css("top"),left:a.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);c(this.originalElement);return this},_mouseCapture:function(c){var a= +!1,h;for(h in this.handles)b(this.handles[h])[0]==c.target&&(a=!0);return!this.options.disabled&&a},_mouseStart:function(c){var g=this.options,h=this.element.position(),e=this.element;this.resizing=!0;this.documentScroll={top:b(document).scrollTop(),left:b(document).scrollLeft()};(e.is(".ui-draggable")||/absolute/.test(e.css("position")))&&e.css({position:"absolute",top:h.top,left:h.left});b.browser.opera&&/relative/.test(e.css("position"))&&e.css({position:"relative",top:"auto",left:"auto"});this._renderProxy(); +var h=a(this.helper.css("left")),f=a(this.helper.css("top"));g.containment&&(h+=b(g.containment).scrollLeft()||0,f+=b(g.containment).scrollTop()||0);this.offset=this.helper.offset();this.position={left:h,top:f};this.size=this._helper?{width:e.outerWidth(),height:e.outerHeight()}:{width:e.width(),height:e.height()};this.originalSize=this._helper?{width:e.outerWidth(),height:e.outerHeight()}:{width:e.width(),height:e.height()};this.originalPosition={left:h,top:f};this.sizeDiff={width:e.outerWidth()- +e.width(),height:e.outerHeight()-e.height()};this.originalMousePosition={left:c.pageX,top:c.pageY};this.aspectRatio="number"==typeof g.aspectRatio?g.aspectRatio:this.originalSize.width/this.originalSize.height||1;g=b(".ui-resizable-"+this.axis).css("cursor");b("body").css("cursor","auto"==g?this.axis+"-resize":g);e.addClass("ui-resizable-resizing");this._propagate("start",c);return!0},_mouseDrag:function(b){var c=this.helper,a=this.originalMousePosition,e=this._change[this.axis];if(!e)return!1;a= +e.apply(this,[b,b.pageX-a.left||0,b.pageY-a.top||0]);this._updateVirtualBoundaries(b.shiftKey);if(this._aspectRatio||b.shiftKey)a=this._updateRatio(a,b);a=this._respectSize(a,b);this._propagate("resize",b);c.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize();this._updateCache(a);this._trigger("resize",b,this.ui());return!1},_mouseStop:function(c){this.resizing= +!1;var a=this.options;if(this._helper){var h=this._proportionallyResizeElements,e=h.length&&/textarea/i.test(h[0].nodeName),h=e&&b.ui.hasScroll(h[0],"left")?0:this.sizeDiff.height,e=e?0:this.sizeDiff.width,e={width:this.helper.width()-e,height:this.helper.height()-h},h=parseInt(this.element.css("left"),10)+(this.position.left-this.originalPosition.left)||null,f=parseInt(this.element.css("top"),10)+(this.position.top-this.originalPosition.top)||null;a.animate||this.element.css(b.extend(e,{top:f,left:h})); +this.helper.height(this.size.height);this.helper.width(this.size.width);this._helper&&!a.animate&&this._proportionallyResize()}b("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop",c);this._helper&&this.helper.remove();return!1},_updateVirtualBoundaries:function(b){var a=this.options,h,e,f,a={minWidth:c(a.minWidth)?a.minWidth:0,maxWidth:c(a.maxWidth)?a.maxWidth:Infinity,minHeight:c(a.minHeight)?a.minHeight:0,maxHeight:c(a.maxHeight)?a.maxHeight:Infinity}; +if(this._aspectRatio||b)if(b=a.minHeight*this.aspectRatio,e=a.minWidth/this.aspectRatio,h=a.maxHeight*this.aspectRatio,f=a.maxWidth/this.aspectRatio,b>a.minWidth&&(a.minWidth=b),e>a.minHeight&&(a.minHeight=e),hb.width,j=c(b.height)&&a.minHeight&&a.minHeight> +b.height;i&&(b.width=a.minWidth);j&&(b.height=a.minHeight);e&&(b.width=a.maxWidth);f&&(b.height=a.maxHeight);var k=this.originalPosition.left+this.originalSize.width,l=this.position.top+this.size.height,m=/sw|nw|w/.test(h),h=/nw|ne|n/.test(h);i&&m&&(b.left=k-a.minWidth);e&&m&&(b.left=k-a.maxWidth);j&&h&&(b.top=l-a.minHeight);f&&h&&(b.top=l-a.maxHeight);(a=!b.width&&!b.height)&&!b.left&&b.top?b.top=null:a&&(!b.top&&b.left)&&(b.left=null);return b},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var c= +this.helper||this.element,a=0;a');var a=b.browser.msie&&7>b.browser.version,h=a?1:0,a=a?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+a,height:this.element.outerHeight()+a,position:"absolute",left:this.elementOffset.left-h+"px",top:this.elementOffset.top- +h+"px",zIndex:++c.zIndex});this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(b,c){return{width:this.originalSize.width+c}},w:function(b,c){return{left:this.originalPosition.left+c,width:this.originalSize.width-c}},n:function(b,c,a){return{top:this.originalPosition.top+a,height:this.originalSize.height-a}},s:function(b,c,a){return{height:this.originalSize.height+a}},se:function(c,a,h){return b.extend(this._change.s.apply(this,arguments),this._change.e.apply(this, +[c,a,h]))},sw:function(c,a,h){return b.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[c,a,h]))},ne:function(c,a,h){return b.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[c,a,h]))},nw:function(c,a,h){return b.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[c,a,h]))}},_propagate:function(c,a){b.ui.plugin.call(this,c,[a,this.ui()]);"resize"!=c&&this._trigger(c,a,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement, +element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}});b.extend(b.ui.resizable,{version:"1.8.14"});b.ui.plugin.add("resizable","alsoResize",{start:function(){var c=b(this).data("resizable").options,a=function(c){b(c).each(function(){var c=b(this);c.data("resizable-alsoresize",{width:parseInt(c.width(),10),height:parseInt(c.height(),10),left:parseInt(c.css("left"),10),top:parseInt(c.css("top"),10),position:c.css("position")})})}; +"object"==typeof c.alsoResize&&!c.alsoResize.parentNode?c.alsoResize.length?(c.alsoResize=c.alsoResize[0],a(c.alsoResize)):b.each(c.alsoResize,function(b){a(b)}):a(c.alsoResize)},resize:function(c,a){var h=b(this).data("resizable"),e=h.options,f=h.originalSize,i=h.originalPosition,j={height:h.size.height-f.height||0,width:h.size.width-f.width||0,top:h.position.top-i.top||0,left:h.position.left-i.left||0},k=function(c,f){b(c).each(function(){var c=b(this),d=b(this).data("resizable-alsoresize"),i={}, +e=f&&f.length?f:c.parents(a.originalElement[0]).length?["width","height"]:["width","height","top","left"];b.each(e,function(b,c){var a=(d[c]||0)+(j[c]||0);a&&0<=a&&(i[c]=a||null)});b.browser.opera&&/relative/.test(c.css("position"))&&(h._revertToRelativePosition=!0,c.css({position:"absolute",top:"auto",left:"auto"}));c.css(i)})};"object"==typeof e.alsoResize&&!e.alsoResize.nodeType?b.each(e.alsoResize,function(b,c){k(b,c)}):k(e.alsoResize)},stop:function(){var c=b(this).data("resizable"),a=c.options, +h=function(c){b(c).each(function(){var c=b(this);c.css({position:c.data("resizable-alsoresize").position})})};c._revertToRelativePosition&&(c._revertToRelativePosition=!1,"object"==typeof a.alsoResize&&!a.alsoResize.nodeType?b.each(a.alsoResize,function(b){h(b)}):h(a.alsoResize));b(this).removeData("resizable-alsoresize")}});b.ui.plugin.add("resizable","animate",{stop:function(c){var a=b(this).data("resizable"),h=a.options,e=a._proportionallyResizeElements,f=e.length&&/textarea/i.test(e[0].nodeName), +i=f&&b.ui.hasScroll(e[0],"left")?0:a.sizeDiff.height,f={width:a.size.width-(f?0:a.sizeDiff.width),height:a.size.height-i},i=parseInt(a.element.css("left"),10)+(a.position.left-a.originalPosition.left)||null,j=parseInt(a.element.css("top"),10)+(a.position.top-a.originalPosition.top)||null;a.element.animate(b.extend(f,j&&i?{top:j,left:i}:{}),{duration:h.animateDuration,easing:h.animateEasing,step:function(){var f={width:parseInt(a.element.css("width"),10),height:parseInt(a.element.css("height"),10), +top:parseInt(a.element.css("top"),10),left:parseInt(a.element.css("left"),10)};e&&e.length&&b(e[0]).css({width:f.width,height:f.height});a._updateCache(f);a._propagate("resize",c)}})}});b.ui.plugin.add("resizable","containment",{start:function(){var c=b(this).data("resizable"),g=c.element,h=c.options.containment;if(g=h instanceof b?h.get(0):/parent/.test(h)?g.parent().get(0):h)if(c.containerElement=b(g),/document/.test(h)||h==document)c.containerOffset={left:0,top:0},c.containerPosition={left:0,top:0}, +c.parentData={element:b(document),left:0,top:0,width:b(document).width(),height:b(document).height()||document.body.parentNode.scrollHeight};else{var e=b(g),f=[];b(["Top","Right","Left","Bottom"]).each(function(b,c){f[b]=a(e.css("padding"+c))});c.containerOffset=e.offset();c.containerPosition=e.position();c.containerSize={height:e.innerHeight()-f[3],width:e.innerWidth()-f[1]};var h=c.containerOffset,i=c.containerSize.height,j=c.containerSize.width,j=b.ui.hasScroll(g,"left")?g.scrollWidth:j,i=b.ui.hasScroll(g)? +g.scrollHeight:i;c.parentData={element:g,left:h.left,top:h.top,width:j,height:i}}},resize:function(c){var a=b(this).data("resizable"),h=a.options,e=a.containerOffset,f=a.position,c=a._aspectRatio||c.shiftKey,i={top:0,left:0},j=a.containerElement;j[0]!=document&&/static/.test(j.css("position"))&&(i=e);if(f.left<(a._helper?e.left:0))a.size.width+=a._helper?a.position.left-e.left:a.position.left-i.left,c&&(a.size.height=a.size.width/h.aspectRatio),a.position.left=h.helper?e.left:0;if(f.top<(a._helper? +e.top:0))a.size.height+=a._helper?a.position.top-e.top:a.position.top,c&&(a.size.width=a.size.height*h.aspectRatio),a.position.top=a._helper?e.top:0;a.offset.left=a.parentData.left+a.position.left;a.offset.top=a.parentData.top+a.position.top;h=Math.abs(a.offset.left-i.left+a.sizeDiff.width);e=Math.abs((a._helper?a.offset.top-i.top:a.offset.top-e.top)+a.sizeDiff.height);f=a.containerElement.get(0)==a.element.parent().get(0);i=/relative|absolute/.test(a.containerElement.css("position"));f&&i&&(h-=a.parentData.left); +h+a.size.width>=a.parentData.width&&(a.size.width=a.parentData.width-h,c&&(a.size.height=a.size.width/a.aspectRatio));e+a.size.height>=a.parentData.height&&(a.size.height=a.parentData.height-e,c&&(a.size.width=a.size.height*a.aspectRatio))},stop:function(){var a=b(this).data("resizable"),c=a.options,h=a.containerOffset,e=a.containerPosition,f=a.containerElement,i=b(a.helper),j=i.offset(),k=i.outerWidth()-a.sizeDiff.width,i=i.outerHeight()-a.sizeDiff.height;a._helper&&(!c.animate&&/relative/.test(f.css("position")))&& +b(this).css({left:j.left-e.left-h.left,width:k,height:i});a._helper&&(!c.animate&&/static/.test(f.css("position")))&&b(this).css({left:j.left-e.left-h.left,width:k,height:i})}});b.ui.plugin.add("resizable","ghost",{start:function(){var a=b(this).data("resizable"),c=a.options,h=a.size;a.ghost=a.originalElement.clone();a.ghost.css({opacity:0.25,display:"block",position:"relative",height:h.height,width:h.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass("string"==typeof c.ghost?c.ghost: +"");a.ghost.appendTo(a.helper)},resize:function(){var a=b(this).data("resizable");a.ghost&&a.ghost.css({position:"relative",height:a.size.height,width:a.size.width})},stop:function(){var a=b(this).data("resizable");a.ghost&&a.helper&&a.helper.get(0).removeChild(a.ghost.get(0))}});b.ui.plugin.add("resizable","grid",{resize:function(){var a=b(this).data("resizable"),c=a.options,h=a.size,e=a.originalSize,f=a.originalPosition,i=a.axis;c.grid="number"==typeof c.grid?[c.grid,c.grid]:c.grid;var j=Math.round((h.width- +e.width)/(c.grid[0]||1))*(c.grid[0]||1),c=Math.round((h.height-e.height)/(c.grid[1]||1))*(c.grid[1]||1);/^(se|s|e)$/.test(i)?(a.size.width=e.width+j,a.size.height=e.height+c):/^(ne)$/.test(i)?(a.size.width=e.width+j,a.size.height=e.height+c,a.position.top=f.top-c):(/^(sw)$/.test(i)?(a.size.width=e.width+j,a.size.height=e.height+c):(a.size.width=e.width+j,a.size.height=e.height+c,a.position.top=f.top-c),a.position.left=f.left-j)}});var a=function(b){return parseInt(b,10)||0},c=function(b){return!isNaN(parseInt(b, +10))}})(jQuery); +(function(b){b.widget("ui.selectable",b.ui.mouse,{options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch"},_create:function(){var a=this;this.element.addClass("ui-selectable");this.dragged=!1;var c;this.refresh=function(){c=b(a.options.filter,a.element[0]);c.each(function(){var a=b(this),c=a.offset();b.data(this,"selectable-item",{element:this,$element:a,left:c.left,top:c.top,right:c.left+a.outerWidth(),bottom:c.top+a.outerHeight(),startselected:!1,selected:a.hasClass("ui-selected"),selecting:a.hasClass("ui-selecting"), +unselecting:a.hasClass("ui-unselecting")})})};this.refresh();this.selectees=c.addClass("ui-selectee");this._mouseInit();this.helper=b("
    ")},destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item");this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable");this._mouseDestroy();return this},_mouseStart:function(a){var c=this;this.opos=[a.pageX,a.pageY];if(!this.options.disabled){var d= +this.options;this.selectees=b(d.filter,this.element[0]);this._trigger("start",a);b(d.appendTo).append(this.helper);this.helper.css({left:a.clientX,top:a.clientY,width:0,height:0});d.autoRefresh&&this.refresh();this.selectees.filter(".ui-selected").each(function(){var d=b.data(this,"selectable-item");d.startselected=!0;a.metaKey||(d.$element.removeClass("ui-selected"),d.selected=!1,d.$element.addClass("ui-unselecting"),d.unselecting=!0,c._trigger("unselecting",a,{unselecting:d.element}))});b(a.target).parents().andSelf().each(function(){var d= +b.data(this,"selectable-item");if(d){var h=!a.metaKey||!d.$element.hasClass("ui-selected");d.$element.removeClass(h?"ui-unselecting":"ui-selected").addClass(h?"ui-selecting":"ui-unselecting");d.unselecting=!h;d.selecting=h;(d.selected=h)?c._trigger("selecting",a,{selecting:d.element}):c._trigger("unselecting",a,{unselecting:d.element});return!1}})}},_mouseDrag:function(a){var c=this;this.dragged=!0;if(!this.options.disabled){var d=this.options,g=this.opos[0],h=this.opos[1],e=a.pageX,f=a.pageY;if(g> +e)var i=e,e=g,g=i;h>f&&(i=f,f=h,h=i);this.helper.css({left:g,top:h,width:e-g,height:f-h});this.selectees.each(function(){var i=b.data(this,"selectable-item");if(i&&i.element!=c.element[0]){var k=false;d.tolerance=="touch"?k=!(i.left>e||i.rightf||i.bottomg&&i.righth&&i.bottom *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1E3},_create:function(){var b=this.options;this.containerCache={};this.element.addClass("ui-sortable");this.refresh(); +this.floating=this.items.length?"x"===b.axis||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):!1;this.offset=this.element.offset();this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var b=this.items.length-1;0<=b;b--)this.items[b].item.removeData("sortable-item");return this},_setOption:function(a,c){"disabled"===a?(this.options[a]= +c,this.widget()[c?"addClass":"removeClass"]("ui-sortable-disabled")):b.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(a,c){if(this.reverting||this.options.disabled||"static"==this.options.type)return!1;this._refreshItems(a);var d=null,g=this;b(a.target).parents().each(function(){if(b.data(this,"sortable-item")==g)return d=b(this),!1});b.data(a.target,"sortable-item")==g&&(d=b(a.target));if(!d)return!1;if(this.options.handle&&!c){var h=!1;b(this.options.handle,d).find("*").andSelf().each(function(){this== +a.target&&(h=!0)});if(!h)return!1}this.currentItem=d;this._removeCurrentsFromItems();return!0},_mouseStart:function(a,c,d){c=this.options;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(a);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition= +this.helper.css("position");b.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt);this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};this.helper[0]!=this.currentItem[0]&&this.currentItem.hide();this._createPlaceholder(); +c.containment&&this._setContainment();c.cursor&&(b("body").css("cursor")&&(this._storedCursor=b("body").css("cursor")),b("body").css("cursor",c.cursor));c.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",c.opacity));c.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",c.zIndex));this.scrollParent[0]!=document&&"HTML"!=this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()); +this._trigger("start",a,this._uiHash());this._preserveHelperProportions||this._cacheHelperProportions();if(!d)for(d=this.containers.length-1;0<=d;d--)this.containers[d]._trigger("activate",a,this._uiHash(this));b.ui.ddmanager&&(b.ui.ddmanager.current=this);b.ui.ddmanager&&!c.dropBehaviour&&b.ui.ddmanager.prepareOffsets(this,a);this.dragging=!0;this.helper.addClass("ui-sortable-helper");this._mouseDrag(a);return!0},_mouseDrag:function(a){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute"); +this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);if(this.options.scroll){var c=this.options,d=!1;this.scrollParent[0]!=document&&"HTML"!=this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-a.pageYb[this.floating?"width":"height"]?g+k>i&&g+ke&&c+lthis.containment[2]&&(h=this.containment[2]+this.offset.click.left),a.pageY-this.offset.click.top>this.containment[3]&&(e=this.containment[3]+this.offset.click.top)),c.grid))e=this.originalPageY+Math.round((e-this.originalPageY)/c.grid[1])*c.grid[1],e=this.containment?!(e-this.offset.click.topthis.containment[3])?e:!(e-this.offset.click.top< +this.containment[1])?e-c.grid[1]:e+c.grid[1]:e,h=this.originalPageX+Math.round((h-this.originalPageX)/c.grid[0])*c.grid[0],h=this.containment?!(h-this.offset.click.leftthis.containment[2])?h:!(h-this.offset.click.left li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:!1,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var a=this,c=a.options;a.running=0;a.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix");a.headers= +a.element.find(c.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){c.disabled||b(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){c.disabled||b(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){c.disabled||b(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){c.disabled||b(this).removeClass("ui-state-focus")});a.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom"); +if(c.navigation){var d=a.element.find("a").filter(c.navigationFilter).eq(0);if(d.length){var g=d.closest(".ui-accordion-header");a.active=g.length?g:d.closest(".ui-accordion-content").prev()}}a.active=a._findActive(a.active||c.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top");a.active.next().addClass("ui-accordion-content-active");a._createIcons();a.resize();a.element.attr("role","tablist");a.headers.attr("role","tab").bind("keydown.accordion", +function(b){return a._keydown(b)}).next().attr("role","tabpanel");a.headers.not(a.active||"").attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).next().hide();a.active.length?a.active.attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}):a.headers.eq(0).attr("tabIndex",0);b.browser.safari||a.headers.find("a").attr("tabIndex",-1);c.event&&a.headers.bind(c.event.split(" ").join(".accordion ")+".accordion",function(b){a._clickHandler.call(a,b,this);b.preventDefault()})},_createIcons:function(){var a= +this.options;a.icons&&(b("").addClass("ui-icon "+a.icons.header).prependTo(this.headers),this.active.children(".ui-icon").toggleClass(a.icons.header).toggleClass(a.icons.headerSelected),this.element.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.children(".ui-icon").remove();this.element.removeClass("ui-accordion-icons")},destroy:function(){var a=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role");this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("tabIndex"); +this.headers.find("a").removeAttr("tabIndex");this._destroyIcons();var c=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");(a.autoHeight||a.fillHeight)&&c.css("height","");return b.Widget.prototype.destroy.call(this)},_setOption:function(a,c){b.Widget.prototype._setOption.apply(this,arguments);"active"==a&&this.activate(c);"icons"==a&&(this._destroyIcons(), +c&&this._createIcons());if("disabled"==a)this.headers.add(this.headers.next())[c?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(a){if(!this.options.disabled&&!a.altKey&&!a.ctrlKey){var c=b.ui.keyCode,d=this.headers.length,g=this.headers.index(a.target),h=!1;switch(a.keyCode){case c.RIGHT:case c.DOWN:h=this.headers[(g+1)%d];break;case c.LEFT:case c.UP:h=this.headers[(g-1+d)%d];break;case c.SPACE:case c.ENTER:this._clickHandler({target:a.target},a.target),a.preventDefault()}return h? +(b(a.target).attr("tabIndex",-1),b(h).attr("tabIndex",0),h.focus(),!1):!0}},resize:function(){var a=this.options,c;if(a.fillSpace){if(b.browser.msie){var d=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}c=this.element.parent().height();b.browser.msie&&this.element.parent().css("overflow",d);this.headers.each(function(){c-=b(this).outerHeight(!0)});this.headers.next().each(function(){b(this).height(Math.max(0,c-b(this).innerHeight()+b(this).height()))}).css("overflow", +"auto")}else a.autoHeight&&(c=0,this.headers.next().each(function(){c=Math.max(c,b(this).height("").height())}).height(c));return this},activate:function(b){this.options.active=b;b=this._findActive(b)[0];this._clickHandler({target:b},b);return this},_findActive:function(a){return a?"number"===typeof a?this.headers.filter(":eq("+a+")"):this.headers.not(this.headers.not(a)):!1===a?b([]):this.headers.filter(":eq(0)")},_clickHandler:function(a,c){var d=this.options;if(!d.disabled)if(a.target){var g=b(a.currentTarget|| +c),h=g[0]===this.active[0];d.active=d.collapsible&&h?!1:this.headers.index(g);if(!(this.running||!d.collapsible&&h)){var e=this.active,f=g.next(),i=this.active.next(),j={options:d,newHeader:h&&d.collapsible?b([]):g,oldHeader:this.active,newContent:h&&d.collapsible?b([]):f,oldContent:i},k=this.headers.index(this.active[0])>this.headers.index(g[0]);this.active=h?b([]):g;this._toggle(f,i,j,h,k);e.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header); +h||(g.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected),g.next().addClass("ui-accordion-content-active"))}}else if(d.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);this.active.next().addClass("ui-accordion-content-active");var i=this.active.next(), +j={options:d,newHeader:b([]),oldHeader:d.active,newContent:b([]),oldContent:i},f=this.active=b([]);this._toggle(f,i,j)}},_toggle:function(a,c,d,g,h){var e=this,f=e.options;e.toShow=a;e.toHide=c;e.data=d;var i=function(){if(e)return e._completed.apply(e,arguments)};e._trigger("changestart",null,e.data);e.running=0===c.size()?a.size():c.size();if(f.animated){d={};d=f.collapsible&&g?{toShow:b([]),toHide:c,complete:i,down:h,autoHeight:f.autoHeight||f.fillSpace}:{toShow:a,toHide:c,complete:i,down:h,autoHeight:f.autoHeight|| +f.fillSpace};f.proxied||(f.proxied=f.animated);f.proxiedDuration||(f.proxiedDuration=f.duration);f.animated=b.isFunction(f.proxied)?f.proxied(d):f.proxied;f.duration=b.isFunction(f.proxiedDuration)?f.proxiedDuration(d):f.proxiedDuration;var g=b.ui.accordion.animations,j=f.duration,k=f.animated;k&&(!g[k]&&!b.easing[k])&&(k="slide");g[k]||(g[k]=function(b){this.slide(b,{easing:k,duration:j||700})});g[k](d)}else f.collapsible&&g?a.toggle():(c.hide(),a.show()),i(!0);c.prev().attr({"aria-expanded":"false", +"aria-selected":"false",tabIndex:-1}).blur();a.prev().attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}).focus()},_completed:function(b){this.running=b?0:--this.running;this.running||(this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""}),this.toHide.removeClass("ui-accordion-content-active"),this.toHide.length&&(this.toHide.parent()[0].className=this.toHide.parent()[0].className),this._trigger("change",null,this.data))}});b.extend(b.ui.accordion,{version:"1.8.14", +animations:{slide:function(a,c){a=b.extend({easing:"swing",duration:300},a,c);if(a.toHide.size())if(a.toShow.size()){var d=a.toShow.css("overflow"),g=0,h={},e={},f,i=a.toShow;f=i[0].style.width;i.width(parseInt(i.parent().width(),10)-parseInt(i.css("paddingLeft"),10)-parseInt(i.css("paddingRight"),10)-(parseInt(i.css("borderLeftWidth"),10)||0)-(parseInt(i.css("borderRightWidth"),10)||0));b.each(["height","paddingTop","paddingBottom"],function(c,f){e[f]="hide";var i=(""+b.css(a.toShow[0],f)).match(/^([\d+-.]+)(.*)$/); +h[f]={value:i[1],unit:i[2]||"px"}});a.toShow.css({height:0,overflow:"hidden"}).show();a.toHide.filter(":hidden").each(a.complete).end().filter(":visible").animate(e,{step:function(b,c){"height"==c.prop&&(g=0===c.end-c.start?0:(c.now-c.start)/(c.end-c.start));a.toShow[0].style[c.prop]=g*h[c.prop].value+h[c.prop].unit},duration:a.duration,easing:a.easing,complete:function(){a.autoHeight||a.toShow.css("height","");a.toShow.css({width:f,overflow:d});a.complete()}})}else a.toHide.animate({height:"hide", +paddingTop:"hide",paddingBottom:"hide"},a);else a.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},a)},bounceslide:function(b){this.slide(b,{easing:b.down?"easeOutBounce":"swing",duration:b.down?1E3:200})}}})})(jQuery); +(function(b){var a=0;b.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var c=this,a=this.element[0].ownerDocument,g;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(a){if(!c.options.disabled&&!c.element.attr("readonly")){g=!1;var d= +b.ui.keyCode;switch(a.keyCode){case d.PAGE_UP:c._move("previousPage",a);break;case d.PAGE_DOWN:c._move("nextPage",a);break;case d.UP:c._move("previous",a);a.preventDefault();break;case d.DOWN:c._move("next",a);a.preventDefault();break;case d.ENTER:case d.NUMPAD_ENTER:c.menu.active&&(g=!0,a.preventDefault());case d.TAB:if(!c.menu.active)break;c.menu.select(a);break;case d.ESCAPE:c.element.val(c.term);c.close(a);break;default:clearTimeout(c.searching),c.searching=setTimeout(function(){c.term!=c.element.val()&& +(c.selectedItem=null,c.search(null,a))},c.options.delay)}}}).bind("keypress.autocomplete",function(b){g&&(g=!1,b.preventDefault())}).bind("focus.autocomplete",function(){c.options.disabled||(c.selectedItem=null,c.previous=c.element.val())}).bind("blur.autocomplete",function(b){c.options.disabled||(clearTimeout(c.searching),c.closing=setTimeout(function(){c.close(b);c._change(b)},150))});this._initSource();this.response=function(){return c._response.apply(c,arguments)};this.menu=b("
      ").addClass("ui-autocomplete").appendTo(b(this.options.appendTo|| +"body",a)[0]).mousedown(function(a){var d=c.menu.element[0];b(a.target).closest(".ui-menu-item").length||setTimeout(function(){b(document).one("mousedown",function(a){a.target!==c.element[0]&&(a.target!==d&&!b.ui.contains(d,a.target))&&c.close()})},1);setTimeout(function(){clearTimeout(c.closing)},13)}).menu({focus:function(b,a){var f=a.item.data("item.autocomplete");!1!==c._trigger("focus",b,{item:f})&&/^key/.test(b.originalEvent.type)&&c.element.val(f.value)},selected:function(b,e){var f=e.item.data("item.autocomplete"), +i=c.previous;c.element[0]!==a.activeElement&&(c.element.focus(),c.previous=i,setTimeout(function(){c.previous=i;c.selectedItem=f},1));!1!==c._trigger("select",b,{item:f})&&c.element.val(f.value);c.term=c.element.val();c.close(b);c.selectedItem=f},blur:function(){c.menu.element.is(":visible")&&c.element.val()!==c.term&&c.element.val(c.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu");b.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup"); +this.menu.element.remove();b.Widget.prototype.destroy.call(this)},_setOption:function(a,d){b.Widget.prototype._setOption.apply(this,arguments);"source"===a&&this._initSource();"appendTo"===a&&this.menu.element.appendTo(b(d||"body",this.element[0].ownerDocument)[0]);"disabled"===a&&(d&&this.xhr)&&this.xhr.abort()},_initSource:function(){var c=this,d,g;b.isArray(this.options.source)?(d=this.options.source,this.source=function(a,c){c(b.ui.autocomplete.filter(d,a.term))}):"string"===typeof this.options.source? +(g=this.options.source,this.source=function(d,e){c.xhr&&c.xhr.abort();c.xhr=b.ajax({url:g,data:d,dataType:"json",autocompleteRequest:++a,success:function(b){this.autocompleteRequest===a&&e(b)},error:function(){this.autocompleteRequest===a&&e([])}})}):this.source=this.options.source},search:function(b,a){b=null!=b?b:this.element.val();this.term=this.element.val();if(b.length").data("item.autocomplete",d).append(b("").text(d.label)).appendTo(a)},_move:function(b,a){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(b)||this.menu.last()&& +/^next/.test(b))this.element.val(this.term),this.menu.deactivate();else this.menu[b](a);else this.search(null,a)},widget:function(){return this.menu.element}});b.extend(b.ui.autocomplete,{escapeRegex:function(b){return b.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")},filter:function(a,d){var g=RegExp(b.ui.autocomplete.escapeRegex(d),"i");return b.grep(a,function(b){return g.test(b.label||b.value||b)})}})})(jQuery); +(function(b){b.widget("ui.menu",{_create:function(){var a=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(c){b(c.target).closest(".ui-menu-item a").length&&(c.preventDefault(),a.select(c))});this.refresh()},refresh:function(){var a=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex", +-1).mouseenter(function(c){a.activate(c,b(this).parent())}).mouseleave(function(){a.deactivate()})},activate:function(b,c){this.deactivate();if(this.hasScroll()){var d=c.offset().top-this.element.offset().top,g=this.element.scrollTop(),h=this.element.height();0>d?this.element.scrollTop(g+d):d>=h&&this.element.scrollTop(g+d-h+c.height())}this.active=c.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",b,{item:c})},deactivate:function(){this.active&& +(this.active.children("a").removeClass("ui-state-hover").removeAttr("id"),this._trigger("blur"),this.active=null)},next:function(b){this.move("next",".ui-menu-item:first",b)},previous:function(b){this.move("prev",".ui-menu-item:last",b)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(b,c,d){this.active?(b=this.active[b+"All"](".ui-menu-item").eq(0),b.length?this.activate(d, +b):this.activate(d,this.element.children(c))):this.activate(d,this.element.children(c))},nextPage:function(a){if(this.hasScroll())if(!this.active||this.last())this.activate(a,this.element.children(".ui-menu-item:first"));else{var c=this.active.offset().top,d=this.element.height(),g=this.element.children(".ui-menu-item").filter(function(){var a=b(this).offset().top-c-d+b(this).height();return 10>a&&-10a&&-10").addClass("ui-button-text").html(this.options.label).appendTo(a.empty()).text(),d=this.options.icons,h=d.primary&&d.secondary,e=[];d.primary||d.secondary?(this.options.text&&e.push("ui-button-text-icon"+(h?"s":d.primary?"-primary":"-secondary")),d.primary&&a.prepend(""),d.secondary&&a.append(""),this.options.text||(e.push(h?"ui-button-icons-only": +"ui-button-icon-only"),this.hasTitle||a.attr("title",c))):e.push("ui-button-text-only");a.addClass(e.join(" "))}}});b.widget("ui.buttonset",{options:{items:":button, :submit, :reset, :checkbox, :radio, a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(a,c){"disabled"===a&&this.buttons.button("option",a,c);b.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){var a="ltr"===this.element.css("direction"); +this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return b(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass(a?"ui-corner-left":"ui-corner-right").end().filter(":last").addClass(a?"ui-corner-right":"ui-corner-left").end().end()},destroy:function(){this.element.removeClass("ui-buttonset");this.buttons.map(function(){return b(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy"); +b.Widget.prototype.destroy.call(this)}})})(jQuery); +(function(b,a){var c={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},d={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},g=b.attrFn||{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0,click:!0};b.widget("ui.dialog",{options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",collision:"fit",using:function(a){var c= +b(this).css(a).offset().top;0>c&&b(this).css("top",a.top-c)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1E3},_create:function(){this.originalTitle=this.element.attr("title");"string"!==typeof this.originalTitle&&(this.originalTitle="");this.options.title=this.options.title||this.originalTitle;var a=this,c=a.options,f=c.title||" ",i=b.ui.dialog.getTitleId(a.element),d=(a.uiDialog=b("
      ")).appendTo(document.body).hide().addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+ +c.dialogClass).css({zIndex:c.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(f){if(c.closeOnEscape&&f.keyCode&&f.keyCode===b.ui.keyCode.ESCAPE){a.close(f);f.preventDefault()}}).attr({role:"dialog","aria-labelledby":i}).mousedown(function(b){a.moveToTop(false,b)});a.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(d);var g=(a.uiDialogTitlebar=b("
      ")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(d), +l=b('').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){l.addClass("ui-state-hover")},function(){l.removeClass("ui-state-hover")}).focus(function(){l.addClass("ui-state-focus")}).blur(function(){l.removeClass("ui-state-focus")}).click(function(b){a.close(b);return false}).appendTo(g);(a.uiDialogTitlebarCloseText=b("")).addClass("ui-icon ui-icon-closethick").text(c.closeText).appendTo(l);b("").addClass("ui-dialog-title").attr("id", +i).html(f).prependTo(g);b.isFunction(c.beforeclose)&&!b.isFunction(c.beforeClose)&&(c.beforeClose=c.beforeclose);g.find("*").add(g).disableSelection();c.draggable&&b.fn.draggable&&a._makeDraggable();c.resizable&&b.fn.resizable&&a._makeResizable();a._createButtons(c.buttons);a._isOpen=!1;b.fn.bgiframe&&d.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){this.overlay&&this.overlay.destroy();this.uiDialog.hide();this.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"); +this.uiDialog.remove();this.originalTitle&&this.element.attr("title",this.originalTitle);return this},widget:function(){return this.uiDialog},close:function(a){var c=this,f,d;if(!1!==c._trigger("beforeClose",a))return c.overlay&&c.overlay.destroy(),c.uiDialog.unbind("keypress.ui-dialog"),c._isOpen=!1,c.options.hide?c.uiDialog.hide(c.options.hide,function(){c._trigger("close",a)}):(c.uiDialog.hide(),c._trigger("close",a)),b.ui.dialog.overlay.resize(),c.options.modal&&(f=0,b(".ui-dialog").each(function(){if(this!== +c.uiDialog[0]){d=b(this).css("z-index");isNaN(d)||(f=Math.max(f,d))}}),b.ui.dialog.maxZ=f),c},isOpen:function(){return this._isOpen},moveToTop:function(a,c){var f=this.options;if(f.modal&&!a||!f.stack&&!f.modal)return this._trigger("focus",c);f.zIndex>b.ui.dialog.maxZ&&(b.ui.dialog.maxZ=f.zIndex);this.overlay&&(b.ui.dialog.maxZ+=1,this.overlay.$el.css("z-index",b.ui.dialog.overlay.maxZ=b.ui.dialog.maxZ));f={scrollTop:this.element.attr("scrollTop"),scrollLeft:this.element.attr("scrollLeft")};b.ui.dialog.maxZ+= +1;this.uiDialog.css("z-index",b.ui.dialog.maxZ);this.element.attr(f);this._trigger("focus",c);return this},open:function(){if(!this._isOpen){var a=this.options,c=this.uiDialog;this.overlay=a.modal?new b.ui.dialog.overlay(this):null;this._size();this._position(a.position);c.show(a.show);this.moveToTop(!0);a.modal&&c.bind("keypress.ui-dialog",function(a){if(a.keyCode===b.ui.keyCode.TAB){var c=b(":tabbable",this),d=c.filter(":first"),c=c.filter(":last");if(a.target===c[0]&&!a.shiftKey)return d.focus(1), +!1;if(a.target===d[0]&&a.shiftKey)return c.focus(1),!1}});b(this.element.find(":tabbable").get().concat(c.find(".ui-dialog-buttonpane :tabbable").get().concat(c.get()))).eq(0).focus();this._isOpen=!0;this._trigger("open");return this}},_createButtons:function(a){var c=this,f=!1,d=b("
      ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),j=b("
      ").addClass("ui-dialog-buttonset").appendTo(d);c.uiDialog.find(".ui-dialog-buttonpane").remove();"object"===typeof a&& +null!==a&&b.each(a,function(){return!(f=!0)});f&&(b.each(a,function(a,f){var f=b.isFunction(f)?{click:f,text:a}:f,d=b('').click(function(){f.click.apply(c.element[0],arguments)}).appendTo(j);b.each(f,function(b,a){if("click"!==b)if(b in g)d[b](a);else d.attr(b,a)});b.fn.button&&d.button()}),d.appendTo(c.uiDialog))},_makeDraggable:function(){function a(b){return{position:b.position,offset:b.offset}}var c=this,f=c.options,d=b(document),g;c.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close", +handle:".ui-dialog-titlebar",containment:"document",start:function(d,i){g="auto"===f.height?"auto":b(this).height();b(this).height(b(this).height()).addClass("ui-dialog-dragging");c._trigger("dragStart",d,a(i))},drag:function(b,f){c._trigger("drag",b,a(f))},stop:function(k,l){f.position=[l.position.left-d.scrollLeft(),l.position.top-d.scrollTop()];b(this).removeClass("ui-dialog-dragging").height(g);c._trigger("dragStop",k,a(l));b.ui.dialog.overlay.resize()}})},_makeResizable:function(c){function d(b){return{originalPosition:b.originalPosition, +originalSize:b.originalSize,position:b.position,size:b.size}}var c=c===a?this.options.resizable:c,f=this,i=f.options,g=f.uiDialog.css("position"),c="string"===typeof c?c:"n,e,s,w,se,sw,ne,nw";f.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:f.element,maxWidth:i.maxWidth,maxHeight:i.maxHeight,minWidth:i.minWidth,minHeight:f._minHeight(),handles:c,start:function(a,c){b(this).addClass("ui-dialog-resizing");f._trigger("resizeStart",a,d(c))},resize:function(b,a){f._trigger("resize", +b,d(a))},stop:function(a,c){b(this).removeClass("ui-dialog-resizing");i.height=b(this).height();i.width=b(this).width();f._trigger("resizeStop",a,d(c));b.ui.dialog.overlay.resize()}}).css("position",g).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var b=this.options;return"auto"===b.height?b.minHeight:Math.min(b.minHeight,b.height)},_position:function(a){var c=[],f=[0,0],d;if(a){if("string"===typeof a||"object"===typeof a&&"0"in a)c=a.split?a.split(" "): +[a[0],a[1]],1===c.length&&(c[1]=c[0]),b.each(["left","top"],function(b,a){+c[b]===c[b]&&(f[b]=c[b],c[b]=a)}),a={my:c.join(" "),at:c.join(" "),offset:f.join(" ")};a=b.extend({},b.ui.dialog.prototype.options.position,a)}else a=b.ui.dialog.prototype.options.position;(d=this.uiDialog.is(":visible"))||this.uiDialog.show();this.uiDialog.css({top:0,left:0}).position(b.extend({of:window},a));d||this.uiDialog.hide()},_setOptions:function(a){var g=this,f={},i=!1;b.each(a,function(b,a){g._setOption(b,a);b in +c&&(i=!0);b in d&&(f[b]=a)});i&&this._size();this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",f)},_setOption:function(a,c){var f=this.uiDialog;switch(a){case "beforeclose":a="beforeClose";break;case "buttons":this._createButtons(c);break;case "closeText":this.uiDialogTitlebarCloseText.text(""+c);break;case "dialogClass":f.removeClass(this.options.dialogClass).addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+c);break;case "disabled":c?f.addClass("ui-dialog-disabled"): +f.removeClass("ui-dialog-disabled");break;case "draggable":var d=f.is(":data(draggable)");d&&!c&&f.draggable("destroy");!d&&c&&this._makeDraggable();break;case "position":this._position(c);break;case "resizable":(d=f.is(":data(resizable)"))&&!c&&f.resizable("destroy");d&&"string"===typeof c&&f.resizable("option","handles",c);!d&&!1!==c&&this._makeResizable(c);break;case "title":b(".ui-dialog-title",this.uiDialogTitlebar).html(""+(c||" "))}b.Widget.prototype._setOption.apply(this,arguments)}, +_size:function(){var a=this.options,c,f,d=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0});a.minWidth>a.width&&(a.width=a.minWidth);c=this.uiDialog.css({height:"auto",width:a.width}).height();f=Math.max(0,a.minHeight-c);"auto"===a.height?b.support.minHeight?this.element.css({minHeight:f,height:"auto"}):(this.uiDialog.show(),a=this.element.css("height","auto").height(),d||this.uiDialog.hide(),this.element.height(Math.max(a,f))):this.element.height(Math.max(a.height- +c,0));this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}});b.extend(b.ui.dialog,{version:"1.8.14",uuid:0,maxZ:0,getTitleId:function(b){b=b.attr("id");b||(b=this.uuid+=1);return"ui-dialog-title-"+b},overlay:function(a){this.$el=b.ui.dialog.overlay.create(a)}});b.extend(b.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:b.map("focus mousedown mouseup keydown keypress click".split(" "),function(b){return b+".dialog-overlay"}).join(" "), +create:function(a){0===this.instances.length&&(setTimeout(function(){b.ui.dialog.overlay.instances.length&&b(document).bind(b.ui.dialog.overlay.events,function(a){if(b(a.target).zIndex()").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(), +height:this.height()});b.fn.bgiframe&&c.bgiframe();this.instances.push(c);return c},destroy:function(a){var c=b.inArray(a,this.instances);-1!=c&&this.oldInstances.push(this.instances.splice(c,1)[0]);0===this.instances.length&&b([document,window]).unbind(".dialog-overlay");a.remove();var f=0;b.each(this.instances,function(){f=Math.max(f,this.css("z-index"))});this.maxZ=f},height:function(){var a,c;return b.browser.msie&&7>b.browser.version?(a=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight), +c=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight),a").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+("min"===c.range||"max"===c.range?" ui-slider-range-"+c.range:""))}for(var e=d.length;e"); +this.handles=d.add(b(h.join("")).appendTo(a.element));this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(b){b.preventDefault()}).hover(function(){c.disabled||b(this).addClass("ui-state-hover")},function(){b(this).removeClass("ui-state-hover")}).focus(function(){c.disabled?b(this).blur():(b(".ui-slider .ui-state-focus").removeClass("ui-state-focus"),b(this).addClass("ui-state-focus"))}).blur(function(){b(this).removeClass("ui-state-focus")});this.handles.each(function(a){b(this).data("index.ui-slider-handle", +a)});this.handles.keydown(function(c){var d=!0,g=b(this).data("index.ui-slider-handle"),e,h,m;if(!a.options.disabled){switch(c.keyCode){case b.ui.keyCode.HOME:case b.ui.keyCode.END:case b.ui.keyCode.PAGE_UP:case b.ui.keyCode.PAGE_DOWN:case b.ui.keyCode.UP:case b.ui.keyCode.RIGHT:case b.ui.keyCode.DOWN:case b.ui.keyCode.LEFT:if(d=!1,!a._keySliding&&(a._keySliding=!0,b(this).addClass("ui-state-active"),e=a._start(c,g),!1===e))return}m=a.options.step;e=a.options.values&&a.options.values.length?h=a.values(g): +h=a.value();switch(c.keyCode){case b.ui.keyCode.HOME:h=a._valueMin();break;case b.ui.keyCode.END:h=a._valueMax();break;case b.ui.keyCode.PAGE_UP:h=a._trimAlignValue(e+(a._valueMax()-a._valueMin())/5);break;case b.ui.keyCode.PAGE_DOWN:h=a._trimAlignValue(e-(a._valueMax()-a._valueMin())/5);break;case b.ui.keyCode.UP:case b.ui.keyCode.RIGHT:if(e===a._valueMax())return;h=a._trimAlignValue(e+m);break;case b.ui.keyCode.DOWN:case b.ui.keyCode.LEFT:if(e===a._valueMin())return;h=a._trimAlignValue(e-m)}a._slide(c, +g,h);return d}}).keyup(function(c){var d=b(this).data("index.ui-slider-handle");a._keySliding&&(a._keySliding=!1,a._stop(c,d),a._change(c,d),b(this).removeClass("ui-state-active"))});this._refreshValue();this._animateOff=!1},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider");this._mouseDestroy();return this},_mouseCapture:function(a){var c= +this.options,d,g,h,e,f;if(c.disabled)return!1;this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();d=this._normValueFromMouse({x:a.pageX,y:a.pageY});g=this._valueMax()-this._valueMin()+1;e=this;this.handles.each(function(a){var c=Math.abs(d-e.values(a));g>c&&(g=c,h=b(this),f=a)});!0===c.range&&this.values(1)===c.min&&(f+=1,h=b(this.handles[f]));if(!1===this._start(a,f))return!1;this._mouseSliding=!0;e._handleIndex=f;h.addClass("ui-state-active").focus(); +c=h.offset();this._clickOffset=!b(a.target).parents().andSelf().is(".ui-slider-handle")?{left:0,top:0}:{left:a.pageX-c.left-h.width()/2,top:a.pageY-c.top-h.height()/2-(parseInt(h.css("borderTopWidth"),10)||0)-(parseInt(h.css("borderBottomWidth"),10)||0)+(parseInt(h.css("marginTop"),10)||0)};this.handles.hasClass("ui-state-hover")||this._slide(a,f,d);return this._animateOff=!0},_mouseStart:function(){return!0},_mouseDrag:function(b){var c=this._normValueFromMouse({x:b.pageX,y:b.pageY});this._slide(b, +this._handleIndex,c);return!1},_mouseStop:function(b){this.handles.removeClass("ui-state-active");this._mouseSliding=!1;this._stop(b,this._handleIndex);this._change(b,this._handleIndex);this._clickOffset=this._handleIndex=null;return this._animateOff=!1},_detectOrientation:function(){this.orientation="vertical"===this.options.orientation?"vertical":"horizontal"},_normValueFromMouse:function(b){var c;"horizontal"===this.orientation?(c=this.elementSize.width,b=b.x-this.elementOffset.left-(this._clickOffset? +this._clickOffset.left:0)):(c=this.elementSize.height,b=b.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0));c=b/c;1c&&(c=0);"vertical"===this.orientation&&(c=1-c);b=this._valueMax()-this._valueMin();return this._trimAlignValue(this._valueMin()+c*b)},_start:function(b,c){var d={handle:this.handles[c],value:this.value()};this.options.values&&this.options.values.length&&(d.value=this.values(c),d.values=this.values());return this._trigger("start",b,d)},_slide:function(b, +c,d){var g;if(this.options.values&&this.options.values.length){g=this.values(c?0:1);if(2===this.options.values.length&&!0===this.options.range&&(0===c&&d>g||1===c&&d=this._valueMax())return this._valueMax();var c=0=c&&(alignValue+=0",remove:null,select:null,show:null,spinner:"Loading…",tabTemplate:"
    • #{label}
    • "},_create:function(){this._tabify(!0)},_setOption:function(b,a){"selected"==b?this.options.collapsible&&a==this.options.selected||this.select(a): +(this.options[b]=a,this._tabify())},_tabId:function(b){return b.title&&b.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+ ++c},_sanitizeSelector:function(b){return b.replace(/:/g,"\\:")},_cookie:function(){var a=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+ ++d);return b.cookie.apply(null,[a].concat(b.makeArray(arguments)))},_ui:function(b,a){return{tab:b,panel:a,index:this.anchors.index(b)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var a= +b(this);a.html(a.data("label.tabs")).removeData("label.tabs")})},_tabify:function(c){function d(a,c){a.css("display","");!b.support.opacity&&c.opacity&&a[0].style.removeAttribute("filter")}var e=this,f=this.options,i=/^#.+/;this.list=this.element.find("ol,ul").eq(0);this.lis=b(" > li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return b("a",this)[0]});this.panels=b([]);this.anchors.each(function(a,c){var d=b(c).attr("href"),g=d.split("#")[0],h;if(g&&(g===location.toString().split("#")[0]|| +(h=b("base")[0])&&g===h.href))d=c.hash,c.href=d;i.test(d)?e.panels=e.panels.add(e.element.find(e._sanitizeSelector(d))):d&&"#"!==d?(b.data(c,"href.tabs",d),b.data(c,"load.tabs",d.replace(/#.*$/,"")),d=e._tabId(c),c.href="#"+d,g=e.element.find("#"+d),g.length||(g=b(f.panelTemplate).attr("id",d).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(e.panels[a-1]||e.list),g.data("destroy.tabs",!0)),e.panels=e.panels.add(g)):f.disabled.push(a)});c?(this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"), +this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all"),this.lis.addClass("ui-state-default ui-corner-top"),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom"),f.selected===a?(location.hash&&this.anchors.each(function(b,a){if(a.hash==location.hash)return f.selected=b,!1}),"number"!==typeof f.selected&&f.cookie&&(f.selected=parseInt(e._cookie(),10)),"number"!==typeof f.selected&&this.lis.filter(".ui-tabs-selected").length&&(f.selected= +this.lis.index(this.lis.filter(".ui-tabs-selected"))),f.selected=f.selected||(this.lis.length?0:-1)):null===f.selected&&(f.selected=-1),f.selected=0<=f.selected&&this.anchors[f.selected]||0>f.selected?f.selected:0,f.disabled=b.unique(f.disabled.concat(b.map(this.lis.filter(".ui-state-disabled"),function(b){return e.lis.index(b)}))).sort(),-1!=b.inArray(f.selected,f.disabled)&&f.disabled.splice(b.inArray(f.selected,f.disabled),1),this.panels.addClass("ui-tabs-hide"),this.lis.removeClass("ui-tabs-selected ui-state-active"), +0<=f.selected&&this.anchors.length&&(e.element.find(e._sanitizeSelector(e.anchors[f.selected].hash)).removeClass("ui-tabs-hide"),this.lis.eq(f.selected).addClass("ui-tabs-selected ui-state-active"),e.element.queue("tabs",function(){e._trigger("show",null,e._ui(e.anchors[f.selected],e.element.find(e._sanitizeSelector(e.anchors[f.selected].hash))[0]))}),this.load(f.selected)),b(window).bind("unload",function(){e.lis.add(e.anchors).unbind(".tabs");e.lis=e.anchors=e.panels=null})):f.selected=this.lis.index(this.lis.filter(".ui-tabs-selected")); +this.element[f.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible");f.cookie&&this._cookie(f.selected,f.cookie);for(var c=0,j;j=this.lis[c];c++)b(j)[-1!=b.inArray(c,f.disabled)&&!b(j).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");!1===f.cache&&this.anchors.removeData("cache.tabs");this.lis.add(this.anchors).unbind(".tabs");if("mouseover"!==f.event){var k=function(b,a){a.is(":not(.ui-state-disabled)")&&a.addClass("ui-state-"+b)};this.lis.bind("mouseover.tabs", +function(){k("hover",b(this))});this.lis.bind("mouseout.tabs",function(){b(this).removeClass("ui-state-hover")});this.anchors.bind("focus.tabs",function(){k("focus",b(this).closest("li"))});this.anchors.bind("blur.tabs",function(){b(this).closest("li").removeClass("ui-state-focus")})}var l,m;f.fx&&(b.isArray(f.fx)?(l=f.fx[0],m=f.fx[1]):l=m=f.fx);var p=m?function(a,c){b(a).closest("li").addClass("ui-tabs-selected ui-state-active");c.hide().removeClass("ui-tabs-hide").animate(m,m.duration||"normal", +function(){d(c,m);e._trigger("show",null,e._ui(a,c[0]))})}:function(a,c){b(a).closest("li").addClass("ui-tabs-selected ui-state-active");c.removeClass("ui-tabs-hide");e._trigger("show",null,e._ui(a,c[0]))},n=l?function(b,a){a.animate(l,l.duration||"normal",function(){e.lis.removeClass("ui-tabs-selected ui-state-active");a.addClass("ui-tabs-hide");d(a,l);e.element.dequeue("tabs")})}:function(b,a){e.lis.removeClass("ui-tabs-selected ui-state-active");a.addClass("ui-tabs-hide");e.element.dequeue("tabs")}; +this.anchors.bind(f.event+".tabs",function(){var a=this,c=b(a).closest("li"),d=e.panels.filter(":not(.ui-tabs-hide)"),i=e.element.find(e._sanitizeSelector(a.hash));if(c.hasClass("ui-tabs-selected")&&!f.collapsible||c.hasClass("ui-state-disabled")||c.hasClass("ui-state-processing")||e.panels.filter(":animated").length||e._trigger("select",null,e._ui(this,i[0]))===false){this.blur();return false}f.selected=e.anchors.index(this);e.abort();if(f.collapsible){if(c.hasClass("ui-tabs-selected")){f.selected= +-1;f.cookie&&e._cookie(f.selected,f.cookie);e.element.queue("tabs",function(){n(a,d)}).dequeue("tabs");this.blur();return false}if(!d.length){f.cookie&&e._cookie(f.selected,f.cookie);e.element.queue("tabs",function(){p(a,i)});e.load(e.anchors.index(this));this.blur();return false}}f.cookie&&e._cookie(f.selected,f.cookie);if(i.length){d.length&&e.element.queue("tabs",function(){n(a,d)});e.element.queue("tabs",function(){p(a,i)});e.load(e.anchors.index(this))}else throw"jQuery UI Tabs: Mismatching fragment identifier."; +b.browser.msie&&this.blur()});this.anchors.bind("click.tabs",function(){return false})},_getIndex:function(b){"string"==typeof b&&(b=this.anchors.index(this.anchors.filter("[href$="+b+"]")));return b},destroy:function(){var a=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var a= +b.data(this,"href.tabs");a&&(this.href=a);var c=b(this).unbind(".tabs");b.each(["href","load","cache"],function(b,a){c.removeData(a+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){b.data(this,"destroy.tabs")?b(this).remove():b(this).removeClass("ui-state-default ui-corner-top ui-tabs-selected ui-state-active ui-state-hover ui-state-focus ui-state-disabled ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide")});a.cookie&&this._cookie(null,a.cookie);return this},add:function(c, +d,e){e===a&&(e=this.anchors.length);var f=this,i=this.options,d=b(i.tabTemplate.replace(/#\{href\}/g,c).replace(/#\{label\}/g,d)),c=!c.indexOf("#")?c.replace("#",""):this._tabId(b("a",d)[0]);d.addClass("ui-state-default ui-corner-top").data("destroy.tabs",!0);var j=f.element.find("#"+c);j.length||(j=b(i.panelTemplate).attr("id",c).data("destroy.tabs",!0));j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");e>=this.lis.length?(d.appendTo(this.list),j.appendTo(this.list[0].parentNode)): +(d.insertBefore(this.lis[e]),j.insertBefore(this.panels[e]));i.disabled=b.map(i.disabled,function(b){return b>=e?++b:b});this._tabify();1==this.anchors.length&&(i.selected=0,d.addClass("ui-tabs-selected ui-state-active"),j.removeClass("ui-tabs-hide"),this.element.queue("tabs",function(){f._trigger("show",null,f._ui(f.anchors[0],f.panels[0]))}),this.load(0));this._trigger("add",null,this._ui(this.anchors[e],this.panels[e]));return this},remove:function(a){var a=this._getIndex(a),c=this.options,d=this.lis.eq(a).remove(), +f=this.panels.eq(a).remove();d.hasClass("ui-tabs-selected")&&1=a?--b:b});this._tabify();this._trigger("remove",null,this._ui(d.find("a")[0],f[0]));return this},enable:function(a){var a=this._getIndex(a),c=this.options;if(-1!=b.inArray(a,c.disabled))return this.lis.eq(a).removeClass("ui-state-disabled"),c.disabled=b.grep(c.disabled,function(b){return b!= +a}),this._trigger("enable",null,this._ui(this.anchors[a],this.panels[a])),this},disable:function(b){var b=this._getIndex(b),a=this.options;b!=a.selected&&(this.lis.eq(b).addClass("ui-state-disabled"),a.disabled.push(b),a.disabled.sort(),this._trigger("disable",null,this._ui(this.anchors[b],this.panels[b])));return this},select:function(b){b=this._getIndex(b);if(-1==b)if(this.options.collapsible&&-1!=this.options.selected)b=this.options.selected;else return this;this.anchors.eq(b).trigger(this.options.event+ +".tabs");return this},load:function(a){var a=this._getIndex(a),c=this,d=this.options,f=this.anchors.eq(a)[0],i=b.data(f,"load.tabs");this.abort();if(!i||0!==this.element.queue("tabs").length&&b.data(f,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(a).addClass("ui-state-processing");if(d.spinner){var j=b("span",f);j.data("label.tabs",j.html()).html(d.spinner)}this.xhr=b.ajax(b.extend({},d.ajaxOptions,{url:i,success:function(i,j){c.element.find(c._sanitizeSelector(f.hash)).html(i);c._cleanup(); +d.cache&&b.data(f,"cache.tabs",!0);c._trigger("load",null,c._ui(c.anchors[a],c.panels[a]));try{d.ajaxOptions.success(i,j)}catch(m){}},error:function(b,i){c._cleanup();c._trigger("load",null,c._ui(c.anchors[a],c.panels[a]));try{d.ajaxOptions.error(b,i,a,f)}catch(m){}}}));c.element.dequeue("tabs");return this}},abort:function(){this.element.queue([]);this.panels.stop(!1,!0);this.element.queue("tabs",this.element.queue("tabs").splice(-2,2));this.xhr&&(this.xhr.abort(),delete this.xhr);this._cleanup(); +return this},url:function(b,a){this.anchors.eq(b).removeData("cache.tabs").data("load.tabs",a);return this},length:function(){return this.anchors.length}});b.extend(b.ui.tabs,{version:"1.8.14"});b.extend(b.ui.tabs.prototype,{rotation:null,rotate:function(b,a){var c=this,f=this.options,d=c._rotate||(c._rotate=function(a){clearTimeout(c.rotation);c.rotation=setTimeout(function(){var b=f.selected;c.select(++b'))}function d(a){return a.bind("mouseout",function(a){a=b(a.target).closest("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a"); +a.length&&a.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(c){c=b(c.target).closest("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a");if(!b.datepicker._isDisabledDatepicker(e.inline?a.parent()[0]:e.input[0])&&c.length)c.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),c.addClass("ui-state-hover"),c.hasClass("ui-datepicker-prev")&&c.addClass("ui-datepicker-prev-hover"),c.hasClass("ui-datepicker-next")&& +c.addClass("ui-datepicker-next-hover")})}function g(c,d){b.extend(c,d);for(var e in d)if(null==d[e]||d[e]==a)c[e]=d[e];return c}b.extend(b.ui,{datepicker:{version:"1.8.14"}});var h=(new Date).getTime(),e;b.extend(c.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(b){g(this._defaults,b||{});return this},_attachDatepicker:function(a,c){var d=null,e;for(e in this._defaults){var g= +a.getAttribute("date:"+e);if(g){d=d||{};try{d[e]=eval(g)}catch(m){d[e]=g}}}e=a.nodeName.toLowerCase();g="div"==e||"span"==e;a.id||(this.uuid+=1,a.id="dp"+this.uuid);var h=this._newInst(b(a),g);h.settings=b.extend({},c||{},d||{});"input"==e?this._connectDatepicker(a,h):g&&this._inlineDatepicker(a,h)},_newInst:function(a,c){return{id:a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1"),input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:c,dpDiv:!c?this.dpDiv:d(b('
      '))}},_connectDatepicker:function(a,c){var d=b(a);c.append=b([]);c.trigger=b([]);d.hasClass(this.markerClassName)||(this._attachments(d,c),d.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(b,a,f){c.settings[a]=f}).bind("getData.datepicker",function(b,a){return this._get(c,a)}),this._autoSize(c),b.data(a,"datepicker", +c))},_attachments:function(a,c){var d=this._get(c,"appendText"),e=this._get(c,"isRTL");c.append&&c.append.remove();d&&(c.append=b(''+d+""),a[e?"before":"after"](c.append));a.unbind("focus",this._showDatepicker);c.trigger&&c.trigger.remove();d=this._get(c,"showOn");("focus"==d||"both"==d)&&a.focus(this._showDatepicker);if("button"==d||"both"==d){var d=this._get(c,"buttonText"),g=this._get(c,"buttonImage");c.trigger=b(this._get(c,"buttonImageOnly")?b("").addClass(this._triggerClass).attr({src:g, +alt:d,title:d}):b('').addClass(this._triggerClass).html(""==g?d:b("").attr({src:g,alt:d,title:d})));a[e?"before":"after"](c.trigger);c.trigger.click(function(){b.datepicker._datepickerShowing&&b.datepicker._lastInput==a[0]?b.datepicker._hideDatepicker():b.datepicker._showDatepicker(a[0]);return false})}},_autoSize:function(b){if(this._get(b,"autoSize")&&!b.inline){var a=new Date(2009,11,20),c=this._get(b,"dateFormat");if(c.match(/[DM]/)){var d=function(b){for(var a= +0,c=0,f=0;fa&&(a=b[f].length,c=f);return c};a.setMonth(d(this._get(b,c.match(/MM/)?"monthNames":"monthNamesShort")));a.setDate(d(this._get(b,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-a.getDay())}b.input.attr("size",this._formatDate(b,a).length)}},_inlineDatepicker:function(a,c){var d=b(a);d.hasClass(this.markerClassName)||(d.addClass(this.markerClassName).append(c.dpDiv).bind("setData.datepicker",function(b,a,f){c.settings[a]=f}).bind("getData.datepicker",function(b, +a){return this._get(c,a)}),b.data(a,"datepicker",c),this._setDate(c,this._getDefaultDate(c),!0),this._updateDatepicker(c),this._updateAlternate(c),c.dpDiv.show())},_dialogDatepicker:function(a,c,d,e,h){a=this._dialogInst;a||(this.uuid+=1,this._dialogInput=b(''),this._dialogInput.keydown(this._doKeyDown),b("body").append(this._dialogInput),a=this._dialogInst=this._newInst(this._dialogInput,!1), +a.settings={},b.data(this._dialogInput[0],"datepicker",a));g(a.settings,e||{});c=c&&c.constructor==Date?this._formatDate(a,c):c;this._dialogInput.val(c);this._pos=h?h.length?h:[h.pageX,h.pageY]:null;this._pos||(this._pos=[document.documentElement.clientWidth/2-100+(document.documentElement.scrollLeft||document.body.scrollLeft),document.documentElement.clientHeight/2-150+(document.documentElement.scrollTop||document.body.scrollTop)]);this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+ +"px");a.settings.onSelect=d;this._inDialog=!0;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]);b.blockUI&&b.blockUI(this.dpDiv);b.data(this._dialogInput[0],"datepicker",a);return this},_destroyDatepicker:function(a){var c=b(a),d=b.data(a,"datepicker");if(c.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();b.removeData(a,"datepicker");"input"==e?(d.append.remove(),d.trigger.remove(),c.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown", +this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):("div"==e||"span"==e)&&c.removeClass(this.markerClassName).empty()}},_enableDatepicker:function(a){var c=b(a),d=b.data(a,"datepicker");if(c.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if("input"==e)a.disabled=!1,d.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""});else if("div"==e||"span"==e)c=c.children("."+this._inlineClass),c.children().removeClass("ui-state-disabled"), +c.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled");this._disabledInputs=b.map(this._disabledInputs,function(b){return b==a?null:b})}},_disableDatepicker:function(a){var c=b(a),d=b.data(a,"datepicker");if(c.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if("input"==e)a.disabled=!0,d.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"});else if("div"==e||"span"==e)c=c.children("."+this._inlineClass), +c.children().addClass("ui-state-disabled"),c.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled");this._disabledInputs=b.map(this._disabledInputs,function(b){return b==a?null:b});this._disabledInputs[this._disabledInputs.length]=a}},_isDisabledDatepicker:function(b){if(!b)return!1;for(var a=0;ae||!d||-1n&&n>e?Math.abs(c.left+e-n):0);c.top-=Math.min(c.top,c.top+g>q&&q>g?Math.abs(g+p):0);return c},_findPos:function(a){for(var c= +this._get(this._getInst(a),"isRTL");a&&("hidden"==a.type||1!=a.nodeType||b.expr.filters.hidden(a));)a=a[c?"previousSibling":"nextSibling"];a=b(a).offset();return[a.left,a.top]},_triggerOnClose:function(a){var b=this._get(a,"onClose");b&&b.apply(a.input?a.input[0]:null,[a.input?a.input.val():"",a])},_hideDatepicker:function(a){var c=this._curInst;if(c&&!(a&&c!=b.data(a,"datepicker"))&&this._datepickerShowing){var a=this._get(c,"showAnim"),d=this._get(c,"duration"),e=function(){b.datepicker._tidyDialog(c); +this._curInst=null};if(b.effects&&b.effects[a])c.dpDiv.hide(a,b.datepicker._get(c,"showOptions"),d,e);else c.dpDiv["slideDown"==a?"slideUp":"fadeIn"==a?"fadeOut":"hide"](a?d:null,e);a||e();b.datepicker._triggerOnClose(c);this._datepickerShowing=!1;this._lastInput=null;this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),b.blockUI&&(b.unblockUI(),b("body").append(this.dpDiv)));this._inDialog=!1}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")}, +_checkExternalClick:function(a){b.datepicker._curInst&&(a=b(a.target),a[0].id!=b.datepicker._mainDivId&&(0==a.parents("#"+b.datepicker._mainDivId).length&&!a.hasClass(b.datepicker.markerClassName)&&!a.hasClass(b.datepicker._triggerClass)&&b.datepicker._datepickerShowing&&(!b.datepicker._inDialog||!b.blockUI))&&b.datepicker._hideDatepicker())},_adjustDate:function(a,c,d){var a=b(a),e=this._getInst(a[0]);this._isDisabledDatepicker(a[0])||(this._adjustInstDate(e,c+("M"==d?this._get(e,"showCurrentAtPos"): +0),d),this._updateDatepicker(e))},_gotoToday:function(a){var a=b(a),c=this._getInst(a[0]);if(this._get(c,"gotoCurrent")&&c.currentDay)c.selectedDay=c.currentDay,c.drawMonth=c.selectedMonth=c.currentMonth,c.drawYear=c.selectedYear=c.currentYear;else{var d=new Date;c.selectedDay=d.getDate();c.drawMonth=c.selectedMonth=d.getMonth();c.drawYear=c.selectedYear=d.getFullYear()}this._notifyChange(c);this._adjustDate(a)},_selectMonthYear:function(a,c,d){var a=b(a),e=this._getInst(a[0]);e._selectingMonthYear= +!1;e["selected"+("M"==d?"Month":"Year")]=e["draw"+("M"==d?"Month":"Year")]=parseInt(c.options[c.selectedIndex].value,10);this._notifyChange(e);this._adjustDate(a)},_clickMonthYear:function(a){var c=this._getInst(b(a)[0]);c.input&&c._selectingMonthYear&&setTimeout(function(){c.input.focus()},0);c._selectingMonthYear=!c._selectingMonthYear},_selectDay:function(a,c,d,e){var g=b(a);!b(e).hasClass(this._unselectableClass)&&!this._isDisabledDatepicker(g[0])&&(g=this._getInst(g[0]),g.selectedDay=g.currentDay= +b("a",e).html(),g.selectedMonth=g.currentMonth=c,g.selectedYear=g.currentYear=d,this._selectDate(a,this._formatDate(g,g.currentDay,g.currentMonth,g.currentYear)))},_clearDate:function(a){a=b(a);this._getInst(a[0]);this._selectDate(a,"")},_selectDate:function(a,c){var d=this._getInst(b(a)[0]),c=null!=c?c:this._formatDate(d);d.input&&d.input.val(c);this._updateAlternate(d);var e=this._get(d,"onSelect");e?e.apply(d.input?d.input[0]:null,[c,d]):d.input&&d.input.trigger("change");d.inline?this._updateDatepicker(d): +(this._hideDatepicker(),this._lastInput=d.input[0],"object"!=typeof d.input[0]&&d.input.focus(),this._lastInput=null)},_updateAlternate:function(a){var c=this._get(a,"altField");if(c){var d=this._get(a,"altFormat")||this._get(a,"dateFormat"),e=this._getDate(a),g=this.formatDate(d,e,this._getFormatConfig(a));b(c).each(function(){b(this).val(g)})}},noWeekends:function(a){a=a.getDay();return[0a,""]},iso8601Week:function(a){a=new Date(a.getTime());a.setDate(a.getDate()+4-(a.getDay()||7));var b= +a.getTime();a.setMonth(0);a.setDate(1);return Math.floor(Math.round((b-a)/864E5)/7)+1},parseDate:function(a,c,d){if(null==a||null==c)throw"Invalid arguments";c="object"==typeof c?c.toString():c+"";if(""==c)return null;for(var e=(d?d.shortYearCutoff:null)||this._defaults.shortYearCutoff,e="string"!=typeof e?e:(new Date).getFullYear()%100+parseInt(e,10),g=(d?d.dayNamesShort:null)||this._defaults.dayNamesShort,h=(d?d.dayNames:null)||this._defaults.dayNames,p=(d?d.monthNamesShort:null)||this._defaults.monthNamesShort, +n=(d?d.monthNames:null)||this._defaults.monthNames,q=d=-1,o=-1,w=-1,r=!1,u=function(b){(b=E+1d&&(d+=(new Date).getFullYear()-(new Date).getFullYear()%100+(d<=e?0:-100));if(-1b.getYear()%100?"0":"")+b.getYear()%100;break;case "@":o+=b.getTime();break;case "!":o+=1E4*b.getTime()+this._ticksTo1970;break;case "'":h("'")?o+="'":w=!0;break;default:o+=a.charAt(r)}return o},_possibleChars:function(a){for(var b= +"",c=!1,d=function(b){(b=e+1n&&(n+=12,s--);if(u)for(var v=this._daylightSavingAdjust(new Date(u.getFullYear(),u.getMonth()-p[0]*p[1]+1,u.getDate())),v=r&&vv;)n--,0>n&&(n=11,s--);a.drawMonth=n;a.drawYear=s;var v=this._get(a,"prevText"),v=!m?v:this.formatDate(v,this._daylightSavingAdjust(new Date(s,n-q,1)),this._getFormatConfig(a)), +v=this._canAdjustMonth(a,-1,s,n)?''+v+"":g?"":''+v+"",z=this._get(a,"nextText"),z=!m?z:this.formatDate(z,this._daylightSavingAdjust(new Date(s, +n+q,1)),this._getFormatConfig(a)),g=this._canAdjustMonth(a,1,s,n)?''+z+"":g?"":''+z+"",q=this._get(a,"currentText"),z=this._get(a,"gotoCurrent")&& +a.currentDay?w:c,q=!m?q:this.formatDate(q,z,this._getFormatConfig(a)),m=!a.inline?'":"",e=e?'
      '+(d?m:"")+(this._isInRange(a,z)?'":"")+(d?"":m)+"
      ":"",m=parseInt(this._get(a,"firstDay"),10),m=isNaN(m)?0:m,q=this._get(a,"showWeek"),z=this._get(a,"dayNames");this._get(a,"dayNamesShort");var B=this._get(a,"dayNamesMin"),E=this._get(a,"monthNames"),C=this._get(a,"monthNamesShort"),O=this._get(a,"beforeShowDay"),K=this._get(a,"showOtherMonths"),S=this._get(a,"selectOtherMonths");this._get(a,"calculateWeek");for(var P=this._getDefaultDate(a),G="",H=0;H'+(/all|left/.test(A)&& +0==H?d?g:v:"")+(/all|right/.test(A)&&0==H?d?v:g:"")+this._generateMonthYearHeader(a,n,s,r,u,0
      '),D=q?'":"",A=0;7>A;A++)var x=(A+m)%7,D=D+("'+B[x]+"");y+=D+"";D=this._getDaysInMonth(s,n);s==a.selectedYear&&n==a.selectedMonth&&(a.selectedDay=Math.min(a.selectedDay, +D));A=(this._getFirstDayOfMonth(s,n)-m+7)%7;D=Math.ceil((A+D)/7);this.maxRows=D=o?this.maxRows>D?this.maxRows:D:D;for(var x=this._daylightSavingAdjust(new Date(s,n,1-A)),R=0;R",M=!q?"":'",A=0;7>A;A++){var J=O?O.apply(a.input?a.input[0]:null,[x]):[!0,""],F=x.getMonth()!=n,N=F&&!S||!J[0]||r&&xu,M=M+('");x.setDate(x.getDate()+1);x=this._daylightSavingAdjust(x)}y+=M+""}n++;11
      '+this._get(a,"weekHeader")+"
      '+this._get(a,"calculateWeek")(x)+""+(F&&!K? +" ":N?''+x.getDate()+"":''+x.getDate()+"")+"
      "+(o?""+(0':""):"");L+=y}G+=L}G+=e+(b.browser.msie&& +7>parseInt(b.browser.version,10)&&!a.inline?'':"");a._keyEvent=!1;return G},_generateMonthYearHeader:function(a,b,c,d,e,g,p,n){var q=this._get(a,"changeMonth"),o=this._get(a,"changeYear"),w=this._get(a,"showMonthAfterYear"),r='
      ',u="";if(g||!q)u+=''+p[b]+"";else{for(var p=d&&d.getFullYear()==c,s=e&&e.getFullYear()==c,u=u+('"}w||(r+=u+(g||!q||!o?" ":""));if(!a.yearshtml)if(a.yearshtml="",g||!o)r+=''+c+"";else{var n=this._get(a,"yearRange").split(":"),z=(new Date).getFullYear(),p=function(a){a= +a.match(/c[+-].*/)?c+parseInt(a.substring(1),10):a.match(/[+-].*/)?z+parseInt(a,10):parseInt(a,10);return isNaN(a)?z:a},b=p(n[0]),n=Math.max(b,p(n[1]||"")),b=d?Math.max(b,d.getFullYear()):b,n=e?Math.min(n,e.getFullYear()):n;for(a.yearshtml+='";r+=a.yearshtml;a.yearshtml=null}r+=this._get(a,"yearSuffix");w&&(r+=(g||!q||!o?" ":"")+u);return r+"
      "},_adjustInstDate:function(a,b,c){var d=a.drawYear+("Y"==c?b:0),e=a.drawMonth+("M"==c?b:0),b=Math.min(a.selectedDay,this._getDaysInMonth(d,e))+("D"==c?b:0),d=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(d,e,b)));a.selectedDay=d.getDate();a.drawMonth=a.selectedMonth=d.getMonth();a.drawYear=a.selectedYear=d.getFullYear();("M"==c|| +"Y"==c)&&this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max"),c=c&&bd?d:c},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");b&&b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){a=this._get(a,"numberOfMonths");return null==a?[1,1]:"number"==typeof a?[1,a]:a},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a, +b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,d){var e=this._getNumberOfMonths(a),c=this._daylightSavingAdjust(new Date(c,d+(0>b?b:e[0]*e[1]),1));0>b&&c.setDate(this._getDaysInMonth(c.getFullYear(),c.getMonth()));return this._isInRange(a,c)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!d||b.getTime()<= +d.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff"),b="string"!=typeof b?b:(new Date).getFullYear()%100+parseInt(b,10);return{shortYearCutoff:b,dayNamesShort:this._get(a,"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,d){b||(a.currentDay=a.selectedDay,a.currentMonth=a.selectedMonth,a.currentYear=a.selectedYear);b=b?"object"==typeof b?b:this._daylightSavingAdjust(new Date(d, +c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),b,this._getFormatConfig(a))}});b.fn.datepicker=function(a){if(!this.length)return this;b.datepicker.initialized||(b(document).mousedown(b.datepicker._checkExternalClick).find("body").append(b.datepicker.dpDiv),b.datepicker.initialized=!0);var c=Array.prototype.slice.call(arguments,1);return"string"==typeof a&&("isDisabled"==a||"getDate"==a||"widget"==a)||"option"== +a&&2==arguments.length&&"string"==typeof arguments[1]?b.datepicker["_"+a+"Datepicker"].apply(b.datepicker,[this[0]].concat(c)):this.each(function(){typeof a=="string"?b.datepicker["_"+a+"Datepicker"].apply(b.datepicker,[this].concat(c)):b.datepicker._attachDatepicker(this,a)})};b.datepicker=new c;b.datepicker.initialized=!1;b.datepicker.uuid=(new Date).getTime();b.datepicker.version="1.8.14";window["DP_jQuery_"+h]=b})(jQuery); +(function(b,a){b.widget("ui.progressbar",{options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()});this.valueDiv=b("
      ").appendTo(this.element);this.oldValue=this._value();this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"); +this.valueDiv.remove();b.Widget.prototype.destroy.apply(this,arguments)},value:function(b){if(b===a)return this._value();this._setOption("value",b);return this},_setOption:function(a,d){"value"===a&&(this.options.value=d,this._refreshValue(),this._value()===this.options.max&&this._trigger("complete"));b.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var a=this.options.value;"number"!==typeof a&&(a=0);return Math.min(this.options.max,Math.max(this.min,a))},_percentage:function(){return 100* +this._value()/this.options.max},_refreshValue:function(){var a=this.value(),b=this._percentage();this.oldValue!==a&&(this.oldValue=a,this._trigger("change"));this.valueDiv.toggle(a>this.min).toggleClass("ui-corner-right",a===this.options.max).width(b.toFixed(0)+"%");this.element.attr("aria-valuenow",a)}});b.extend(b.ui.progressbar,{version:"1.8.14"})})(jQuery); +jQuery.effects||function(b,a){function c(a){var c;return a&&a.constructor==Array&&3==a.length?a:(c=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(a))?[parseInt(c[1],10),parseInt(c[2],10),parseInt(c[3],10)]:(c=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(a))?[2.55*parseFloat(c[1]),2.55*parseFloat(c[2]),2.55*parseFloat(c[3])]:(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))?[parseInt(c[1],16),parseInt(c[2], +16),parseInt(c[3],16)]:(c=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(a))?[parseInt(c[1]+c[1],16),parseInt(c[2]+c[2],16),parseInt(c[3]+c[3],16)]:/rgba\(0, 0, 0, 0\)/.exec(a)?i.transparent:i[b.trim(a).toLowerCase()]}function d(){var a=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle,b={},c,d;if(a&&a.length&&a[0]&&a[a[0]])for(var e=a.length;e--;)c=a[e],"string"==typeof a[c]&&(d=c.replace(/\-(\w)/g,function(a,b){return b.toUpperCase()}),b[d]=a[c]);else for(c in a)"string"=== +typeof a[c]&&(b[c]=a[c]);return b}function g(a){var c,d;for(c in a)d=a[c],(null==d||b.isFunction(d)||c in k||/scrollbar/.test(c)||!/color/i.test(c)&&isNaN(parseFloat(d)))&&delete a[c];return a}function h(a,b){var c={_:0},d;for(d in b)a[d]!=b[d]&&(c[d]=b[d]);return c}function e(a,c,d,e){"object"==typeof a&&(e=c,d=null,c=a,a=c.effect);b.isFunction(c)&&(e=c,d=null,c={});if("number"==typeof c||b.fx.speeds[c])e=d,d=c,c={};b.isFunction(d)&&(e=d,d=null);c=c||{};d=d||c.duration;d=b.fx.off?0:"number"==typeof d? +d:d in b.fx.speeds?b.fx.speeds[d]:b.fx.speeds._default;e=e||c.complete;return[a,c,d,e]}function f(a){return!a||("number"===typeof a||b.fx.speeds[a])||"string"===typeof a&&!b.effects[a]?!0:!1}b.effects={};b.each("backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor borderColor color outlineColor".split(" "),function(a,d){b.fx.step[d]=function(a){if(!a.colorInit){var e;e=a.elem;var f=d,g;do{g=b.curCSS(e,f);if(g!=""&&g!="transparent"||b.nodeName(e,"body"))break;f="backgroundColor"}while(e= +e.parentNode);e=c(g);a.start=e;a.end=c(a.end);a.colorInit=true}a.elem.style[d]="rgb("+Math.max(Math.min(parseInt(a.pos*(a.end[0]-a.start[0])+a.start[0],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[1]-a.start[1])+a.start[1],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[2]-a.start[2])+a.start[2],10),255),0)+")"}});var i={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139], +darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255], +maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},j=["add","remove","toggle"],k={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};b.effects.animateClass=function(a,c,e,f){b.isFunction(e)&&(f=e,e=null);return this.queue(function(){var i=b(this),o=i.attr("style")|| +" ",k=g(d.call(this)),r,u=i.attr("class");b.each(j,function(b,c){if(a[c])i[c+"Class"](a[c])});r=g(d.call(this));i.attr("class",u);i.animate(h(k,r),{queue:false,duration:c,easing:e,complete:function(){b.each(j,function(b,c){if(a[c])i[c+"Class"](a[c])});if(typeof i.attr("style")=="object"){i.attr("style").cssText="";i.attr("style").cssText=o}else i.attr("style",o);f&&f.apply(this,arguments);b.dequeue(this)}})})};b.fn.extend({_addClass:b.fn.addClass,addClass:function(a,c,d,e){return c?b.effects.animateClass.apply(this, +[{add:a},c,d,e]):this._addClass(a)},_removeClass:b.fn.removeClass,removeClass:function(a,c,d,e){return c?b.effects.animateClass.apply(this,[{remove:a},c,d,e]):this._removeClass(a)},_toggleClass:b.fn.toggleClass,toggleClass:function(c,d,e,f,g){return"boolean"==typeof d||d===a?e?b.effects.animateClass.apply(this,[d?{add:c}:{remove:c},e,f,g]):this._toggleClass(c,d):b.effects.animateClass.apply(this,[{toggle:c},d,e,f])},switchClass:function(a,c,d,e,f){return b.effects.animateClass.apply(this,[{add:c, +remove:a},d,e,f])}});b.extend(b.effects,{version:"1.8.14",save:function(a,b){for(var c=0;c").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0});a.wrap(d);d=a.parent();"static"==a.css("position")?(d.css({position:"relative"}),a.css({position:"relative"})): +(b.extend(c,{position:a.css("position"),zIndex:a.css("z-index")}),b.each(["top","left","bottom","right"],function(b,d){c[d]=a.css(d);isNaN(parseInt(c[d],10))&&(c[d]="auto")}),a.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"}));return d.css(c).show()},removeWrapper:function(a){return a.parent().is(".ui-effects-wrapper")?a.parent().replaceWith(a):a},setTransition:function(a,c,d,e){e=e||{};b.each(c,function(b,c){unit=a.cssUnit(c);0(b/=e/2)?d/2*b*b+c:-d/2*(--b*(b-2)-1)+c},easeInCubic:function(a,b,c,d,e){return d*(b/=e)*b*b+c},easeOutCubic:function(a,b,c,d,e){return d*((b=b/e-1)*b*b+1)+c},easeInOutCubic:function(a,b,c,d,e){return 1>(b/=e/2)?d/2*b*b*b+c:d/2*((b-=2)*b*b+2)+c},easeInQuart:function(a,b,c,d,e){return d*(b/=e)*b*b*b+c}, +easeOutQuart:function(a,b,c,d,e){return-d*((b=b/e-1)*b*b*b-1)+c},easeInOutQuart:function(a,b,c,d,e){return 1>(b/=e/2)?d/2*b*b*b*b+c:-d/2*((b-=2)*b*b*b-2)+c},easeInQuint:function(a,b,c,d,e){return d*(b/=e)*b*b*b*b+c},easeOutQuint:function(a,b,c,d,e){return d*((b=b/e-1)*b*b*b*b+1)+c},easeInOutQuint:function(a,b,c,d,e){return 1>(b/=e/2)?d/2*b*b*b*b*b+c:d/2*((b-=2)*b*b*b*b+2)+c},easeInSine:function(a,b,c,d,e){return-d*Math.cos(b/e*(Math.PI/2))+d+c},easeOutSine:function(a,b,c,d,e){return d*Math.sin(b/ +e*(Math.PI/2))+c},easeInOutSine:function(a,b,c,d,e){return-d/2*(Math.cos(Math.PI*b/e)-1)+c},easeInExpo:function(a,b,c,d,e){return 0==b?c:d*Math.pow(2,10*(b/e-1))+c},easeOutExpo:function(a,b,c,d,e){return b==e?c+d:d*(-Math.pow(2,-10*b/e)+1)+c},easeInOutExpo:function(a,b,c,d,e){return 0==b?c:b==e?c+d:1>(b/=e/2)?d/2*Math.pow(2,10*(b-1))+c:d/2*(-Math.pow(2,-10*--b)+2)+c},easeInCirc:function(a,b,c,d,e){return-d*(Math.sqrt(1-(b/=e)*b)-1)+c},easeOutCirc:function(a,b,c,d,e){return d*Math.sqrt(1-(b=b/e-1)* +b)+c},easeInOutCirc:function(a,b,c,d,e){return 1>(b/=e/2)?-d/2*(Math.sqrt(1-b*b)-1)+c:d/2*(Math.sqrt(1-(b-=2)*b)+1)+c},easeInElastic:function(a,b,c,d,e){var a=1.70158,f=0,g=d;if(0==b)return c;if(1==(b/=e))return c+d;f||(f=0.3*e);gb?-0.5*g*Math.pow(2,10*(b-=1))*Math.sin((b*e-a)*2*Math.PI/f)+c:0.5*g*Math.pow(2,-10*(b-=1))*Math.sin((b*e-a)*2*Math.PI/f)+d+c},easeInBack:function(b,c,d,e,f,g){g==a&&(g=1.70158);return e*(c/=f)*c*((g+1)*c-g)+d},easeOutBack:function(b,c,d,e, +f,g){g==a&&(g=1.70158);return e*((c=c/f-1)*c*((g+1)*c+g)+1)+d},easeInOutBack:function(b,c,d,e,f,g){g==a&&(g=1.70158);return 1>(c/=f/2)?e/2*c*c*(((g*=1.525)+1)*c-g)+d:e/2*((c-=2)*c*(((g*=1.525)+1)*c+g)+2)+d},easeInBounce:function(a,c,d,e,f){return e-b.easing.easeOutBounce(a,f-c,0,e,f)+d},easeOutBounce:function(a,b,c,d,e){return(b/=e)<1/2.75?d*7.5625*b*b+c:b<2/2.75?d*(7.5625*(b-=1.5/2.75)*b+0.75)+c:b<2.5/2.75?d*(7.5625*(b-=2.25/2.75)*b+0.9375)+c:d*(7.5625*(b-=2.625/2.75)*b+0.984375)+c},easeInOutBounce:function(a, +c,d,e,f){return c").css({position:"absolute",visibility:"visible",left:-j*(e/d),top:-i*(f/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:e/d,height:f/c,left:h.left+j*(e/d)+("show"==a.options.mode?(j-Math.floor(d/2))*(e/d):0),top:h.top+i*(f/c)+("show"==a.options.mode?(i-Math.floor(c/2))*(f/c):0),opacity:"show"==a.options.mode?0:1}).animate({left:h.left+j*(e/d)+("show"==a.options.mode?0:(j-Math.floor(d/2))*(e/d)),top:h.top+ +i*(f/c)+("show"==a.options.mode?0:(i-Math.floor(c/2))*(f/c)),opacity:"show"==a.options.mode?1:0},a.duration||500);setTimeout(function(){"show"==a.options.mode?g.css({visibility:"visible"}):g.css({visibility:"visible"}).hide();a.callback&&a.callback.apply(g[0]);g.dequeue();b("div.ui-effects-explode").remove()},a.duration||500)})}})(jQuery); +(function(b){b.effects.fade=function(a){return this.queue(function(){var c=b(this),d=b.effects.setMode(c,a.options.mode||"hide");c.animate({opacity:d},{queue:!1,duration:a.duration,easing:a.options.easing,complete:function(){a.callback&&a.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery); +(function(b){b.effects.fold=function(a){return this.queue(function(){var c=b(this),d=["position","top","bottom","left","right"],g=b.effects.setMode(c,a.options.mode||"hide"),h=a.options.size||15,e=!!a.options.horizFirst,f=a.duration?a.duration/2:b.fx.speeds._default/2;b.effects.save(c,d);c.show();var i=b.effects.createWrapper(c).css({overflow:"hidden"}),j="show"==g!=e,k=j?["width","height"]:["height","width"],j=j?[i.width(),i.height()]:[i.height(),i.width()],l=/([0-9]+)%/.exec(h);l&&(h=parseInt(l[1], +10)/100*j["hide"==g?0:1]);"show"==g&&i.css(e?{height:0,width:h}:{height:h,width:0});e={};l={};e[k[0]]="show"==g?j[0]:h;l[k[1]]="show"==g?j[1]:0;i.animate(e,f,a.options.easing).animate(l,f,a.options.easing,function(){"hide"==g&&c.hide();b.effects.restore(c,d);b.effects.removeWrapper(c);a.callback&&a.callback.apply(c[0],arguments);c.dequeue()})})}})(jQuery); +(function(b){b.effects.highlight=function(a){return this.queue(function(){var c=b(this),d=["backgroundImage","backgroundColor","opacity"],g=b.effects.setMode(c,a.options.mode||"show"),h={backgroundColor:c.css("backgroundColor")};"hide"==g&&(h.opacity=0);b.effects.save(c,d);c.show().css({backgroundImage:"none",backgroundColor:a.options.color||"#ffff99"}).animate(h,{queue:!1,duration:a.duration,easing:a.options.easing,complete:function(){g=="hide"&&c.hide();b.effects.restore(c,d);g=="show"&&!b.support.opacity&& +this.style.removeAttribute("filter");a.callback&&a.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery); +(function(b){b.effects.pulsate=function(a){return this.queue(function(){var c=b(this),d=b.effects.setMode(c,a.options.mode||"show");times=2*(a.options.times||5)-1;duration=a.duration?a.duration/2:b.fx.speeds._default/2;isVisible=c.is(":visible");animateTo=0;isVisible||(c.css("opacity",0).show(),animateTo=1);("hide"==d&&isVisible||"show"==d&&!isVisible)&×--;for(d=0;d').appendTo(document.body).addClass(a.options.className).css({top:g.top,left:g.left,height:c.innerHeight(),width:c.innerWidth(),position:"absolute"}).animate(d,a.duration,a.options.easing,function(){h.remove();a.callback&&a.callback.apply(c[0],arguments);c.dequeue()})})}})(jQuery); +/* + * jQuery Highlight plugin + * Based on highlight v3 by Johann Burkard + * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html + * Copyright (c) 2009 Bartek Szopka http://bartaz.github.com/sandbox.js/jquery.highlight.html + * Licensed under MIT license. + */ +jQuery.extend({highlight:function(a,c,b,e){if(a.nodeType===3){if(c=a.data.match(c)){b=document.createElement(b||"span");b.className=e||"highlight";a=a.splitText(c.index);a.splitText(c[0].length);e=a.cloneNode(true);b.appendChild(e);a.parentNode.replaceChild(b,a);return 1}}else if(a.nodeType===1&&a.childNodes&&!/(script|style)/i.test(a.tagName)&&!(a.tagName===b.toUpperCase()&&a.className===e))for(var d=0;d').appendTo("body"); + var d = { width: $c.width() - $c[0].clientWidth, height: $c.height() - $c[0].clientHeight }; + $c.remove(); + window.scrollbarWidth = d.width; + window.scrollbarHeight = d.height; + return dim.match(/^(width|height)$/) ? d[dim] : d; + } + + + /** + * Returns hash container 'display' and 'visibility' + * + * @see $.swap() - swaps CSS, runs callback, resets CSS + */ +, showInvisibly: function ($E, force) { + if (!$E) return {}; + if (!$E.jquery) $E = $($E); + var CSS = { + display: $E.css('display') + , visibility: $E.css('visibility') + }; + if (force || CSS.display === "none") { // only if not *already hidden* + $E.css({ display: "block", visibility: "hidden" }); // show element 'invisibly' so can be measured + return CSS; + } + else return {}; + } + + /** + * Returns data for setting size of an element (container or a pane). + * + * @see _create(), onWindowResize() for container, plus others for pane + * @return JSON Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc + */ +, getElementDimensions: function ($E) { + var + d = {} // dimensions hash + , x = d.css = {} // CSS hash + , i = {} // TEMP insets + , b, p // TEMP border, padding + , N = $.layout.cssNum + , off = $E.offset() + ; + d.offsetLeft = off.left; + d.offsetTop = off.top; + + $.each("Left,Right,Top,Bottom".split(","), function (idx, e) { // e = edge + b = x["border" + e] = $.layout.borderWidth($E, e); + p = x["padding"+ e] = $.layout.cssNum($E, "padding"+e); + i[e] = b + p; // total offset of content from outer side + d["inset"+ e] = p; + }); + + d.offsetWidth = $E.innerWidth(); // offsetWidth is used in calc when doing manual resize + d.offsetHeight = $E.innerHeight(); // ditto + d.outerWidth = $E.outerWidth(); + d.outerHeight = $E.outerHeight(); + d.innerWidth = max(0, d.outerWidth - i.Left - i.Right); + d.innerHeight = max(0, d.outerHeight - i.Top - i.Bottom); + + x.width = $E.width(); + x.height = $E.height(); + x.top = N($E,"top",true); + x.bottom = N($E,"bottom",true); + x.left = N($E,"left",true); + x.right = N($E,"right",true); + + //d.visible = $E.is(":visible");// && x.width > 0 && x.height > 0; + + return d; + } + +, getElementCSS: function ($E, list) { + var + CSS = {} + , style = $E[0].style + , props = list.split(",") + , sides = "Top,Bottom,Left,Right".split(",") + , attrs = "Color,Style,Width".split(",") + , p, s, a, i, j, k + ; + for (i=0; i < props.length; i++) { + p = props[i]; + if (p.match(/(border|padding|margin)$/)) + for (j=0; j < 4; j++) { + s = sides[j]; + if (p === "border") + for (k=0; k < 3; k++) { + a = attrs[k]; + CSS[p+s+a] = style[p+s+a]; + } + else + CSS[p+s] = style[p+s]; + } + else + CSS[p] = style[p]; + }; + return CSS + } + + /** + * Return the innerWidth for the current browser/doctype + * + * @see initPanes(), sizeMidPanes(), initHandles(), sizeHandles() + * @param {Array.} $E Must pass a jQuery object - first element is processed + * @param {number=} outerWidth (optional) Can pass a width, allowing calculations BEFORE element is resized + * @return {number} Returns the innerWidth of the elem by subtracting padding and borders + */ +, cssWidth: function ($E, outerWidth) { + var + b = $.layout.borderWidth + , n = $.layout.cssNum + ; + // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed + if (outerWidth <= 0) return 0; + + if (!$.support.boxModel) return outerWidth; + + // strip border and padding from outerWidth to get CSS Width + var W = outerWidth + - b($E, "Left") + - b($E, "Right") + - n($E, "paddingLeft") + - n($E, "paddingRight") + ; + + return max(0,W); + } + + /** + * Return the innerHeight for the current browser/doctype + * + * @see initPanes(), sizeMidPanes(), initHandles(), sizeHandles() + * @param {Array.} $E Must pass a jQuery object - first element is processed + * @param {number=} outerHeight (optional) Can pass a width, allowing calculations BEFORE element is resized + * @return {number} Returns the innerHeight of the elem by subtracting padding and borders + */ +, cssHeight: function ($E, outerHeight) { + var + b = $.layout.borderWidth + , n = $.layout.cssNum + ; + // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed + if (outerHeight <= 0) return 0; + + if (!$.support.boxModel) return outerHeight; + + // strip border and padding from outerHeight to get CSS Height + var H = outerHeight + - b($E, "Top") + - b($E, "Bottom") + - n($E, "paddingTop") + - n($E, "paddingBottom") + ; + + return max(0,H); + } + + /** + * Returns the 'current CSS numeric value' for a CSS property - 0 if property does not exist + * + * @see Called by many methods + * @param {Array.} $E Must pass a jQuery object - first element is processed + * @param {string} prop The name of the CSS property, eg: top, width, etc. + * @param {boolean=} [allowAuto=false] true = return 'auto' if that is value; false = return 0 + * @return {(string|number)} Usually used to get an integer value for position (top, left) or size (height, width) + */ +, cssNum: function ($E, prop, allowAuto) { + if (!$E.jquery) $E = $($E); + var CSS = $.layout.showInvisibly($E) + , p = $.curCSS($E[0], prop, true) + , v = allowAuto && p=="auto" ? p : (parseInt(p, 10) || 0); + $E.css( CSS ); // RESET + return v; + } + +, borderWidth: function (el, side) { + if (el.jquery) el = el[0]; + var b = "border"+ side.substr(0,1).toUpperCase() + side.substr(1); // left => Left + return $.curCSS(el, b+"Style", true) === "none" ? 0 : (parseInt($.curCSS(el, b+"Width", true), 10) || 0); + } + + /** + * Mouse-tracking utility - FUTURE REFERENCE + * + * init: if (!window.mouse) { + * window.mouse = { x: 0, y: 0 }; + * $(document).mousemove( $.layout.trackMouse ); + * } + * + * @param {Object} evt + * +, trackMouse: function (evt) { + window.mouse = { x: evt.clientX, y: evt.clientY }; + } + */ + + /** + * SUBROUTINE for preventPrematureSlideClose option + * + * @param {Object} evt + * @param {Object=} el + */ +, isMouseOverElem: function (evt, el) { + var + $E = $(el || this) + , d = $E.offset() + , T = d.top + , L = d.left + , R = L + $E.outerWidth() + , B = T + $E.outerHeight() + , x = evt.pageX // evt.clientX ? + , y = evt.pageY // evt.clientY ? + ; + // if X & Y are < 0, probably means is over an open SELECT + return ($.layout.browser.msie && x < 0 && y < 0) || ((x >= L && x <= R) && (y >= T && y <= B)); + } + + /** + * Message/Logging Utility + * + * @example $.layout.msg("My message"); // log text + * @example $.layout.msg("My message", true); // alert text + * @example $.layout.msg({ foo: "bar" }, "Title"); // log hash-data, with custom title + * @example $.layout.msg({ foo: "bar" }, true, "Title", { sort: false }); -OR- + * @example $.layout.msg({ foo: "bar" }, "Title", { sort: false, display: true }); // alert hash-data + * + * @param {(Object|string)} info String message OR Hash/Array + * @param {(Boolean|string|Object)=} [popup=false] True means alert-box - can be skipped + * @param {(Object|string)=} [debugTitle=""] Title for Hash data - can be skipped + * @param {Object=} [debutOpts={}] Extra options for debug output + */ +, msg: function (info, popup, debugTitle, debugOpts) { + if ($.isPlainObject(info) && window.debugData) { + if (typeof popup === "string") { + debugOpts = debugTitle; + debugTitle = popup; + } + else if (typeof debugTitle === "object") { + debugOpts = debugTitle; + debugTitle = null; + } + var t = debugTitle || "log( )" + , o = $.extend({ sort: false, returnHTML: false, display: false }, debugOpts); + if (popup === true || o.display) + debugData( info, t, o ); + else if (window.console) + console.log(debugData( info, t, o )); + } + else if (popup) + alert(info); + else if (window.console) + console.log(info); + else { + var id = "#layoutLogger" + , $l = $(id); + if (!$l.length) + $l = createLog(); + $l.children("ul").append('
    • '+ info.replace(/\/g,">") +'
    • '); + } + + function createLog () { + var pos = $.support.fixedPosition ? 'fixed' : 'absolute' + , $e = $('
      ' + + '
      ' + + 'XLayout console.log
      ' + + '
        ' + + '
        ' + ).appendTo("body"); + $e.css('left', $(window).width() - $e.outerWidth() - 5) + if ($.ui.draggable) $e.draggable({ handle: ':first-child' }); + return $e; + }; + } + +}; + +var lang = $.layout.language; // alias used in defaults... + +// DEFAULT OPTIONS - CHANGE IF DESIRED +$.layout.defaults = { +/* + * LAYOUT & LAYOUT-CONTAINER OPTIONS + * - none of these options are applicable to individual panes + */ + name: "" // Not required, but useful for buttons and used for the state-cookie +, containerSelector: "" // ONLY used when specifying a childOptions - to find container-element that is NOT directly-nested +, containerClass: "ui-layout-container" // layout-container element +, scrollToBookmarkOnLoad: true // after creating a layout, scroll to bookmark in URL (.../page.htm#myBookmark) +, resizeWithWindow: true // bind thisLayout.resizeAll() to the window.resize event +, resizeWithWindowDelay: 200 // delay calling resizeAll because makes window resizing very jerky +, resizeWithWindowMaxDelay: 0 // 0 = none - force resize every XX ms while window is being resized +, onresizeall_start: null // CALLBACK when resizeAll() STARTS - NOT pane-specific +, onresizeall_end: null // CALLBACK when resizeAll() ENDS - NOT pane-specific +, onload_start: null // CALLBACK when Layout inits - after options initialized, but before elements +, onload_end: null // CALLBACK when Layout inits - after EVERYTHING has been initialized +, onunload_start: null // CALLBACK when Layout is destroyed OR onWindowUnload +, onunload_end: null // CALLBACK when Layout is destroyed OR onWindowUnload +, autoBindCustomButtons: false // search for buttons with ui-layout-button class and auto-bind them +, initPanes: true // false = DO NOT initialize the panes onLoad - will init later +, showErrorMessages: true // enables fatal error messages to warn developers of common errors +, showDebugMessages: false // display console-and-alert debug msgs - IF this Layout version _has_ debugging code! +// Changing this zIndex value will cause other zIndex values to automatically change +, zIndex: null // the PANE zIndex - resizers and masks will be +1 +// DO NOT CHANGE the zIndex values below unless you clearly understand their relationships +, zIndexes: { // set _default_ z-index values here... + pane_normal: 0 // normal z-index for panes + , content_mask: 1 // applied to overlays used to mask content INSIDE panes during resizing + , resizer_normal: 2 // normal z-index for resizer-bars + , pane_sliding: 100 // applied to *BOTH* the pane and its resizer when a pane is 'slid open' + , pane_animate: 1000 // applied to the pane when being animated - not applied to the resizer + , resizer_drag: 10000 // applied to the CLONED resizer-bar when being 'dragged' + } +/* + * PANE DEFAULT SETTINGS + * - settings under the 'panes' key become the default settings for *all panes* + * - ALL pane-options can also be set specifically for each panes, which will override these 'default values' + */ +, panes: { // default options for 'all panes' - will be overridden by 'per-pane settings' + applyDemoStyles: false // NOTE: renamed from applyDefaultStyles for clarity + , closable: true // pane can open & close + , resizable: true // when open, pane can be resized + , slidable: true // when closed, pane can 'slide open' over other panes - closes on mouse-out + , initClosed: false // true = init pane as 'closed' + , initHidden: false // true = init pane as 'hidden' - no resizer-bar/spacing + // SELECTORS + //, paneSelector: "" // MUST be pane-specific - jQuery selector for pane + , contentSelector: ".ui-layout-content" // INNER div/element to auto-size so only it scrolls, not the entire pane! + , contentIgnoreSelector: ".ui-layout-ignore" // element(s) to 'ignore' when measuring 'content' + , findNestedContent: false // true = $P.find(contentSelector), false = $P.children(contentSelector) + // GENERIC ROOT-CLASSES - for auto-generated classNames + , paneClass: "ui-layout-pane" // Layout Pane + , resizerClass: "ui-layout-resizer" // Resizer Bar + , togglerClass: "ui-layout-toggler" // Toggler Button + , buttonClass: "ui-layout-button" // CUSTOM Buttons - eg: '[ui-layout-button]-toggle/-open/-close/-pin' + // ELEMENT SIZE & SPACING + //, size: 100 // MUST be pane-specific -initial size of pane + , minSize: 0 // when manually resizing a pane + , maxSize: 0 // ditto, 0 = no limit + , spacing_open: 6 // space between pane and adjacent panes - when pane is 'open' + , spacing_closed: 6 // ditto - when pane is 'closed' + , togglerLength_open: 50 // Length = WIDTH of toggler button on north/south sides - HEIGHT on east/west sides + , togglerLength_closed: 50 // 100% OR -1 means 'full height/width of resizer bar' - 0 means 'hidden' + , togglerAlign_open: "center" // top/left, bottom/right, center, OR... + , togglerAlign_closed: "center" // 1 => nn = offset from top/left, -1 => -nn == offset from bottom/right + , togglerTip_open: lang.Close // Toggler tool-tip (title) + , togglerTip_closed: lang.Open // ditto + , togglerContent_open: "" // text or HTML to put INSIDE the toggler + , togglerContent_closed: "" // ditto + // RESIZING OPTIONS + , resizerDblClickToggle: true // + , autoResize: true // IF size is 'auto' or a percentage, then recalc 'pixel size' whenever the layout resizes + , autoReopen: true // IF a pane was auto-closed due to noRoom, reopen it when there is room? False = leave it closed + , resizerDragOpacity: 1 // option for ui.draggable + //, resizerCursor: "" // MUST be pane-specific - cursor when over resizer-bar + , maskContents: false // true = add DIV-mask over-or-inside this pane so can 'drag' over IFRAMES + , maskObjects: false // true = add IFRAME-mask over-or-inside this pane to cover objects/applets - content-mask will overlay this mask + , maskZindex: null // will override zIndexes.content_mask if specified - not applicable to iframe-panes + , resizingGrid: false // grid size that the resizers will snap-to during resizing, eg: [20,20] + , livePaneResizing: false // true = LIVE Resizing as resizer is dragged + , liveContentResizing: false // true = re-measure header/footer heights as resizer is dragged + , liveResizingTolerance: 1 // how many px change before pane resizes, to control performance + // TIPS & MESSAGES - also see lang object + , noRoomToOpenTip: lang.noRoomToOpenTip + , resizerTip: lang.Resize // Resizer tool-tip (title) + , sliderTip: lang.Slide // resizer-bar triggers 'sliding' when pane is closed + , sliderCursor: "pointer" // cursor when resizer-bar will trigger 'sliding' + , slideTrigger_open: "click" // click, dblclick, mouseenter + , slideTrigger_close: "mouseleave"// click, mouseleave + , slideDelay_open: 300 // applies only for mouseenter event - 0 = instant open + , slideDelay_close: 300 // applies only for mouseleave event (300ms is the minimum!) + , hideTogglerOnSlide: false // when pane is slid-open, should the toggler show? + , preventQuickSlideClose: $.layout.browser.webkit // Chrome triggers slideClosed as it is opening + , preventPrematureSlideClose: false // handle incorrect mouseleave trigger, like when over a SELECT-list in IE + // HOT-KEYS & MISC + , showOverflowOnHover: false // will bind allowOverflow() utility to pane.onMouseOver + , enableCursorHotkey: true // enabled 'cursor' hotkeys + //, customHotkey: "" // MUST be pane-specific - EITHER a charCode OR a character + , customHotkeyModifier: "SHIFT" // either 'SHIFT', 'CTRL' or 'CTRL+SHIFT' - NOT 'ALT' + // PANE ANIMATION + // NOTE: fxSss_open, fxSss_close & fxSss_size options (eg: fxName_open) are auto-generated if not passed + , fxName: "slide" // ('none' or blank), slide, drop, scale -- only relevant to 'open' & 'close', NOT 'size' + , fxSpeed: null // slow, normal, fast, 200, nnn - if passed, will OVERRIDE fxSettings.duration + , fxSettings: {} // can be passed, eg: { easing: "easeOutBounce", duration: 1500 } + , fxOpacityFix: true // tries to fix opacity in IE to restore anti-aliasing after animation + , animatePaneSizing: false // true = animate resizing after dragging resizer-bar OR sizePane() is called + /* NOTE: Action-specific FX options are auto-generated from the options above if not specifically set: + fxName_open: "slide" // 'Open' pane animation + fnName_close: "slide" // 'Close' pane animation + fxName_size: "slide" // 'Size' pane animation - when animatePaneSizing = true + fxSpeed_open: null + fxSpeed_close: null + fxSpeed_size: null + fxSettings_open: {} + fxSettings_close: {} + fxSettings_size: {} + */ + // CHILD/NESTED LAYOUTS + , childOptions: null // Layout-options for nested/child layout - even {} is valid as options + , initChildLayout: true // true = child layout will be created as soon as _this_ layout completes initialization + , destroyChildLayout: true // true = destroy child-layout if this pane is destroyed + , resizeChildLayout: true // true = trigger child-layout.resizeAll() when this pane is resized + // PANE CALLBACKS + , triggerEventsOnLoad: false // true = trigger onopen OR onclose callbacks when layout initializes + , triggerEventsDuringLiveResize: true // true = trigger onresize callback REPEATEDLY if livePaneResizing==true + , onshow_start: null // CALLBACK when pane STARTS to Show - BEFORE onopen/onhide_start + , onshow_end: null // CALLBACK when pane ENDS being Shown - AFTER onopen/onhide_end + , onhide_start: null // CALLBACK when pane STARTS to Close - BEFORE onclose_start + , onhide_end: null // CALLBACK when pane ENDS being Closed - AFTER onclose_end + , onopen_start: null // CALLBACK when pane STARTS to Open + , onopen_end: null // CALLBACK when pane ENDS being Opened + , onclose_start: null // CALLBACK when pane STARTS to Close + , onclose_end: null // CALLBACK when pane ENDS being Closed + , onresize_start: null // CALLBACK when pane STARTS being Resized ***FOR ANY REASON*** + , onresize_end: null // CALLBACK when pane ENDS being Resized ***FOR ANY REASON*** + , onsizecontent_start: null // CALLBACK when sizing of content-element STARTS + , onsizecontent_end: null // CALLBACK when sizing of content-element ENDS + , onswap_start: null // CALLBACK when pane STARTS to Swap + , onswap_end: null // CALLBACK when pane ENDS being Swapped + , ondrag_start: null // CALLBACK when pane STARTS being ***MANUALLY*** Resized + , ondrag_end: null // CALLBACK when pane ENDS being ***MANUALLY*** Resized + } +/* + * PANE-SPECIFIC SETTINGS + * - options listed below MUST be specified per-pane - they CANNOT be set under 'panes' + * - all options under the 'panes' key can also be set specifically for any pane + * - most options under the 'panes' key apply only to 'border-panes' - NOT the the center-pane + */ +, north: { + paneSelector: ".ui-layout-north" + , size: "auto" // eg: "auto", "30%", .30, 200 + , resizerCursor: "n-resize" // custom = url(myCursor.cur) + , customHotkey: "" // EITHER a charCode (43) OR a character ("o") + } +, south: { + paneSelector: ".ui-layout-south" + , size: "auto" + , resizerCursor: "s-resize" + , customHotkey: "" + } +, east: { + paneSelector: ".ui-layout-east" + , size: 200 + , resizerCursor: "e-resize" + , customHotkey: "" + } +, west: { + paneSelector: ".ui-layout-west" + , size: 200 + , resizerCursor: "w-resize" + , customHotkey: "" + } +, center: { + paneSelector: ".ui-layout-center" + , minWidth: 0 + , minHeight: 0 + } +}; + +$.layout.optionsMap = { + // layout/global options - NOT pane-options + layout: ("stateManagement,effects,zIndexes," + + "name,zIndex,scrollToBookmarkOnLoad,showErrorMessages," + + "resizeWithWindow,resizeWithWindowDelay,resizeWithWindowMaxDelay," + + "onresizeall,onresizeall_start,onresizeall_end,onload,onunload,autoBindCustomButtons").split(",") +// borderPanes: [ ALL options that are NOT specified as 'layout' ] + // default.panes options that apply to the center-pane (most options apply _only_ to border-panes) +, center: ("paneClass,contentSelector,contentIgnoreSelector,findNestedContent,applyDemoStyles,triggerEventsOnLoad," + + "showOverflowOnHover,maskContents,maskObjects,liveContentResizing," + + "childOptions,initChildLayout,resizeChildLayout,destroyChildLayout," + + "onresize,onresize_start,onresize_end,onsizecontent,onsizecontent_start,onsizecontent_end").split(",") + // options that MUST be specifically set 'per-pane' - CANNOT set in the panes (defaults) key +, noDefault: ("paneSelector,resizerCursor,customHotkey").split(",") +}; + +/** + * Processes options passed in converts flat-format data into subkey (JSON) format + * In flat-format, subkeys are _currently_ separated with 2 underscores, like north__optName + * Plugins may also call this method so they can transform their own data + * + * @param {!Object} hash Data/options passed by user - may be a single level or nested levels + * @return {Object} Returns hash of minWidth & minHeight + */ +$.layout.transformData = function (hash) { + var json = { panes: {}, center: {} } // init return object + , data, branch, optKey, keys, key, val, i, c; + + if (typeof hash !== "object") return json; // no options passed + + // convert all 'flat-keys' to 'sub-key' format + for (optKey in hash) { + branch = json; + data = $.layout.optionsMap.layout; + val = hash[ optKey ]; + keys = optKey.split("__"); // eg: west__size or north__fxSettings__duration + c = keys.length - 1; + // convert underscore-delimited to subkeys + for (i=0; i <= c; i++) { + key = keys[i]; + if (i === c) + branch[key] = val; + else if (!branch[key]) + branch[key] = {}; // create the subkey + // recurse to sub-key for next loop - if not done + branch = branch[key]; + } + } + + return json; +} + +// INTERNAL CONFIG DATA - DO NOT CHANGE THIS! +$.layout.backwardCompatibility = { + // data used by renameOldOptions() + map: { + // OLD Option Name: NEW Option Name + applyDefaultStyles: "applyDemoStyles" + , resizeNestedLayout: "resizeChildLayout" + , resizeWhileDragging: "livePaneResizing" + , resizeContentWhileDragging: "liveContentResizing" + , triggerEventsWhileDragging: "triggerEventsDuringLiveResize" + , maskIframesOnResize: "maskContents" + , useStateCookie: "stateManagement.enabled" + , "cookie.autoLoad": "stateManagement.autoLoad" + , "cookie.autoSave": "stateManagement.autoSave" + , "cookie.keys": "stateManagement.stateKeys" + , "cookie.name": "stateManagement.cookie.name" + , "cookie.domain": "stateManagement.cookie.domain" + , "cookie.path": "stateManagement.cookie.path" + , "cookie.expires": "stateManagement.cookie.expires" + , "cookie.secure": "stateManagement.cookie.secure" + } + /** + * @param {Object} opts + */ +, renameOptions: function (opts) { + var map = $.layout.backwardCompatibility.map + , oldData, newData, value + ; + for (var itemPath in map) { + oldData = getBranch( itemPath ); + value = oldData.branch[ oldData.key ] + if (value !== undefined) { + newData = getBranch( map[itemPath], true ) + newData.branch[ newData.key ] = value; + delete oldData.branch[ oldData.key ]; + } + } + + /** + * @param {string} path + * @param {boolean=} [create=false] Create path if does not exist + */ + function getBranch (path, create) { + var a = path.split(".") // split keys into array + , c = a.length - 1 + , D = { branch: opts, key: a[c] } // init branch at top & set key (last item) + , i = 0, k, undef; + for (; i 0) { + if (autoHide && $E.data('autoHidden') && $E.innerHeight() > 0) { + $E.show().data('autoHidden', false); + if (!browser.mozilla) // FireFox refreshes iframes - IE does not + // make hidden, then visible to 'refresh' display after animation + $E.css(_c.hidden).css(_c.visible); + } + } + else if (autoHide && !$E.data('autoHidden')) + $E.hide().data('autoHidden', true); + } + + /** + * @param {(string|!Object)} el + * @param {number=} outerHeight + * @param {boolean=} [autoHide=false] + */ +, setOuterHeight = function (el, outerHeight, autoHide) { + var $E = el, h; + if (isStr(el)) $E = $Ps[el]; // west + else if (!el.jquery) $E = $(el); + h = cssH($E, outerHeight); + $E.css({ height: h, visibility: "visible" }); // may have been 'hidden' by sizeContent + if (h > 0 && $E.innerWidth() > 0) { + if (autoHide && $E.data('autoHidden')) { + $E.show().data('autoHidden', false); + if (!browser.mozilla) // FireFox refreshes iframes - IE does not + $E.css(_c.hidden).css(_c.visible); + } + } + else if (autoHide && !$E.data('autoHidden')) + $E.hide().data('autoHidden', true); + } + + /** + * @param {(string|!Object)} el + * @param {number=} outerSize + * @param {boolean=} [autoHide=false] + */ +, setOuterSize = function (el, outerSize, autoHide) { + if (_c[pane].dir=="horz") // pane = north or south + setOuterHeight(el, outerSize, autoHide); + else // pane = east or west + setOuterWidth(el, outerSize, autoHide); + } + + + /** + * Converts any 'size' params to a pixel/integer size, if not already + * If 'auto' or a decimal/percentage is passed as 'size', a pixel-size is calculated + * + /** + * @param {string} pane + * @param {(string|number)=} size + * @param {string=} [dir] + * @return {number} + */ +, _parseSize = function (pane, size, dir) { + if (!dir) dir = _c[pane].dir; + + if (isStr(size) && size.match(/%/)) + size = (size === '100%') ? -1 : parseInt(size, 10) / 100; // convert % to decimal + + if (size === 0) + return 0; + else if (size >= 1) + return parseInt(size, 10); + + var o = options, avail = 0; + if (dir=="horz") // north or south or center.minHeight + avail = sC.innerHeight - ($Ps.north ? o.north.spacing_open : 0) - ($Ps.south ? o.south.spacing_open : 0); + else if (dir=="vert") // east or west or center.minWidth + avail = sC.innerWidth - ($Ps.west ? o.west.spacing_open : 0) - ($Ps.east ? o.east.spacing_open : 0); + + if (size === -1) // -1 == 100% + return avail; + else if (size > 0) // percentage, eg: .25 + return round(avail * size); + else if (pane=="center") + return 0; + else { // size < 0 || size=='auto' || size==Missing || size==Invalid + // auto-size the pane + var dim = (dir === "horz" ? "height" : "width") + , $P = $Ps[pane] + , $C = dim === 'height' ? $Cs[pane] : false + , vis = $.layout.showInvisibly($P) // show pane invisibly if hidden + , szP = $P.css(dim) // SAVE current pane size + , szC = $C ? $C.css(dim) : 0 // SAVE current content size + ; + $P.css(dim, "auto"); + if ($C) $C.css(dim, "auto"); + size = (dim === "height") ? $P.outerHeight() : $P.outerWidth(); // MEASURE + $P.css(dim, szP).css(vis); // RESET size & visibility + if ($C) $C.css(dim, szC); + return size; + } + } + + /** + * Calculates current 'size' (outer-width or outer-height) of a border-pane - optionally with 'pane-spacing' added + * + * @param {(string|!Object)} pane + * @param {boolean=} [inclSpace=false] + * @return {number} Returns EITHER Width for east/west panes OR Height for north/south panes - adjusted for boxModel & browser + */ +, getPaneSize = function (pane, inclSpace) { + var + $P = $Ps[pane] + , o = options[pane] + , s = state[pane] + , oSp = (inclSpace ? o.spacing_open : 0) + , cSp = (inclSpace ? o.spacing_closed : 0) + ; + if (!$P || s.isHidden) + return 0; + else if (s.isClosed || (s.isSliding && inclSpace)) + return cSp; + else if (_c[pane].dir === "horz") + return $P.outerHeight() + oSp; + else // dir === "vert" + return $P.outerWidth() + oSp; + } + + /** + * Calculate min/max pane dimensions and limits for resizing + * + * @param {string} pane + * @param {boolean=} [slide=false] + */ +, setSizeLimits = function (pane, slide) { + if (!isInitialized()) return; + var + o = options[pane] + , s = state[pane] + , c = _c[pane] + , dir = c.dir + , side = c.side.toLowerCase() + , type = c.sizeType.toLowerCase() + , isSliding = (slide != undefined ? slide : s.isSliding) // only open() passes 'slide' param + , $P = $Ps[pane] + , paneSpacing = o.spacing_open + // measure the pane on the *opposite side* from this pane + , altPane = _c.oppositeEdge[pane] + , altS = state[altPane] + , $altP = $Ps[altPane] + , altPaneSize = (!$altP || altS.isVisible===false || altS.isSliding ? 0 : (dir=="horz" ? $altP.outerHeight() : $altP.outerWidth())) + , altPaneSpacing = ((!$altP || altS.isHidden ? 0 : options[altPane][ altS.isClosed !== false ? "spacing_closed" : "spacing_open" ]) || 0) + // limitSize prevents this pane from 'overlapping' opposite pane + , containerSize = (dir=="horz" ? sC.innerHeight : sC.innerWidth) + , minCenterDims = cssMinDims("center") + , minCenterSize = dir=="horz" ? max(options.center.minHeight, minCenterDims.minHeight) : max(options.center.minWidth, minCenterDims.minWidth) + // if pane is 'sliding', then ignore center and alt-pane sizes - because 'overlays' them + , limitSize = (containerSize - paneSpacing - (isSliding ? 0 : (_parseSize("center", minCenterSize, dir) + altPaneSize + altPaneSpacing))) + , minSize = s.minSize = max( _parseSize(pane, o.minSize), cssMinDims(pane).minSize ) + , maxSize = s.maxSize = min( (o.maxSize ? _parseSize(pane, o.maxSize) : 100000), limitSize ) + , r = s.resizerPosition = {} // used to set resizing limits + , top = sC.insetTop + , left = sC.insetLeft + , W = sC.innerWidth + , H = sC.innerHeight + , rW = o.spacing_open // subtract resizer-width to get top/left position for south/east + ; + switch (pane) { + case "north": r.min = top + minSize; + r.max = top + maxSize; + break; + case "west": r.min = left + minSize; + r.max = left + maxSize; + break; + case "south": r.min = top + H - maxSize - rW; + r.max = top + H - minSize - rW; + break; + case "east": r.min = left + W - maxSize - rW; + r.max = left + W - minSize - rW; + break; + }; + } + + /** + * Returns data for setting the size/position of center pane. Also used to set Height for east/west panes + * + * @return JSON Returns a hash of all dimensions: top, bottom, left, right, (outer) width and (outer) height + */ +, calcNewCenterPaneDims = function () { + var d = { + top: getPaneSize("north", true) // true = include 'spacing' value for pane + , bottom: getPaneSize("south", true) + , left: getPaneSize("west", true) + , right: getPaneSize("east", true) + , width: 0 + , height: 0 + }; + + // NOTE: sC = state.container + // calc center-pane outer dimensions + d.width = sC.innerWidth - d.left - d.right; // outerWidth + d.height = sC.innerHeight - d.bottom - d.top; // outerHeight + // add the 'container border/padding' to get final positions relative to the container + d.top += sC.insetTop; + d.bottom += sC.insetBottom; + d.left += sC.insetLeft; + d.right += sC.insetRight; + + return d; + } + + + /** + * @param {!Object} el + * @param {boolean=} [allStates=false] + */ +, getHoverClasses = function (el, allStates) { + var + $El = $(el) + , type = $El.data("layoutRole") + , pane = $El.data("layoutEdge") + , o = options[pane] + , root = o[type +"Class"] + , _pane = "-"+ pane // eg: "-west" + , _open = "-open" + , _closed = "-closed" + , _slide = "-sliding" + , _hover = "-hover " // NOTE the trailing space + , _state = $El.hasClass(root+_closed) ? _closed : _open + , _alt = _state === _closed ? _open : _closed + , classes = (root+_hover) + (root+_pane+_hover) + (root+_state+_hover) + (root+_pane+_state+_hover) + ; + if (allStates) // when 'removing' classes, also remove alternate-state classes + classes += (root+_alt+_hover) + (root+_pane+_alt+_hover); + + if (type=="resizer" && $El.hasClass(root+_slide)) + classes += (root+_slide+_hover) + (root+_pane+_slide+_hover); + + return $.trim(classes); + } +, addHover = function (evt, el) { + var $E = $(el || this); + if (evt && $E.data("layoutRole") === "toggler") + evt.stopPropagation(); // prevent triggering 'slide' on Resizer-bar + $E.addClass( getHoverClasses($E) ); + } +, removeHover = function (evt, el) { + var $E = $(el || this); + $E.removeClass( getHoverClasses($E, true) ); + } + +, onResizerEnter = function (evt) { // ALSO called by toggler.mouseenter + if ($.fn.disableSelection) + $("body").disableSelection(); + } +, onResizerLeave = function (evt, el) { + var + e = el || this // el is only passed when called by the timer + , pane = $(e).data("layoutEdge") + , name = pane +"ResizerLeave" + ; + timer.clear(pane+"_openSlider"); // cancel slideOpen timer, if set + timer.clear(name); // cancel enableSelection timer - may re/set below + // this method calls itself on a timer because it needs to allow + // enough time for dragging to kick-in and set the isResizing flag + // dragging has a 100ms delay set, so this delay must be >100 + if (!el) // 1st call - mouseleave event + timer.set(name, function(){ onResizerLeave(evt, e); }, 200); + // if user is resizing, then dragStop will enableSelection(), so can skip it here + else if (!state[pane].isResizing && $.fn.enableSelection) // 2nd call - by timer + $("body").enableSelection(); + } + +/* + * ########################### + * INITIALIZATION METHODS + * ########################### + */ + + /** + * Initialize the layout - called automatically whenever an instance of layout is created + * + * @see none - triggered onInit + * @return mixed true = fully initialized | false = panes not initialized (yet) | 'cancel' = abort + */ +, _create = function () { + // initialize config/options + initOptions(); + var o = options; + + // TEMP state so isInitialized returns true during init process + state.creatingLayout = true; + + // init plugins for this layout, if there are any (eg: stateManagement) + runPluginCallbacks( Instance, $.layout.onCreate ); + + // options & state have been initialized, so now run beforeLoad callback + // onload will CANCEL layout creation if it returns false + if (false === _runCallbacks("onload_start")) + return 'cancel'; + + // initialize the container element + _initContainer(); + + // bind hotkey function - keyDown - if required + initHotkeys(); + + // bind window.onunload + $(window).bind("unload."+ sID, unload); + + // init plugins for this layout, if there are any (eg: customButtons) + runPluginCallbacks( Instance, $.layout.onLoad ); + + // if layout elements are hidden, then layout WILL NOT complete initialization! + // initLayoutElements will set initialized=true and run the onload callback IF successful + if (o.initPanes) _initLayoutElements(); + + delete state.creatingLayout; + + return state.initialized; + } + + /** + * Initialize the layout IF not already + * + * @see All methods in Instance run this test + * @return boolean true = layoutElements have been initialized | false = panes are not initialized (yet) + */ +, isInitialized = function () { + if (state.initialized || state.creatingLayout) return true; // already initialized + else return _initLayoutElements(); // try to init panes NOW + } + + /** + * Initialize the layout - called automatically whenever an instance of layout is created + * + * @see _create() & isInitialized + * @return An object pointer to the instance created + */ +, _initLayoutElements = function (retry) { + // initialize config/options + var o = options; + + // CANNOT init panes inside a hidden container! + if (!$N.is(":visible")) { + // handle Chrome bug where popup window 'has no height' + // if layout is BODY element, try again in 50ms + // SEE: http://layout.jquery-dev.net/samples/test_popup_window.html + if ( !retry && browser.webkit && $N[0].tagName === "BODY" ) + setTimeout(function(){ _initLayoutElements(true); }, 50); + return false; + } + + // a center pane is required, so make sure it exists + if (!getPane("center").length) { + if (options.showErrorMessages) + _log( lang.errCenterPaneMissing, true ); + return false; + } + + // TEMP state so isInitialized returns true during init process + state.creatingLayout = true; + + // update Container dims + $.extend(sC, elDims( $N )); + + // initialize all layout elements + initPanes(); // size & position panes - calls initHandles() - which calls initResizable() + + if (o.scrollToBookmarkOnLoad) { + var l = self.location; + if (l.hash) l.replace( l.hash ); // scrollTo Bookmark + } + + // check to see if this layout 'nested' inside a pane + if (Instance.hasParentLayout) + o.resizeWithWindow = false; + // bind resizeAll() for 'this layout instance' to window.resize event + else if (o.resizeWithWindow) + $(window).bind("resize."+ sID, windowResize); + + delete state.creatingLayout; + state.initialized = true; + + // init plugins for this layout, if there are any + runPluginCallbacks( Instance, $.layout.onReady ); + + // now run the onload callback, if exists + _runCallbacks("onload_end"); + + return true; // elements initialized successfully + } + + /** + * Initialize nested layouts - called when _initLayoutElements completes + * + * NOT CURRENTLY USED + * + * @see _initLayoutElements + * @return An object pointer to the instance created + */ +, _initChildLayouts = function () { + $.each(_c.allPanes, function (idx, pane) { + if (options[pane].initChildLayout) + createChildLayout( pane ); + }); + } + + /** + * Initialize nested layouts for a specific pane - can optionally pass layout-options + * + * @see _initChildLayouts + * @param {string} pane The pane being opened, ie: north, south, east, or west + * @param {Object=} [opts] Layout-options - if passed, will OVERRRIDE options[pane].childOptions + * @return An object pointer to the layout instance created - or null + */ +, createChildLayout = function (evt_or_pane, opts) { + var pane = evtPane.call(this, evt_or_pane) + , $P = $Ps[pane] + , C = children + ; + if ($P) { + var $C = $Cs[pane] + , o = opts || options[pane].childOptions + , d = "layout" + // determine which element is supposed to be the 'child container' + // if pane has a 'containerSelector' OR a 'content-div', use those instead of the pane + , $Cont = o.containerSelector ? $P.find( o.containerSelector ) : ($C || $P) + , containerFound = $Cont.length + // see if a child-layout ALREADY exists on this element + , child = containerFound ? (C[pane] = $Cont.data(d) || null) : null + ; + // if no layout exists, but childOptions are set, try to create the layout now + if (!child && containerFound && o) + child = C[pane] = $Cont.eq(0).layout(o) || null; + if (child) + child.hasParentLayout = true; // set parent-flag in child + } + Instance[pane].child = C[pane]; // ALWAYS set pane-object pointer, even if null + } + +, windowResize = function () { + var delay = Number(options.resizeWithWindowDelay); + if (delay < 10) delay = 100; // MUST have a delay! + // resizing uses a delay-loop because the resize event fires repeatly - except in FF, but delay anyway + timer.clear("winResize"); // if already running + timer.set("winResize", function(){ + timer.clear("winResize"); + timer.clear("winResizeRepeater"); + var dims = elDims( $N ); + // only trigger resizeAll() if container has changed size + if (dims.innerWidth !== sC.innerWidth || dims.innerHeight !== sC.innerHeight) + resizeAll(); + }, delay); + // ALSO set fixed-delay timer, if not already running + if (!timer.data["winResizeRepeater"]) setWindowResizeRepeater(); + } + +, setWindowResizeRepeater = function () { + var delay = Number(options.resizeWithWindowMaxDelay); + if (delay > 0) + timer.set("winResizeRepeater", function(){ setWindowResizeRepeater(); resizeAll(); }, delay); + } + +, unload = function () { + var o = options; + + _runCallbacks("onunload_start"); + + // trigger plugin callabacks for this layout (eg: stateManagement) + runPluginCallbacks( Instance, $.layout.onUnload ); + + _runCallbacks("onunload_end"); + } + + /** + * Validate and initialize container CSS and events + * + * @see _create() + */ +, _initContainer = function () { + var + N = $N[0] + , tag = sC.tagName = N.tagName + , id = sC.id = N.id + , cls = sC.className = N.className + , o = options + , name = o.name + , fullPage= (tag === "BODY") + , props = "overflow,position,margin,padding,border" + , css = "layoutCSS" + , CSS = {} + , hid = "hidden" // used A LOT! + // see if this container is a 'pane' inside an outer-layout + , parent = $N.data("parentLayout") // parent-layout Instance + , pane = $N.data("layoutEdge") // pane-name in parent-layout + , isChild = parent && pane + ; + // sC -> state.container + sC.selector = $N.selector.split(".slice")[0]; + sC.ref = (o.name ? o.name +' layout / ' : '') + tag + (id ? "#"+id : cls ? '.['+cls+']' : ''); // used in messages + + $N .data({ + layout: Instance + , layoutContainer: sID // FLAG to indicate this is a layout-container - contains unique internal ID + }) + .addClass(o.containerClass) + ; + var layoutMethods = { + destroy: '' + , initPanes: '' + , resizeAll: 'resizeAll' + , resize: 'resizeAll' + } + , name; + // loop hash and bind all methods - include layoutID namespacing + for (name in layoutMethods) { + $N.bind("layout"+ name.toLowerCase() +"."+ sID, Instance[ layoutMethods[name] || name ]); + } + + // if this container is another layout's 'pane', then set child/parent pointers + if (isChild) { + // update parent flag + Instance.hasParentLayout = true; + // set pointers to THIS child-layout (Instance) in parent-layout + // NOTE: parent.PANE.child is an ALIAS to parent.children.PANE + parent[pane].child = parent.children[pane] = $N.data("layout"); + } + + // SAVE original container CSS for use in destroy() + if (!$N.data(css)) { + // handle props like overflow different for BODY & HTML - has 'system default' values + if (fullPage) { + CSS = $.extend( elCSS($N, props), { + height: $N.css("height") + , overflow: $N.css("overflow") + , overflowX: $N.css("overflowX") + , overflowY: $N.css("overflowY") + }); + // ALSO SAVE CSS + var $H = $("html"); + $H.data(css, { + height: "auto" // FF would return a fixed px-size! + , overflow: $H.css("overflow") + , overflowX: $H.css("overflowX") + , overflowY: $H.css("overflowY") + }); + } + else // handle props normally for non-body elements + CSS = elCSS($N, props+",top,bottom,left,right,width,height,overflow,overflowX,overflowY"); + + $N.data(css, CSS); + } + + try { // format html/body if this is a full page layout + if (fullPage) { + $("html").css({ + height: "100%" + , overflow: hid + , overflowX: hid + , overflowY: hid + }); + $("body").css({ + position: "relative" + , height: "100%" + , overflow: hid + , overflowX: hid + , overflowY: hid + , margin: 0 + , padding: 0 // TODO: test whether body-padding could be handled? + , border: "none" // a body-border creates problems because it cannot be measured! + }); + + // set current layout-container dimensions + $.extend(sC, elDims( $N )); + } + else { // set required CSS for overflow and position + // ENSURE container will not 'scroll' + CSS = { overflow: hid, overflowX: hid, overflowY: hid } + var + p = $N.css("position") + , h = $N.css("height") + ; + // if this is a NESTED layout, then container/outer-pane ALREADY has position and height + if (!isChild) { + if (!p || !p.match(/fixed|absolute|relative/)) + CSS.position = "relative"; // container MUST have a 'position' + /* + if (!h || h=="auto") + CSS.height = "100%"; // container MUST have a 'height' + */ + } + $N.css( CSS ); + + // set current layout-container dimensions + if ( $N.is(":visible") ) { + $.extend(sC, elDims( $N )); + if (o.showErrorMessages && sC.innerHeight < 1) + _log( lang.errContainerHeight.replace(/CONTAINER/, sC.ref), true ); + } + } + } catch (ex) {} + } + + /** + * Bind layout hotkeys - if options enabled + * + * @see _create() and addPane() + * @param {string=} [panes=""] The edge(s) to process + */ +, initHotkeys = function (panes) { + panes = panes ? panes.split(",") : _c.borderPanes; + // bind keyDown to capture hotkeys, if option enabled for ANY pane + $.each(panes, function (i, pane) { + var o = options[pane]; + if (o.enableCursorHotkey || o.customHotkey) { + $(document).bind("keydown."+ sID, keyDown); // only need to bind this ONCE + return false; // BREAK - binding was done + } + }); + } + + /** + * Build final OPTIONS data + * + * @see _create() + */ +, initOptions = function () { + var data, d, pane, key, val, i, c, o; + + // reprocess user's layout-options to have correct options sub-key structure + opts = $.layout.transformData( opts ); // panes = default subkey + + // auto-rename old options for backward compatibility + opts = $.layout.backwardCompatibility.renameAllOptions( opts ); + + // if user-options has 'panes' key (pane-defaults), process it... + if (!$.isEmptyObject(opts.panes)) { + // REMOVE any pane-defaults that MUST be set per-pane + data = $.layout.optionsMap.noDefault; + for (i=0, c=data.length; i 0) { + z.pane_normal = zo; + z.content_mask = max(zo+1, z.content_mask); // MIN = +1 + z.resizer_normal = max(zo+2, z.resizer_normal); // MIN = +2 + } + + function createFxOptions ( pane ) { + var o = options[pane] + , d = options.panes; + // ensure fxSettings key to avoid errors + if (!o.fxSettings) o.fxSettings = {}; + if (!d.fxSettings) d.fxSettings = {}; + + $.each(["_open","_close","_size"], function (i,n) { + var + sName = "fxName"+ n + , sSpeed = "fxSpeed"+ n + , sSettings = "fxSettings"+ n + // recalculate fxName according to specificity rules + , fxName = o[sName] = + o[sName] // options.west.fxName_open + || d[sName] // options.panes.fxName_open + || o.fxName // options.west.fxName + || d.fxName // options.panes.fxName + || "none" // MEANS $.layout.defaults.panes.fxName == "" || false || null || 0 + ; + // validate fxName to ensure is valid effect - MUST have effect-config data in options.effects + if (fxName === "none" || !$.effects || !$.effects[fxName] || !options.effects[fxName]) + fxName = o[sName] = "none"; // effect not loaded OR unrecognized fxName + + // set vars for effects subkeys to simplify logic + var fx = options.effects[fxName] || {} // effects.slide + , fx_all = fx.all || null // effects.slide.all + , fx_pane = fx[pane] || null // effects.slide.west + ; + // create fxSpeed[_open|_close|_size] + o[sSpeed] = + o[sSpeed] // options.west.fxSpeed_open + || d[sSpeed] // options.west.fxSpeed_open + || o.fxSpeed // options.west.fxSpeed + || d.fxSpeed // options.panes.fxSpeed + || null // DEFAULT - let fxSetting.duration control speed + ; + // create fxSettings[_open|_close|_size] + o[sSettings] = $.extend( + {} + , fx_all // effects.slide.all + , fx_pane // effects.slide.west + , d.fxSettings // options.panes.fxSettings + , o.fxSettings // options.west.fxSettings + , d[sSettings] // options.panes.fxSettings_open + , o[sSettings] // options.west.fxSettings_open + ); + }); + + // DONE creating action-specific-settings for this pane, + // so DELETE generic options - are no longer meaningful + delete o.fxName; + delete o.fxSpeed; + delete o.fxSettings; + } + + // DELETE 'panes' key now that we are done - values were copied to EACH pane + delete options.panes; + } + + /** + * Initialize module objects, styling, size and position for all panes + * + * @see _initElements() + * @param {string} pane The pane to process + */ +, getPane = function (pane) { + var sel = options[pane].paneSelector + if (sel.substr(0,1)==="#") // ID selector + // NOTE: elements selected 'by ID' DO NOT have to be 'children' + return $N.find(sel).eq(0); + else { // class or other selector + var $P = $N.children(sel).eq(0); + // look for the pane nested inside a 'form' element + return $P.length ? $P : $N.children("form:first").children(sel).eq(0); + } + } + +, initPanes = function () { + // NOTE: do north & south FIRST so we can measure their height - do center LAST + $.each(_c.allPanes, function (idx, pane) { + addPane( pane, true ); + }); + + // init the pane-handles NOW in case we have to hide or close the pane below + initHandles(); + + // now that all panes have been initialized and initially-sized, + // make sure there is really enough space available for each pane + $.each(_c.borderPanes, function (i, pane) { + if ($Ps[pane] && state[pane].isVisible) { // pane is OPEN + setSizeLimits(pane); + makePaneFit(pane); // pane may be Closed, Hidden or Resized by makePaneFit() + } + }); + // size center-pane AGAIN in case we 'closed' a border-pane in loop above + sizeMidPanes("center"); + + // Chrome/Webkit sometimes fires callbacks BEFORE it completes resizing! + // Before RC30.3, there was a 10ms delay here, but that caused layout + // to load asynchrously, which is BAD, so try skipping delay for now + + // process pane contents and callbacks, and init/resize child-layout if exists + $.each(_c.allPanes, function (i, pane) { + var o = options[pane]; + if ($Ps[pane]) { + if (state[pane].isVisible) { // pane is OPEN + sizeContent(pane); + // trigger pane.onResize if triggerEventsOnLoad = true + if (o.triggerEventsOnLoad) + _runCallbacks("onresize_end", pane); + else // automatic if onresize called, otherwise call it specifically + // resize child - IF inner-layout already exists (created before this layout) + resizeChildLayout(pane); + } + // init childLayout - even if pane is not visible + if (o.initChildLayout && o.childOptions) + createChildLayout(pane); + } + }); + } + + /** + * Add a pane to the layout - subroutine of initPanes() + * + * @see initPanes() + * @param {string} pane The pane to process + * @param {boolean=} [force=false] Size content after init + */ +, addPane = function (pane, force) { + if (!force && !isInitialized()) return; + var + o = options[pane] + , s = state[pane] + , c = _c[pane] + , fx = s.fx + , dir = c.dir + , spacing = o.spacing_open || 0 + , isCenter = (pane === "center") + , CSS = {} + , $P = $Ps[pane] + , size, minSize, maxSize + ; + // if pane-pointer already exists, remove the old one first + if ($P) + removePane( pane, false, true, false ); + else + $Cs[pane] = false; // init + + $P = $Ps[pane] = getPane(pane); + if (!$P.length) { + $Ps[pane] = false; // logic + return; + } + + // SAVE original Pane CSS + if (!$P.data("layoutCSS")) { + var props = "position,top,left,bottom,right,width,height,overflow,zIndex,display,backgroundColor,padding,margin,border"; + $P.data("layoutCSS", elCSS($P, props)); + } + + // create alias for pane data in Instance - initHandles will add more + Instance[pane] = { name: pane, pane: $Ps[pane], content: $Cs[pane], options: options[pane], state: state[pane], child: children[pane] }; + + // add classes, attributes & events + $P .data({ + parentLayout: Instance // pointer to Layout Instance + , layoutPane: Instance[pane] // NEW pointer to pane-alias-object + , layoutEdge: pane + , layoutRole: "pane" + }) + .css(c.cssReq).css("zIndex", options.zIndexes.pane_normal) + .css(o.applyDemoStyles ? c.cssDemo : {}) // demo styles + .addClass( o.paneClass +" "+ o.paneClass+"-"+pane ) // default = "ui-layout-pane ui-layout-pane-west" - may be a dupe of 'paneSelector' + .bind("mouseenter."+ sID, addHover ) + .bind("mouseleave."+ sID, removeHover ) + ; + var paneMethods = { + hide: '' + , show: '' + , toggle: '' + , close: '' + , open: '' + , slideOpen: '' + , slideClose: '' + , slideToggle: '' + , size: 'manualSizePane' + , sizePane: 'manualSizePane' + , sizeContent: '' + , sizeHandles: '' + , enableClosable: '' + , disableClosable: '' + , enableSlideable: '' + , disableSlideable: '' + , enableResizable: '' + , disableResizable: '' + , swapPanes: 'swapPanes' + , swap: 'swapPanes' + , move: 'swapPanes' + , removePane: 'removePane' + , remove: 'removePane' + , createChildLayout: '' + , resizeChildLayout: '' + , resizeAll: 'resizeAll' + , resizeLayout: 'resizeAll' + } + , name; + // loop hash and bind all methods - include layoutID namespacing + for (name in paneMethods) { + $P.bind("layoutpane"+ name.toLowerCase() +"."+ sID, Instance[ paneMethods[name] || name ]); + } + + // see if this pane has a 'scrolling-content element' + initContent(pane, false); // false = do NOT sizeContent() - called later + + if (!isCenter) { + // call _parseSize AFTER applying pane classes & styles - but before making visible (if hidden) + // if o.size is auto or not valid, then MEASURE the pane and use that as its 'size' + size = s.size = _parseSize(pane, o.size); + minSize = _parseSize(pane,o.minSize) || 1; + maxSize = _parseSize(pane,o.maxSize) || 100000; + if (size > 0) size = max(min(size, maxSize), minSize); + + // state for border-panes + s.isClosed = false; // true = pane is closed + s.isSliding = false; // true = pane is currently open by 'sliding' over adjacent panes + s.isResizing= false; // true = pane is in process of being resized + s.isHidden = false; // true = pane is hidden - no spacing, resizer or toggler is visible! + + // array for 'pin buttons' whose classNames are auto-updated on pane-open/-close + if (!s.pins) s.pins = []; + } + // states common to ALL panes + s.tagName = $P[0].tagName; + s.edge = pane; // useful if pane is (or about to be) 'swapped' - easy find out where it is (or is going) + s.noRoom = false; // true = pane 'automatically' hidden due to insufficient room - will unhide automatically + s.isVisible = true; // false = pane is invisible - closed OR hidden - simplify logic + + // set css-position to account for container borders & padding + switch (pane) { + case "north": CSS.top = sC.insetTop; + CSS.left = sC.insetLeft; + CSS.right = sC.insetRight; + break; + case "south": CSS.bottom = sC.insetBottom; + CSS.left = sC.insetLeft; + CSS.right = sC.insetRight; + break; + case "west": CSS.left = sC.insetLeft; // top, bottom & height set by sizeMidPanes() + break; + case "east": CSS.right = sC.insetRight; // ditto + break; + case "center": // top, left, width & height set by sizeMidPanes() + } + + if (dir === "horz") // north or south pane + CSS.height = cssH($P, size); + else if (dir === "vert") // east or west pane + CSS.width = cssW($P, size); + //else if (isCenter) {} + + $P.css(CSS); // apply size -- top, bottom & height will be set by sizeMidPanes + if (dir != "horz") sizeMidPanes(pane, true); // true = skipCallback + + // close or hide the pane if specified in settings + if (o.initClosed && o.closable && !o.initHidden) + close(pane, true, true); // true, true = force, noAnimation + else if (o.initHidden || o.initClosed) + hide(pane); // will be completely invisible - no resizer or spacing + else if (!s.noRoom) + // make the pane visible - in case was initially hidden + $P.css("display","block"); + // ELSE setAsOpen() - called later by initHandles() + + // RESET visibility now - pane will appear IF display:block + $P.css("visibility","visible"); + + // check option for auto-handling of pop-ups & drop-downs + if (o.showOverflowOnHover) + $P.hover( allowOverflow, resetOverflow ); + + // if manually adding a pane AFTER layout initialization, then... + if (state.initialized) { + initHandles( pane ); + initHotkeys( pane ); + resizeAll(); // will sizeContent if pane is visible + if (s.isVisible) { // pane is OPEN + if (o.triggerEventsOnLoad) + _runCallbacks("onresize_end", pane); + else // automatic if onresize called, otherwise call it specifically + // resize child - IF inner-layout already exists (created before this layout) + resizeChildLayout(pane); // a previously existing childLayout + } + if (o.initChildLayout && o.childOptions) + createChildLayout(pane); + } + } + + /** + * Initialize module objects, styling, size and position for all resize bars and toggler buttons + * + * @see _create() + * @param {string=} [panes=""] The edge(s) to process + */ +, initHandles = function (panes) { + panes = panes ? panes.split(",") : _c.borderPanes; + + // create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV + $.each(panes, function (i, pane) { + var $P = $Ps[pane]; + $Rs[pane] = false; // INIT + $Ts[pane] = false; + if (!$P) return; // pane does not exist - skip + + var + o = options[pane] + , s = state[pane] + , c = _c[pane] + , rClass = o.resizerClass + , tClass = o.togglerClass + , side = c.side.toLowerCase() + , spacing = (s.isVisible ? o.spacing_open : o.spacing_closed) + , _pane = "-"+ pane // used for classNames + , _state = (s.isVisible ? "-open" : "-closed") // used for classNames + , I = Instance[pane] + // INIT RESIZER BAR + , $R = I.resizer = $Rs[pane] = $("
        ") + // INIT TOGGLER BUTTON + , $T = I.toggler = (o.closable ? $Ts[pane] = $("
        ") : false) + ; + + //if (s.isVisible && o.resizable) ... handled by initResizable + if (!s.isVisible && o.slidable) + $R.attr("title", o.sliderTip).css("cursor", o.sliderCursor); + + $R // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-resizer" + .attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-resizer" : "")) + .data({ + parentLayout: Instance + , layoutPane: Instance[pane] // NEW pointer to pane-alias-object + , layoutEdge: pane + , layoutRole: "resizer" + }) + .css(_c.resizers.cssReq).css("zIndex", options.zIndexes.resizer_normal) + .css(o.applyDemoStyles ? _c.resizers.cssDemo : {}) // add demo styles + .addClass(rClass +" "+ rClass+_pane) + .hover(addHover, removeHover) // ALWAYS add hover-classes, even if resizing is not enabled - handle with CSS instead + .hover(onResizerEnter, onResizerLeave) // ALWAYS NEED resizer.mouseleave to balance toggler.mouseenter + .appendTo($N) // append DIV to container + ; + + if ($T) { + $T // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "#paneLeft-toggler" + .attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-toggler" : "")) + .data({ + parentLayout: Instance + , layoutPane: Instance[pane] // NEW pointer to pane-alias-object + , layoutEdge: pane + , layoutRole: "toggler" + }) + .css(_c.togglers.cssReq) // add base/required styles + .css(o.applyDemoStyles ? _c.togglers.cssDemo : {}) // add demo styles + .addClass(tClass +" "+ tClass+_pane) + .hover(addHover, removeHover) // ALWAYS add hover-classes, even if toggling is not enabled - handle with CSS instead + .bind("mouseenter", onResizerEnter) // NEED toggler.mouseenter because mouseenter MAY NOT fire on resizer + .appendTo($R) // append SPAN to resizer DIV + ; + // ADD INNER-SPANS TO TOGGLER + if (o.togglerContent_open) // ui-layout-open + $(""+ o.togglerContent_open +"") + .data({ + layoutEdge: pane + , layoutRole: "togglerContent" + }) + .data("layoutRole", "togglerContent") + .data("layoutEdge", pane) + .addClass("content content-open") + .css("display","none") + .appendTo( $T ) + //.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-open instead! + ; + if (o.togglerContent_closed) // ui-layout-closed + $(""+ o.togglerContent_closed +"") + .data({ + layoutEdge: pane + , layoutRole: "togglerContent" + }) + .addClass("content content-closed") + .css("display","none") + .appendTo( $T ) + //.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-closed instead! + ; + // ADD TOGGLER.click/.hover + enableClosable(pane); + } + + // add Draggable events + initResizable(pane); + + // ADD CLASSNAMES & SLIDE-BINDINGS - eg: class="resizer resizer-west resizer-open" + if (s.isVisible) + setAsOpen(pane); // onOpen will be called, but NOT onResize + else { + setAsClosed(pane); // onClose will be called + bindStartSlidingEvent(pane, true); // will enable events IF option is set + } + + }); + + // SET ALL HANDLE DIMENSIONS + sizeHandles(); + } + + + /** + * Initialize scrolling ui-layout-content div - if exists + * + * @see initPane() - or externally after an Ajax injection + * @param {string} [pane] The pane to process + * @param {boolean=} [resize=true] Size content after init + */ +, initContent = function (pane, resize) { + if (!isInitialized()) return; + var + o = options[pane] + , sel = o.contentSelector + , I = Instance[pane] + , $P = $Ps[pane] + , $C + ; + if (sel) $C = I.content = $Cs[pane] = (o.findNestedContent) + ? $P.find(sel).eq(0) // match 1-element only + : $P.children(sel).eq(0) + ; + if ($C && $C.length) { + $C.data("layoutRole", "content"); + // SAVE original Pane CSS + if (!$C.data("layoutCSS")) + $C.data("layoutCSS", elCSS($C, "height")); + $C.css( _c.content.cssReq ); + if (o.applyDemoStyles) { + $C.css( _c.content.cssDemo ); // add padding & overflow: auto to content-div + $P.css( _c.content.cssDemoPane ); // REMOVE padding/scrolling from pane + } + state[pane].content = {}; // init content state + if (resize !== false) sizeContent(pane); + // sizeContent() is called AFTER init of all elements + } + else + I.content = $Cs[pane] = false; + } + + + /** + * Add resize-bars to all panes that specify it in options + * -dependancy: $.fn.resizable - will skip if not found + * + * @see _create() + * @param {string=} [panes=""] The edge(s) to process + */ +, initResizable = function (panes) { + var draggingAvailable = $.layout.plugins.draggable + , side // set in start() + ; + panes = panes ? panes.split(",") : _c.borderPanes; + + $.each(panes, function (idx, pane) { + var o = options[pane]; + if (!draggingAvailable || !$Ps[pane] || !o.resizable) { + o.resizable = false; + return true; // skip to next + } + + var s = state[pane] + , z = options.zIndexes + , c = _c[pane] + , side = c.dir=="horz" ? "top" : "left" + , opEdge = _c.oppositeEdge[pane] + , masks = pane +",center,"+ opEdge + (c.dir=="horz" ? ",west,east" : "") + , $P = $Ps[pane] + , $R = $Rs[pane] + , base = o.resizerClass + , lastPos = 0 // used when live-resizing + , r, live // set in start because may change + // 'drag' classes are applied to the ORIGINAL resizer-bar while dragging is in process + , resizerClass = base+"-drag" // resizer-drag + , resizerPaneClass = base+"-"+pane+"-drag" // resizer-north-drag + // 'helper' class is applied to the CLONED resizer-bar while it is being dragged + , helperClass = base+"-dragging" // resizer-dragging + , helperPaneClass = base+"-"+pane+"-dragging" // resizer-north-dragging + , helperLimitClass = base+"-dragging-limit" // resizer-drag + , helperPaneLimitClass = base+"-"+pane+"-dragging-limit" // resizer-north-drag + , helperClassesSet = false // logic var + ; + + if (!s.isClosed) + $R.attr("title", o.resizerTip) + .css("cursor", o.resizerCursor); // n-resize, s-resize, etc + + $R.draggable({ + containment: $N[0] // limit resizing to layout container + , axis: (c.dir=="horz" ? "y" : "x") // limit resizing to horz or vert axis + , delay: 0 + , distance: 1 + , grid: o.resizingGrid + // basic format for helper - style it using class: .ui-draggable-dragging + , helper: "clone" + , opacity: o.resizerDragOpacity + , addClasses: false // avoid ui-state-disabled class when disabled + //, iframeFix: o.draggableIframeFix // TODO: consider using when bug is fixed + , zIndex: z.resizer_drag + + , start: function (e, ui) { + // REFRESH options & state pointers in case we used swapPanes + o = options[pane]; + s = state[pane]; + // re-read options + live = o.livePaneResizing; + + // ondrag_start callback - will CANCEL hide if returns false + // TODO: dragging CANNOT be cancelled like this, so see if there is a way? + if (false === _runCallbacks("ondrag_start", pane)) return false; + + s.isResizing = true; // prevent pane from closing while resizing + timer.clear(pane+"_closeSlider"); // just in case already triggered + + // SET RESIZER LIMITS - used in drag() + setSizeLimits(pane); // update pane/resizer state + r = s.resizerPosition; + lastPos = ui.position[ side ] + + $R.addClass( resizerClass +" "+ resizerPaneClass ); // add drag classes + helperClassesSet = false; // reset logic var - see drag() + + // DISABLE TEXT SELECTION (probably already done by resizer.mouseOver) + $('body').disableSelection(); + + // MASK PANES CONTAINING IFRAMES, APPLETS OR OTHER TROUBLESOME ELEMENTS + showMasks( masks ); + } + + , drag: function (e, ui) { + if (!helperClassesSet) { // can only add classes after clone has been added to the DOM + //$(".ui-draggable-dragging") + ui.helper + .addClass( helperClass +" "+ helperPaneClass ) // add helper classes + .css({ right: "auto", bottom: "auto" }) // fix dir="rtl" issue + .children().css("visibility","hidden") // hide toggler inside dragged resizer-bar + ; + helperClassesSet = true; + // draggable bug!? RE-SET zIndex to prevent E/W resize-bar showing through N/S pane! + if (s.isSliding) $Ps[pane].css("zIndex", z.pane_sliding); + } + // CONTAIN RESIZER-BAR TO RESIZING LIMITS + var limit = 0; + if (ui.position[side] < r.min) { + ui.position[side] = r.min; + limit = -1; + } + else if (ui.position[side] > r.max) { + ui.position[side] = r.max; + limit = 1; + } + // ADD/REMOVE dragging-limit CLASS + if (limit) { + ui.helper.addClass( helperLimitClass +" "+ helperPaneLimitClass ); // at dragging-limit + window.defaultStatus = (limit>0 && pane.match(/north|west/)) || (limit<0 && pane.match(/south|east/)) ? lang.maxSizeWarning : lang.minSizeWarning; + } + else { + ui.helper.removeClass( helperLimitClass +" "+ helperPaneLimitClass ); // not at dragging-limit + window.defaultStatus = ""; + } + // DYNAMICALLY RESIZE PANES IF OPTION ENABLED + // won't trigger unless resizer has actually moved! + if (live && Math.abs(ui.position[side] - lastPos) >= o.liveResizingTolerance) { + lastPos = ui.position[side]; + resizePanes(e, ui, pane) + } + } + + , stop: function (e, ui) { + $('body').enableSelection(); // RE-ENABLE TEXT SELECTION + window.defaultStatus = ""; // clear 'resizing limit' message from statusbar + $R.removeClass( resizerClass +" "+ resizerPaneClass ); // remove drag classes from Resizer + s.isResizing = false; + resizePanes(e, ui, pane, true, masks); // true = resizingDone + } + + }); + }); + + /** + * resizePanes + * + * Sub-routine called from stop() - and drag() if livePaneResizing + * + * @param {!Object} evt + * @param {!Object} ui + * @param {string} pane + * @param {boolean=} [resizingDone=false] + */ + var resizePanes = function (evt, ui, pane, resizingDone, masks) { + var dragPos = ui.position + , c = _c[pane] + , o = options[pane] + , s = state[pane] + , resizerPos + ; + switch (pane) { + case "north": resizerPos = dragPos.top; break; + case "west": resizerPos = dragPos.left; break; + case "south": resizerPos = sC.offsetHeight - dragPos.top - o.spacing_open; break; + case "east": resizerPos = sC.offsetWidth - dragPos.left - o.spacing_open; break; + }; + // remove container margin from resizer position to get the pane size + var newSize = resizerPos - sC["inset"+ c.side]; + + // Disable OR Resize Mask(s) created in drag.start + if (!resizingDone) { + // ensure we meet liveResizingTolerance criteria + if (Math.abs(newSize - s.size) < o.liveResizingTolerance) + return; // SKIP resize this time + // resize the pane + manualSizePane(pane, newSize, false, true); // true = noAnimation + sizeMasks(); // resize all visible masks + } + else { // resizingDone + // ondrag_end callback + if (false !== _runCallbacks("ondrag_end", pane)) + manualSizePane(pane, newSize, false, true); // true = noAnimation + hideMasks(); // hide all masks, which include panes with 'content/iframe-masks' + if (s.isSliding && masks) // RE-SHOW only 'object-masks' so objects won't show through sliding pane + showMasks( masks, true ); // true = onlyForObjects + } + }; + } + + /** + * sizeMask + * + * Needed to overlay a DIV over an IFRAME-pane because mask CANNOT be *inside* the pane + * Called when mask created, and during livePaneResizing + */ +, sizeMask = function () { + var $M = $(this) + , pane = $M.data("layoutMask") // eg: "west" + , s = state[pane] + ; + // only masks over an IFRAME-pane need manual resizing + if (s.tagName == "IFRAME" && s.isVisible) // no need to mask closed/hidden panes + $M.css({ + top: s.offsetTop + , left: s.offsetLeft + , width: s.outerWidth + , height: s.outerHeight + }); + /* ALT Method... + var $P = $Ps[pane]; + $M.css( $P.position() ).css({ width: $P[0].offsetWidth, height: $P[0].offsetHeight }); + */ + } +, sizeMasks = function () { + $Ms.each( sizeMask ); // resize all 'visible' masks + } + +, showMasks = function (panes, onlyForObjects) { + var a = panes ? panes.split(",") : $.layout.config.allPanes + , z = options.zIndexes + , o, s; + $.each(a, function(i,p){ + s = state[p]; + o = options[p]; + if (s.isVisible && ( (!onlyForObjects && o.maskContents) || o.maskObjects )) { + getMasks(p).each(function(){ + sizeMask.call(this); + this.style.zIndex = s.isSliding ? z.pane_sliding+1 : z.pane_normal+1 + this.style.display = "block"; + }); + } + }); + } + +, hideMasks = function () { + // ensure no pane is resizing - could be a timing issue + var skip; + $.each( $.layout.config.borderPanes, function(i,p){ + if (state[p].isResizing) { + skip = true; + return false; // BREAK + } + }); + if (!skip) + $Ms.hide(); // hide ALL masks + } + +, getMasks = function (pane) { + var $Masks = $([]) + , $M, i = 0, c = $Ms.length + ; + for (; i CSS + if (sC.tagName === "BODY" && ($N = $("html")).data(css)) // RESET CSS + $N.css( $N.data(css) ).removeData(css); + + // trigger plugins for this layout, if there are any + runPluginCallbacks( Instance, $.layout.onDestroy ); + + // trigger state-management and onunload callback + unload(); + + // clear the Instance of everything except for container & options (so could recreate) + // RE-CREATE: myLayout = myLayout.container.layout( myLayout.options ); + for (n in Instance) + if (!n.match(/^(container|options)$/)) delete Instance[ n ]; + // add a 'destroyed' flag to make it easy to check + Instance.destroyed = true; + + // if this is a child layout, CLEAR the child-pointer in the parent + /* for now the pointer REMAINS, but with only container, options and destroyed keys + if (parentPane) { + var layout = parentPane.pane.data("parentLayout"); + parentPane.child = layout.children[ parentPane.name ] = null; + } + */ + + return Instance; // for coding convenience + } + + /** + * Remove a pane from the layout - subroutine of destroy() + * + * @see destroy() + * @param {string} pane The pane to process + * @param {boolean=} [remove=false] Remove the DOM element? + * @param {boolean=} [skipResize=false] Skip calling resizeAll()? + */ +, removePane = function (evt_or_pane, remove, skipResize, destroyChild) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $P = $Ps[pane] + , $C = $Cs[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + ; + //alert( '$P.length = '+ $P.length ); + // NOTE: elements can still exist even after remove() + // so check for missing data(), which is cleared by removed() + if ($P && $.isEmptyObject( $P.data() )) $P = false; + if ($C && $.isEmptyObject( $C.data() )) $C = false; + if ($R && $.isEmptyObject( $R.data() )) $R = false; + if ($T && $.isEmptyObject( $T.data() )) $T = false; + + if ($P) $P.stop(true, true); + + // check for a child layout + var o = options[pane] + , s = state[pane] + , d = "layout" + , css = "layoutCSS" + , child = children[pane] || ($P ? $P.data(d) : 0) || ($C ? $C.data(d) : 0) || null + , destroy = destroyChild !== undefined ? destroyChild : o.destroyChildLayout + ; + + // FIRST destroy the child-layout(s) + if (destroy && child && !child.destroyed) { + child.destroy(true); // tell child-layout to destroy ALL its child-layouts too + if (child.destroyed) // destroy was successful + child = null; // clear pointer for logic below + } + + if ($P && remove && !child) + $P.remove(); + else if ($P && $P[0]) { + // create list of ALL pane-classes that need to be removed + var root = o.paneClass // default="ui-layout-pane" + , pRoot = root +"-"+ pane // eg: "ui-layout-pane-west" + , _open = "-open" + , _sliding= "-sliding" + , _closed = "-closed" + , classes = [ root, root+_open, root+_closed, root+_sliding, // generic classes + pRoot, pRoot+_open, pRoot+_closed, pRoot+_sliding ] // pane-specific classes + ; + $.merge(classes, getHoverClasses($P, true)); // ADD hover-classes + // remove all Layout classes from pane-element + $P .removeClass( classes.join(" ") ) // remove ALL pane-classes + .removeData("parentLayout") + .removeData("layoutPane") + .removeData("layoutRole") + .removeData("layoutEdge") + .removeData("autoHidden") // in case set + .unbind("."+ sID) // remove ALL Layout events + // TODO: remove these extra unbind commands when jQuery is fixed + //.unbind("mouseenter"+ sID) + //.unbind("mouseleave"+ sID) + ; + // do NOT reset CSS if this pane/content is STILL the container of a nested layout! + // the nested layout will reset its 'container' CSS when/if it is destroyed + if ($C && $C.data(d)) { + // a content-div may not have a specific width, so give it one to contain the Layout + $C.width( $C.width() ); + child.resizeAll(); // now resize the Layout + } + else if ($C) + $C.css( $C.data(css) ).removeData(css).removeData("layoutRole"); + // remove pane AFTER content in case there was a nested layout + if (!$P.data(d)) + $P.css( $P.data(css) ).removeData(css); + } + + // REMOVE pane resizer and toggler elements + if ($T) $T.remove(); + if ($R) $R.remove(); + + // CLEAR all pointers and state data + Instance[pane] = $Ps[pane] = $Cs[pane] = $Rs[pane] = $Ts[pane] = children[pane] = false; + s = { removed: true }; + + if (!skipResize) + resizeAll(); + } + + +/* + * ########################### + * ACTION METHODS + * ########################### + */ + +, _hidePane = function (pane) { + var $P = $Ps[pane] + , o = options[pane] + , s = $P[0].style + ; + if (o.useOffscreenClose) { + if (!$P.data(_c.offscreenReset)) + $P.data(_c.offscreenReset, { left: s.left, right: s.right }); + $P.css( _c.offscreenCSS ); + } + else + $P.hide().removeData(_c.offscreenReset); + } + +, _showPane = function (pane) { + var $P = $Ps[pane] + , o = options[pane] + , off = _c.offscreenCSS + , old = $P.data(_c.offscreenReset) + , s = $P[0].style + ; + $P .show() // ALWAYS show, just in case + .removeData(_c.offscreenReset); + if (o.useOffscreenClose && old) { + if (s.left == off.left) + s.left = old.left; + if (s.right == off.right) + s.right = old.right; + } + } + + + /** + * Completely 'hides' a pane, including its spacing - as if it does not exist + * The pane is not actually 'removed' from the source, so can use 'show' to un-hide it + * + * @param {string} pane The pane being hidden, ie: north, south, east, or west + * @param {boolean=} [noAnimation=false] + */ +, hide = function (evt_or_pane, noAnimation) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + ; + if (!$P || s.isHidden) return; // pane does not exist OR is already hidden + + // onhide_start callback - will CANCEL hide if returns false + if (state.initialized && false === _runCallbacks("onhide_start", pane)) return; + + s.isSliding = false; // just in case + + // now hide the elements + if ($R) $R.hide(); // hide resizer-bar + if (!state.initialized || s.isClosed) { + s.isClosed = true; // to trigger open-animation on show() + s.isHidden = true; + s.isVisible = false; + if (!state.initialized) + _hidePane(pane); // no animation when loading page + sizeMidPanes(_c[pane].dir === "horz" ? "" : "center"); + if (state.initialized || o.triggerEventsOnLoad) + _runCallbacks("onhide_end", pane); + } + else { + s.isHiding = true; // used by onclose + close(pane, false, noAnimation); // adjust all panes to fit + } + } + + /** + * Show a hidden pane - show as 'closed' by default unless openPane = true + * + * @param {string} pane The pane being opened, ie: north, south, east, or west + * @param {boolean=} [openPane=false] + * @param {boolean=} [noAnimation=false] + * @param {boolean=} [noAlert=false] + */ +, show = function (evt_or_pane, openPane, noAnimation, noAlert) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + ; + if (!$P || !s.isHidden) return; // pane does not exist OR is not hidden + + // onshow_start callback - will CANCEL show if returns false + if (false === _runCallbacks("onshow_start", pane)) return; + + s.isSliding = false; // just in case + s.isShowing = true; // used by onopen/onclose + //s.isHidden = false; - will be set by open/close - if not cancelled + + // now show the elements + //if ($R) $R.show(); - will be shown by open/close + if (openPane === false) + close(pane, true); // true = force + else + open(pane, false, noAnimation, noAlert); // adjust all panes to fit + } + + + /** + * Toggles a pane open/closed by calling either open or close + * + * @param {string} pane The pane being toggled, ie: north, south, east, or west + * @param {boolean=} [slide=false] + */ +, toggle = function (evt_or_pane, slide) { + if (!isInitialized()) return; + var evt = evtObj(evt_or_pane) + , pane = evtPane.call(this, evt_or_pane) + , s = state[pane] + ; + if (evt) // called from to $R.dblclick OR triggerPaneEvent + evt.stopImmediatePropagation(); + if (s.isHidden) + show(pane); // will call 'open' after unhiding it + else if (s.isClosed) + open(pane, !!slide); + else + close(pane); + } + + + /** + * Utility method used during init or other auto-processes + * + * @param {string} pane The pane being closed + * @param {boolean=} [setHandles=false] + */ +, _closePane = function (pane, setHandles) { + var + $P = $Ps[pane] + , s = state[pane] + ; + _hidePane(pane); + s.isClosed = true; + s.isVisible = false; + // UNUSED: if (setHandles) setAsClosed(pane, true); // true = force + } + + /** + * Close the specified pane (animation optional), and resize all other panes as needed + * + * @param {string} pane The pane being closed, ie: north, south, east, or west + * @param {boolean=} [force=false] + * @param {boolean=} [noAnimation=false] + * @param {boolean=} [skipCallback=false] + */ +, close = function (evt_or_pane, force, noAnimation, skipCallback) { + var pane = evtPane.call(this, evt_or_pane); + // if pane has been initialized, but NOT the complete layout, close pane instantly + if (!state.initialized && $Ps[pane]) { + _closePane(pane); // INIT pane as closed + return; + } + if (!isInitialized()) return; + + var + $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , o = options[pane] + , s = state[pane] + , c = _c[pane] + , doFX, isShowing, isHiding, wasSliding; + + // QUEUE in case another action/animation is in progress + $N.queue(function( queueNext ){ + + if ( !$P + || (!o.closable && !s.isShowing && !s.isHiding) // invalid request // (!o.resizable && !o.closable) ??? + || (!force && s.isClosed && !s.isShowing) // already closed + ) return queueNext(); + + // onclose_start callback - will CANCEL hide if returns false + // SKIP if just 'showing' a hidden pane as 'closed' + var abort = !s.isShowing && false === _runCallbacks("onclose_start", pane); + + // transfer logic vars to temp vars + isShowing = s.isShowing; + isHiding = s.isHiding; + wasSliding = s.isSliding; + // now clear the logic vars (REQUIRED before aborting) + delete s.isShowing; + delete s.isHiding; + + if (abort) return queueNext(); + + doFX = !noAnimation && !s.isClosed && (o.fxName_close != "none"); + s.isMoving = true; + s.isClosed = true; + s.isVisible = false; + // update isHidden BEFORE sizing panes + if (isHiding) s.isHidden = true; + else if (isShowing) s.isHidden = false; + + if (s.isSliding) // pane is being closed, so UNBIND trigger events + bindStopSlidingEvents(pane, false); // will set isSliding=false + else // resize panes adjacent to this one + sizeMidPanes(_c[pane].dir === "horz" ? "" : "center", false); // false = NOT skipCallback + + // if this pane has a resizer bar, move it NOW - before animation + setAsClosed(pane); + + // CLOSE THE PANE + if (doFX) { // animate the close + // mask panes with objects + var masks = "center"+ (c.dir=="horz" ? ",west,east" : ""); + showMasks( masks, true ); // true = ONLY mask panes with maskObjects=true + lockPaneForFX(pane, true); // need to set left/top so animation will work + $P.hide( o.fxName_close, o.fxSettings_close, o.fxSpeed_close, function () { + lockPaneForFX(pane, false); // undo + if (s.isClosed) close_2(); + queueNext(); + }); + } + else { // hide the pane without animation + _hidePane(pane); + close_2(); + queueNext(); + }; + }); + + // SUBROUTINE + function close_2 () { + s.isMoving = false; + bindStartSlidingEvent(pane, true); // will enable if o.slidable = true + + // if opposite-pane was autoClosed, see if it can be autoOpened now + var altPane = _c.oppositeEdge[pane]; + if (state[ altPane ].noRoom) { + setSizeLimits( altPane ); + makePaneFit( altPane ); + } + + // hide any masks shown while closing + hideMasks(); + + if (!skipCallback && (state.initialized || o.triggerEventsOnLoad)) { + // onclose callback - UNLESS just 'showing' a hidden pane as 'closed' + if (!isShowing) _runCallbacks("onclose_end", pane); + // onhide OR onshow callback + if (isShowing) _runCallbacks("onshow_end", pane); + if (isHiding) _runCallbacks("onhide_end", pane); + } + } + } + + /** + * @param {string} pane The pane just closed, ie: north, south, east, or west + */ +, setAsClosed = function (pane) { + var + $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , o = options[pane] + , s = state[pane] + , side = _c[pane].side.toLowerCase() + , inset = "inset"+ _c[pane].side + , rClass = o.resizerClass + , tClass = o.togglerClass + , _pane = "-"+ pane // used for classNames + , _open = "-open" + , _sliding= "-sliding" + , _closed = "-closed" + ; + $R + .css(side, sC[inset]) // move the resizer + .removeClass( rClass+_open +" "+ rClass+_pane+_open ) + .removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding ) + .addClass( rClass+_closed +" "+ rClass+_pane+_closed ) + .unbind("dblclick."+ sID) + ; + // DISABLE 'resizing' when closed - do this BEFORE bindStartSlidingEvent? + if (o.resizable && $.layout.plugins.draggable) + $R + .draggable("disable") + .removeClass("ui-state-disabled") // do NOT apply disabled styling - not suitable here + .css("cursor", "default") + .attr("title","") + ; + + // if pane has a toggler button, adjust that too + if ($T) { + $T + .removeClass( tClass+_open +" "+ tClass+_pane+_open ) + .addClass( tClass+_closed +" "+ tClass+_pane+_closed ) + .attr("title", o.togglerTip_closed) // may be blank + ; + // toggler-content - if exists + $T.children(".content-open").hide(); + $T.children(".content-closed").css("display","block"); + } + + // sync any 'pin buttons' + syncPinBtns(pane, false); + + if (state.initialized) { + // resize 'length' and position togglers for adjacent panes + sizeHandles(); + } + } + + /** + * Open the specified pane (animation optional), and resize all other panes as needed + * + * @param {string} pane The pane being opened, ie: north, south, east, or west + * @param {boolean=} [slide=false] + * @param {boolean=} [noAnimation=false] + * @param {boolean=} [noAlert=false] + */ +, open = function (evt_or_pane, slide, noAnimation, noAlert) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , o = options[pane] + , s = state[pane] + , c = _c[pane] + , doFX, isShowing + ; + // QUEUE in case another action/animation is in progress + $N.queue(function( queueNext ){ + + if ( !$P + || (!o.resizable && !o.closable && !s.isShowing) // invalid request + || (s.isVisible && !s.isSliding) // already open + ) return queueNext(); + + // pane can ALSO be unhidden by just calling show(), so handle this scenario + if (s.isHidden && !s.isShowing) { + queueNext(); // call before show() because it needs the queue free + show(pane, true); + return; + } + + if (o.autoResize && s.size != o.size) // resize pane to original size set in options + sizePane(pane, o.size, true, true, true); // true=skipCallback/forceResize/noAnimation + else + // make sure there is enough space available to open the pane + setSizeLimits(pane, slide); + + // onopen_start callback - will CANCEL open if returns false + var cbReturn = _runCallbacks("onopen_start", pane); + + if (cbReturn === "abort") + return queueNext(); + + // update pane-state again in case options were changed in onopen_start + if (cbReturn !== "NC") // NC = "No Callback" + setSizeLimits(pane, slide); + + if (s.minSize > s.maxSize) { // INSUFFICIENT ROOM FOR PANE TO OPEN! + syncPinBtns(pane, false); // make sure pin-buttons are reset + if (!noAlert && o.noRoomToOpenTip) + alert(o.noRoomToOpenTip); + return queueNext(); // ABORT + } + + if (slide) // START Sliding - will set isSliding=true + bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane + else if (s.isSliding) // PIN PANE (stop sliding) - open pane 'normally' instead + bindStopSlidingEvents(pane, false); // UNBIND trigger events - will set isSliding=false + else if (o.slidable) + bindStartSlidingEvent(pane, false); // UNBIND trigger events + + s.noRoom = false; // will be reset by makePaneFit if 'noRoom' + makePaneFit(pane); + + // transfer logic var to temp var + isShowing = s.isShowing; + // now clear the logic var + delete s.isShowing; + + doFX = !noAnimation && s.isClosed && (o.fxName_open != "none"); + s.isMoving = true; + s.isVisible = true; + s.isClosed = false; + // update isHidden BEFORE sizing panes - WHY??? Old? + if (isShowing) s.isHidden = false; + + if (doFX) { // ANIMATE + // mask panes with objects + var masks = "center"+ (c.dir=="horz" ? ",west,east" : ""); + if (s.isSliding) masks += ","+ _c.oppositeEdge[pane]; + showMasks( masks, true ); // true = ONLY mask panes with maskObjects=true + lockPaneForFX(pane, true); // need to set left/top so animation will work + $P.show( o.fxName_open, o.fxSettings_open, o.fxSpeed_open, function() { + lockPaneForFX(pane, false); // undo + if (s.isVisible) open_2(); // continue + queueNext(); + }); + } + else { // no animation + _showPane(pane);// just show pane and... + open_2(); // continue + queueNext(); + }; + }); + + // SUBROUTINE + function open_2 () { + s.isMoving = false; + + // cure iframe display issues + _fixIframe(pane); + + // NOTE: if isSliding, then other panes are NOT 'resized' + if (!s.isSliding) { // resize all panes adjacent to this one + hideMasks(); // remove any masks shown while opening + sizeMidPanes(_c[pane].dir=="vert" ? "center" : "", false); // false = NOT skipCallback + } + + // set classes, position handles and execute callbacks... + setAsOpen(pane); + }; + + } + + /** + * @param {string} pane The pane just opened, ie: north, south, east, or west + * @param {boolean=} [skipCallback=false] + */ +, setAsOpen = function (pane, skipCallback) { + var + $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , o = options[pane] + , s = state[pane] + , side = _c[pane].side.toLowerCase() + , inset = "inset"+ _c[pane].side + , rClass = o.resizerClass + , tClass = o.togglerClass + , _pane = "-"+ pane // used for classNames + , _open = "-open" + , _closed = "-closed" + , _sliding= "-sliding" + ; + $R + .css(side, sC[inset] + getPaneSize(pane)) // move the resizer + .removeClass( rClass+_closed +" "+ rClass+_pane+_closed ) + .addClass( rClass+_open +" "+ rClass+_pane+_open ) + ; + if (s.isSliding) + $R.addClass( rClass+_sliding +" "+ rClass+_pane+_sliding ) + else // in case 'was sliding' + $R.removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding ) + + if (o.resizerDblClickToggle) + $R.bind("dblclick", toggle ); + removeHover( 0, $R ); // remove hover classes + if (o.resizable && $.layout.plugins.draggable) + $R .draggable("enable") + .css("cursor", o.resizerCursor) + .attr("title", o.resizerTip); + else if (!s.isSliding) + $R.css("cursor", "default"); // n-resize, s-resize, etc + + // if pane also has a toggler button, adjust that too + if ($T) { + $T .removeClass( tClass+_closed +" "+ tClass+_pane+_closed ) + .addClass( tClass+_open +" "+ tClass+_pane+_open ) + .attr("title", o.togglerTip_open); // may be blank + removeHover( 0, $T ); // remove hover classes + // toggler-content - if exists + $T.children(".content-closed").hide(); + $T.children(".content-open").css("display","block"); + } + + // sync any 'pin buttons' + syncPinBtns(pane, !s.isSliding); + + // update pane-state dimensions - BEFORE resizing content + $.extend(s, elDims($P)); + + if (state.initialized) { + // resize resizer & toggler sizes for all panes + sizeHandles(); + // resize content every time pane opens - to be sure + sizeContent(pane, true); // true = remeasure headers/footers, even if 'pane.isMoving' + } + + if (!skipCallback && (state.initialized || o.triggerEventsOnLoad) && $P.is(":visible")) { + // onopen callback + _runCallbacks("onopen_end", pane); + // onshow callback - TODO: should this be here? + if (s.isShowing) _runCallbacks("onshow_end", pane); + + // ALSO call onresize because layout-size *may* have changed while pane was closed + if (state.initialized) + _runCallbacks("onresize_end", pane); + } + + // TODO: Somehow sizePane("north") is being called after this point??? + } + + + /** + * slideOpen / slideClose / slideToggle + * + * Pass-though methods for sliding + */ +, slideOpen = function (evt_or_pane) { + if (!isInitialized()) return; + var evt = evtObj(evt_or_pane) + , pane = evtPane.call(this, evt_or_pane) + , s = state[pane] + , delay = options[pane].slideDelay_open + ; + // prevent event from triggering on NEW resizer binding created below + if (evt) evt.stopImmediatePropagation(); + + if (s.isClosed && evt && evt.type === "mouseenter" && delay > 0) + // trigger = mouseenter - use a delay + timer.set(pane+"_openSlider", open_NOW, delay); + else + open_NOW(); // will unbind events if is already open + + /** + * SUBROUTINE for timed open + */ + function open_NOW () { + if (!s.isClosed) // skip if no longer closed! + bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane + else if (!s.isMoving) + open(pane, true); // true = slide - open() will handle binding + }; + } + +, slideClose = function (evt_or_pane) { + if (!isInitialized()) return; + var evt = evtObj(evt_or_pane) + , pane = evtPane.call(this, evt_or_pane) + , o = options[pane] + , s = state[pane] + , delay = s.isMoving ? 1000 : 300 // MINIMUM delay - option may override + ; + if (s.isClosed || s.isResizing) + return; // skip if already closed OR in process of resizing + else if (o.slideTrigger_close === "click") + close_NOW(); // close immediately onClick + else if (o.preventQuickSlideClose && s.isMoving) + return; // handle Chrome quick-close on slide-open + else if (o.preventPrematureSlideClose && evt && $.layout.isMouseOverElem(evt, $Ps[pane])) + return; // handle incorrect mouseleave trigger, like when over a SELECT-list in IE + else if (evt) // trigger = mouseleave - use a delay + // 1 sec delay if 'opening', else .3 sec + timer.set(pane+"_closeSlider", close_NOW, max(o.slideDelay_close, delay)); + else // called programically + close_NOW(); + + /** + * SUBROUTINE for timed close + */ + function close_NOW () { + if (s.isClosed) // skip 'close' if already closed! + bindStopSlidingEvents(pane, false); // UNBIND trigger events - TODO: is this needed here? + else if (!s.isMoving) + close(pane); // close will handle unbinding + }; + } + + /** + * @param {string} pane The pane being opened, ie: north, south, east, or west + */ +, slideToggle = function (evt_or_pane) { + var pane = evtPane.call(this, evt_or_pane); + toggle(pane, true); + } + + + /** + * Must set left/top on East/South panes so animation will work properly + * + * @param {string} pane The pane to lock, 'east' or 'south' - any other is ignored! + * @param {boolean} doLock true = set left/top, false = remove + */ +, lockPaneForFX = function (pane, doLock) { + var $P = $Ps[pane] + , s = state[pane] + , o = options[pane] + , z = options.zIndexes + ; + if (doLock) { + $P.css({ zIndex: z.pane_animate }); // overlay all elements during animation + if (pane=="south") + $P.css({ top: sC.insetTop + sC.innerHeight - $P.outerHeight() }); + else if (pane=="east") + $P.css({ left: sC.insetLeft + sC.innerWidth - $P.outerWidth() }); + } + else { // animation DONE - RESET CSS + // TODO: see if this can be deleted. It causes a quick-close when sliding in Chrome + $P.css({ zIndex: (s.isSliding ? z.pane_sliding : z.pane_normal) }); + if (pane=="south") + $P.css({ top: "auto" }); + // if pane is positioned 'off-screen', then DO NOT screw with it! + else if (pane=="east" && !$P.css("left").match(/\-99999/)) + $P.css({ left: "auto" }); + // fix anti-aliasing in IE - only needed for animations that change opacity + if (browser.msie && o.fxOpacityFix && o.fxName_open != "slide" && $P.css("filter") && $P.css("opacity") == 1) + $P[0].style.removeAttribute('filter'); + } + } + + + /** + * Toggle sliding functionality of a specific pane on/off by adding removing 'slide open' trigger + * + * @see open(), close() + * @param {string} pane The pane to enable/disable, 'north', 'south', etc. + * @param {boolean} enable Enable or Disable sliding? + */ +, bindStartSlidingEvent = function (pane, enable) { + var o = options[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + , evtName = o.slideTrigger_open.toLowerCase() + ; + if (!$R || (enable && !o.slidable)) return; + + // make sure we have a valid event + if (evtName.match(/mouseover/)) + evtName = o.slideTrigger_open = "mouseenter"; + else if (!evtName.match(/click|dblclick|mouseenter/)) + evtName = o.slideTrigger_open = "click"; + + $R + // add or remove event + [enable ? "bind" : "unbind"](evtName +'.'+ sID, slideOpen) + // set the appropriate cursor & title/tip + .css("cursor", enable ? o.sliderCursor : "default") + .attr("title", enable ? o.sliderTip : "") + ; + } + + /** + * Add or remove 'mouseleave' events to 'slide close' when pane is 'sliding' open or closed + * Also increases zIndex when pane is sliding open + * See bindStartSlidingEvent for code to control 'slide open' + * + * @see slideOpen(), slideClose() + * @param {string} pane The pane to process, 'north', 'south', etc. + * @param {boolean} enable Enable or Disable events? + */ +, bindStopSlidingEvents = function (pane, enable) { + var o = options[pane] + , s = state[pane] + , c = _c[pane] + , z = options.zIndexes + , evtName = o.slideTrigger_close.toLowerCase() + , action = (enable ? "bind" : "unbind") + , $P = $Ps[pane] + , $R = $Rs[pane] + ; + s.isSliding = enable; // logic + timer.clear(pane+"_closeSlider"); // just in case + + // remove 'slideOpen' event from resizer + // ALSO will raise the zIndex of the pane & resizer + if (enable) bindStartSlidingEvent(pane, false); + + // RE/SET zIndex - increases when pane is sliding-open, resets to normal when not + $P.css("zIndex", enable ? z.pane_sliding : z.pane_normal); + $R.css("zIndex", enable ? z.pane_sliding+2 : z.resizer_normal); // NOTE: mask = pane_sliding+1 + + // make sure we have a valid event + if (!evtName.match(/click|mouseleave/)) + evtName = o.slideTrigger_close = "mouseleave"; // also catches 'mouseout' + + // add/remove slide triggers + $R[action](evtName, slideClose); // base event on resize + // need extra events for mouseleave + if (evtName === "mouseleave") { + // also close on pane.mouseleave + $P[action]("mouseleave."+ sID, slideClose); + // cancel timer when mouse moves between 'pane' and 'resizer' + $R[action]("mouseenter."+ sID, cancelMouseOut); + $P[action]("mouseenter."+ sID, cancelMouseOut); + } + + if (!enable) + timer.clear(pane+"_closeSlider"); + else if (evtName === "click" && !o.resizable) { + // IF pane is not resizable (which already has a cursor and tip) + // then set the a cursor & title/tip on resizer when sliding + $R.css("cursor", enable ? o.sliderCursor : "default"); + $R.attr("title", enable ? o.togglerTip_open : ""); // use Toggler-tip, eg: "Close Pane" + } + + // SUBROUTINE for mouseleave timer clearing + function cancelMouseOut (evt) { + timer.clear(pane+"_closeSlider"); + evt.stopPropagation(); + } + } + + + /** + * Hides/closes a pane if there is insufficient room - reverses this when there is room again + * MUST have already called setSizeLimits() before calling this method + * + * @param {string} pane The pane being resized + * @param {boolean=} [isOpening=false] Called from onOpen? + * @param {boolean=} [skipCallback=false] Should the onresize callback be run? + * @param {boolean=} [force=false] + */ +, makePaneFit = function (pane, isOpening, skipCallback, force) { + var + o = options[pane] + , s = state[pane] + , c = _c[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + , isSidePane = c.dir==="vert" + , hasRoom = false + ; + // special handling for center & east/west panes + if (pane === "center" || (isSidePane && s.noVerticalRoom)) { + // see if there is enough room to display the pane + // ERROR: hasRoom = s.minHeight <= s.maxHeight && (isSidePane || s.minWidth <= s.maxWidth); + hasRoom = (s.maxHeight >= 0); + if (hasRoom && s.noRoom) { // previously hidden due to noRoom, so show now + _showPane(pane); + if ($R) $R.show(); + s.isVisible = true; + s.noRoom = false; + if (isSidePane) s.noVerticalRoom = false; + _fixIframe(pane); + } + else if (!hasRoom && !s.noRoom) { // not currently hidden, so hide now + _hidePane(pane); + if ($R) $R.hide(); + s.isVisible = false; + s.noRoom = true; + } + } + + // see if there is enough room to fit the border-pane + if (pane === "center") { + // ignore center in this block + } + else if (s.minSize <= s.maxSize) { // pane CAN fit + hasRoom = true; + if (s.size > s.maxSize) // pane is too big - shrink it + sizePane(pane, s.maxSize, skipCallback, force, true); // true = noAnimation + else if (s.size < s.minSize) // pane is too small - enlarge it + sizePane(pane, s.minSize, skipCallback, force, true); + // need s.isVisible because new pseudoClose method keeps pane visible, but off-screen + else if ($R && s.isVisible && $P.is(":visible")) { + // make sure resizer-bar is positioned correctly + // handles situation where nested layout was 'hidden' when initialized + var side = c.side.toLowerCase() + , pos = s.size + sC["inset"+ c.side] + ; + if ($.layout.cssNum($R, side) != pos) $R.css( side, pos ); + } + + // if was previously hidden due to noRoom, then RESET because NOW there is room + if (s.noRoom) { + // s.noRoom state will be set by open or show + if (s.wasOpen && o.closable) { + if (o.autoReopen) + open(pane, false, true, true); // true = noAnimation, true = noAlert + else // leave the pane closed, so just update state + s.noRoom = false; + } + else + show(pane, s.wasOpen, true, true); // true = noAnimation, true = noAlert + } + } + else { // !hasRoom - pane CANNOT fit + if (!s.noRoom) { // pane not set as noRoom yet, so hide or close it now... + s.noRoom = true; // update state + s.wasOpen = !s.isClosed && !s.isSliding; + if (s.isClosed){} // SKIP + else if (o.closable) // 'close' if possible + close(pane, true, true); // true = force, true = noAnimation + else // 'hide' pane if cannot just be closed + hide(pane, true); // true = noAnimation + } + } + } + + + /** + * sizePane / manualSizePane + * sizePane is called only by internal methods whenever a pane needs to be resized + * manualSizePane is an exposed flow-through method allowing extra code when pane is 'manually resized' + * + * @param {string} pane The pane being resized + * @param {number} size The *desired* new size for this pane - will be validated + * @param {boolean=} [skipCallback=false] Should the onresize callback be run? + * @param {boolean=} [noAnimation=false] + */ +, manualSizePane = function (evt_or_pane, size, skipCallback, noAnimation) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , o = options[pane] + , s = state[pane] + // if resizing callbacks have been delayed and resizing is now DONE, force resizing to complete... + , forceResize = o.livePaneResizing && !s.isResizing + ; + // ANY call to manualSizePane disables autoResize - ie, percentage sizing + o.autoResize = false; + // flow-through... + sizePane(pane, size, skipCallback, forceResize, noAnimation); // will animate resize if option enabled + } + + /** + * @param {string} pane The pane being resized + * @param {number} size The *desired* new size for this pane - will be validated + * @param {boolean=} [skipCallback=false] Should the onresize callback be run? + * @param {boolean=} [force=false] Force resizing even if does not seem necessary + * @param {boolean=} [noAnimation=false] + */ +, sizePane = function (evt_or_pane, size, skipCallback, force, noAnimation) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) // probably NEVER called from event? + , o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + , side = _c[pane].side.toLowerCase() + , dimName = _c[pane].sizeType.toLowerCase() + , inset = "inset"+ _c[pane].side + , skipResizeWhileDragging = s.isResizing && !o.triggerEventsDuringLiveResize + , doFX = noAnimation !== true && o.animatePaneSizing + , oldSize, newSize + ; + // QUEUE in case another action/animation is in progress + $N.queue(function( queueNext ){ + // calculate 'current' min/max sizes + setSizeLimits(pane); // update pane-state + oldSize = s.size; + size = _parseSize(pane, size); // handle percentages & auto + size = max(size, _parseSize(pane, o.minSize)); + size = min(size, s.maxSize); + if (size < s.minSize) { // not enough room for pane! + queueNext(); // call before makePaneFit() because it needs the queue free + makePaneFit(pane, false, skipCallback); // will hide or close pane + return; + } + + // IF newSize is same as oldSize, then nothing to do - abort + if (!force && size === oldSize) + return queueNext(); + + // onresize_start callback CANNOT cancel resizing because this would break the layout! + if (!skipCallback && state.initialized && s.isVisible) + _runCallbacks("onresize_start", pane); + + // resize the pane, and make sure its visible + newSize = cssSize(pane, size); + + if (doFX && $P.is(":visible")) { // ANIMATE + var fx = $.layout.effects.size[pane] || $.layout.effects.size.all + , easing = o.fxSettings_size.easing || fx.easing + , z = options.zIndexes + , props = {}; + props[ dimName ] = newSize +'px'; + s.isMoving = true; + // overlay all elements during animation + $P.css({ zIndex: z.pane_animate }) + .show().animate( props, o.fxSpeed_size, easing, function(){ + // reset zIndex after animation + $P.css({ zIndex: (s.isSliding ? z.pane_sliding : z.pane_normal) }); + s.isMoving = false; + sizePane_2(); // continue + queueNext(); + }); + } + else { // no animation + $P.css( dimName, newSize ); // resize pane + // if pane is visible, then + if ($P.is(":visible")) + sizePane_2(); // continue + else { + // pane is NOT VISIBLE, so just update state data... + // when pane is *next opened*, it will have the new size + s.size = size; // update state.size + $.extend(s, elDims($P)); // update state dimensions + } + queueNext(); + }; + + }); + + // SUBROUTINE + function sizePane_2 () { + /* Panes are sometimes not sized precisely in some browsers!? + * This code will resize the pane up to 3 times to nudge the pane to the correct size + */ + var actual = dimName==='width' ? $P.outerWidth() : $P.outerHeight() + , tries = [{ + pane: pane + , count: 1 + , target: size + , actual: actual + , correct: (size === actual) + , attempt: size + , cssSize: newSize + }] + , lastTry = tries[0] + , msg = 'Inaccurate size after resizing the '+ pane +'-pane.' + ; + while ( !lastTry.correct ) { + thisTry = { pane: pane, count: lastTry.count+1, target: size }; + + if (lastTry.actual > size) + thisTry.attempt = max(0, lastTry.attempt - (lastTry.actual - size)); + else // lastTry.actual < size + thisTry.attempt = max(0, lastTry.attempt + (size - lastTry.actual)); + + thisTry.cssSize = cssSize(pane, thisTry.attempt); + $P.css( dimName, thisTry.cssSize ); + + thisTry.actual = dimName=='width' ? $P.outerWidth() : $P.outerHeight(); + thisTry.correct = (size === thisTry.actual); + + // if showDebugMessages, log attempts and alert the user of this *non-fatal error* + if (options.showDebugMessages) { + if ( tries.length === 1) { + _log(msg, false); + _log(lastTry, false); + } + _log(thisTry, false); + } + + // after 4 tries, is as close as its gonna get! + if (tries.length > 3) break; + + tries.push( thisTry ); + lastTry = tries[ tries.length - 1 ]; + } + // END TESTING CODE + + // update pane-state dimensions + s.size = size; + $.extend(s, elDims($P)); + + if (s.isVisible && $P.is(":visible")) { + // reposition the resizer-bar + if ($R) $R.css( side, size + sC[inset] ); + // resize the content-div + sizeContent(pane); + } + + if (!skipCallback && !skipResizeWhileDragging && state.initialized && s.isVisible) + _runCallbacks("onresize_end", pane); + + // resize all the adjacent panes, and adjust their toggler buttons + // when skipCallback passed, it means the controlling method will handle 'other panes' + if (!skipCallback) { + // also no callback if live-resize is in progress and NOT triggerEventsDuringLiveResize + if (!s.isSliding) sizeMidPanes(_c[pane].dir=="horz" ? "" : "center", skipResizeWhileDragging, force); + sizeHandles(); + } + + // if opposite-pane was autoClosed, see if it can be autoOpened now + var altPane = _c.oppositeEdge[pane]; + if (size < oldSize && state[ altPane ].noRoom) { + setSizeLimits( altPane ); + makePaneFit( altPane, false, skipCallback ); + } + + // DEBUG - ALERT user/developer so they know there was a sizing problem + if (options.showDebugMessages && tries.length > 1) + _log(msg +'\nSee the Error Console for details.', true); + } + } + + /** + * @see initPanes(), sizePane(), resizeAll(), open(), close(), hide() + * @param {string} panes The pane(s) being resized, comma-delmited string + * @param {boolean=} [skipCallback=false] Should the onresize callback be run? + * @param {boolean=} [force=false] + */ +, sizeMidPanes = function (panes, skipCallback, force) { + panes = (panes ? panes : "east,west,center").split(","); + + $.each(panes, function (i, pane) { + if (!$Ps[pane]) return; // NO PANE - skip + var + o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + , isCenter= (pane=="center") + , hasRoom = true + , CSS = {} + , newCenter = calcNewCenterPaneDims() + ; + // update pane-state dimensions + $.extend(s, elDims($P)); + + if (pane === "center") { + if (!force && s.isVisible && newCenter.width === s.outerWidth && newCenter.height === s.outerHeight) + return true; // SKIP - pane already the correct size + // set state for makePaneFit() logic + $.extend(s, cssMinDims(pane), { + maxWidth: newCenter.width + , maxHeight: newCenter.height + }); + CSS = newCenter; + // convert OUTER width/height to CSS width/height + CSS.width = cssW($P, CSS.width); + // NEW - allow pane to extend 'below' visible area rather than hide it + CSS.height = cssH($P, CSS.height); + hasRoom = CSS.width >= 0 && CSS.height >= 0; // height >= 0 = ALWAYS TRUE NOW + // during layout init, try to shrink east/west panes to make room for center + if (!state.initialized && o.minWidth > s.outerWidth) { + var + reqPx = o.minWidth - s.outerWidth + , minE = options.east.minSize || 0 + , minW = options.west.minSize || 0 + , sizeE = state.east.size + , sizeW = state.west.size + , newE = sizeE + , newW = sizeW + ; + if (reqPx > 0 && state.east.isVisible && sizeE > minE) { + newE = max( sizeE-minE, sizeE-reqPx ); + reqPx -= sizeE-newE; + } + if (reqPx > 0 && state.west.isVisible && sizeW > minW) { + newW = max( sizeW-minW, sizeW-reqPx ); + reqPx -= sizeW-newW; + } + // IF we found enough extra space, then resize the border panes as calculated + if (reqPx === 0) { + if (sizeE != minE) + sizePane('east', newE, true, force, true); // true = skipCallback/noAnimation - initPanes will handle when done + if (sizeW != minW) + sizePane('west', newW, true, force, true); + // now start over! + sizeMidPanes('center', skipCallback, force); + return; // abort this loop + } + } + } + else { // for east and west, set only the height, which is same as center height + // set state.min/maxWidth/Height for makePaneFit() logic + if (s.isVisible && !s.noVerticalRoom) + $.extend(s, elDims($P), cssMinDims(pane)) + if (!force && !s.noVerticalRoom && newCenter.height === s.outerHeight) + return true; // SKIP - pane already the correct size + // east/west have same top, bottom & height as center + CSS.top = newCenter.top; + CSS.bottom = newCenter.bottom; + // NEW - allow pane to extend 'below' visible area rather than hide it + CSS.height = cssH($P, newCenter.height); + s.maxHeight = CSS.height; + hasRoom = (s.maxHeight >= 0); // ALWAYS TRUE NOW + if (!hasRoom) s.noVerticalRoom = true; // makePaneFit() logic + } + + if (hasRoom) { + // resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized + if (!skipCallback && state.initialized) + _runCallbacks("onresize_start", pane); + + $P.css(CSS); // apply the CSS to pane + sizeHandles(pane); // also update resizer length + if (s.noRoom && !s.isClosed && !s.isHidden) + makePaneFit(pane); // will re-open/show auto-closed/hidden pane + if (s.isVisible) { + $.extend(s, elDims($P)); // update pane dimensions + if (state.initialized) sizeContent(pane); // also resize the contents, if exists + } + } + else if (!s.noRoom && s.isVisible) // no room for pane + makePaneFit(pane); // will hide or close pane + + if (!s.isVisible) + return true; // DONE - next pane + + /* + * Extra CSS for IE6 or IE7 in Quirks-mode - add 'width' to NORTH/SOUTH panes + * Normally these panes have only 'left' & 'right' positions so pane auto-sizes + * ALSO required when pane is an IFRAME because will NOT default to 'full width' + */ + if (pane === "center") { // finished processing midPanes + var b = $.layout.browser; + var fix = b.isIE6 || (b.msie && !$.support.boxModel); + if ($Ps.north && (fix || state.north.tagName=="IFRAME")) + $Ps.north.css("width", cssW($Ps.north, sC.innerWidth)); + if ($Ps.south && (fix || state.south.tagName=="IFRAME")) + $Ps.south.css("width", cssW($Ps.south, sC.innerWidth)); + } + + // resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized + if (!skipCallback && state.initialized) + _runCallbacks("onresize_end", pane); + }); + } + + + /** + * @see window.onresize(), callbacks or custom code + */ +, resizeAll = function () { + if (!state.initialized) { + _initLayoutElements(); + return; // no need to resize since we just initialized! + } + var oldW = sC.innerWidth + , oldH = sC.innerHeight + ; + // cannot size layout when 'container' is hidden or collapsed + if (!$N.is(":visible:") ) return; + $.extend( state.container, elDims( $N ) ); // UPDATE container dimensions + if (!sC.outerHeight) return; + + // onresizeall_start will CANCEL resizing if returns false + // state.container has already been set, so user can access this info for calcuations + if (false === _runCallbacks("onresizeall_start")) return false; + + var // see if container is now 'smaller' than before + shrunkH = (sC.innerHeight < oldH) + , shrunkW = (sC.innerWidth < oldW) + , $P, o, s, dir + ; + // NOTE special order for sizing: S-N-E-W + $.each(["south","north","east","west"], function (i, pane) { + if (!$Ps[pane]) return; // no pane - SKIP + s = state[pane]; + o = options[pane]; + dir = _c[pane].dir; + + if (o.autoResize && s.size != o.size) // resize pane to original size set in options + sizePane(pane, o.size, true, true, true); // true=skipCallback/forceResize/noAnimation + else { + setSizeLimits(pane); + makePaneFit(pane, false, true, true); // true=skipCallback/forceResize + } + }); + + sizeMidPanes("", true, true); // true=skipCallback, true=forceResize + sizeHandles(); // reposition the toggler elements + + // trigger all individual pane callbacks AFTER layout has finished resizing + o = options; // reuse alias + $.each(_c.allPanes, function (i, pane) { + $P = $Ps[pane]; + if (!$P) return; // SKIP + if (state[pane].isVisible) // undefined for non-existent panes + _runCallbacks("onresize_end", pane); // callback - if exists + }); + + _runCallbacks("onresizeall_end"); + //_triggerLayoutEvent(pane, 'resizeall'); + } + + /** + * Whenever a pane resizes or opens that has a nested layout, trigger resizeAll + * + * @param {string} pane The pane just resized or opened + */ +, resizeChildLayout = function (evt_or_pane) { + var pane = evtPane.call(this, evt_or_pane); + if (!options[pane].resizeChildLayout) return; + var $P = $Ps[pane] + , $C = $Cs[pane] + , d = "layout" + , P = Instance[pane] + , L = children[pane] + ; + // user may have manually set EITHER instance pointer, so handle that + if (P.child && !L) { + // have to reverse the pointers! + var el = P.child.container; + L = children[pane] = (el ? el.data(d) : 0) || null; // set pointer _directly_ to layout instance + } + + // if a layout-pointer exists, see if child has been destroyed + if (L && L.destroyed) + L = children[pane] = null; // clear child pointers + // no child layout pointer is set - see if there is a child layout NOW + if (!L) L = children[pane] = $P.data(d) || ($C ? $C.data(d) : 0) || null; // set/update child pointers + + // ALWAYS refresh the pane.child alias + P.child = children[pane]; + + if (L) L.resizeAll(); + } + + + /** + * IF pane has a content-div, then resize all elements inside pane to fit pane-height + * + * @param {string=} [panes=""] The pane(s) being resized + * @param {boolean=} [remeasure=false] Should the content (header/footer) be remeasured? + */ +, sizeContent = function (evt_or_panes, remeasure) { + if (!isInitialized()) return; + + var panes = evtPane.call(this, evt_or_panes); + panes = panes ? panes.split(",") : _c.allPanes; + + $.each(panes, function (idx, pane) { + var + $P = $Ps[pane] + , $C = $Cs[pane] + , o = options[pane] + , s = state[pane] + , m = s.content // m = measurements + ; + if (!$P || !$C || !$P.is(":visible")) return true; // NOT VISIBLE - skip + + // if content-element was REMOVED, update OR remove the pointer + if (!$C.length) { + initContent(pane, false); // false = do NOT sizeContent() - already there! + if (!$C) return; // no replacement element found - pointer have been removed + } + + // onsizecontent_start will CANCEL resizing if returns false + if (false === _runCallbacks("onsizecontent_start", pane)) return; + + // skip re-measuring offsets if live-resizing + if ((!s.isMoving && !s.isResizing) || o.liveContentResizing || remeasure || m.top == undefined) { + _measure(); + // if any footers are below pane-bottom, they may not measure correctly, + // so allow pane overflow and re-measure + if (m.hiddenFooters > 0 && $P.css("overflow") === "hidden") { + $P.css("overflow", "visible"); + _measure(); // remeasure while overflowing + $P.css("overflow", "hidden"); + } + } + // NOTE: spaceAbove/Below *includes* the pane paddingTop/Bottom, but not pane.borders + var newH = s.innerHeight - (m.spaceAbove - s.css.paddingTop) - (m.spaceBelow - s.css.paddingBottom); + + if (!$C.is(":visible") || m.height != newH) { + // size the Content element to fit new pane-size - will autoHide if not enough room + setOuterHeight($C, newH, true); // true=autoHide + m.height = newH; // save new height + }; + + if (state.initialized) + _runCallbacks("onsizecontent_end", pane); + + function _below ($E) { + return max(s.css.paddingBottom, (parseInt($E.css("marginBottom"), 10) || 0)); + }; + + function _measure () { + var + ignore = options[pane].contentIgnoreSelector + , $Fs = $C.nextAll().not(ignore || ':lt(0)') // not :lt(0) = ALL + , $Fs_vis = $Fs.filter(':visible') + , $F = $Fs_vis.filter(':last') + ; + m = { + top: $C[0].offsetTop + , height: $C.outerHeight() + , numFooters: $Fs.length + , hiddenFooters: $Fs.length - $Fs_vis.length + , spaceBelow: 0 // correct if no content footer ($E) + } + m.spaceAbove = m.top; // just for state - not used in calc + m.bottom = m.top + m.height; + if ($F.length) + //spaceBelow = (LastFooter.top + LastFooter.height) [footerBottom] - Content.bottom + max(LastFooter.marginBottom, pane.paddingBotom) + m.spaceBelow = ($F[0].offsetTop + $F.outerHeight()) - m.bottom + _below($F); + else // no footer - check marginBottom on Content element itself + m.spaceBelow = _below($C); + }; + }); + } + + + /** + * Called every time a pane is opened, closed, or resized to slide the togglers to 'center' and adjust their length if necessary + * + * @see initHandles(), open(), close(), resizeAll() + * @param {string=} [panes=""] The pane(s) being resized + */ +, sizeHandles = function (evt_or_panes) { + var panes = evtPane.call(this, evt_or_panes) + panes = panes ? panes.split(",") : _c.borderPanes; + + $.each(panes, function (i, pane) { + var + o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , $TC + ; + if (!$P || !$R) return; + + var + dir = _c[pane].dir + , _state = (s.isClosed ? "_closed" : "_open") + , spacing = o["spacing"+ _state] + , togAlign = o["togglerAlign"+ _state] + , togLen = o["togglerLength"+ _state] + , paneLen + , left + , offset + , CSS = {} + ; + + if (spacing === 0) { + $R.hide(); + return; + } + else if (!s.noRoom && !s.isHidden) // skip if resizer was hidden for any reason + $R.show(); // in case was previously hidden + + // Resizer Bar is ALWAYS same width/height of pane it is attached to + if (dir === "horz") { // north/south + //paneLen = $P.outerWidth(); // s.outerWidth || + paneLen = sC.innerWidth; // handle offscreen-panes + s.resizerLength = paneLen; + left = $.layout.cssNum($P, "left") + $R.css({ + width: cssW($R, paneLen) // account for borders & padding + , height: cssH($R, spacing) // ditto + , left: left > -9999 ? left : sC.insetLeft // handle offscreen-panes + }); + } + else { // east/west + paneLen = $P.outerHeight(); // s.outerHeight || + s.resizerLength = paneLen; + $R.css({ + height: cssH($R, paneLen) // account for borders & padding + , width: cssW($R, spacing) // ditto + , top: sC.insetTop + getPaneSize("north", true) // TODO: what if no North pane? + //, top: $.layout.cssNum($Ps["center"], "top") + }); + } + + // remove hover classes + removeHover( o, $R ); + + if ($T) { + if (togLen === 0 || (s.isSliding && o.hideTogglerOnSlide)) { + $T.hide(); // always HIDE the toggler when 'sliding' + return; + } + else + $T.show(); // in case was previously hidden + + if (!(togLen > 0) || togLen === "100%" || togLen > paneLen) { + togLen = paneLen; + offset = 0; + } + else { // calculate 'offset' based on options.PANE.togglerAlign_open/closed + if (isStr(togAlign)) { + switch (togAlign) { + case "top": + case "left": offset = 0; + break; + case "bottom": + case "right": offset = paneLen - togLen; + break; + case "middle": + case "center": + default: offset = round((paneLen - togLen) / 2); // 'default' catches typos + } + } + else { // togAlign = number + var x = parseInt(togAlign, 10); // + if (togAlign >= 0) offset = x; + else offset = paneLen - togLen + x; // NOTE: x is negative! + } + } + + if (dir === "horz") { // north/south + var width = cssW($T, togLen); + $T.css({ + width: width // account for borders & padding + , height: cssH($T, spacing) // ditto + , left: offset // TODO: VERIFY that toggler positions correctly for ALL values + , top: 0 + }); + // CENTER the toggler content SPAN + $T.children(".content").each(function(){ + $TC = $(this); + $TC.css("marginLeft", round((width-$TC.outerWidth())/2)); // could be negative + }); + } + else { // east/west + var height = cssH($T, togLen); + $T.css({ + height: height // account for borders & padding + , width: cssW($T, spacing) // ditto + , top: offset // POSITION the toggler + , left: 0 + }); + // CENTER the toggler content SPAN + $T.children(".content").each(function(){ + $TC = $(this); + $TC.css("marginTop", round((height-$TC.outerHeight())/2)); // could be negative + }); + } + + // remove ALL hover classes + removeHover( 0, $T ); + } + + // DONE measuring and sizing this resizer/toggler, so can be 'hidden' now + if (!state.initialized && (o.initHidden || s.noRoom)) { + $R.hide(); + if ($T) $T.hide(); + } + }); + } + + + /** + * @param {string} pane + */ +, enableClosable = function (evt_or_pane) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $T = $Ts[pane] + , o = options[pane] + ; + if (!$T) return; + o.closable = true; + $T .bind("click."+ sID, function(evt){ evt.stopPropagation(); toggle(pane); }) + .css("visibility", "visible") + .css("cursor", "pointer") + .attr("title", state[pane].isClosed ? o.togglerTip_closed : o.togglerTip_open) // may be blank + .show(); + } + /** + * @param {string} pane + * @param {boolean=} [hide=false] + */ +, disableClosable = function (evt_or_pane, hide) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $T = $Ts[pane] + ; + if (!$T) return; + options[pane].closable = false; + // is closable is disable, then pane MUST be open! + if (state[pane].isClosed) open(pane, false, true); + $T .unbind("."+ sID) + .css("visibility", hide ? "hidden" : "visible") // instead of hide(), which creates logic issues + .css("cursor", "default") + .attr("title", ""); + } + + + /** + * @param {string} pane + */ +, enableSlidable = function (evt_or_pane) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $R = $Rs[pane] + ; + if (!$R || !$R.data('draggable')) return; + options[pane].slidable = true; + if (s.isClosed) + bindStartSlidingEvent(pane, true); + } + /** + * @param {string} pane + */ +, disableSlidable = function (evt_or_pane) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $R = $Rs[pane] + ; + if (!$R) return; + options[pane].slidable = false; + if (state[pane].isSliding) + close(pane, false, true); + else { + bindStartSlidingEvent(pane, false); + $R .css("cursor", "default") + .attr("title", ""); + removeHover(null, $R[0]); // in case currently hovered + } + } + + + /** + * @param {string} pane + */ +, enableResizable = function (evt_or_pane) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $R = $Rs[pane] + , o = options[pane] + ; + if (!$R || !$R.data('draggable')) return; + o.resizable = true; + $R.draggable("enable"); + if (!state[pane].isClosed) + $R .css("cursor", o.resizerCursor) + .attr("title", o.resizerTip); + } + /** + * @param {string} pane + */ +, disableResizable = function (evt_or_pane) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $R = $Rs[pane] + ; + if (!$R || !$R.data('draggable')) return; + options[pane].resizable = false; + $R .draggable("disable") + .css("cursor", "default") + .attr("title", ""); + removeHover(null, $R[0]); // in case currently hovered + } + + + /** + * Move a pane from source-side (eg, west) to target-side (eg, east) + * If pane exists on target-side, move that to source-side, ie, 'swap' the panes + * + * @param {string} pane1 The pane/edge being swapped + * @param {string} pane2 ditto + */ +, swapPanes = function (evt_or_pane1, pane2) { + if (!isInitialized()) return; + var pane1 = evtPane.call(this, evt_or_pane1); + // change state.edge NOW so callbacks can know where pane is headed... + state[pane1].edge = pane2; + state[pane2].edge = pane1; + // run these even if NOT state.initialized + if (false === _runCallbacks("onswap_start", pane1) + || false === _runCallbacks("onswap_start", pane2) + ) { + state[pane1].edge = pane1; // reset + state[pane2].edge = pane2; + return; + } + + var + oPane1 = copy( pane1 ) + , oPane2 = copy( pane2 ) + , sizes = {} + ; + sizes[pane1] = oPane1 ? oPane1.state.size : 0; + sizes[pane2] = oPane2 ? oPane2.state.size : 0; + + // clear pointers & state + $Ps[pane1] = false; + $Ps[pane2] = false; + state[pane1] = {}; + state[pane2] = {}; + + // ALWAYS remove the resizer & toggler elements + if ($Ts[pane1]) $Ts[pane1].remove(); + if ($Ts[pane2]) $Ts[pane2].remove(); + if ($Rs[pane1]) $Rs[pane1].remove(); + if ($Rs[pane2]) $Rs[pane2].remove(); + $Rs[pane1] = $Rs[pane2] = $Ts[pane1] = $Ts[pane2] = false; + + // transfer element pointers and data to NEW Layout keys + move( oPane1, pane2 ); + move( oPane2, pane1 ); + + // cleanup objects + oPane1 = oPane2 = sizes = null; + + // make panes 'visible' again + if ($Ps[pane1]) $Ps[pane1].css(_c.visible); + if ($Ps[pane2]) $Ps[pane2].css(_c.visible); + + // fix any size discrepancies caused by swap + resizeAll(); + + // run these even if NOT state.initialized + _runCallbacks("onswap_end", pane1); + _runCallbacks("onswap_end", pane2); + + return; + + function copy (n) { // n = pane + var + $P = $Ps[n] + , $C = $Cs[n] + ; + return !$P ? false : { + pane: n + , P: $P ? $P[0] : false + , C: $C ? $C[0] : false + , state: $.extend(true, {}, state[n]) + , options: $.extend(true, {}, options[n]) + } + }; + + function move (oPane, pane) { + if (!oPane) return; + var + P = oPane.P + , C = oPane.C + , oldPane = oPane.pane + , c = _c[pane] + , side = c.side.toLowerCase() + , inset = "inset"+ c.side + // save pane-options that should be retained + , s = $.extend({}, state[pane]) + , o = options[pane] + // RETAIN side-specific FX Settings - more below + , fx = { resizerCursor: o.resizerCursor } + , re, size, pos + ; + $.each("fxName,fxSpeed,fxSettings".split(","), function (i, k) { + fx[k +"_open"] = o[k +"_open"]; + fx[k +"_close"] = o[k +"_close"]; + fx[k +"_size"] = o[k +"_size"]; + }); + + // update object pointers and attributes + $Ps[pane] = $(P) + .data({ + layoutPane: Instance[pane] // NEW pointer to pane-alias-object + , layoutEdge: pane + }) + .css(_c.hidden) + .css(c.cssReq) + ; + $Cs[pane] = C ? $(C) : false; + + // set options and state + options[pane] = $.extend({}, oPane.options, fx); + state[pane] = $.extend({}, oPane.state); + + // change classNames on the pane, eg: ui-layout-pane-east ==> ui-layout-pane-west + re = new RegExp(o.paneClass +"-"+ oldPane, "g"); + P.className = P.className.replace(re, o.paneClass +"-"+ pane); + + // ALWAYS regenerate the resizer & toggler elements + initHandles(pane); // create the required resizer & toggler + + // if moving to different orientation, then keep 'target' pane size + if (c.dir != _c[oldPane].dir) { + size = sizes[pane] || 0; + setSizeLimits(pane); // update pane-state + size = max(size, state[pane].minSize); + // use manualSizePane to disable autoResize - not useful after panes are swapped + manualSizePane(pane, size, true, true); // true/true = skipCallback/noAnimation + } + else // move the resizer here + $Rs[pane].css(side, sC[inset] + (state[pane].isVisible ? getPaneSize(pane) : 0)); + + + // ADD CLASSNAMES & SLIDE-BINDINGS + if (oPane.state.isVisible && !s.isVisible) + setAsOpen(pane, true); // true = skipCallback + else { + setAsClosed(pane); + bindStartSlidingEvent(pane, true); // will enable events IF option is set + } + + // DESTROY the object + oPane = null; + }; + } + + + /** + * INTERNAL method to sync pin-buttons when pane is opened or closed + * Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes + * + * @see open(), setAsOpen(), setAsClosed() + * @param {string} pane These are the params returned to callbacks by layout() + * @param {boolean} doPin True means set the pin 'down', False means 'up' + */ +, syncPinBtns = function (pane, doPin) { + if ($.layout.plugins.buttons) + $.each(state[pane].pins, function (i, selector) { + $.layout.buttons.setPinState(Instance, $(selector), pane, doPin); + }); + } + +; // END var DECLARATIONS + + /** + * Capture keys when enableCursorHotkey - toggle pane if hotkey pressed + * + * @see document.keydown() + */ + function keyDown (evt) { + if (!evt) return true; + var code = evt.keyCode; + if (code < 33) return true; // ignore special keys: ENTER, TAB, etc + + var + PANE = { + 38: "north" // Up Cursor - $.ui.keyCode.UP + , 40: "south" // Down Cursor - $.ui.keyCode.DOWN + , 37: "west" // Left Cursor - $.ui.keyCode.LEFT + , 39: "east" // Right Cursor - $.ui.keyCode.RIGHT + } + , ALT = evt.altKey // no worky! + , SHIFT = evt.shiftKey + , CTRL = evt.ctrlKey + , CURSOR = (CTRL && code >= 37 && code <= 40) + , o, k, m, pane + ; + + if (CURSOR && options[PANE[code]].enableCursorHotkey) // valid cursor-hotkey + pane = PANE[code]; + else if (CTRL || SHIFT) // check to see if this matches a custom-hotkey + $.each(_c.borderPanes, function (i, p) { // loop each pane to check its hotkey + o = options[p]; + k = o.customHotkey; + m = o.customHotkeyModifier; // if missing or invalid, treated as "CTRL+SHIFT" + if ((SHIFT && m=="SHIFT") || (CTRL && m=="CTRL") || (CTRL && SHIFT)) { // Modifier matches + if (k && code === (isNaN(k) || k <= 9 ? k.toUpperCase().charCodeAt(0) : k)) { // Key matches + pane = p; + return false; // BREAK + } + } + }); + + // validate pane + if (!pane || !$Ps[pane] || !options[pane].closable || state[pane].isHidden) + return true; + + toggle(pane); + + evt.stopPropagation(); + evt.returnValue = false; // CANCEL key + return false; + }; + + +/* + * ###################################### + * UTILITY METHODS + * called externally or by initButtons + * ###################################### + */ + + /** + * Change/reset a pane overflow setting & zIndex to allow popups/drop-downs to work + * + * @param {Object=} [el] (optional) Can also be 'bound' to a click, mouseOver, or other event + */ + function allowOverflow (el) { + if (!isInitialized()) return; + if (this && this.tagName) el = this; // BOUND to element + var $P; + if (isStr(el)) + $P = $Ps[el]; + else if ($(el).data("layoutRole")) + $P = $(el); + else + $(el).parents().each(function(){ + if ($(this).data("layoutRole")) { + $P = $(this); + return false; // BREAK + } + }); + if (!$P || !$P.length) return; // INVALID + + var + pane = $P.data("layoutEdge") + , s = state[pane] + ; + + // if pane is already raised, then reset it before doing it again! + // this would happen if allowOverflow is attached to BOTH the pane and an element + if (s.cssSaved) + resetOverflow(pane); // reset previous CSS before continuing + + // if pane is raised by sliding or resizing, or its closed, then abort + if (s.isSliding || s.isResizing || s.isClosed) { + s.cssSaved = false; + return; + } + + var + newCSS = { zIndex: (options.zIndexes.resizer_normal + 1) } + , curCSS = {} + , of = $P.css("overflow") + , ofX = $P.css("overflowX") + , ofY = $P.css("overflowY") + ; + // determine which, if any, overflow settings need to be changed + if (of != "visible") { + curCSS.overflow = of; + newCSS.overflow = "visible"; + } + if (ofX && !ofX.match(/visible|auto/)) { + curCSS.overflowX = ofX; + newCSS.overflowX = "visible"; + } + if (ofY && !ofY.match(/visible|auto/)) { + curCSS.overflowY = ofX; + newCSS.overflowY = "visible"; + } + + // save the current overflow settings - even if blank! + s.cssSaved = curCSS; + + // apply new CSS to raise zIndex and, if necessary, make overflow 'visible' + $P.css( newCSS ); + + // make sure the zIndex of all other panes is normal + $.each(_c.allPanes, function(i, p) { + if (p != pane) resetOverflow(p); + }); + + }; + /** + * @param {Object=} [el] (optional) Can also be 'bound' to a click, mouseOver, or other event + */ + function resetOverflow (el) { + if (!isInitialized()) return; + if (this && this.tagName) el = this; // BOUND to element + var $P; + if (isStr(el)) + $P = $Ps[el]; + else if ($(el).data("layoutRole")) + $P = $(el); + else + $(el).parents().each(function(){ + if ($(this).data("layoutRole")) { + $P = $(this); + return false; // BREAK + } + }); + if (!$P || !$P.length) return; // INVALID + + var + pane = $P.data("layoutEdge") + , s = state[pane] + , CSS = s.cssSaved || {} + ; + // reset the zIndex + if (!s.isSliding && !s.isResizing) + $P.css("zIndex", options.zIndexes.pane_normal); + + // reset Overflow - if necessary + $P.css( CSS ); + + // clear var + s.cssSaved = false; + }; + +/* + * ##################### + * CREATE/RETURN LAYOUT + * ##################### + */ + + // validate that container exists + var $N = $(this).eq(0); // FIRST matching Container element + if (!$N.length) { + if (options.showErrorMessages) + _log( lang.errContainerMissing, true ); + return null; + }; + + // Users retrieve Instance of a layout with: $N.layout() OR $N.data("layout") + // return the Instance-pointer if layout has already been initialized + if ($N.data("layoutContainer") && $N.data("layout")) + return $N.data("layout"); // cached pointer + + // init global vars + var + $Ps = {} // Panes x5 - set in initPanes() + , $Cs = {} // Content x5 - set in initPanes() + , $Rs = {} // Resizers x4 - set in initHandles() + , $Ts = {} // Togglers x4 - set in initHandles() + , $Ms = $([]) // Masks - up to 2 masks per pane (IFRAME + DIV) + // aliases for code brevity + , sC = state.container // alias for easy access to 'container dimensions' + , sID = state.id // alias for unique layout ID/namespace - eg: "layout435" + ; + + // create Instance object to expose data & option Properties, and primary action Methods + var Instance = { + // layout data + options: options // property - options hash + , state: state // property - dimensions hash + // object pointers + , container: $N // property - object pointers for layout container + , panes: $Ps // property - object pointers for ALL Panes: panes.north, panes.center + , contents: $Cs // property - object pointers for ALL Content: contents.north, contents.center + , resizers: $Rs // property - object pointers for ALL Resizers, eg: resizers.north + , togglers: $Ts // property - object pointers for ALL Togglers, eg: togglers.north + // border-pane open/close + , hide: hide // method - ditto + , show: show // method - ditto + , toggle: toggle // method - pass a 'pane' ("north", "west", etc) + , open: open // method - ditto + , close: close // method - ditto + , slideOpen: slideOpen // method - ditto + , slideClose: slideClose // method - ditto + , slideToggle: slideToggle // method - ditto + // pane actions + , setSizeLimits: setSizeLimits // method - pass a 'pane' - update state min/max data + , _sizePane: sizePane // method -intended for user by plugins only! + , sizePane: manualSizePane // method - pass a 'pane' AND an 'outer-size' in pixels or percent, or 'auto' + , sizeContent: sizeContent // method - pass a 'pane' + , swapPanes: swapPanes // method - pass TWO 'panes' - will swap them + // pane element methods + , initContent: initContent // method - ditto + , addPane: addPane // method - pass a 'pane' + , removePane: removePane // method - pass a 'pane' to remove from layout, add 'true' to delete the pane-elem + , createChildLayout: createChildLayout// method - pass a 'pane' and (optional) layout-options (OVERRIDES options[pane].childOptions + // special pane option setting + , enableClosable: enableClosable // method - pass a 'pane' + , disableClosable: disableClosable // method - ditto + , enableSlidable: enableSlidable // method - ditto + , disableSlidable: disableSlidable // method - ditto + , enableResizable: enableResizable // method - ditto + , disableResizable: disableResizable// method - ditto + // utility methods for panes + , allowOverflow: allowOverflow // utility - pass calling element (this) + , resetOverflow: resetOverflow // utility - ditto + // layout control + , destroy: destroy // method - no parameters + , initPanes: isInitialized // method - no parameters + , resizeAll: resizeAll // method - no parameters + // callback triggering + , runCallbacks: _runCallbacks // method - pass evtName & pane (if a pane-event), eg: trigger("onopen", "west") + // alias collections of options, state and children - created in addPane and extended elsewhere + , hasParentLayout: false // set by initContainer() + , children: children // pointers to child-layouts, eg: Instance.children["west"] + , north: false // alias group: { name: pane, pane: $Ps[pane], options: options[pane], state: state[pane], child: children[pane] } + , south: false // ditto + , west: false // ditto + , east: false // ditto + , center: false // ditto + }; + + // create the border layout NOW + if (_create() === 'cancel') // onload_start callback returned false to CANCEL layout creation + return null; + else // true OR false -- if layout-elements did NOT init (hidden or do not exist), can auto-init later + return Instance; // return the Instance object + +} + + + + +/** + * jquery.layout.state 1.0 + * $Date: 2011-07-16 08:00:00 (Sat, 16 July 2011) $ + * + * Copyright (c) 2010 + * Kevin Dalman (http://allpro.net) + * + * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html) + * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses. + * + * @dependancies: UI Layout 1.3.0.rc30.1 or higher + * @dependancies: $.ui.cookie (above) + * + * @support: http://groups.google.com/group/jquery-ui-layout + */ +/* + * State-management options stored in options.stateManagement, which includes a .cookie hash + * Default options saves ALL KEYS for ALL PANES, ie: pane.size, pane.isClosed, pane.isHidden + * + * // STATE/COOKIE OPTIONS + * @example $(el).layout({ + stateManagement: { + enabled: true + , stateKeys: "east.size,west.size,east.isClosed,west.isClosed" + , cookie: { name: "appLayout", path: "/" } + } + }) + * @example $(el).layout({ stateManagement__enabled: true }) // enable auto-state-management using cookies + * @example $(el).layout({ stateManagement__cookie: { name: "appLayout", path: "/" } }) + * @example $(el).layout({ stateManagement__cookie__name: "appLayout", stateManagement__cookie__path: "/" }) + * + * // STATE/COOKIE METHODS + * @example myLayout.saveCookie( "west.isClosed,north.size,south.isHidden", {expires: 7} ); + * @example myLayout.loadCookie(); + * @example myLayout.deleteCookie(); + * @example var JSON = myLayout.readState(); // CURRENT Layout State + * @example var JSON = myLayout.readCookie(); // SAVED Layout State (from cookie) + * @example var JSON = myLayout.state.stateData; // LAST LOADED Layout State (cookie saved in layout.state hash) + * + * CUSTOM STATE-MANAGEMENT (eg, saved in a database) + * @example var JSON = myLayout.readState( "west.isClosed,north.size,south.isHidden" ); + * @example myLayout.loadState( JSON ); + */ + +/** + * UI COOKIE UTILITY + * + * A $.cookie OR $.ui.cookie namespace *should be standard*, but until then... + * This creates $.ui.cookie so Layout does not need the cookie.jquery.js plugin + * NOTE: This utility is REQUIRED by the layout.state plugin + * + * Cookie methods in Layout are created as part of State Management + */ +if (!$.ui) $.ui = {}; +$.ui.cookie = { + + // cookieEnabled is not in DOM specs, but DOES works in all browsers,including IE6 + acceptsCookies: !!navigator.cookieEnabled + +, read: function (name) { + var + c = document.cookie + , cs = c ? c.split(';') : [] + , pair // loop var + ; + for (var i=0, n=cs.length; i < n; i++) { + pair = $.trim(cs[i]).split('='); // name=value pair + if (pair[0] == name) // found the layout cookie + return decodeURIComponent(pair[1]); + + } + return null; + } + +, write: function (name, val, cookieOpts) { + var + params = '' + , date = '' + , clear = false + , o = cookieOpts || {} + , x = o.expires + ; + if (x && x.toUTCString) + date = x; + else if (x === null || typeof x === 'number') { + date = new Date(); + if (x > 0) + date.setDate(date.getDate() + x); + else { + date.setFullYear(1970); + clear = true; + } + } + if (date) params += ';expires='+ date.toUTCString(); + if (o.path) params += ';path='+ o.path; + if (o.domain) params += ';domain='+ o.domain; + if (o.secure) params += ';secure'; + document.cookie = name +'='+ (clear ? "" : encodeURIComponent( val )) + params; // write or clear cookie + } + +, clear: function (name) { + $.ui.cookie.write(name, '', {expires: -1}); + } + +}; +// if cookie.jquery.js is not loaded, create an alias to replicate it +// this may be useful to other plugins or code dependent on that plugin +if (!$.cookie) $.cookie = function (k, v, o) { + var C = $.ui.cookie; + if (v === null) + C.clear(k); + else if (v === undefined) + return C.read(k); + else + C.write(k, v, o); +}; + + +// tell Layout that the state plugin is available +$.layout.plugins.stateManagement = true; + +// Add State-Management options to layout.defaults +$.layout.config.optionRootKeys.push("stateManagement"); +$.layout.defaults.stateManagement = { + enabled: false // true = enable state-management, even if not using cookies +, autoSave: true // Save a state-cookie when page exits? +, autoLoad: true // Load the state-cookie when Layout inits? + // List state-data to save - must be pane-specific +, stateKeys: "north.size,south.size,east.size,west.size,"+ + "north.isClosed,south.isClosed,east.isClosed,west.isClosed,"+ + "north.isHidden,south.isHidden,east.isHidden,west.isHidden" +, cookie: { + name: "" // If not specified, will use Layout.name, else just "Layout" + , domain: "" // blank = current domain + , path: "" // blank = current page, '/' = entire website + , expires: "" // 'days' to keep cookie - leave blank for 'session cookie' + , secure: false + } +}; +// Set stateManagement as a layout-option, NOT a pane-option +$.layout.optionsMap.layout.push("stateManagement"); + +/* + * State Management methods + */ +$.layout.state = { + + /** + * Get the current layout state and save it to a cookie + * + * myLayout.saveCookie( keys, cookieOpts ) + * + * @param {Object} inst + * @param {(string|Array)=} keys + * @param {Object=} opts + */ + saveCookie: function (inst, keys, cookieOpts) { + var o = inst.options + , oS = o.stateManagement + , oC = $.extend(true, {}, oS.cookie, cookieOpts || null) + , data = inst.state.stateData = inst.readState( keys || oS.stateKeys ) // read current panes-state + ; + $.ui.cookie.write( oC.name || o.name || "Layout", $.layout.state.encodeJSON(data), oC ); + return $.extend(true, {}, data); // return COPY of state.stateData data + } + + /** + * Remove the state cookie + * + * @param {Object} inst + */ +, deleteCookie: function (inst) { + var o = inst.options; + $.ui.cookie.clear( o.stateManagement.cookie.name || o.name || "Layout" ); + } + + /** + * Read & return data from the cookie - as JSON + * + * @param {Object} inst + */ +, readCookie: function (inst) { + var o = inst.options; + var c = $.ui.cookie.read( o.stateManagement.cookie.name || o.name || "Layout" ); + // convert cookie string back to a hash and return it + return c ? $.layout.state.decodeJSON(c) : {}; + } + + /** + * Get data from the cookie and USE IT to loadState + * + * @param {Object} inst + */ +, loadCookie: function (inst) { + var c = $.layout.state.readCookie(inst); // READ the cookie + if (c) { + inst.state.stateData = $.extend(true, {}, c); // SET state.stateData + inst.loadState(c); // LOAD the retrieved state + } + return c; + } + + /** + * Update layout options from the cookie, if one exists + * + * @param {Object} inst + * @param {Object=} stateData + * @param {boolean=} animate + */ +, loadState: function (inst, stateData, animate) { + stateData = $.layout.transformData( stateData ); // panes = default subkey + if ($.isEmptyObject( stateData )) return; + $.extend(true, inst.options, stateData); // update layout options + // if layout has already been initialized, then UPDATE layout state + if (inst.state.initialized) { + var pane, vis, o, s, h, c + , noAnimate = (animate===false) + ; + $.each($.layout.config.borderPanes, function (idx, pane) { + state = inst.state[pane]; + o = stateData[ pane ]; + if (typeof o != 'object') return; // no key, continue + s = o.size; + c = o.initClosed; + h = o.initHidden; + vis = state.isVisible; + // resize BEFORE opening + if (!vis) + inst.sizePane(pane, s, false, false); + if (h === true) inst.hide(pane, noAnimate); + else if (c === false) inst.open (pane, false, noAnimate); + else if (c === true) inst.close(pane, false, noAnimate); + else if (h === false) inst.show (pane, false, noAnimate); + // resize AFTER any other actions + if (vis) + inst.sizePane(pane, s, false, noAnimate); // animate resize if option passed + }); + }; + } + + /** + * Get the *current layout state* and return it as a hash + * + * @param {Object=} inst + * @param {(string|Array)=} keys + */ +, readState: function (inst, keys) { + var + data = {} + , alt = { isClosed: 'initClosed', isHidden: 'initHidden' } + , state = inst.state + , panes = $.layout.config.allPanes + , pair, pane, key, val + ; + if (!keys) keys = inst.options.stateManagement.stateKeys; // if called by user + if ($.isArray(keys)) keys = keys.join(","); + // convert keys to an array and change delimiters from '__' to '.' + keys = keys.replace(/__/g, ".").split(','); + // loop keys and create a data hash + for (var i=0, n=keys.length; i < n; i++) { + pair = keys[i].split("."); + pane = pair[0]; + key = pair[1]; + if ($.inArray(pane, panes) < 0) continue; // bad pane! + val = state[ pane ][ key ]; + if (val == undefined) continue; + if (key=="isClosed" && state[pane]["isSliding"]) + val = true; // if sliding, then *really* isClosed + ( data[pane] || (data[pane]={}) )[ alt[key] ? alt[key] : key ] = val; + } + return data; + } + + /** + * Stringify a JSON hash so can save in a cookie or db-field + */ +, encodeJSON: function (JSON) { + return parse(JSON); + function parse (h) { + var D=[], i=0, k, v, t; // k = key, v = value + for (k in h) { + v = h[k]; + t = typeof v; + if (t == 'string') // STRING - add quotes + v = '"'+ v +'"'; + else if (t == 'object') // SUB-KEY - recurse into it + v = parse(v); + D[i++] = '"'+ k +'":'+ v; + } + return '{'+ D.join(',') +'}'; + }; + } + + /** + * Convert stringified JSON back to a hash object + * @see $.parseJSON(), adding in jQuery 1.4.1 + */ +, decodeJSON: function (str) { + try { return $.parseJSON ? $.parseJSON(str) : window["eval"]("("+ str +")") || {}; } + catch (e) { return {}; } + } + + +, _create: function (inst) { + var _ = $.layout.state; + // ADD State-Management plugin methods to inst + $.extend( inst, { + // readCookie - update options from cookie - returns hash of cookie data + readCookie: function () { return _.readCookie(inst); } + // deleteCookie + , deleteCookie: function () { _.deleteCookie(inst); } + // saveCookie - optionally pass keys-list and cookie-options (hash) + , saveCookie: function (keys, cookieOpts) { return _.saveCookie(inst, keys, cookieOpts); } + // loadCookie - readCookie and use to loadState() - returns hash of cookie data + , loadCookie: function () { return _.loadCookie(inst); } + // loadState - pass a hash of state to use to update options + , loadState: function (stateData, animate) { _.loadState(inst, stateData, animate); } + // readState - returns hash of current layout-state + , readState: function (keys) { return _.readState(inst, keys); } + // add JSON utility methods too... + , encodeJSON: _.encodeJSON + , decodeJSON: _.decodeJSON + }); + + // init state.stateData key, even if plugin is initially disabled + inst.state.stateData = {}; + + // read and load cookie-data per options + var oS = inst.options.stateManagement; + if (oS.enabled) { + if (oS.autoLoad) // update the options from the cookie + inst.loadCookie(); + else // don't modify options - just store cookie data in state.stateData + inst.state.stateData = inst.readCookie(); + } + } + +, _unload: function (inst) { + var oS = inst.options.stateManagement; + if (oS.enabled) { + if (oS.autoSave) // save a state-cookie automatically + inst.saveCookie(); + else // don't save a cookie, but do store state-data in state.stateData key + inst.state.stateData = inst.readState(); + } + } + +}; + +// add state initialization method to Layout's onCreate array of functions +$.layout.onCreate.push( $.layout.state._create ); +$.layout.onUnload.push( $.layout.state._unload ); + + + + +/** + * jquery.layout.buttons 1.0 + * $Date: 2011-07-16 08:00:00 (Sat, 16 July 2011) $ + * + * Copyright (c) 2010 + * Kevin Dalman (http://allpro.net) + * + * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html) + * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses. + * + * @dependancies: UI Layout 1.3.0.rc30.1 or higher + * + * @support: http://groups.google.com/group/jquery-ui-layout + * + * Docs: [ to come ] + * Tips: [ to come ] + */ + +// tell Layout that the state plugin is available +$.layout.plugins.buttons = true; + +// Add buttons options to layout.defaults +$.layout.defaults.autoBindCustomButtons = false; +// Specify autoBindCustomButtons as a layout-option, NOT a pane-option +$.layout.optionsMap.layout.push("autoBindCustomButtons"); + +var lang = $.layout.language; + +/* + * Button methods + */ +$.layout.buttons = { + + /** + * Searches for .ui-layout-button-xxx elements and auto-binds them as layout-buttons + * + * @see _create() + * + * @param {Object} inst Layout Instance object + */ + init: function (inst) { + var pre = "ui-layout-button-" + , layout = inst.options.name || "" + , name; + $.each("toggle,open,close,pin,toggle-slide,open-slide".split(","), function (i, action) { + $.each($.layout.config.borderPanes, function (ii, pane) { + $("."+pre+action+"-"+pane).each(function(){ + // if button was previously 'bound', data.layoutName was set, but is blank if layout has no 'name' + name = $(this).data("layoutName") || $(this).attr("layoutName"); + if (name == undefined || name === layout) + inst.bindButton(this, action, pane); + }); + }); + }); + } + + /** + * Helper function to validate params received by addButton utilities + * + * Two classes are added to the element, based on the buttonClass... + * The type of button is appended to create the 2nd className: + * - ui-layout-button-pin // action btnClass + * - ui-layout-button-pin-west // action btnClass + pane + * - ui-layout-button-toggle + * - ui-layout-button-open + * - ui-layout-button-close + * + * @param {Object} inst Layout Instance object + * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" + * @param {string} pane Name of the pane the button is for: 'north', 'south', etc. + * + * @return {Array.} If both params valid, the element matching 'selector' in a jQuery wrapper - otherwise returns null + */ +, get: function (inst, selector, pane, action) { + var $E = $(selector) + , o = inst.options + , err = o.showErrorMessages + ; + if (!$E.length) { // element not found + if (err) $.layout.msg(lang.errButton + lang.selector +": "+ selector, true); + } + else if ($.inArray(pane, $.layout.config.borderPanes) < 0) { // invalid 'pane' sepecified + if (err) $.layout.msg(lang.errButton + lang.pane +": "+ pane, true); + $E = $(""); // NO BUTTON + } + else { // VALID + var btn = o[pane].buttonClass +"-"+ action; + $E .addClass( btn +" "+ btn +"-"+ pane ) + .data("layoutName", o.name); // add layout identifier - even if blank! + } + return $E; + } + + + /** + * NEW syntax for binding layout-buttons - will eventually replace addToggle, addOpen, etc. + * + * @param {Object} inst Layout Instance object + * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" + * @param {string} action + * @param {string} pane + */ +, bind: function (inst, selector, action, pane) { + var _ = $.layout.buttons; + switch (action.toLowerCase()) { + case "toggle": _.addToggle (inst, selector, pane); break; + case "open": _.addOpen (inst, selector, pane); break; + case "close": _.addClose (inst, selector, pane); break; + case "pin": _.addPin (inst, selector, pane); break; + case "toggle-slide": _.addToggle (inst, selector, pane, true); break; + case "open-slide": _.addOpen (inst, selector, pane, true); break; + } + return inst; + } + + /** + * Add a custom Toggler button for a pane + * + * @param {Object} inst Layout Instance object + * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" + * @param {string} pane Name of the pane the button is for: 'north', 'south', etc. + * @param {boolean=} slide true = slide-open, false = pin-open + */ +, addToggle: function (inst, selector, pane, slide) { + $.layout.buttons.get(inst, selector, pane, "toggle") + .click(function(evt){ + inst.toggle(pane, !!slide); + evt.stopPropagation(); + }); + return inst; + } + + /** + * Add a custom Open button for a pane + * + * @param {Object} inst Layout Instance object + * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" + * @param {string} pane Name of the pane the button is for: 'north', 'south', etc. + * @param {boolean=} slide true = slide-open, false = pin-open + */ +, addOpen: function (inst, selector, pane, slide) { + $.layout.buttons.get(inst, selector, pane, "open") + .attr("title", lang.Open) + .click(function (evt) { + inst.open(pane, !!slide); + evt.stopPropagation(); + }); + return inst; + } + + /** + * Add a custom Close button for a pane + * + * @param {Object} inst Layout Instance object + * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" + * @param {string} pane Name of the pane the button is for: 'north', 'south', etc. + */ +, addClose: function (inst, selector, pane) { + $.layout.buttons.get(inst, selector, pane, "close") + .attr("title", lang.Close) + .click(function (evt) { + inst.close(pane); + evt.stopPropagation(); + }); + return inst; + } + + /** + * Add a custom Pin button for a pane + * + * Four classes are added to the element, based on the paneClass for the associated pane... + * Assuming the default paneClass and the pin is 'up', these classes are added for a west-pane pin: + * - ui-layout-pane-pin + * - ui-layout-pane-west-pin + * - ui-layout-pane-pin-up + * - ui-layout-pane-west-pin-up + * + * @param {Object} inst Layout Instance object + * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" + * @param {string} pane Name of the pane the pin is for: 'north', 'south', etc. + */ +, addPin: function (inst, selector, pane) { + var _ = $.layout.buttons + , $E = _.get(inst, selector, pane, "pin"); + if ($E.length) { + var s = inst.state[pane]; + $E.click(function (evt) { + _.setPinState(inst, $(this), pane, (s.isSliding || s.isClosed)); + if (s.isSliding || s.isClosed) inst.open( pane ); // change from sliding to open + else inst.close( pane ); // slide-closed + evt.stopPropagation(); + }); + // add up/down pin attributes and classes + _.setPinState(inst, $E, pane, (!s.isClosed && !s.isSliding)); + // add this pin to the pane data so we can 'sync it' automatically + // PANE.pins key is an array so we can store multiple pins for each pane + s.pins.push( selector ); // just save the selector string + } + return inst; + } + + /** + * Change the class of the pin button to make it look 'up' or 'down' + * + * @see addPin(), syncPins() + * + * @param {Object} inst Layout Instance object + * @param {Array.} $Pin The pin-span element in a jQuery wrapper + * @param {string} pane These are the params returned to callbacks by layout() + * @param {boolean} doPin true = set the pin 'down', false = set it 'up' + */ +, setPinState: function (inst, $Pin, pane, doPin) { + var updown = $Pin.attr("pin"); + if (updown && doPin === (updown=="down")) return; // already in correct state + var + pin = inst.options[pane].buttonClass +"-pin" + , side = pin +"-"+ pane + , UP = pin +"-up "+ side +"-up" + , DN = pin +"-down "+side +"-down" + ; + $Pin + .attr("pin", doPin ? "down" : "up") // logic + .attr("title", doPin ? lang.Unpin : lang.Pin) + .removeClass( doPin ? UP : DN ) + .addClass( doPin ? DN : UP ) + ; + } + + /** + * INTERNAL function to sync 'pin buttons' when pane is opened or closed + * Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes + * + * @see open(), close() + * + * @param {Object} inst Layout Instance object + * @param {string} pane These are the params returned to callbacks by layout() + * @param {boolean} doPin True means set the pin 'down', False means 'up' + */ +, syncPinBtns: function (inst, pane, doPin) { + // REAL METHOD IS _INSIDE_ LAYOUT - THIS IS HERE JUST FOR REFERENCE + $.each(state[pane].pins, function (i, selector) { + $.layout.buttons.setPinState(inst, $(selector), pane, doPin); + }); + } + + +, _load: function (inst) { + var _ = $.layout.buttons; + // ADD Button methods to Layout Instance + // Note: sel = jQuery Selector string + $.extend( inst, { + bindButton: function (sel, action, pane) { return _.bind(inst, sel, action, pane); } + // DEPRECATED METHODS + , addToggleBtn: function (sel, pane, slide) { return _.addToggle(inst, sel, pane, slide); } + , addOpenBtn: function (sel, pane, slide) { return _.addOpen(inst, sel, pane, slide); } + , addCloseBtn: function (sel, pane) { return _.addClose(inst, sel, pane); } + , addPinBtn: function (sel, pane) { return _.addPin(inst, sel, pane); } + }); + + // init state array to hold pin-buttons + for (var i=0; i<4; i++) { + var pane = $.layout.config.borderPanes[i]; + inst.state[pane].pins = []; + } + + // auto-init buttons onLoad if option is enabled + if ( inst.options.autoBindCustomButtons ) + _.init(inst); + } + +, _unload: function (inst) { + // TODO: unbind all buttons??? + } + +}; + +// add initialization method to Layout's onLoad array of functions +$.layout.onLoad.push( $.layout.buttons._load ); +//$.layout.onUnload.push( $.layout.buttons._unload ); + + + +/** + * jquery.layout.browserZoom 1.0 + * $Date: 2011-12-29 08:00:00 (Thu, 29 Dec 2011) $ + * + * Copyright (c) 2012 + * Kevin Dalman (http://allpro.net) + * + * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html) + * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses. + * + * @dependancies: UI Layout 1.3.0.rc30.1 or higher + * + * @support: http://groups.google.com/group/jquery-ui-layout + * + * @todo: Extend logic to handle other problematic zooming in browsers + * @todo: Add hotkey/mousewheel bindings to _instantly_ respond to these zoom event + */ + +// tell Layout that the plugin is available +$.layout.plugins.browserZoom = true; + +$.layout.defaults.browserZoomCheckInterval = 1000; +$.layout.optionsMap.layout.push("browserZoomCheckInterval"); + +/* + * browserZoom methods + */ +$.layout.browserZoom = { + + _init: function (inst) { + // abort if browser does not need this check + if ($.layout.browserZoom.ratio() !== false) + $.layout.browserZoom._setTimer(inst); + } + +, _setTimer: function (inst) { + // abort if layout destroyed or browser does not need this check + if (inst.destroyed) return; + var o = inst.options + , s = inst.state + // don't need check if inst has parentLayout, but check occassionally in case parent destroyed! + // MINIMUM 100ms interval, for performance + , ms = inst.hasParentLayout ? 5000 : Math.max( o.browserZoomCheckInterval, 100 ) + ; + // set the timer + setTimeout(function(){ + if (inst.destroyed || !o.resizeWithWindow) return; + var d = $.layout.browserZoom.ratio(); + if (d !== s.browserZoom) { + s.browserZoom = d; + inst.resizeAll(); + } + // set a NEW timeout + $.layout.browserZoom._setTimer(inst); + } + , ms ); + } + +, ratio: function () { + var w = window + , s = screen + , d = document + , dE = d.documentElement || d.body + , b = $.layout.browser + , v = b.version + , r, sW, cW + ; + // we can ignore all browsers that fire window.resize event onZoom + if ((b.msie && v > 8) + || !b.msie + ) return false; // don't need to track zoom + + if (s.deviceXDPI) + return calc(s.deviceXDPI, s.systemXDPI); + // everything below is just for future reference! + if (b.webkit && (r = d.body.getBoundingClientRect)) + return calc((r.left - r.right), d.body.offsetWidth); + if (b.webkit && (sW = w.outerWidth)) + return calc(sW, w.innerWidth); + if ((sW = s.width) && (cW = dE.clientWidth)) + return calc(sW, cW); + return false; // no match, so cannot - or don't need to - track zoom + + function calc (x,y) { return (parseInt(x,10) / parseInt(y,10) * 100).toFixed(); } + } + +}; +// add initialization method to Layout's onLoad array of functions +$.layout.onReady.push( $.layout.browserZoom._init ); + + + +})( jQuery ); \ No newline at end of file diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-anim_basic_16x16.gif b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-anim_basic_16x16.gif new file mode 100644 index 00000000..085ccaec Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-anim_basic_16x16.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_flat_0_aaaaaa_40x100.png b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100644 index 00000000..5b5dab2a Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_flat_0_aaaaaa_40x100.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_flat_55_fbec88_40x100.png b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_flat_55_fbec88_40x100.png new file mode 100644 index 00000000..47acaadd Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_flat_55_fbec88_40x100.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_glass_75_d0e5f5_1x400.png b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_glass_75_d0e5f5_1x400.png new file mode 100644 index 00000000..9d149b1c Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_glass_75_d0e5f5_1x400.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_glass_85_dfeffc_1x400.png b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_glass_85_dfeffc_1x400.png new file mode 100644 index 00000000..01495152 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_glass_85_dfeffc_1x400.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_glass_95_fef1ec_1x400.png b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 00000000..4443fdc1 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png new file mode 100644 index 00000000..81ecc362 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.png b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.png new file mode 100644 index 00000000..4f3faf8a Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png new file mode 100644 index 00000000..38c38335 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_217bc0_256x240.png b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_217bc0_256x240.png new file mode 100644 index 00000000..6f4bd87c Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_217bc0_256x240.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_2e83ff_256x240.png b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 00000000..09d1cdc8 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_2e83ff_256x240.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_469bdd_256x240.png b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_469bdd_256x240.png new file mode 100644 index 00000000..bd2cf079 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_469bdd_256x240.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_6da8d5_256x240.png b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_6da8d5_256x240.png new file mode 100644 index 00000000..3d6f567f Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_6da8d5_256x240.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_cd0a0a_256x240.png b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 00000000..2ab019b7 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_cd0a0a_256x240.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_d8e7f3_256x240.png b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_d8e7f3_256x240.png new file mode 100644 index 00000000..ad2dc6f9 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_d8e7f3_256x240.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_f9bd01_256x240.png b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_f9bd01_256x240.png new file mode 100644 index 00000000..c7c53cb1 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/theme-redmond/images/ui-icons_f9bd01_256x240.png differ diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/jquery-ui-1.8.2.custom.css b/src/doc/user/webhelp/template/common/jquery/theme-redmond/jquery-ui-1.8.2.custom.css new file mode 100644 index 00000000..0b173632 --- /dev/null +++ b/src/doc/user/webhelp/template/common/jquery/theme-redmond/jquery-ui-1.8.2.custom.css @@ -0,0 +1,398 @@ +/* +* jQuery UI CSS Framework +* Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +*/ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute; left: -99999999px; } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + +/* +* jQuery UI CSS Framework +* Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Lucida%20Grande,%20Lucida%20Sans,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=5px&bgColorHeader=5c9ccc&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=55&borderColorHeader=4297d7&fcHeader=ffffff&iconColorHeader=d8e7f3&bgColorContent=fcfdfd&bgTextureContent=06_inset_hard.png&bgImgOpacityContent=100&borderColorContent=a6c9e2&fcContent=222222&iconColorContent=469bdd&bgColorDefault=dfeffc&bgTextureDefault=02_glass.png&bgImgOpacityDefault=85&borderColorDefault=c5dbec&fcDefault=2e6e9e&iconColorDefault=6da8d5&bgColorHover=d0e5f5&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=79b7e7&fcHover=1d5987&iconColorHover=217bc0&bgColorActive=f5f8f9&bgTextureActive=06_inset_hard.png&bgImgOpacityActive=100&borderColorActive=79b7e7&fcActive=e17009&iconColorActive=f9bd01&bgColorHighlight=fbec88&bgTextureHighlight=01_flat.png&bgImgOpacityHighlight=55&borderColorHighlight=fad42e&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px +*/ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Lucida Grande, Lucida Sans, Arial, sans-serif; font-size: 1.1em; } +.ui-widget .ui-widget { font-size: 1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Lucida Grande, Lucida Sans, Arial, sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #a6c9e2; background: #fcfdfd url(images/ui-bg_inset-hard_100_fcfdfd_1x100.png) 50% bottom repeat-x; color: #222222; } +.ui-widget-content a { color: #222222; } +.ui-widget-header { border: 1px solid #4297d7; background: #5c9ccc url(images/ui-bg_gloss-wave_55_5c9ccc_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; } +.ui-widget-header a { color: #ffffff; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #c5dbec; background: #dfeffc url(images/ui-bg_glass_85_dfeffc_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #2e6e9e; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #2e6e9e; text-decoration: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #79b7e7; background: #d0e5f5 url(images/ui-bg_glass_75_d0e5f5_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1d5987; } +.ui-state-hover a, .ui-state-hover a:hover { color: #1d5987; text-decoration: none; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #79b7e7; background: #f5f8f9 url(images/ui-bg_inset-hard_100_f5f8f9_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #e17009; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #e17009; text-decoration: none; } +.ui-widget :active { outline: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fad42e; background: #fbec88 url(images/ui-bg_flat_55_fbec88_40x100.png) 50% 50% repeat-x; color: #363636; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; } +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_469bdd_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_469bdd_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_d8e7f3_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_6da8d5_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_217bc0_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_f9bd01_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-tl { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; border-top-left-radius: 5px; } +.ui-corner-tr { -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; border-top-right-radius: 5px; } +.ui-corner-bl { -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; border-bottom-left-radius: 5px; } +.ui-corner-br { -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; border-bottom-right-radius: 5px; } +.ui-corner-top { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; border-top-left-radius: 5px; -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; border-top-right-radius: 5px; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; border-bottom-left-radius: 5px; -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; border-bottom-right-radius: 5px; } +.ui-corner-right { -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; border-top-right-radius: 5px; -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; border-bottom-right-radius: 5px; } +.ui-corner-left { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; border-top-left-radius: 5px; -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; border-bottom-left-radius: 5px; } +.ui-corner-all { -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } + +/* Overlays */ +.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } +.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/* Resizable +----------------------------------*/ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;} +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* Selectable +----------------------------------*/ +.ui-selectable-helper { border:1px dotted black } +/* Autocomplete +----------------------------------*/ +.ui-autocomplete { position: absolute; cursor: default; } +.ui-autocomplete-loading { background: white url('images/ui-anim_basic_16x16.gif') right center no-repeat; } + +/* workarounds */ +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ + +/* Menu +----------------------------------*/ +.ui-menu { + list-style:none; + padding: 2px; + margin: 0; + display:block; +} +.ui-menu .ui-menu { + margin-top: -3px; +} +.ui-menu .ui-menu-item { + margin:0; + padding: 0; + zoom: 1; + float: left; + clear: left; + width: 100%; +} +.ui-menu .ui-menu-item a { + text-decoration:none; + display:block; + padding:.2em .4em; + line-height:1.5; + zoom:1; +} +.ui-menu .ui-menu-item a.ui-state-hover, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} +/* Button +----------------------------------*/ + +.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ +.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ +button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ +.ui-button-icons-only { width: 3.4em; } +button.ui-button-icons-only { width: 3.7em; } + +/*button text element */ +.ui-button .ui-button-text { display: block; line-height: 1.4; } +.ui-button-text-only .ui-button-text { padding: .4em 1em; } +.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } +.ui-button-text-icon .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } +.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } +/* no icon support for input elements, provide padding by default */ +input.ui-button { padding: .4em 1em; } + +/*button icon element(s) */ +.ui-button-icon-only .ui-icon, .ui-button-text-icon .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } +.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } +.ui-button-text-icon .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } +.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } + +/*button sets*/ +.ui-buttonset { margin-right: 7px; } +.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } + +/* workarounds */ +button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ + + + + + +/* Dialog +----------------------------------*/ +.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } +.ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } +/* Tabs +----------------------------------*/ +.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ +.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } +.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } diff --git a/src/doc/user/webhelp/template/common/jquery/theme-redmond/jquery-ui-1.8.21.custom.css b/src/doc/user/webhelp/template/common/jquery/theme-redmond/jquery-ui-1.8.21.custom.css new file mode 100644 index 00000000..c02c76f5 --- /dev/null +++ b/src/doc/user/webhelp/template/common/jquery/theme-redmond/jquery-ui-1.8.21.custom.css @@ -0,0 +1,304 @@ +/*! + * jQuery UI CSS Framework 1.8.21 + * + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; } +.ui-helper-clearfix:after { clear: both; } +.ui-helper-clearfix { zoom: 1; } +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + +/*! + * jQuery UI CSS Framework 1.8.21 + * + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + * + * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Lucida%20Grande,%20Lucida%20Sans,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=5px&bgColorHeader=5c9ccc&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=55&borderColorHeader=4297d7&fcHeader=ffffff&iconColorHeader=d8e7f3&bgColorContent=fcfdfd&bgTextureContent=06_inset_hard.png&bgImgOpacityContent=100&borderColorContent=a6c9e2&fcContent=222222&iconColorContent=469bdd&bgColorDefault=dfeffc&bgTextureDefault=02_glass.png&bgImgOpacityDefault=85&borderColorDefault=c5dbec&fcDefault=2e6e9e&iconColorDefault=6da8d5&bgColorHover=d0e5f5&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=79b7e7&fcHover=1d5987&iconColorHover=217bc0&bgColorActive=f5f8f9&bgTextureActive=06_inset_hard.png&bgImgOpacityActive=100&borderColorActive=79b7e7&fcActive=e17009&iconColorActive=f9bd01&bgColorHighlight=fbec88&bgTextureHighlight=01_flat.png&bgImgOpacityHighlight=55&borderColorHighlight=fad42e&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px + */ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Lucida Grande, Lucida Sans, Arial, sans-serif; font-size: 1.1em; } +.ui-widget .ui-widget { font-size: 1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Lucida Grande, Lucida Sans, Arial, sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #a6c9e2; background: #fcfdfd url(images/ui-bg_inset-hard_100_fcfdfd_1x100.png) 50% bottom repeat-x; color: #222222; } +.ui-widget-content a { color: #222222; } +.ui-widget-header { border: 1px solid #4297d7; background: #5c9ccc url(images/ui-bg_gloss-wave_55_5c9ccc_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; } +.ui-widget-header a { color: #ffffff; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #c5dbec; background: #dfeffc url(images/ui-bg_glass_85_dfeffc_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #2e6e9e; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #2e6e9e; text-decoration: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #79b7e7; background: #d0e5f5 url(images/ui-bg_glass_75_d0e5f5_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1d5987; } +.ui-state-hover a, .ui-state-hover a:hover { color: #1d5987; text-decoration: none; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #79b7e7; background: #f5f8f9 url(images/ui-bg_inset-hard_100_f5f8f9_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #e17009; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #e17009; text-decoration: none; } +.ui-widget :active { outline: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fad42e; background: #fbec88 url(images/ui-bg_flat_55_fbec88_40x100.png) 50% 50% repeat-x; color: #363636; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; } +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_469bdd_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_469bdd_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_d8e7f3_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_6da8d5_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_217bc0_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_f9bd01_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; -khtml-border-top-left-radius: 5px; border-top-left-radius: 5px; } +.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; -khtml-border-top-right-radius: 5px; border-top-right-radius: 5px; } +.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; -khtml-border-bottom-left-radius: 5px; border-bottom-left-radius: 5px; } +.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; -khtml-border-bottom-right-radius: 5px; border-bottom-right-radius: 5px; } + +/* Overlays */ +.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } +.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -khtml-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/*! + * jQuery UI Tabs 1.8.21 + * + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Tabs#theming + */ +.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ +.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } +.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/file.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/file.gif new file mode 100644 index 00000000..bd4f9654 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/file.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/folder-closed.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/folder-closed.gif new file mode 100644 index 00000000..be6b59c2 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/folder-closed.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/folder-closed2.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/folder-closed2.gif new file mode 100644 index 00000000..54110788 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/folder-closed2.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/folder.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/folder.gif new file mode 100644 index 00000000..be6b59c2 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/folder.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/folder2.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/folder2.gif new file mode 100644 index 00000000..2b31631c Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/folder2.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/minus.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/minus.gif new file mode 100644 index 00000000..47fb7b76 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/minus.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/plus.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/plus.gif new file mode 100644 index 00000000..69066216 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/plus.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-black-line.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-black-line.gif new file mode 100644 index 00000000..e5496877 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-black-line.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-black.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-black.gif new file mode 100644 index 00000000..d549b9fc Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-black.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-default-line.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-default-line.gif new file mode 100644 index 00000000..37114d30 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-default-line.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-default.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-default.gif new file mode 100644 index 00000000..a12ac52f Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-default.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-famfamfam-line.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-famfamfam-line.gif new file mode 100644 index 00000000..6e289cec Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-famfamfam-line.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-famfamfam.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-famfamfam.gif new file mode 100644 index 00000000..0cb178e8 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-famfamfam.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-gray-line.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-gray-line.gif new file mode 100644 index 00000000..37600447 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-gray-line.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-gray.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-gray.gif new file mode 100644 index 00000000..cfb8a2f0 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-gray.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-red-line.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-red-line.gif new file mode 100644 index 00000000..df9e749a Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-red-line.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-red.gif b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-red.gif new file mode 100644 index 00000000..3bbb3a15 Binary files /dev/null and b/src/doc/user/webhelp/template/common/jquery/treeview/images/treeview-red.gif differ diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/jquery.treeview.css b/src/doc/user/webhelp/template/common/jquery/treeview/jquery.treeview.css new file mode 100644 index 00000000..dbf425b2 --- /dev/null +++ b/src/doc/user/webhelp/template/common/jquery/treeview/jquery.treeview.css @@ -0,0 +1,85 @@ +.treeview, .treeview ul { + padding: 0; + margin: 0; + list-style: none; +} + +.treeview ul { + background-color: white; + margin-top: 4px; +} + +.treeview .hitarea { + background: url(images/treeview-default.gif) -64px -25px no-repeat; + height: 16px; + width: 16px; + margin-left: -16px; + float: left; + cursor: pointer; +} +/* fix for IE6 */ +* html .hitarea { + display: inline; + float:none; +} + +.treeview li { + margin: 0; + padding: 3px 0 3px 16px; +} + +.treeview a.selected { + background-color: #eee; +} + +#treecontrol { margin: 1em 0; display: none; } + +.treeview .hover { color: red; cursor: pointer; } + +.treeview li { background: url(images/treeview-default-line.gif) 0 0 no-repeat; } +.treeview li.collapsable, .treeview li.expandable { background-position: 0 -176px; } + +.treeview .expandable-hitarea { background-position: -80px -3px; } + +.treeview li.last { background-position: 0 -1766px } +.treeview li.lastCollapsable, .treeview li.lastExpandable { background-image: url(images/treeview-default.gif); } +.treeview li.lastCollapsable { background-position: 0 -111px } +.treeview li.lastExpandable { background-position: -32px -67px } + +.treeview div.lastCollapsable-hitarea, .treeview div.lastExpandable-hitarea { background-position: 0; } + +.treeview-red li { background-image: url(images/treeview-red-line.gif); } +.treeview-red .hitarea, .treeview-red li.lastCollapsable, .treeview-red li.lastExpandable { background-image: url(images/treeview-red.gif); } + +.treeview-black li { background-image: url(images/treeview-black-line.gif); } +.treeview-black .hitarea, .treeview-black li.lastCollapsable, .treeview-black li.lastExpandable { background-image: url(images/treeview-black.gif); } + +.treeview-gray li { background-image: url(images/treeview-gray-line.gif); } +.treeview-gray .hitarea, .treeview-gray li.lastCollapsable, .treeview-gray li.lastExpandable { background-image: url(images/treeview-gray.gif); } + +.treeview-famfamfam li { background-image: url(images/treeview-famfamfam-line.gif); } +.treeview-famfamfam .hitarea, .treeview-famfamfam li.lastCollapsable, .treeview-famfamfam li.lastExpandable { background-image: url(images/treeview-famfamfam.gif); } + + +.filetree li { padding: 3px 0 2px 16px; } +.filetree span.folder, .filetree span.file { padding: 1px 0 1px 16px; display: block; } +.filetree span.folder { background: url(images/folder.gif) 0 0 no-repeat; } +.filetree li.expandable span.folder { background: url(images/folder-closed.gif) 0 0 no-repeat; } +.filetree span.file { background: url(images/file.gif) 0 0 no-repeat; } + +html, body {height:100%; margin: 0; padding: 0; } + +/* +html>body { + font-size: 16px; + font-size: 68.75%; +} Reset Base Font Size */ + /* +body { + font-family: Verdana, helvetica, arial, sans-serif; + font-size: 68.75%; + background: #fff; + color: #333; +} */ + +a img { border: none; } \ No newline at end of file diff --git a/src/doc/user/webhelp/template/common/jquery/treeview/jquery.treeview.min.js b/src/doc/user/webhelp/template/common/jquery/treeview/jquery.treeview.min.js new file mode 100644 index 00000000..e693321d --- /dev/null +++ b/src/doc/user/webhelp/template/common/jquery/treeview/jquery.treeview.min.js @@ -0,0 +1,16 @@ +/* + * Treeview 1.4 - jQuery plugin to hide and show branches of a tree + * + * http://bassistance.de/jquery-plugins/jquery-plugin-treeview/ + * http://docs.jquery.com/Plugins/Treeview + * + * Copyright (c) 2007 Jörn Zaefferer + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Revision: $Id: jquery.treeview.js 4684 2008-02-07 19:08:06Z joern.zaefferer $ + * kasunbg: changed the cookieid name + * + */;(function($){$.extend($.fn,{swapClass:function(c1,c2){var c1Elements=this.filter('.'+c1);this.filter('.'+c2).removeClass(c2).addClass(c1);c1Elements.removeClass(c1).addClass(c2);return this;},replaceClass:function(c1,c2){return this.filter('.'+c1).removeClass(c1).addClass(c2).end();},hoverClass:function(className){className=className||"hover";return this.hover(function(){$(this).addClass(className);},function(){$(this).removeClass(className);});},heightToggle:function(animated,callback){animated?this.animate({height:"toggle"},animated,callback):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();if(callback)callback.apply(this,arguments);});},heightHide:function(animated,callback){if(animated){this.animate({height:"hide"},animated,callback);}else{this.hide();if(callback)this.each(callback);}},prepareBranches:function(settings){if(!settings.prerendered){this.filter(":last-child:not(ul)").addClass(CLASSES.last);this.filter((settings.collapsed?"":"."+CLASSES.closed)+":not(."+CLASSES.open+")").find(">ul").hide();}return this.filter(":has(>ul)");},applyClasses:function(settings,toggler){this.filter(":has(>ul):not(:has(>a))").find(">span").click(function(event){toggler.apply($(this).next());}).add($("a",this)).hoverClass();if(!settings.prerendered){this.filter(":has(>ul:hidden)").addClass(CLASSES.expandable).replaceClass(CLASSES.last,CLASSES.lastExpandable);this.not(":has(>ul:hidden)").addClass(CLASSES.collapsable).replaceClass(CLASSES.last,CLASSES.lastCollapsable);this.prepend("
        ").find("div."+CLASSES.hitarea).each(function(){var classes="";$.each($(this).parent().attr("class").split(" "),function(){classes+=this+"-hitarea ";});$(this).addClass(classes);});}this.find("div."+CLASSES.hitarea).click(toggler);},treeview:function(settings){if(typeof(window.treeCookieId) === 'undefined' || window.treeCookieId === ""){treeCookieId = "treeview";} settings=$.extend({cookieId: treeCookieId},settings);if(settings.add){return this.trigger("add",[settings.add]);}if(settings.toggle){var callback=settings.toggle;settings.toggle=function(){return callback.apply($(this).parent()[0],arguments);};}function treeController(tree,control){function handler(filter){return function(){toggler.apply($("div."+CLASSES.hitarea,tree).filter(function(){return filter?$(this).parent("."+filter).length:true;}));return false;};}$("a:eq(0)",control).click(handler(CLASSES.collapsable));$("a:eq(1)",control).click(handler(CLASSES.expandable));$("a:eq(2)",control).click(handler());}function toggler(){$(this).parent().find(">.hitarea").swapClass(CLASSES.collapsableHitarea,CLASSES.expandableHitarea).swapClass(CLASSES.lastCollapsableHitarea,CLASSES.lastExpandableHitarea).end().swapClass(CLASSES.collapsable,CLASSES.expandable).swapClass(CLASSES.lastCollapsable,CLASSES.lastExpandable).find(">ul").heightToggle(settings.animated,settings.toggle);if(settings.unique){$(this).parent().siblings().find(">.hitarea").replaceClass(CLASSES.collapsableHitarea,CLASSES.expandableHitarea).replaceClass(CLASSES.lastCollapsableHitarea,CLASSES.lastExpandableHitarea).end().replaceClass(CLASSES.collapsable,CLASSES.expandable).replaceClass(CLASSES.lastCollapsable,CLASSES.lastExpandable).find(">ul").heightHide(settings.animated,settings.toggle);}}function serialize(){function binary(arg){return arg?1:0;}var data=[];branches.each(function(i,e){data[i]=$(e).is(":has(>ul:visible)")?1:0;});$.cookie(settings.cookieId,data.join(""));}function deserialize(){var stored=$.cookie(settings.cookieId);if(stored){var data=stored.split("");branches.each(function(i,e){$(e).find(">ul")[parseInt(data[i])?"show":"hide"]();});}}this.addClass("treeview");var branches=this.find("li").prepareBranches(settings);switch(settings.persist){case"cookie":var toggleCallback=settings.toggle;settings.toggle=function(){serialize();if(toggleCallback){toggleCallback.apply(this,arguments);}};deserialize();break;case"location":var current=this.find("a").filter(function(){return this.href.toLowerCase()==location.href.toLowerCase();});if(current.length){current.addClass("selected").parents("ul, li").add(current.next()).show();}break;}branches.applyClasses(settings,toggler);if(settings.control){treeController(this,settings.control);$(settings.control).show();}return this.bind("add",function(event,branches){$(branches).prev().removeClass(CLASSES.last).removeClass(CLASSES.lastCollapsable).removeClass(CLASSES.lastExpandable).find(">.hitarea").removeClass(CLASSES.lastCollapsableHitarea).removeClass(CLASSES.lastExpandableHitarea);$(branches).find("li").andSelf().prepareBranches(settings).applyClasses(settings,toggler);});}});var CLASSES=$.fn.treeview.classes={open:"open",closed:"closed",expandable:"expandable",expandableHitarea:"expandable-hitarea",lastExpandableHitarea:"lastExpandable-hitarea",collapsable:"collapsable",collapsableHitarea:"collapsable-hitarea",lastCollapsableHitarea:"lastCollapsable-hitarea",lastCollapsable:"lastCollapsable",lastExpandable:"lastExpandable",last:"last",hitarea:"hitarea"};$.fn.Treeview=$.fn.treeview;})(jQuery); \ No newline at end of file diff --git a/src/doc/user/webhelp/template/common/main.js b/src/doc/user/webhelp/template/common/main.js new file mode 100644 index 00000000..bcc116fb --- /dev/null +++ b/src/doc/user/webhelp/template/common/main.js @@ -0,0 +1,276 @@ +/** + * Miscellaneous js functions for WebHelp + * Kasun Gajasinghe, http://kasunbg.blogspot.com + * David Cramer, http://www.thingbag.net + * + */ + +//Turn ON and OFF the animations for Show/Hide Sidebar. Extend this to other anime as well if any. +var noAnimations=false; + +$(document).ready(function() { + // When you click on a link to an anchor, scroll down + // 105 px to cope with the fact that the banner + // hides the top 95px or so of the page. + // This code deals with the problem when + // you click on a link within a page. + $('a[href*=#]').click(function() { + if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') + && location.hostname == this.hostname) { + var $target = $(this.hash); + $target = $target.length && $target + || $('[id=' + this.hash.slice(1) +']'); + if (!(this.hash == "#searchDiv" || this.hash == "#treeDiv" || this.hash == "") && $target.length) { + var targetOffset = $target.offset().top - 120; + $('#content') + .animate({scrollTop: targetOffset}, 200); + return false; + } + } + }); + + // $("#showHideHighlight").button(); //add jquery button styling to 'Go' button + //Generate tabs in nav-pane with JQuery + $(function() { + $("#tabs").tabs({ + cookie: { + expires: 2 // store cookie for 2 days. + } + }); + }); + + //Generate the tree + $("#ulTreeDiv").attr("style", ""); + $("#tree").treeview({ + collapsed: true, + animated: "medium", + control: "#sidetreecontrol", + persist: "cookie" + }); + + //after toc fully styled, display it. Until loading, a 'loading' image will be displayed + $("#tocLoading").attr("style", "display:none;"); + // $("#ulTreeDiv").attr("style","display:block;"); + + //.searchButton is the css class applied to 'Go' button + $(function() { + $("button", ".searchButton").button(); + + $("button", ".searchButton").click(function() { + return false; + }); + }); + + //'ui-tabs-1' is the cookie name which is used for the persistence of the tabs.(Content/Search tab) + if ($.cookie('ui-tabs-1') === '1') { //search tab is active + if ($.cookie('textToSearch') != undefined && $.cookie('textToSearch').length > 0) { + document.getElementById('textToSearch').value = $.cookie('textToSearch'); + Verifie('searchForm'); + searchHighlight($.cookie('textToSearch')); + $("#showHideHighlight").css("display", "block"); + } + } + + syncToc(); //Synchronize the toc tree with the content pane, when loading the page. + //$("#doSearch").button(); //add jquery button styling to 'Go' button + + // When you click on a link to an anchor, scroll down + // 120 px to cope with the fact that the banner + // hides the top 95px or so of the page. + // This code deals with the problem when + // you click on a link from another page. + var hash = window.location.hash; + if(hash){ + var targetOffset = $(hash).offset().top - 120; + $('html,body').animate({scrollTop: targetOffset}, 200); + return false; + } +}); + + +/** + * If an user moved to another page by clicking on a toc link, and then clicked on #searchDiv, + * search should be performed if the cookie textToSearch is not empty. + */ +function doSearch() { +//'ui-tabs-1' is the cookie name which is used for the persistence of the tabs.(Content/Search tab) + if ($.cookie('textToSearch') != undefined && $.cookie('textToSearch').length > 0) { + document.getElementById('textToSearch').value = $.cookie('textToSearch'); + Verifie('searchForm'); + } +} + +/** + * Synchronize with the tableOfContents + */ +function syncToc() { + var a = document.getElementById("webhelp-currentid"); + if (a != undefined) { + //Expanding the child sections of the selected node. + var nodeClass = a.getAttribute("class"); + if (nodeClass != null && !nodeClass.match(/collapsable/)) { + a.setAttribute("class", "collapsable"); + //remove display:none; css style from
          block in the selected node. + var ulNode = a.getElementsByTagName("ul")[0]; + if (ulNode != undefined) { + if (ulNode.hasAttribute("style")) { + ulNode.setAttribute("style", "display: block; background-color: #D8D8D8 !important;"); + } else { + var ulStyle = document.createAttribute("style"); + ulStyle.nodeValue = "display: block; background-color: #D8D8D8 !important;"; + ulNode.setAttributeNode(ulStyle); + } } + //adjust tree's + sign to - + var divNode = a.getElementsByTagName("div")[0]; + if (divNode != undefined) { + if (divNode.hasAttribute("class")) { + divNode.setAttribute("class", "hitarea collapsable-hitarea"); + } else { + var divClass = document.createAttribute("class"); + divClass.nodeValue = "hitarea collapsable-hitarea"; + divNode.setAttributeNode(divClass); + } } + //set persistence cookie when a node is auto expanded + // setCookieForExpandedNode("webhelp-currentid"); + } + var b = a.getElementsByTagName("a")[0]; + + if (b != undefined) { + //Setting the background for selected node. + var style = a.getAttribute("style", 2); + if (style != null && !style.match(/background-color: Background;/)) { + a.setAttribute("style", "background-color: #D8D8D8; " + style); + b.setAttribute("style", "color: black;"); + } else if (style != null) { + a.setAttribute("style", "background-color: #D8D8D8; " + style); + b.setAttribute("style", "color: black;"); + } else { + a.setAttribute("style", "background-color: #D8D8D8; "); + b.setAttribute("style", "color: black;"); + } + } + + //shows the node related to current content. + //goes a recursive call from current node to ancestor nodes, displaying all of them. + while (a.parentNode && a.parentNode.nodeName) { + var parentNode = a.parentNode; + var nodeName = parentNode.nodeName; + + if (nodeName.toLowerCase() == "ul") { + parentNode.setAttribute("style", "display: block;"); + } else if (nodeName.toLocaleLowerCase() == "li") { + parentNode.setAttribute("class", "collapsable"); + parentNode.firstChild.setAttribute("class", "hitarea collapsable-hitarea "); + } + a = parentNode; +} } } +/* + function setCookieForExpandedNode(nodeName) { + var tocDiv = document.getElementById("tree"); //get table of contents Div + var divs = tocDiv.getElementsByTagName("div"); + var matchedDivNumber; + var i; + for (i = 0; i < divs.length; i++) { //1101001 + var div = divs[i]; + var liNode = div.parentNode; + } +//create a new cookie if a treeview does not exist + if ($.cookie(treeCookieId) == null || $.cookie(treeCookieId) == "") { + var branches = $("#tree").find("li");//.prepareBranches(treesettings); + var data = []; + branches.each(function(i, e) { + data[i] = $(e).is(":has(>ul:visible)") ? 1 : 0; + }); + $.cookie(treeCookieId, data.join("")); + + } + + if (i < divs.length) { + var treeviewCookie = $.cookie(treeCookieId); + var tvCookie1 = treeviewCookie.substring(0, i); + var tvCookie2 = treeviewCookie.substring(i + 1); + var newTVCookie = tvCookie1 + "1" + tvCookie2; + $.cookie(treeCookieId, newTVCookie); + } + } */ + +/** + * Code for Show/Hide TOC + * + */ +function showHideToc() { + var showHideButton = $("#showHideButton"); + var leftNavigation = $("#sidebar"); //hide the parent div of leftnavigation, ie sidebar + var content = $("#content"); + var animeTime=75 + + if (showHideButton != undefined && showHideButton.hasClass("pointLeft")) { + //Hide TOC + showHideButton.removeClass('pointLeft').addClass('pointRight'); + + if(noAnimations) { + leftNavigation.css("display", "none"); + content.css("margin", "125px 0 0 0"); + } else { + leftNavigation.hide(animeTime); + content.animate( { "margin-left": 0 }, animeTime); + } + showHideButton.attr("title", "Show Sidebar"); + } else { + //Show the TOC + showHideButton.removeClass('pointRight').addClass('pointLeft'); + if(noAnimations) { + content.css("margin", "125px 0 0 280px"); + leftNavigation.css("display", "block"); + } else { + content.animate( { "margin-left": '280px' }, animeTime); + leftNavigation.show(animeTime); + } + showHideButton.attr("title", "Hide Sidebar"); + } +} + +/** + * Code for search highlighting + */ +var highlightOn = true; +function searchHighlight(searchText) { + highlightOn = true; + if (searchText != undefined) { + var wList; + var sList = new Array(); //stem list + //Highlight the search terms + searchText = searchText.toLowerCase().replace(/<\//g, "_st_").replace(/\$_/g, "_di_").replace(/\.|%2C|%3B|%21|%3A|@|\/|\*/g, " ").replace(/(%20)+/g, " ").replace(/_st_/g, "There is no page containing all the search terms.
          Partial results:"; +var warningMsg = '
          '; +warningMsg+='Please note that due to security settings, Google Chrome does not highlight'; +warningMsg+=' the search results in the right frame.
          '; +warningMsg+='This happens only when the WebHelp files are loaded from the local file system.
          '; +warningMsg+='Workarounds:'; +warningMsg+='
            '; +warningMsg+='
          • Try using another web browser.
          • '; +warningMsg+='
          • Deploy the WebHelp files on a web server.
          • '; +warningMsg+='
          '; +txt_filesfound = 'Results'; +txt_enter_at_least_1_char = "You must enter at least one character."; +txt_enter_more_than_10_words = "Only first 10 words will be processed."; +txt_browser_not_supported = "Your browser is not supported. Use of Mozilla Firefox is recommended."; +txt_please_wait = "Please wait. Search in progress..."; +txt_results_for = "Results for: "; + +/* This function verify the validity of search input by the user + Cette fonction verifie la validite de la recherche entrre par l utilisateur */ +function Verifie(searchForm) { + + // Check browser compatibility + if (navigator.userAgent.indexOf("Konquerer") > -1) { + + alert(txt_browser_not_supported); + return; + } + + searchTextField = trim(document.searchForm.textToSearch.value); + searchTextField = searchTextField.replace(/['"]/g,''); + var expressionInput = searchTextField; + $.cookie('textToSearch', expressionInput); + + if (expressionInput.length < 1) { + + // expression is invalid + alert(txt_enter_at_least_1_char); + // reactive la fenetre de search (utile car cadres) + + document.searchForm.textToSearch.focus(); + } + else { + var splitSpace = searchTextField.split(" "); + var splitWords = []; + for (var i = 0 ; i < splitSpace.length ; i++) { + var splitDot = splitSpace[i].split("."); + + if(!(splitDot.length == 1)){ + splitWords.push(splitSpace[i]); + } + + for (var i1 = 0; i1 < splitDot.length; i1++) { + var splitColon = splitDot[i1].split(":"); + for (var i2 = 0; i2 < splitColon.length; i2++) { + var splitDash = splitColon[i2].split("-"); + for (var i3 = 0; i3 < splitDash.length; i3++) { + if (splitDash[i3].split("").length > 0) { + splitWords.push(splitDash[i3]); + } + } + } + } + } + noWords = splitWords; + if (noWords.length > 9){ + // Allow to search maximum 10 words + alert(txt_enter_more_than_10_words); + expressionInput = ''; + for (var x = 0 ; x < 10 ; x++){ + expressionInput = expressionInput + " " + noWords[x]; + } + Effectuer_recherche(expressionInput); + document.searchForm.textToSearch.focus(); + } else { + // Effectuer la recherche + expressionInput = ''; + for (var x = 0 ; x < noWords.length ; x++) { + expressionInput = expressionInput + " " + noWords[x]; + } + Effectuer_recherche(expressionInput); + // reactive la fenetre de search (utile car cadres) + document.searchForm.textToSearch.focus(); + } + } +} + +var stemQueryMap = new Array(); // A hashtable which maps stems to query words + +/* This function parses the search expression, loads the indices and displays the results*/ +function Effectuer_recherche(expressionInput) { + + /* Display a waiting message */ + //DisplayWaitingMessage(); + + /*data initialisation*/ + var searchFor = ""; // expression en lowercase et sans les caracte res speciaux + //w = new Object(); // hashtable, key=word, value = list of the index of the html files + scriptLetterTab = new Scriptfirstchar(); // Array containing the first letter of each word to look for + var wordsList = new Array(); // Array with the words to look for + var finalWordsList = new Array(); // Array with the words to look for after removing spaces + var linkTab = new Array(); + var fileAndWordList = new Array(); + var txt_wordsnotfound = ""; + + + // -------------------------------------- + // Begin Thu's patch + /*nqu: expressionInput, la recherche est lower cased, plus remplacement des char speciaux*/ + //The original replacement expression is: + //searchFor = expressionInput.toLowerCase().replace(/<\//g, "_st_").replace(/\$_/g, "_di_").replace(/\.|%2C|%3B|%21|%3A|@|\/|\*/g, " ").replace(/(%20)+/g, " ").replace(/_st_/g, " 0){ + var searchedWords = noWords.length; + var foundedWords = fileAndWordList[0][0].motslisteDisplay.split(",").length; + //console.info("search : " + noWords.length + " found : " + fileAndWordList[0][0].motslisteDisplay.split(",").length); + if (searchedWords != foundedWords){ + linkTab.push(partialSearch); + } + } + + + for (var i = 0; i < cpt; i++) { + + var hundredProcent = fileAndWordList[i][0].scoring + 100 * fileAndWordList[i][0].motsnb; + var ttScore_first = fileAndWordList[i][0].scoring; + var numberOfWords = fileAndWordList[i][0].motsnb; + + if (fileAndWordList[i] != undefined) { + linkTab.push("

          " + txt_results_for + " " + "" + fileAndWordList[i][0].motslisteDisplay + "" + "

          "); + + linkTab.push("
            "); + for (t in fileAndWordList[i]) { + //linkTab.push("
          • "+fl[fileAndWordList[i][t].filenb]+"
          • "); + + var ttInfo = fileAndWordList[i][t].filenb; + // Get scoring + var ttScore = fileAndWordList[i][t].scoring; + var tempInfo = fil[ttInfo]; + + var pos1 = tempInfo.indexOf("@@@"); + var pos2 = tempInfo.lastIndexOf("@@@"); + var tempPath = tempInfo.substring(0, pos1); + var tempTitle = tempInfo.substring(pos1 + 3, pos2); + var tempShortdesc = tempInfo.substring(pos2 + 3, tempInfo.length); + + + // toc.html will not be displayed on search result + if (tempPath == 'toc.html'){ + continue; + } + /* + //file:///home/kasun/docbook/WEBHELP/webhelp-draft-output-format-idea/src/main/resources/web/webhelp/installation.html + var linkString = "
          • " + tempTitle + ""; + // var linkString = "
          • " + tempTitle + ""; + */ + var split = fileAndWordList[i][t].motsliste.split(","); + // var splitedValues = expressionInput.split(" "); + // var finalArray = split.concat(splitedValues); + + arrayString = 'Array('; + for(var x in finalArray){ + if (finalArray[x].length > 2 || useCJKTokenizing){ + arrayString+= "'" + finalArray[x] + "',"; + } + } + arrayString = arrayString.substring(0,arrayString.length - 1) + ")"; + var idLink = 'foundLink' + no; + var linkString = '
          • ' + tempTitle + ''; + var starWidth = (ttScore * 100/ hundredProcent)/(ttScore_first/hundredProcent) * (numberOfWords/maxNumberOfWords); + starWidth = starWidth < 10 ? (starWidth + 5) : starWidth; + // Keep the 5 stars format + if (starWidth > 85){ + starWidth = 85; + } + /* + var noFullStars = Math.ceil(starWidth/17); + var fullStar = "curr"; + var emptyStar = ""; + if (starWidth % 17 == 0){ + // am stea plina + + } else { + + } + console.info(noFullStars); + */ + // Also check if we have a valid description + if ((tempShortdesc != "null" && tempShortdesc != '...')) { + + linkString += "\n
            " + tempShortdesc + "
            "; + } + linkString += "
          • "; + + // Add rating values for scoring at the list of matches + linkString += "
            "; + linkString += "
            "; + //linkString += "
            " + // + ((ttScore * 100/ hundredProcent)/(ttScore_first/hundredProcent)) * 1 + "
            "; + linkString += "
              "; + linkString += "
            • "; + linkString += "
            "; + + linkString += "
            "; + linkString += "
            "; + linkString += "
            "; + //linkString += 'Rating: ' + ttScore + ''; + + linkTab.push(linkString); + no++; + } + linkTab.push("
          "); + } + } + } + + var results = ""; + if (linkTab.length > 0) { + /*writeln ("

          " + txt_results_for + " " + "" + cleanwordsList + "" + "
          "+"

          ");*/ + results = "

          "; + //write("

            "); + for (t in linkTab) { + results += linkTab[t].toString(); + } + results += "

            "; + } else { + results = "

            " + localeresource.search_no_results + " " + txt_wordsnotfound + "" + "

            "; + } + + + // Verify if the browser is Google Chrome and the WebHelp is used on a local machine + // If browser is Google Chrome and WebHelp is used on a local machine a warning message will appear + // Highlighting will not work in this conditions. There is 2 workarounds + if (verifyBrowser()){ + document.getElementById('searchResults').innerHTML = results; + } else { + document.getElementById('searchResults').innerHTML = warningMsg + results; + } + +} + + +// Verify if the stemmed word is aproximately the same as the searched word +function verifyWord(word, arr){ + for (var i = 0 ; i < arr.length ; i++){ + if (word[0] == arr[i][0] + && word[1] == arr[i][1] + //&& word[2] == arr[i][2] + ){ + return true; + } + } + return false; +} + +// Look for elements that start with searchedValue. +function wordsStartsWith(searchedValue){ + var toReturn = ''; + for (var sv in w){ + if (searchedValue.length < 3){ + continue; + } else { + if (sv.toLowerCase().indexOf(searchedValue.toLowerCase()) == 0){ + toReturn+=sv + ","; + } + } + } + return toReturn.length > 0 ? toReturn : undefined; +} + + +function tokenize(wordsList){ + var stemmedWordsList = new Array(); // Array with the words to look for after removing spaces + var cleanwordsList = new Array(); // Array with the words to look for + // ------------------------------------------------- + // Thu's patch + for(var j=0;j"; + return this.input.substring(this.offset,this.offset+2); + } + + function getAllTokens(){ + while(this.incrementToken()){ + var tmp = this.tokenize(); + this.tokens.push(tmp); + } + return this.unique(this.tokens); +// document.getElementById("content").innerHTML += tokens+" "; +// document.getElementById("content").innerHTML += "
            dada"+sortedTokens+" "; +// console.log(tokens.length+"dsdsds"); + /*for(i=0;i t2.length) { + return 1; + } else { + return -1; + } + //return t1.length - t2.length); +} + +// return false if browser is Google Chrome and WebHelp is used on a local machine, not a web server +function verifyBrowser(){ + var returnedValue = true; + var browser = BrowserDetect.browser; + var addressBar = window.location.href; + if (browser == 'Chrome' && addressBar.indexOf('file://') === 0){ + returnedValue = false; + } + + return returnedValue; +} + +// Remove duplicate values from an array +function removeDuplicate(arr) { + var r = new Array(); + o:for(var i = 0, n = arr.length; i < n; i++) { + for(var x = 0, y = r.length; x < y; x++) { + if(r[x]==arr[i]) continue o; + } + r[r.length] = arr[i]; + } + return r; +} + +// Create startsWith method +String.prototype.startsWith = function(str) { + return (this.match("^"+str)==str); +} + +function trim(str, chars) { + return ltrim(rtrim(str, chars), chars); +} + +function ltrim(str, chars) { + chars = chars || "\\s"; + return str.replace(new RegExp("^[" + chars + "]+", "g"), ""); +} + +function rtrim(str, chars) { + chars = chars || "\\s"; + return str.replace(new RegExp("[" + chars + "]+$", "g"), ""); +} diff --git a/src/doc/user/webhelp/template/search/punctuation.props b/src/doc/user/webhelp/template/search/punctuation.props new file mode 100644 index 00000000..d3e3fcd2 --- /dev/null +++ b/src/doc/user/webhelp/template/search/punctuation.props @@ -0,0 +1,31 @@ +Punct01=\\u3002 +Punct02=\\u3003 +Punct03=\\u300C +Punct04=\\u300D +Punct05=\\u300E +Punct06=\\u300F +Punct07=\\u301D +Punct08=\\u301E +Punct09=\\u301F +Punct10=\\u309B +Punct11=\\u2018 +Punct12=\\u2019 +Punct13=\\u201A +Punct14=\\u201C +Punct15=\\u201D +Punct16=\\u201E +Punct17=\\u2032 +Punct18=\\u2033 +Punct19=\\u2035 +Punct20=\\u2039 +Punct21=\\u203A +Punct22=\\u201E +Punct23=\\u00BB +Punct24=\\u00AB +Punct25=© +Punct26=’ +Punct27=\\u00A0 +Punct28=\\u2014 + + + diff --git a/src/doc/user/webhelp/template/search/stemmers/de_stemmer.js b/src/doc/user/webhelp/template/search/stemmers/de_stemmer.js new file mode 100644 index 00000000..7ff3822a --- /dev/null +++ b/src/doc/user/webhelp/template/search/stemmers/de_stemmer.js @@ -0,0 +1,247 @@ +/* + * Author: Joder Illi + * + * Copyright (c) 2010, FormBlitz AG + * All rights reserved. + * Implementation of the stemming algorithm from http://snowball.tartarus.org/algorithms/german/stemmer.html + * Copyright of the algorithm is: Copyright (c) 2001, Dr Martin Porter and can be found at http://snowball.tartarus.org/license.php + * + * Redistribution and use in source and binary forms, with or without modification, is covered by the standard BSD license. + * + */ + +//var stemmer = function Stemmer() { + /* + German includes the following accented forms, + ä ö ü + and a special letter, ß, equivalent to double s. + The following letters are vowels: + a e i o u y ä ö ü + */ + + var stemmer = function(word) { + /* + Put u and y between vowels into upper case + */ + word = word.replace(/([aeiouyäöü])u([aeiouyäöü])/g, '$1U$2'); + word = word.replace(/([aeiouyäöü])y([aeiouyäöü])/g, '$1Y$2'); + + /* + and then do the following mappings, + (a) replace ß with ss, + (a) replace ae with ä, Not doing these, have trouble with diphtongs + (a) replace oe with ö, Not doing these, have trouble with diphtongs + (a) replace ue with ü unless preceded by q. Not doing these, have trouble with diphtongs + So in quelle, ue is not mapped to ü because it follows q, and in feuer it is not mapped because the first part of the rule changes it to feUer, so the u is not found. + */ + word = word.replace(/ß/g, 'ss'); + //word = word.replace(/ae/g, 'ä'); + //word = word.replace(/oe/g, 'ö'); + //word = word.replace(/([^q])ue/g, '$1ü'); + + /* + R1 and R2 are first set up in the standard way (see the note on R1 and R2), but then R1 is adjusted so that the region before it contains at least 3 letters. + R1 is the region after the first non-vowel following a vowel, or is the null region at the end of the word if there is no such non-vowel. + R2 is the region after the first non-vowel following a vowel in R1, or is the null region at the end of the word if there is no such non-vowel. + */ + + var r1Index = word.search(/[aeiouyäöü][^aeiouyäöü]/); + var r1 = ''; + if (r1Index != -1) { + r1Index += 2; + r1 = word.substring(r1Index); + } + + var r2Index = -1; + var r2 = ''; + + if (r1Index != -1) { + var r2Index = r1.search(/[aeiouyäöü][^aeiouyäöü]/); + if (r2Index != -1) { + r2Index += 2; + r2 = r1.substring(r2Index); + r2Index += r1Index; + } else { + r2 = ''; + } + } + + if (r1Index != -1 && r1Index < 3) { + r1Index = 3; + r1 = word.substring(r1Index); + } + + /* + Define a valid s-ending as one of b, d, f, g, h, k, l, m, n, r or t. + Define a valid st-ending as the same list, excluding letter r. + */ + + /* + Do each of steps 1, 2 and 3. + */ + + /* + Step 1: + Search for the longest among the following suffixes, + (a) em ern er + (b) e en es + (c) s (preceded by a valid s-ending) + */ + var a1Index = word.search(/(em|ern|er)$/g); + var b1Index = word.search(/(e|en|es)$/g); + var c1Index = word.search(/([bdfghklmnrt]s)$/g); + if (c1Index != -1) { + c1Index++; + } + var index1 = 10000; + var optionUsed1 = ''; + if (a1Index != -1 && a1Index < index1) { + optionUsed1 = 'a'; + index1 = a1Index; + } + if (b1Index != -1 && b1Index < index1) { + optionUsed1 = 'b'; + index1 = b1Index; + } + if (c1Index != -1 && c1Index < index1) { + optionUsed1 = 'c'; + index1 = c1Index; + } + + /* + and delete if in R1. (Of course the letter of the valid s-ending is not necessarily in R1.) If an ending of group (b) is deleted, and the ending is preceded by niss, delete the final s. + (For example, äckern -> äck, ackers -> acker, armes -> arm, bedürfnissen -> bedürfnis) + */ + + if (index1 != 10000 && r1Index != -1) { + if (index1 >= r1Index) { + word = word.substring(0, index1); + if (optionUsed1 == 'b') { + if (word.search(/niss$/) != -1) { + word = word.substring(0, word.length -1); + } + } + } + } + /* + Step 2: + Search for the longest among the following suffixes, + (a) en er est + (b) st (preceded by a valid st-ending, itself preceded by at least 3 letters) + */ + + var a2Index = word.search(/(en|er|est)$/g); + var b2Index = word.search(/(.{3}[bdfghklmnt]st)$/g); + if (b2Index != -1) { + b2Index += 4; + } + + var index2 = 10000; + var optionUsed2 = ''; + if (a2Index != -1 && a2Index < index2) { + optionUsed2 = 'a'; + index2 = a2Index; + } + if (b2Index != -1 && b2Index < index2) { + optionUsed2 = 'b'; + index2 = b2Index; + } + + /* + and delete if in R1. + (For example, derbsten -> derbst by step 1, and derbst -> derb by step 2, since b is a valid st-ending, and is preceded by just 3 letters) + */ + + if (index2 != 10000 && r1Index != -1) { + if (index2 >= r1Index) { + word = word.substring(0, index2); + } + } + + /* + Step 3: d-suffixes (*) + Search for the longest among the following suffixes, and perform the action indicated. + end ung + delete if in R2 + if preceded by ig, delete if in R2 and not preceded by e + ig ik isch + delete if in R2 and not preceded by e + lich heit + delete if in R2 + if preceded by er or en, delete if in R1 + keit + delete if in R2 + if preceded by lich or ig, delete if in R2 + */ + + var a3Index = word.search(/(end|ung)$/g); + var b3Index = word.search(/[^e](ig|ik|isch)$/g); + var c3Index = word.search(/(lich|heit)$/g); + var d3Index = word.search(/(keit)$/g); + if (b3Index != -1) { + b3Index ++; + } + + var index3 = 10000; + var optionUsed3 = ''; + if (a3Index != -1 && a3Index < index3) { + optionUsed3 = 'a'; + index3 = a3Index; + } + if (b3Index != -1 && b3Index < index3) { + optionUsed3 = 'b'; + index3 = b3Index; + } + if (c3Index != -1 && c3Index < index3) { + optionUsed3 = 'c'; + index3 = c3Index; + } + if (d3Index != -1 && d3Index < index3) { + optionUsed3 = 'd'; + index3 = d3Index; + } + + if (index3 != 10000 && r2Index != -1) { + if (index3 >= r2Index) { + word = word.substring(0, index3); + var optionIndex = -1; + var optionSubsrt = ''; + if (optionUsed3 == 'a') { + optionIndex = word.search(/[^e](ig)$/); + if (optionIndex != -1) { + optionIndex++; + if (optionIndex >= r2Index) { + word = word.substring(0, optionIndex); + } + } + } else if (optionUsed3 == 'c') { + optionIndex = word.search(/(er|en)$/); + if (optionIndex != -1) { + if (optionIndex >= r1Index) { + word = word.substring(0, optionIndex); + } + } + } else if (optionUsed3 == 'd') { + optionIndex = word.search(/(lich|ig)$/); + if (optionIndex != -1) { + if (optionIndex >= r2Index) { + word = word.substring(0, optionIndex); + } + } + } + } + } + + /* + Finally, + turn U and Y back into lower case, and remove the umlaut accent from a, o and u. + */ + word = word.replace(/U/g, 'u'); + word = word.replace(/Y/g, 'y'); + word = word.replace(/ä/g, 'a'); + word = word.replace(/ö/g, 'o'); + word = word.replace(/ü/g, 'u'); + + return word; + }; +//} \ No newline at end of file diff --git a/src/doc/user/webhelp/template/search/stemmers/en_stemmer.js b/src/doc/user/webhelp/template/search/stemmers/en_stemmer.js new file mode 100644 index 00000000..2117c1bf --- /dev/null +++ b/src/doc/user/webhelp/template/search/stemmers/en_stemmer.js @@ -0,0 +1,234 @@ +// Porter stemmer in Javascript. Few comments, but it's easy to follow against the rules in the original +// paper, in +// +// Porter, 1980, An algorithm for suffix stripping, Program, Vol. 14, +// no. 3, pp 130-137, +// +// see also http://www.tartarus.org/~martin/PorterStemmer + +// Release 1 +// Derived from (http://tartarus.org/~martin/PorterStemmer/js.txt) - cjm (iizuu) Aug 24, 2009 + +var stemmer = (function(){ + var step2list = { + "ational" : "ate", + "tional" : "tion", + "enci" : "ence", + "anci" : "ance", + "izer" : "ize", + "bli" : "ble", + "alli" : "al", + "entli" : "ent", + "eli" : "e", + "ousli" : "ous", + "ization" : "ize", + "ation" : "ate", + "ator" : "ate", + "alism" : "al", + "iveness" : "ive", + "fulness" : "ful", + "ousness" : "ous", + "aliti" : "al", + "iviti" : "ive", + "biliti" : "ble", + "logi" : "log" + }, + + step3list = { + "icate" : "ic", + "ative" : "", + "alize" : "al", + "iciti" : "ic", + "ical" : "ic", + "ful" : "", + "ness" : "" + }, + + c = "[^aeiou]", // consonant + v = "[aeiouy]", // vowel + C = c + "[^aeiouy]*", // consonant sequence + V = v + "[aeiou]*", // vowel sequence + + mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0 + meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1 + mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1 + s_v = "^(" + C + ")?" + v; // vowel in stem + + return function (w) { + var stem, + suffix, + firstch, + re, + re2, + re3, + re4, + origword = w; + + if (w.length < 3) { return w; } + + firstch = w.substr(0,1); + if (firstch == "y") { + w = firstch.toUpperCase() + w.substr(1); + } + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) { w = w.replace(re,"$1$2"); } + else if (re2.test(w)) { w = w.replace(re2,"$1$2"); } + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) { w = w + "e"; } + else if (re3.test(w)) { re = /.$/; w = w.replace(re,""); } + else if (re4.test(w)) { w = w + "e"; } + } + } + + // Step 1c + re = new RegExp("^(.+" + c + ")y$"); + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) { + w = stem + step2list[suffix]; + } + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) { + w = stem + step3list[suffix]; + } + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) { + w = stem; + } + } else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) { + w = stem; + } + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) { + w = stem; + } + } + + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + + if (firstch == "y") { + w = firstch.toLowerCase() + w.substr(1); + } + + // See http://snowball.tartarus.org/algorithms/english/stemmer.html + // "Exceptional forms in general" + var specialWords = { + "skis" : "ski", + "skies" : "sky", + "dying" : "die", + "lying" : "lie", + "tying" : "tie", + "idly" : "idl", + "gently" : "gentl", + "ugly" : "ugli", + "early": "earli", + "only": "onli", + "singly": "singl" + }; + + if(specialWords[origword]){ + w = specialWords[origword]; + } + + if( "sky news howe atlas cosmos bias \ + andes inning outing canning herring \ + earring proceed exceed succeed".indexOf(origword) !== -1 ){ + w = origword; + } + + // Address words overstemmed as gener- + re = /.*generate?s?d?(ing)?$/; + if( re.test(origword) ){ + w = w + 'at'; + } + re = /.*general(ly)?$/; + if( re.test(origword) ){ + w = w + 'al'; + } + re = /.*generic(ally)?$/; + if( re.test(origword) ){ + w = w + 'ic'; + } + re = /.*generous(ly)?$/; + if( re.test(origword) ){ + w = w + 'ous'; + } + // Address words overstemmed as commun- + re = /.*communit(ies)?y?/; + if( re.test(origword) ){ + w = w + 'iti'; + } + + return w; + } +})(); diff --git a/src/doc/user/webhelp/template/search/stemmers/fr_stemmer.js b/src/doc/user/webhelp/template/search/stemmers/fr_stemmer.js new file mode 100644 index 00000000..34f97431 --- /dev/null +++ b/src/doc/user/webhelp/template/search/stemmers/fr_stemmer.js @@ -0,0 +1,299 @@ +/* + * Author: Kasun Gajasinghe + * E-Mail: kasunbg AT gmail DOT com + * Date: 09.08.2010 + * + * usage: stemmer(word); + * ex: var stem = stemmer(foobar); + * Implementation of the stemming algorithm from http://snowball.tartarus.org/algorithms/french/stemmer.html + * + * LICENSE: + * + * Copyright (c) 2010, Kasun Gajasinghe. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * + * THIS SOFTWARE IS PROVIDED BY KASUN GAJASINGHE ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KASUN GAJASINGHE BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +var stemmer = function(word){ +// Letters in French include the following accented forms, +// â à ç ë é ê è ï î ô û ù +// The following letters are vowels: +// a e i o u y â à ë é ê è ï î ô û ù + + word = word.toLowerCase(); + var oriWord = word; + word = word.replace(/qu/g, 'qU'); //have to perform first, as after the operation, capital U is not treated as a vowel + word = word.replace(/([aeiouyâàëéêèïîôûù])u([aeiouyâàëéêèïîôûù])/g, '$1U$2'); + word = word.replace(/([aeiouyâàëéêèïîôûù])i([aeiouyâàëéêèïîôûù])/g, '$1I$2'); + word = word.replace(/([aeiouyâàëéêèïîôûù])y/g, '$1Y'); + word = word.replace(/y([aeiouyâàëéêèïîôûù])/g, 'Y$1'); + + var rv=''; + var rvIndex = -1; + if(word.search(/^(par|col|tap)/) != -1 || word.search(/^[aeiouyâàëéêèïîôûù]{2}/) != -1){ + rv = word.substring(3); + rvIndex = 3; + } else { + rvIndex = word.substring(1).search(/[aeiouyâàëéêèïîôûù]/); + if(rvIndex != -1){ + rvIndex +=2; //+2 is to supplement the substring(1) used to find rvIndex + rv = word.substring(rvIndex); + } else { + rvIndex = word.length; + } + } + +// R1 is the region after the first non-vowel following a vowel, or the end of the word if there is no such non-vowel. +// R2 is the region after the first non-vowel following a vowel in R1, or the end of the word if there is no such non-vowel + var r1Index = word.search(/[aeiouyâàëéêèïîôûù][^aeiouyâàëéêèïîôûù]/); + var r1 = ''; + if (r1Index != -1) { + r1Index += 2; + r1 = word.substring(r1Index); + } else { + r1Index = word.length; + } + + var r2Index = -1; + var r2 = ''; + if (r1Index != -1) { + r2Index = r1.search(/[aeiouyâàëéêèïîôûù][^aeiouyâàëéêèïîôûù]/); + if (r2Index != -1) { + r2Index += 2; + r2 = r1.substring(r2Index); + r2Index += r1Index; + } else { + r2 = ''; + r2Index = word.length; + } + } + if (r1Index != -1 && r1Index < 3) { + r1Index = 3; + r1 = word.substring(r1Index); + } + + /* + Step 1: Standard suffix removal + */ + var a1Index = word.search(/(ance|iqUe|isme|able|iste|eux|ances|iqUes|ismes|ables|istes)$/); + var a2Index = word.search(/(atrice|ateur|ation|atrices|ateurs|ations)$/); + var a3Index = word.search(/(logie|logies)$/); + var a4Index = word.search(/(usion|ution|usions|utions)$/); + var a5Index = word.search(/(ence|ences)$/); + var a6Index = word.search(/(ement|ements)$/); + var a7Index = word.search(/(ité|ités)$/); + var a8Index = word.search(/(if|ive|ifs|ives)$/); + var a9Index = word.search(/(eaux)$/); + var a10Index = word.search(/(aux)$/); + var a11Index = word.search(/(euse|euses)$/); + var a12Index = word.search(/[^aeiouyâàëéêèïîôûù](issement|issements)$/); + var a13Index = word.search(/(amment)$/); + var a14Index = word.search(/(emment)$/); + var a15Index = word.search(/[aeiouyâàëéêèïîôûù](ment|ments)$/); + + if(a1Index != -1 && a1Index >= r2Index){ + word = word.substring(0,a1Index); + } else if(a2Index != -1 && a2Index >= r2Index){ + word = word.substring(0,a2Index); + var a2Index2 = word.search(/(ic)$/); + if(a2Index2 != -1 && a2Index2 >= r2Index){ + word = word.substring(0, a2Index2); //if preceded by ic, delete if in R2, + } else { //else replace by iqU + word = word.replace(/(ic)$/,'iqU'); + } + } else if(a3Index != -1 && a3Index >= r2Index){ + word = word.replace(/(logie|logies)$/,'log'); //replace with log if in R2 + } else if(a4Index != -1 && a4Index >= r2Index){ + word = word.replace(/(usion|ution|usions|utions)$/,'u'); //replace with u if in R2 + } else if(a5Index != -1 && a5Index >= r2Index){ + word = word.replace(/(ence|ences)$/,'ent'); //replace with ent if in R2 + } else if(a6Index != -1 && a6Index >= rvIndex){ + word = word.substring(0,a6Index); + if(word.search(/(iv)$/) >= r2Index){ + word = word.replace(/(iv)$/, ''); + if(word.search(/(at)$/) >= r2Index){ + word = word.replace(/(at)$/, ''); + } + } else if(word.search(/(eus)$/) != -1){ + var a6Index2 = word.search(/(eus)$/); + if(a6Index2 >=r2Index){ + word = word.substring(0, a6Index2); + } else if(a6Index2 >= r1Index){ + word = word.substring(0,a6Index2)+"eux"; + } + } else if(word.search(/(abl|iqU)$/) >= r2Index){ + word = word.replace(/(abl|iqU)$/,''); //if preceded by abl or iqU, delete if in R2, + } else if(word.search(/(ièr|Ièr)$/) >= rvIndex){ + word = word.replace(/(ièr|Ièr)$/,'i'); //if preceded by abl or iqU, delete if in R2, + } + } else if(a7Index != -1 && a7Index >= r2Index){ + word = word.substring(0,a7Index); //delete if in R2 + if(word.search(/(abil)$/) != -1){ //if preceded by abil, delete if in R2, else replace by abl, otherwise, + var a7Index2 = word.search(/(abil)$/); + if(a7Index2 >=r2Index){ + word = word.substring(0, a7Index2); + } else { + word = word.substring(0,a7Index2)+"abl"; + } + } else if(word.search(/(ic)$/) != -1){ + var a7Index3 = word.search(/(ic)$/); + if(a7Index3 != -1 && a7Index3 >= r2Index){ + word = word.substring(0, a7Index3); //if preceded by ic, delete if in R2, + } else { //else replace by iqU + word = word.replace(/(ic)$/,'iqU'); + } + } else if(word.search(/(iv)$/) != r2Index){ + word = word.replace(/(iv)$/,''); + } + } else if(a8Index != -1 && a8Index >= r2Index){ + word = word.substring(0,a8Index); + if(word.search(/(at)$/) >= r2Index){ + word = word.replace(/(at)$/, ''); + if(word.search(/(ic)$/) >= r2Index){ + word = word.replace(/(ic)$/, ''); + } else { word = word.replace(/(ic)$/, 'iqU'); } + } + } else if(a9Index != -1){ word = word.replace(/(eaux)/,'eau') + } else if(a10Index >= r1Index){ word = word.replace(/(aux)/,'al') + } else if(a11Index != -1 ){ + var a11Index2 = word.search(/(euse|euses)$/); + if(a11Index2 >=r2Index){ + word = word.substring(0, a11Index2); + } else if(a11Index2 >= r1Index){ + word = word.substring(0, a11Index2)+"eux"; + } + } else if(a12Index!=-1 && a12Index>=r1Index){ + word = word.substring(0,a12Index+1); //+1- amendment to non-vowel + } else if(a13Index!=-1 && a13Index>=rvIndex){ + word = word.replace(/(amment)$/,'ant'); + } else if(a14Index!=-1 && a14Index>=rvIndex){ + word = word.replace(/(emment)$/,'ent'); + } else if(a15Index!=-1 && a15Index>=rvIndex){ + word = word.substring(0,a15Index+1); + } + + /* Step 2a: Verb suffixes beginning i*/ + var wordStep1 = word; + var step2aDone = false; + if(oriWord == word.toLowerCase() || oriWord.search(/(amment|emment|ment|ments)$/) != -1){ + step2aDone = true; + var b1Regex = /([^aeiouyâàëéêèïîôûù])(îmes|ît|îtes|i|ie|ies|ir|ira|irai|iraIent|irais|irait|iras|irent|irez|iriez|irions|irons|iront|is|issaIent|issais|issait|issant|issante|issantes|issants|isse|issent|isses|issez|issiez|issions|issons|it)$/i; + if(word.search(b1Regex) >= rvIndex){ + word = word.replace(b1Regex,'$1'); + } + } + + /* Step 2b: Other verb suffixes*/ + if (step2aDone && wordStep1 == word) { + if (word.search(/(ions)$/) >= r2Index) { + word = word.replace(/(ions)$/, ''); + } else { + var b2Regex = /(é|ée|ées|és|èrent|er|era|erai|eraIent|erais|erait|eras|erez|eriez|erions|erons|eront|ez|iez)$/i; + if (word.search(b2Regex) >= rvIndex) { + word = word.replace(b2Regex, ''); + } else { + var b3Regex = /e(âmes|ât|âtes|a|ai|aIent|ais|ait|ant|ante|antes|ants|as|asse|assent|asses|assiez|assions)$/i; + if (word.search(b3Regex) >= rvIndex) { + word = word.replace(b3Regex, ''); + } else { + var b3Regex2 = /(âmes|ât|âtes|a|ai|aIent|ais|ait|ant|ante|antes|ants|as|asse|assent|asses|assiez|assions)$/i; + if (word.search(b3Regex2) >= rvIndex) { + word = word.replace(b3Regex2, ''); + } + } + } + } + } + + if(oriWord != word.toLowerCase()){ + /* Step 3 */ + var rep = ''; + if(word.search(/Y$/) != -1) { + word = word.replace(/Y$/, 'i'); + } else if(word.search(/ç$/) != -1){ + word = word.replace(/ç$/, 'c'); + } + } else { + /* Step 4 */ + //If the word ends s, not preceded by a, i, o, u, è or s, delete it. + if (word.search(/([^aiouès])s$/) >= rvIndex) { + word = word.replace(/([^aiouès])s$/, '$1'); + } + var e1Index = word.search(/ion$/); + if (e1Index >= r2Index && word.search(/[st]ion$/) >= rvIndex) { + word = word.substring(0, e1Index); + } else { + var e2Index = word.search(/(ier|ière|Ier|Ière)$/); + if (e2Index != -1 && e2Index >= rvIndex) { + word = word.substring(0, e2Index) + "i"; + } else { + if (word.search(/e$/) >= rvIndex) { + word = word.replace(/e$/, ''); //delete last e + } else if (word.search(/guë$/) >= rvIndex) { + word = word.replace(/guë$/, 'gu'); + } + } + } + } + + /* Step 5: Undouble */ + //word = word.replace(/(en|on|et|el|eil)(n|t|l)$/,'$1'); + word = word.replace(/(en|on)(n)$/,'$1'); + word = word.replace(/(ett)$/,'et'); + word = word.replace(/(el|eil)(l)$/,'$1'); + + /* Step 6: Un-accent */ + word = word.replace(/[éè]([^aeiouyâàëéêèïîôûù]+)$/,'e$1'); + word = word.toLowerCase(); + return word; +}; + +var eqOut = new Array(); +var noteqOut = new Array(); +var eqCount = 0; +/* +To test the stemming, create two arrays named "voc" and "COut" which are for vocabualary and the stemmed output. +Then add the vocabulary strings and output strings. This method will generate the stemmed output for "voc" and will +compare the output with COut. + (I used porter's voc and out files and did a regex to convert them to js objects. regex: /");\nvoc.push("/g . This + will add strings to voc array such that output would look like: voc.push("foobar"); ) drop me an email for any help. + */ +function testFr(){ + var start = new Date().getTime(); //execution time + eqCount = 0; + eqOut = new Array(); + noteqOut = new Array(); + for(var k=0;k= len(self.sevenzip.getnames()) -1: iseof = rclexecm.RclExecM.eofnext - if isinstance(ipath, unicode): - ipath = ipath.encode("utf-8") - return (ok, docdata, ipath, iseof) + return (ok, docdata, rclexecm.makebytes(ipath), iseof) ###### File type handler api, used by rclexecm ----------> def openfile(self, params): @@ -71,7 +69,7 @@ class SevenZipExtractor: fp = open(filename, 'rb') self.sevenzip = Archive7z(fp) return True - except Exception, err: + except Exception as err: self.em.rclog("openfile: failed: [%s]" % err) return False @@ -84,7 +82,7 @@ class SevenZipExtractor: try: ipath = ipath.decode("utf-8") return self.extractone(ipath) - except Exception, err: + except Exception as err: return (ok, data, ipath, eof) def getnext(self, params): diff --git a/src/filters/rclaudio b/src/filters/rclaudio index 51d3b12d..03f95ad9 100755 --- a/src/filters/rclaudio +++ b/src/filters/rclaudio @@ -12,7 +12,7 @@ try: from mutagen.flac import FLAC from mutagen.oggvorbis import OggVorbis except: - print "RECFILTERROR HELPERNOTFOUND python:mutagen" + print("RECFILTERROR HELPERNOTFOUND python:mutagen") sys.exit(1); # prototype for the html document we're returning @@ -42,21 +42,24 @@ class AudioTagExtractor: #self.em.rclog("extractone %s %s" % (params["filename:"], params["mimetype:"])) docdata = "" ok = False - if not params.has_key("mimetype:") or not params.has_key("filename:"): + if not "mimetype:" in params or not "filename:" in params: self.em.rclog("extractone: no mime or file name") return (ok, docdata, "", rclexecm.RclExecM.eofnow) filename = params["filename:"] mimetype = params["mimetype:"] try: - if mimetype == "audio/mpeg": + if mimetype == b'audio/mpeg': tags = MP3(filename, ID3=EasyID3) - elif mimetype == "application/ogg": + elif mimetype == b'application/ogg' or \ + mimetype == b'audio/x-vorbis+ogg': tags = OggVorbis(filename) - elif mimetype == "application/x-flac": + elif mimetype == b'application/x-flac' or \ + mimetype == 'audio/x-flac' or \ + mimetype == b'audio/flac': tags = FLAC(filename) else: - raise Exception, "Bad mime type %s" % mimetype - except Exception, err: + raise Exception("Bad mime type %s" % mimetype) + except Exception as err: self.em.rclog("extractone: extract failed: [%s]" % err) return (ok, docdata, "", rclexecm.RclExecM.eofnow) @@ -64,21 +67,22 @@ class AudioTagExtractor: artist = "" title = "" try: - album = self.em.htmlescape(tags["album"][0].encode("utf-8")) + album = self.em.htmlescape(tags["album"][0]) except: pass try: - artist = self.em.htmlescape(tags["artist"][0].encode("utf-8")) + artist = self.em.htmlescape(tags["artist"][0]) except: pass try: - title = self.em.htmlescape(tags["title"][0].encode("utf-8")) + title = self.em.htmlescape(tags["title"][0]) except: pass self.em.setmimetype("text/html") - alldata = self.em.htmlescape(tags.pprint().encode("utf-8")) + alldata = self.em.htmlescape(tags.pprint()) alldata = alldata.replace("\n", "
            ") - docdata = htmltemplate % (album, artist, title, alldata) + docdata = (htmltemplate % (album, artist, title, alldata))\ + .encode('UTF-8') ok = True return (ok, docdata, "", rclexecm.RclExecM.eofnext) @@ -98,6 +102,14 @@ class AudioTagExtractor: self.currentindex += 1 return ret -proto = rclexecm.RclExecM() -extract = AudioTagExtractor(proto) -rclexecm.main(proto, extract) +def makeObject(): + print("makeObject"); + proto = rclexecm.RclExecM() + print("makeObject: rclexecm ok"); + extract = AudioTagExtractor(proto) + return 17 + +if __name__ == '__main__': + proto = rclexecm.RclExecM() + extract = AudioTagExtractor(proto) + rclexecm.main(proto, extract) diff --git a/src/filters/rclcheckneedretry.sh b/src/filters/rclcheckneedretry.sh index 400cf2d5..4d9e3ccc 100755 --- a/src/filters/rclcheckneedretry.sh +++ b/src/filters/rclcheckneedretry.sh @@ -1,19 +1,26 @@ #!/bin/sh -# Check /usr/bin and /usr/local/bin modification date against recorded -# state, as recorded inside ~/.config/Recoll.org/needidxretrydate +# This script is called by recollindex to determine if it would be +# worth retrying files which previously failed to index. +# +# This is the default implementation, it is pointed to by the +# 'checkneedretryindexscript' variable in the default recoll.conf +# +# The script exits with 0 if retrying should be performed (something +# changed), 1 else. +# +# We check /usr/bin and /usr/local/bin modification date against the +# previous value recorded inside ~/.config/Recoll.org/needidxretrydate # # If any argument is given, we record the new state instead of # generating it (this should be used at the end of an indexing pass # with retry set). # -# The script exits with 0 if retrying should be performed (something -# changed), 1 else. # Bin dirs to be tested: bindirs="/usr/bin /usr/local/bin $HOME/bin /opt/*/bin" -rfiledir=~/.config/Recoll.org +rfiledir=$HOME/.config/Recoll.org rfile=$rfiledir/needidxretrydate nrfile=$rfiledir/tneedidxretrydate diff --git a/src/filters/rclchm b/src/filters/rclchm index a9c2bbc7..e3046d39 100755 --- a/src/filters/rclchm +++ b/src/filters/rclchm @@ -1,7 +1,12 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 """Extract Html files from a Microsoft Compiled Html Help file (.chm) Needs at least python 2.2 for HTMLParser (chmlib needs 2.2 too)""" +from __future__ import print_function + +# Note: this is not converted to python3, libchm does not have a +# python3 wrapper at this point (2015-11) + # Do we return individual chapters as html pages or concatenate everything? rclchm_catenate = 0 # Use special html type to allow for mimeconf/mimeview Open magic, @@ -23,13 +28,13 @@ import rclexecm try: from chm import chm,chmlib except: - print "RECFILTERROR HELPERNOTFOUND python:chm" + print("RECFILTERROR HELPERNOTFOUND python:chm") sys.exit(1); try: from HTMLParser import HTMLParser except: - print "RECFILTERROR HELPERNOTFOUND python:HTMLParser" + print("RECFILTERROR HELPERNOTFOUND python:HTMLParser") sys.exit(1); # Small helper routines @@ -37,11 +42,11 @@ def getfile(chmfile, path): """Extract internal file text from chm object, given path""" res, ui = chmfile.ResolveObject(path) if res != chmlib.CHM_RESOLVE_SUCCESS: - #print "ResolveObject failed", path + #print("ResolveObject failed: %s" % path, file=sys.stderr) return "" res, doc = chmfile.RetrieveObject(ui) if not res: - print "RetrieveObject failed", path + print("RetrieveObject failed: %s" % path, file=sys.stderr) return "" return doc diff --git a/src/filters/rcldia b/src/filters/rcldia index 937204f5..1d00ea76 100755 --- a/src/filters/rcldia +++ b/src/filters/rcldia @@ -1,5 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +from __future__ import print_function + # dia (http://live.gnome.org/Dia) file filter for recoll # stefan.friedel@iwr.uni-heidelberg.de 2012 # @@ -66,7 +68,7 @@ class DiaExtractor: try: docdata = self.ExtractDiaText() ok = True - except Exception, err: + except Exception as err: ok = False iseof = rclexecm.RclExecM.eofnext self.em.setmimetype("text/plain") @@ -76,7 +78,7 @@ class DiaExtractor: def openfile(self, params): try: self.dia = GzipFile(params["filename:"], 'r') - # Dial files are sometimes not compressed. Quite weirdly, + # Dia files are sometimes not compressed. Quite weirdly, # GzipFile does not complain until we try to read. Have to do it # here to be able to retry an uncompressed open. data = self.dia.readline() diff --git a/src/filters/rcldjvu b/src/filters/rcldjvu deleted file mode 100755 index 93210a71..00000000 --- a/src/filters/rcldjvu +++ /dev/null @@ -1,180 +0,0 @@ -#!/bin/sh -# @(#$Id: rcldjvu,v 1.6 2008-10-08 08:27:34 dockes Exp $ (C) 2005 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 -# (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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -#================================================================ -# Extract text from a djvu file by executing djvused and djvutxt -# -# We use djvused to extract a possible title, djvutxt for the text -# -# Of course this only means anything if the djvu document actually has -# a text layer ! -# -# djvu utilities (04-2010) have a bug in which they try to interpret -# and convert file paths as character data, and fail miserably if the -# locale is not consistent with the actual encoding of the path (which -# could be arbitrary binary for all they know). We use a temporary -# symbolic link to get around this. -# -#================================================================ - -progname="rcldjvu" -filetype=dejavu - - -#RECFILTCOMMONCODE -############################################################################## -# !! Leave the previous line unmodified!! Code imported from the -# recfiltcommon file - -# Utility code common to all shell filters. This could be sourced at run -# time, but it's slightly more efficient to include the code in the -# filters at build time (with a sed script). - -# Describe error in a way that can be interpreted by our caller -senderror() -{ - echo RECFILTERROR $* - # Also alert on stderr just in case - echo ":2:$progname::: $*" 1>&2 - exit 1 -} - -iscmd() -{ - cmd=$1 - case $cmd in - */*) - if test -x $cmd -a ! -d $cmd ; then return 0; else return 1; fi ;; - *) - oldifs=$IFS; IFS=":"; set -- $PATH; IFS=$oldifs - for d in $*;do test -x $d/$cmd -a ! -d $d/$cmd && return 0;done - return 1 ;; - esac -} - -checkcmds() -{ - for cmd in $*;do - if iscmd $cmd - then - a=1 - else - senderror HELPERNOTFOUND $cmd - fi - done -} - -# show help message -if test $# -ne 1 -o "$1" = "--help" -then - echo "Convert a $filetype file to HTML text for Recoll indexing." - echo "Usage: $progname [infile]" - exit 1 -fi - -infile="$1" - -# check the input file existence (may be '-' for stdin) -if test "X$infile" != X- -a ! -f "$infile" -then - senderror INPUTNOSUCHFILE "$infile" -fi - -# protect access to our temp files and directories -umask 77 - -############################################################################## -# !! Leave the following line unmodified ! -#ENDRECFILTCOMMONCODE - -checkcmds djvutxt djvused awk - -# We need a temporary symlink to avoid path encoding issues -if test z"$RECOLL_TMPDIR" != z; then - ttdir=$RECOLL_TMPDIR -elif test z"$TMPDIR" != z ; then - ttdir=$TMPDIR -else - ttdir=/tmp -fi -tmplink=$ttdir/rcldjvu_tmp$$.djvu -rm -f $tmplink -ln -s "$infile" $tmplink || exit 1 - -cleanup() -{ - rm -f $tmplink -} - -trap cleanup EXIT HUP QUIT INT TERM - -# Title: we try to extract it from the annotations. djvused outputs string -# in C/awk \-escaped notation. Awk can only process this in string -# constants, so we have a first awk pass to create an awk program to parse -# the string as a constant (...). This is not exactly robust or nice -title=`djvused "$tmplink" -e 'select 1;output-ant' | \ -grep ' (title ' | sed -e 's/^.* (title //' -e 's/)$//' |\ -awk ' -{ - printf("BEGIN" " {s = %s; print s}\n", $0) -}' | awk -f -` - - -cat < - - $title - - - -
            -EOF
            -
            -# The strange 'BEGIN' setup is to prevent 'file' from thinking this file
            -# is an awk program
            -djvutxt "$tmplink" | sed -e 's/[ 	][ 	]*$//' | \
            -awk 'BEGIN'\
            -' {
            -  cont = ""
            -}
            -{
            -    $0 = cont $0
            -    cont = ""
            -
            -    if ($0 == "\f") {
            -       print "

            \n
            \n

            " - next - } else if ($0 ~ /[-]$/) { - # Break at last whitespace - match($0, "[ \t][^ \t]+$") - line = substr($0, 0, RSTART) - cont = substr($0, RSTART, RLENGTH) - $0 = line - gsub("-", "", cont) - } - gsub(/&/, "\\&", $0) - gsub(//, "\\>", $0) - print $0 -}' - -cat < - - -EOF diff --git a/src/filters/rcldjvu.py b/src/filters/rcldjvu.py new file mode 100755 index 00000000..46198a51 --- /dev/null +++ b/src/filters/rcldjvu.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# Copyright (C) 2016 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 +# (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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# Recoll DJVU extractor + +from __future__ import print_function + +import os +import sys +import re +import rclexecm +import subprocess + +class DJVUExtractor: + def __init__(self, em): + self.currentindex = 0 + self.djvused = None + self.djvutxt = None + self.em = em + + def extractone(self, params): + self.em.setmimetype('text/html') + + # Extract metadata + if self.djvused: + try: + metadata = subprocess.check_output([self.djvused, self.filename, + "-e", "select 1;print-meta"]) + except Exception as e: + self.em.rclog("djvused failed: %s" % e) + author = "" + title = "" + metadata = metadata.decode('UTF-8', 'replace') + for line in metadata.split('\n'): + line = line.split('"') + if len(line) >= 2: + nm = line[0].strip() + if nm == "author": + author = ' '.join(line[1:]) + elif nm == "title": + title = ' '.join(line[1:]) + + # Main text + try: + txtdata = subprocess.check_output([self.djvutxt, "--escape", self.filename]) + except Exception as e: + self.em.rclog("djvused failed: %s" % e) + return (False, "", "", rclexecm.RclExecM.eofnow) + txtdata = txtdata.decode('UTF-8', 'replace') + + data = '''''' + self.em.htmlescape(title) + '''''' + data += '''''' + if author: + data += '''''' + data += '''

            '''
            +
            +        data += self.em.htmlescape(txtdata)
            +        data += '''
            ''' + return (True, data, "", rclexecm.RclExecM.eofnext) + + ###### File type handler api, used by rclexecm ----------> + def openfile(self, params): + self.filename = params["filename:"] + self.currentindex = 0 + #self.em.rclog("openfile: [%s]" % self.filename) + + if not self.djvutxt: + self.djvutxt = rclexecm.which("djvutxt") + if not self.djvutxt: + print("RECFILTERROR HELPERNOTFOUND djvutxt") + sys.exit(1); + self.djvused = rclexecm.which("djvused") + + return True + + def getipath(self, params): + return self.extractone(params) + return (ok, data, ipath, eof) + + def getnext(self, params): + if self.currentindex >= 1: + return (False, "", "", rclexecm.RclExecM.eofnow) + else: + ret= self.extractone(params) + self.currentindex += 1 + return ret + +# Main program: create protocol handler and extractor and run them +proto = rclexecm.RclExecM() +extract = DJVUExtractor(proto) +rclexecm.main(proto, extract) diff --git a/src/filters/rcldoc b/src/filters/rcldoc deleted file mode 100755 index 78d28cbd..00000000 --- a/src/filters/rcldoc +++ /dev/null @@ -1,176 +0,0 @@ -#!/bin/sh -# @(#$Id: rcldoc,v 1.8 2007-06-08 13:51:08 dockes Exp $ (C) 2004 J.F.Dockes -# Parts taken from Estraier: -#================================================================ -# Estraier: a personal full-text search system -# Copyright (C) 2003-2004 Mikio Hirabayashi -#================================================================ -#================================================================ -# Extract text from an msword file by executing either antiword -# or wvware -# -#================================================================ - - -# set variables -LANG=C ; export LANG -LC_ALL=C ; export LC_ALL -progname="rcldoc" -filetype=ms-word - -decoder="antiword -t -i 1 -m UTF-8" - - - -#RECFILTCOMMONCODE -############################################################################## -# !! Leave the previous line unmodified!! Code imported from the -# recfiltcommon file - -# Utility code common to all shell filters. This could be sourced at run -# time, but it's slightly more efficient to include the code in the -# filters at build time (with a sed script). - -# Describe error in a way that can be interpreted by our caller -senderror() -{ - echo RECFILTERROR $* - # Also alert on stderr just in case - echo ":2:$progname::: $*" 1>&2 - exit 1 -} - -iscmd() -{ - cmd=$1 - case $cmd in - */*) - if test -x $cmd -a ! -d $cmd ; then return 0; else return 1; fi ;; - *) - oldifs=$IFS; IFS=":"; set -- $PATH; IFS=$oldifs - for d in $*;do test -x $d/$cmd -a ! -d $d/$cmd && return 0;done - return 1 ;; - esac -} - -checkcmds() -{ - for cmd in $*;do - if iscmd $cmd - then - a=1 - else - senderror HELPERNOTFOUND $cmd - fi - done -} - -# show help message -if test $# -ne 1 -o "$1" = "--help" -then - echo "Convert a $filetype file to HTML text for Recoll indexing." - echo "Usage: $progname [infile]" - exit 1 -fi - -infile="$1" - -# check the input file existence (may be '-' for stdin) -if test "X$infile" != X- -a ! -f "$infile" -then - senderror INPUTNOSUCHFILE "$infile" -fi - -# protect access to our temp files and directories -umask 77 - -############################################################################## -# !! Leave the following line unmodified ! -#ENDRECFILTCOMMONCODE - -checkcmds awk antiword iconv - -# We need to do some strange stuff to retrieve the status from antiword. Things -# would be simpler if we relied on using bash. -# Explanations: -#http://stackoverflow.com/questions/1221833/bash-pipe-output-and-capture-exit-status - -stdintoexitstatus() { - read exitstatus - return $exitstatus -} - -# The strange 'BEGIN' setup is to prevent 'file' from thinking this file -# is an awk program -(((($decoder "$infile"; echo $? >&3) | -awk 'BEGIN'\ -' { - cont = "" - gotdata = 0 -} -{ - if (!($0 ~ /^[ ]*$/) && gotdata == 0) { - print "" - print "" - print "\n\n

            " - gotdata = 1 - } - $0 = cont $0 - cont = "" - - if ($0 ~ /[­-]$/) { - # Note : soft-hyphen is iso8859 0xad - # Break at last whitespace - match($0, "[ \t][^ \t]+$") - line = substr($0, 0, RSTART) - cont = substr($0, RSTART, RLENGTH-1) - $0 = line - } - - if($0 == "\f") { - print "


            " - next - } - - if (gotdata == 1) { - gsub(/&/, "\\&", $0) - gsub(//, "\\>", $0) - - print $0 "
            " - } -} -END { - if (gotdata == 1) - print "

            " -}' >&4) 3>&1) | stdintoexitstatus) 4>&1 - - -# Antiword rarely fails, we try to catch the most common reasons: -if test $? -eq 1 ; then - # Check actual document type - mtype=`file -b -i "$infile" | awk '{sub(";", "", $1);print $1}'` - - if test X"$mtype" = Xtext/rtf; then - # RTF document disguising as msword either because it has a .doc - # extension or because it's an attachment with a wrong mime. - exec `dirname $0`/rclrtf "$infile" - fi - - if test X"$mtype" = Xtext/plain; then - # Someone gave a .doc ext to their texts. Happens... - exec `dirname $0`/rcltext "$infile" - fi - - if test X"$mtype" = Xapplication/msword; then - # Actually application/msword: try wvWare, which is much - # slower and we don't use it by default, but it handles some - # files that antiword won't, so use it as a last resort. - if iscmd wvWare ; then - exec wvWare --nographics --charset=utf-8 "$infile" - fi - fi - - # else let the error be... - exit 1 -fi diff --git a/src/filters/rcldoc.py b/src/filters/rcldoc.py new file mode 100755 index 00000000..52a62b78 --- /dev/null +++ b/src/filters/rcldoc.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python +from __future__ import print_function + +import rclexecm +import rclexec1 +import re +import sys +import os + +# Processing the output from antiword: create html header and tail, process +# continuation lines escape, HTML special characters, accumulate the data. +class WordProcessData: + def __init__(self, em): + self.em = em + self.out = b'' + self.cont = b'' + self.gotdata = False + # Line with continued word (ending in -) + # we strip the - which is not nice for actually hyphenated word. + # What to do ? + self.patcont = re.compile(b'''[\w][-]$''') + # Pattern for breaking continuation at last word start + self.patws = re.compile(b'''([\s])([\w]+)(-)$''') + + def takeLine(self, line): + if not self.gotdata: + if line == b'': + return + self.out = b'' + \ + b'' + \ + b'

            ' + self.gotdata = True + + if self.cont: + line = self.cont + line + self.cont = "" + + if line == b'\f': + self.out += '


            ' + return + + if self.patcont.search(line): + # Break at last whitespace + match = self.patws.search(line) + if match: + self.cont = line[match.start(2):match.end(2)] + line = line[0:match.start(1)] + else: + self.cont = line + line = b'' + + if line: + self.out += self.em.htmlescape(line) + b'
            ' + else: + self.out += b'
            ' + + def wrapData(self): + if self.gotdata: + self.out += b'

            ' + self.em.setmimetype("text/html") + return self.out + +# Null data accumulator. We use this when antiword has fail, and the +# data actually comes from rclrtf, rcltext or vwWare, which all +# output HTML +class WordPassData: + def __init__(self, em): + self.out = b'' + self.em = em + + def takeLine(self, line): + self.out += line + + def wrapData(self): + self.em.setmimetype("text/html") + return self.out + + +# Filter for msword docs. Try antiword, and if this fails, check for +# an rtf or text document (.doc are sometimes like this...). Also try +# vwWare if the doc is actually a word doc +class WordFilter: + def __init__(self, em, td): + self.em = em + self.ntry = 0 + self.execdir = td + + def reset(self): + self.ntry = 0 + + def hasControlChars(self, data): + for c in data: + if c < chr(32) and c != '\n' and c != '\t' and \ + c != '\f' and c != '\r': + return True + return False + + def mimetype(self, fn): + rtfprolog = b'{\\rtf1' + docprolog = b'\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1' + try: + f = open(fn, "rb") + except: + return "" + data = f.read(100) + if data[0:6] == rtfprolog: + return "text/rtf" + elif data[0:8] == docprolog: + return "application/msword" + elif self.hasControlChars(data): + return "application/octet-stream" + else: + return "text/plain" + + def getCmd(self, fn): + '''Return command to execute, and postprocessor, according to + our state: first try antiword, then others depending on mime + identification. Do 2 tries at most''' + if self.ntry == 0: + self.ntry = 1 + cmd = rclexecm.which("antiword") + if cmd: + return ([cmd, "-t", "-i", "1", "-m", "UTF-8"], + WordProcessData(self.em)) + else: + return ([],None) + elif self.ntry == 1: + self.ntry = 2 + # antiword failed. Check for an rtf file, or text and + # process accordingly. It the doc is actually msword, try + # wvWare. + mt = self.mimetype(fn) + self.em.rclog("rcldoc.py: actual MIME type %s" % mt) + if mt == "text/plain": + return ([sys.executable, os.path.join(self.execdir, "rcltext.py")], + WordPassData(self.em)) + elif mt == "text/rtf": + cmd = [sys.executable, os.path.join(self.execdir, "rclrtf.py"), + "-s"] + self.em.rclog("rcldoc.py: returning cmd %s" % cmd) + return (cmd, WordPassData(self.em)) + elif mt == "application/msword": + cmd = rclexecm.which("wvWare") + if cmd: + return ([cmd, "--nographics", "--charset=utf-8"], + WordPassData(self.em)) + else: + return ([],None) + else: + return ([],None) + else: + return ([],None) + +if __name__ == '__main__': + # Remember where we execute filters from, in case we need to exec another + execdir = os.path.dirname(sys.argv[0]) + # Check that we have antiword. We could fallback to wvWare, but + # this is not what the old filter did. + if not rclexecm.which("antiword"): + print("RECFILTERROR HELPERNOTFOUND antiword") + sys.exit(1) + proto = rclexecm.RclExecM() + filter = WordFilter(proto, execdir) + extract = rclexec1.Executor(proto, filter) + rclexecm.main(proto, extract) diff --git a/src/filters/rclepub b/src/filters/rclepub index 1c50592f..c4868d26 100755 --- a/src/filters/rclepub +++ b/src/filters/rclepub @@ -1,5 +1,6 @@ #!/usr/bin/env python """Extract Html content from an EPUB file (.chm)""" +from __future__ import print_function rclepub_html_mtype = "text/html" @@ -12,7 +13,7 @@ import rclexecm try: import epub except: - print "RECFILTERROR HELPERNOTFOUND python:epub" + print("RECFILTERROR HELPERNOTFOUND python:epub") sys.exit(1); class rclEPUB: @@ -63,11 +64,11 @@ class rclEPUB: if item is None: raise Exception("Item not found for id %s" % (id,)) doc = self.book.read_item(item) - doc = re.sub('''''', - '''''', doc) + doc = re.sub(b'''''', + b'''''', doc) self.em.setmimetype(rclepub_html_mtype) return (True, doc, id, iseof) - except Exception, err: + except Exception as err: self.em.rclog("extractone: failed: [%s]" % err) return (False, "", id, iseof) @@ -76,11 +77,11 @@ class rclEPUB: self.currentindex = -1 self.contents = [] try: - self.book = epub.open(params["filename:"]) - except Exception, err: + self.book = epub.open_epub(params["filename:"].decode('UTF-8')) + except Exception as err: self.em.rclog("openfile: epub.open failed: [%s]" % err) return False - for id, item in self.book.opf.manifest.iteritems(): + for id, item in self.book.opf.manifest.items(): if item.media_type == 'application/xhtml+xml': self.contents.append(id) return True diff --git a/src/filters/rclepub1 b/src/filters/rclepub1 new file mode 100755 index 00000000..22922652 --- /dev/null +++ b/src/filters/rclepub1 @@ -0,0 +1,106 @@ +#!/usr/bin/env python +"""Extract Html content from an EPUB file (.chm), concatenating all sections""" +from __future__ import print_function + +import sys +import os +import re + +import rclexecm + +try: + import epub +except: + print("RECFILTERROR HELPERNOTFOUND python:epub") + sys.exit(1); + +class rclEPUB: + """RclExecM slave worker for extracting all text from an EPUB + file. This version concatenates all nodes.""" + + def __init__(self, em): + self.em = em + self.em.setmimetype("text/html") + self.currentindex = 0 + + def _header(self): + meta = self.book.opf.metadata + title = "" + for tt, lang in meta.titles: + title += tt + " " + author = "" + for name, role, fileas in meta.creators: + author += name + " " + data = "\n\n" + if title: + data += "" + self.em.htmlescape(title) + "\n" + if author: + data += '\n' + if meta.description: + data += '\n' + data += "" + data = data.encode('UTF-8') + + return data + + def extractone(self, params): + """Extract EPUB data as concatenated HTML""" + + ok = True + data = self._header() + ids = [] + if self.book.opf.spine: + for id, linear in self.book.opf.spine.itemrefs: + ids.append(id) + else: + for id, item in self.book.opf.manifest.items(): + ids.append(id) + + for id in ids: + item = self.book.get_item(id) + if item is None or item.media_type != 'application/xhtml+xml': + continue + doc = self.book.read_item(item) + doc = re.sub(b'''<\?.*\?>''', b'', doc) + doc = re.sub(b'''<[hH][tT][mM][lL].*<[bB][oO][dD][yY][^>]*>''', + b'', doc, 1, re.DOTALL) + doc = re.sub(b'''''', b'', doc) + doc = re.sub(b'''''', b'', doc) + data += doc + + data += b'' + if ok: + return (ok, data, "", rclexecm.RclExecM.eofnext) + else: + return (ok, "", "", rclexecm.RclExecM.eofnow) + + def openfile(self, params): + """Open the EPUB file""" + self.currentindex = 0 + if not "filename:" in params: + self.em.rclog("openfile: no file name") + return (ok, "", "", rclexecm.RclExecM.eofnow) + + try: + self.book = epub.open_epub(params["filename:"].decode('UTF-8')) + except Exception as err: + self.em.rclog("openfile: epub.open failed: [%s]" % err) + return False + return True + + def getipath(self, params): + return self.extractone(params) + + def getnext(self, params): + if self.currentindex >= 1: + return (False, "", "", rclexecm.RclExecM.eofnow) + else: + ret= self.extractone(params) + self.currentindex += 1 + return ret + +proto = rclexecm.RclExecM() +extract = rclEPUB(proto) +rclexecm.main(proto, extract) diff --git a/src/filters/rclexec1.py b/src/filters/rclexec1.py new file mode 100644 index 00000000..295fb714 --- /dev/null +++ b/src/filters/rclexec1.py @@ -0,0 +1,127 @@ +################################# +# Copyright (C) 2014 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 +# (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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +######################################################## + +# Common code for replacing the old shell scripts with Python execm +# ones: this implements the basic functions for a filter which +# executes a command to translate a simple file (like rclword with +# antiword). +# +# This was motivated by the Windows port: to replace shell and Unix +# utility (awk , etc usage). We can't just execute python scripts, +# this would be to slow. So this helps implementing a permanent script +# to repeatedly execute single commands. + +from __future__ import print_function + +import subprocess +import rclexecm + +# This class has the code to execute the subprocess and call a +# data-specific post-processor. Command and processor are supplied by +# the object which we receive as a parameter, which in turn is defined +# in the actual executable filter (e.g. rcldoc.py) +class Executor: + opt_ignxval = 1 + + def __init__(self, em, flt): + self.em = em + self.flt = flt + self.currentindex = 0 + + def runCmd(self, cmd, filename, postproc, opt): + ''' Substitute parameters and execute command, process output + with the specific postprocessor and return the complete text. + We expect cmd as a list of command name + arguments, except that, for + the special value "cat", we just read the file''' + + if cmd == "cat": + try: + data = open(filename, 'rb').read() + ok = True + except Exception as err: + self.em.rclog("runCmd: error reading %s: %s"%(filename, err)) + return(False, "") + for line in data.split('\n'): + postproc.takeLine(line) + return True, postproc.wrapData() + else: + try: + fullcmd = cmd + [filename] + proc = subprocess.Popen(fullcmd, + stdout = subprocess.PIPE) + stdout = proc.stdout + except subprocess.CalledProcessError as err: + self.em.rclog("extractone: Popen(%s) error: %s" % (fullcmd, err)) + return (False, "") + except OSError as err: + self.em.rclog("extractone: Popen(%s) OS error: %s" % + (fullcmd, err)) + return (False, "") + + for line in stdout: + postproc.takeLine(line.strip()) + + proc.wait() + if (opt & self.opt_ignxval) == 0 and proc.returncode: + self.em.rclog("extractone: [%s] returncode %d" % \ + (filename, proc.returncode)) + return False, postproc.wrapData() + else: + return True, postproc.wrapData() + + def extractone(self, params): + #self.em.rclog("extractone %s %s" % (params["filename:"], \ + # params["mimetype:"])) + self.flt.reset() + ok = False + if not "filename:" in params: + self.em.rclog("extractone: no file name") + return (ok, "", "", rclexecm.RclExecM.eofnow) + + fn = params["filename:"] + while True: + cmdseq = self.flt.getCmd(fn) + cmd = cmdseq[0] + postproc = cmdseq[1] + opt = cmdseq[2] if len(cmdseq) == 3 else 0 + if cmd: + ok, data = self.runCmd(cmd, fn, postproc, opt) + if ok: + break + else: + break + if ok: + return (ok, data, "", rclexecm.RclExecM.eofnext) + else: + return (ok, "", "", rclexecm.RclExecM.eofnow) + + ###### File type handler api, used by rclexecm ----------> + def openfile(self, params): + self.currentindex = 0 + return True + + def getipath(self, params): + return self.extractone(params) + + def getnext(self, params): + if self.currentindex >= 1: + return (False, "", "", rclexecm.RclExecM.eofnow) + else: + ret= self.extractone(params) + self.currentindex += 1 + return ret diff --git a/src/filters/rclexecm.py b/src/filters/rclexecm.py index eadf19cd..4bb86390 100644 --- a/src/filters/rclexecm.py +++ b/src/filters/rclexecm.py @@ -1,12 +1,57 @@ -#!/usr/bin/env python +################################# +# Copyright (C) 2014 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 +# (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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +######################################################## +## Recoll multifilter communication module and utilities +# +# All data is binary. This is important for Python3 +# All parameter names are converted to and processed as str/unicode + +from __future__ import print_function -########################################### -## Generic recoll multifilter communication code import sys import os +import tempfile +import shutil +import getopt +import rclconfig +PY3 = sys.version > '3' + +if PY3: + def makebytes(data): + if isinstance(data, bytes): + return data + else: + return data.encode("UTF-8") +else: + def makebytes(data): + if isinstance(data, unicode): + return data.encode("UTF-8") + else: + return data + +my_config = rclconfig.RclConfig() + +############################################ +# RclExecM implements the +# communication protocol with the recollindex process. It calls the +# object specific of the document type to actually get the data. class RclExecM: - noteof = 0 + noteof = 0 eofnext = 1 eofnow = 2 @@ -15,105 +60,160 @@ class RclExecM: fileerror = 2 def __init__(self): - self.myname = os.path.basename(sys.argv[0]) - self.mimetype = "" + try: + self.myname = os.path.basename(sys.argv[0]) + except: + self.myname = "???" + self.mimetype = b"" + self.fields = {} + if os.environ.get("RECOLL_FILTER_MAXMEMBERKB"): self.maxmembersize = \ int(os.environ.get("RECOLL_FILTER_MAXMEMBERKB")) else: self.maxmembersize = 50 * 1024 self.maxmembersize = self.maxmembersize * 1024 - + if sys.platform == "win32": + import msvcrt + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) + self.debugfile = None + if self.debugfile: + self.errfout = open(self.debugfile, "a") + else: + self.errfout = sys.stderr + def rclog(self, s, doexit = 0, exitvalue = 1): - print >> sys.stderr, "RCLMFILT:", self.myname, ":", s + print("RCLMFILT: %s: %s" % (self.myname, s), file=self.errfout) if doexit: sys.exit(exitvalue) + def breakwrite(self, outfile, data): + if sys.platform != "win32": + outfile.write(data) + else: + # On windows, writing big chunks can fail with a "not enough space" + # error. Seems a combined windows/python bug, depending on versions. + # See https://bugs.python.org/issue11395 + # In any case, just break it up + total = len(data) + bs = 4*1024 + offset = 0 + while total > 0: + if total < bs: + tow = total + else: + tow = bs + #self.rclog("Total %d Writing %d to stdout: %s" % (total,tow,data[offset:offset+tow])) + outfile.write(data[offset:offset+tow]) + offset += tow + total -= tow + # Note: tried replacing this with a multiple replacer according to # http://stackoverflow.com/a/15221068, which was **10 times** slower def htmlescape(self, txt): - # This must stay first (it somehow had managed to skip after - # the next line, with rather interesting results) - txt = txt.replace("&", "&") - - txt = txt.replace("<", "<") - txt = txt.replace(">", ">") - txt = txt.replace('"', """) + # & must stay first (it somehow had managed to skip + # after the next replace, with rather interesting results) + try: + txt = txt.replace(b'&', b'&').replace(b'<', b'<').\ + replace(b'>', b'>').replace(b'"', b'"') + except: + txt = txt.replace("&", "&").replace("<", "<").\ + replace(">", ">").replace("\"", """) return txt # Our worker sometimes knows the mime types of the data it sends def setmimetype(self, mt): - self.mimetype = mt + self.mimetype = makebytes(mt) + + def setfield(self, nm, value): + self.fields[nm] = value # Read single parameter from process input: line with param name and size - # followed by data. + # followed by data. The param name is returned as str/unicode, the data + # as bytes def readparam(self): - s = sys.stdin.readline() - if s == '': + if PY3: + inf = sys.stdin.buffer + else: + inf = sys.stdin + s = inf.readline() + if s == b'': sys.exit(0) -# self.rclog(": EOF on input", 1, 0) - s = s.rstrip("\n") + s = s.rstrip(b'\n') - if s == "": - return ("","") + if s == b'': + return ('', b'') l = s.split() if len(l) != 2: - self.rclog("bad line: [" + s + "]", 1, 1) + self.rclog(b'bad line: [' + s + b']', 1, 1) - paramname = l[0].lower() + paramname = l[0].decode('ASCII').lower() paramsize = int(l[1]) if paramsize > 0: - paramdata = sys.stdin.read(paramsize) + paramdata = inf.read(paramsize) if len(paramdata) != paramsize: self.rclog("Bad read: wanted %d, got %d" % - (paramsize, len(paramdata)), 1,1) + (paramsize, len(paramdata)), 1, 1) else: - paramdata = "" + paramdata = b'' #self.rclog("paramname [%s] paramsize %d value [%s]" % # (paramname, paramsize, paramdata)) return (paramname, paramdata) + if PY3: + def senditem(self, nm, data): + data = makebytes(data) + l = len(data) + sys.stdout.buffer.write(makebytes("%s: %d\n" % (nm, l))) + self.breakwrite(sys.stdout.buffer, data) + else: + def senditem(self, nm, data): + data = makebytes(data) + l = len(data) + sys.stdout.write(makebytes("%s: %d\n" % (nm, l))) + self.breakwrite(sys.stdout, data) + # Send answer: document, ipath, possible eof. def answer(self, docdata, ipath, iseof = noteof, iserror = noerror): if iserror != RclExecM.fileerror and iseof != RclExecM.eofnow: - if isinstance(docdata, unicode): - self.rclog("GOT UNICODE for ipath [%s]" % (ipath,)) - docdata = docdata.encode("UTF-8") - - print "Document:", len(docdata) - sys.stdout.write(docdata) + self.senditem("Document", docdata) if len(ipath): - print "Ipath:", len(ipath) - sys.stdout.write(ipath) + self.senditem("Ipath", ipath) if len(self.mimetype): - print "Mimetype:", len(self.mimetype) - sys.stdout.write(self.mimetype) + self.senditem("Mimetype", self.mimetype) + for nm,value in self.fields.iteritems(): + #self.rclog("Senditem: [%s] -> [%s]" % (nm, value)) + self.senditem("%s:"%nm, value) + self.fields = {} + # If we're at the end of the contents, say so if iseof == RclExecM.eofnow: - print "Eofnow: 0" + self.senditem("Eofnow", b'') elif iseof == RclExecM.eofnext: - print "Eofnext: 0" + self.senditem("Eofnext", b'') if iserror == RclExecM.subdocerror: - print "Subdocerror: 0" + self.senditem("Subdocerror", b'') elif iserror == RclExecM.fileerror: - print "Fileerror: 0" + self.senditem("Fileerror", b'') # End of message - print + print() sys.stdout.flush() #self.rclog("done writing data") def processmessage(self, processor, params): # We must have a filename entry (even empty). Else exit - if not params.has_key("filename:"): + if "filename:" not in params: + print("%s" % params, file=sys.stderr) self.rclog("no filename ??", 1, 1) # If we're given a file name, open it. @@ -122,7 +222,7 @@ class RclExecM: if not processor.openfile(params): self.answer("", "", iserror = RclExecM.fileerror) return - except Exception, err: + except Exception as err: self.rclog("processmessage: openfile raised: [%s]" % err) self.answer("", "", iserror = RclExecM.fileerror) return @@ -132,11 +232,11 @@ class RclExecM: eof = True self.mimetype = "" try: - if params.has_key("ipath:") and len(params["ipath:"]): + if "ipath:" in params and len(params["ipath:"]): ok, data, ipath, eof = processor.getipath(params) else: ok, data, ipath, eof = processor.getnext(params) - except Exception, err: + except Exception as err: self.answer("", "", eof, RclExecM.fileerror) return @@ -165,53 +265,179 @@ class RclExecM: self.processmessage(processor, params) +# Helper routine to test for program accessibility +# Note that this works a bit differently from Linux 'which', which +# won't search the PATH if there is a path part in the program name, +# even if not absolute (e.g. will just try subdir/cmd in current +# dir). We will find such a command if it exists in a matching subpath +# of any PATH element. +# This is very useful esp. on Windows so that we can have several bin +# filter directories under filters (to avoid dll clashes). The +# corresponding c++ routine in recoll execcmd works the same. +def which(program): + def is_exe(fpath): + return os.path.exists(fpath) and os.access(fpath, os.X_OK) + def ext_candidates(fpath): + yield fpath + for ext in os.environ.get("PATHEXT", "").split(os.pathsep): + yield fpath + ext + + def path_candidates(): + yield os.path.dirname(sys.argv[0]) + rclpath = my_config.getConfParam("recollhelperpath") + if rclpath: + for path in rclpath.split(os.pathsep): + yield path + for path in os.environ["PATH"].split(os.pathsep): + yield path + + if os.path.isabs(program): + if is_exe(program): + return program + else: + for path in path_candidates(): + exe_file = os.path.join(path, program) + for candidate in ext_candidates(exe_file): + if is_exe(candidate): + return candidate + return None + +# Temp dir helper +class SafeTmpDir: + def __init__(self, em): + self.em = em + self.toptmp = "" + self.tmpdir = "" + + def __del__(self): + try: + if self.toptmp: + shutil.rmtree(self.tmpdir, True) + os.rmdir(self.toptmp) + except Exception as err: + self.em.rclog("delete dir failed for " + self.toptmp) + + def getpath(self): + if not self.tmpdir: + envrcltmp = os.getenv('RECOLL_TMPDIR') + if envrcltmp: + self.toptmp = tempfile.mkdtemp(prefix='rcltmp', dir=envrcltmp) + else: + self.toptmp = tempfile.mkdtemp(prefix='rcltmp') + + self.tmpdir = os.path.join(self.toptmp, 'rclsofftmp') + os.makedirs(self.tmpdir) + + return self.tmpdir + + # Common main routine for all python execm filters: either run the # normal protocol engine or a local loop to test without recollindex def main(proto, extract): if len(sys.argv) == 1: proto.mainloop(extract) + # mainloop does not return. Just in case + sys.exit(1) + + + # Not running the main loop: either acting as single filter (when called + # from other filter for example), or debugging + def usage(): + print("Usage: rclexecm.py [-d] [-s] [-i ipath] ", + file=sys.stderr) + print(" rclexecm.py -w ", + file=sys.stderr) + sys.exit(1) + + actAsSingle = False + debugDumpData = False + ipath = b"" + + args = sys.argv[1:] + opts, args = getopt.getopt(args, "hdsi:w:") + for opt, arg in opts: + if opt in ['-h']: + usage() + elif opt in ['-s']: + actAsSingle = True + elif opt in ['-i']: + ipath = makebytes(arg) + elif opt in ['-w']: + ret = which(arg) + if ret: + print("%s" % ret) + sys.exit(0) + else: + sys.exit(1) + elif opt in ['-d']: + debugDumpData = True + else: + print("unknown option %s\n"%opt, file=sys.stderr) + usage() + + if len(args) != 1: + usage() + + def mimetype_with_file(f): + cmd = 'file -i "' + f + '"' + fileout = os.popen(cmd).read() + lst = fileout.split(':') + mimetype = lst[len(lst)-1].strip() + lst = mimetype.split(';') + return makebytes(lst[0].strip()) + + def mimetype_with_xdg(f): + cmd = 'xdg-mime query filetype "' + f + '"' + return makebytes(os.popen(cmd).read().strip()) + + def debprint(out, s): + if not actAsSingle: + proto.breakwrite(out, makebytes(s+'\n')) + + params = {'filename:': makebytes(args[0])} + # Some filters (e.g. rclaudio) need/get a MIME type from the indexer + mimetype = mimetype_with_xdg(args[0]) + params['mimetype:'] = mimetype + + if not extract.openfile(params): + print("Open error", file=sys.stderr) + sys.exit(1) + + if PY3: + ioout = sys.stdout.buffer else: - # Got a file name parameter: TESTING without an execm parent - # Loop on all entries or get specific ipath - params = {'filename:':sys.argv[1]} - if not extract.openfile(params): - print "Open error" - sys.exit(1) - ipath = "" - if len(sys.argv) == 3: - ipath = sys.argv[2] - - if ipath != "": - params['ipath:'] = ipath - ok, data, ipath, eof = extract.getipath(params) - if ok: - print "== Found entry for ipath %s (mimetype [%s]):" % \ - (ipath, proto.mimetype) - if isinstance(data, unicode): - bdata = data.encode("UTF-8") - else: - bdata = data - sys.stdout.write(bdata) - print - else: - print "Got error, eof %d"%eof + ioout = sys.stdout + if ipath != b"" or actAsSingle: + params['ipath:'] = ipath + ok, data, ipath, eof = extract.getipath(params) + if ok: + debprint(ioout, "== Found entry for ipath %s (mimetype [%s]):" % \ + (ipath, proto.mimetype)) + bdata = makebytes(data) + if debugDumpData or actAsSingle: + proto.breakwrite(ioout, bdata) + ioout.write(b'\n') sys.exit(0) + else: + print("Got error, eof %d"%eof, file=sys.stderr) + sys.exit(1) - ecnt = 0 - while 1: - ok, data, ipath, eof = extract.getnext(params) - if ok: - ecnt = ecnt + 1 - print "== Entry %d ipath %s (mimetype [%s]):" % \ - (ecnt, ipath, proto.mimetype) - if isinstance(data, unicode): - bdata = data.encode("UTF-8") - else: - bdata = data - #sys.stdout.write(bdata) - print - if eof != RclExecM.noteof: - break - else: - print "Not ok, eof %d" % eof - break + ecnt = 0 + while 1: + ok, data, ipath, eof = extract.getnext(params) + if ok: + ecnt = ecnt + 1 + bdata = makebytes(data) + debprint(ioout, "== Entry %d dlen %d ipath %s (mimetype [%s]):" % \ + (ecnt, len(data), ipath, proto.mimetype)) + if debugDumpData: + proto.breakwrite(ioout, bdata) + ioout.write(b'\n') + if eof != RclExecM.noteof: + sys.exit(0) + else: + print("Not ok, eof %d" % eof, file=sys.stderr) + sys.exit(1) + # Not sure this makes sense, but going on looping certainly does not + if actAsSingle: + sys.exit(0) diff --git a/src/filters/rclics b/src/filters/rclics index 6ad3f632..3f28a057 100755 --- a/src/filters/rclics +++ b/src/filters/rclics @@ -1,4 +1,5 @@ #!/usr/bin/env python +from __future__ import print_function # Read an ICS file, break it into "documents" which are events, todos, # or journal entries, and interface with recoll execm @@ -13,36 +14,36 @@ import rclexecm import sys # Decide how we'll process the file. -modules = ('internal', 'icalendar', 'vobject') -usemodule = 'internal' +modules = ("internal", "icalendar", "vobject") +usemodule = "internal" forcevobject = 0 -if usemodule != 'internal': +if usemodule != "internal": try: if forcevobject: raise Exception from icalendar import Calendar, Event - usemodule = 'icalendar' + usemodule = "icalendar" except: try: import vobject - usemodule = 'vobject' + usemodule = "vobject" except: - print "RECFILTERROR HELPERNOTFOUND python:icalendar" - print "RECFILTERROR HELPERNOTFOUND python:vobject" + print("RECFILTERROR HELPERNOTFOUND python:icalendar") + print("RECFILTERROR HELPERNOTFOUND python:vobject") sys.exit(1); class IcalExtractor: def __init__(self, em): self.file = "" - self.contents = [] + self.contents = [] self.em = em def extractone(self, index): if index >= len(self.contents): return(False, "", "", True) docdata = self.contents[index] - #self.em.rclog(docdata) + #self.em.rclog(docdata) iseof = rclexecm.RclExecM.noteof if self.currentindex >= len(self.contents) -1: @@ -55,32 +56,32 @@ class IcalExtractor: self.file = params["filename:"] try: - calstr = open(self.file, 'rb') - except Exception, e: + calstr = open(self.file, "rb") + except Exception as e: self.em.rclog("Openfile: open: %s" % str(e)) return False self.currentindex = -1 - if usemodule == 'internal': + if usemodule == "internal": self.contents = ICalSimpleSplitter().splitcalendar(calstr) - elif usemodule == 'icalendar': + elif usemodule == "icalendar": try: cal = Calendar.from_string(calstr.read()) - except Exception, e: + except Exception as e: self.em.rclog("Openfile: read or parse error: %s" % str(e)) return False self.contents = cal.walk() self.contents = [item.as_string() for item in self.contents - if (item.name == 'VEVENT' or item.name == 'VTODO' - or item.name == 'VJOURNAL')] + if (item.name == "VEVENT" or item.name == "VTODO" + or item.name == "VJOURNAL")] else: try: cal = vobject.readOne(calstr) - except Exception, e: + except Exception as e: self.em.rclog("Openfile: cant parse object: %s" % str(e)) return False - for lstnm in ('vevent_list', 'vtodo_list', 'vjournal_list'): + for lstnm in ("vevent_list", "vtodo_list", "vjournal_list"): lst = getattr(cal, lstnm, []) for ev in lst: self.contents.append(ev.serialize()) @@ -90,7 +91,10 @@ class IcalExtractor: def getipath(self, params): try: - index = int(params["ipath:"]) + if params["ipath:"] == b'': + index = 0 + else: + index = int(params["ipath:"]) except: return (False, "", "", True) return self.extractone(index) @@ -100,7 +104,7 @@ class IcalExtractor: if self.currentindex == -1: # Return "self" doc self.currentindex = 0 - self.em.setmimetype('text/plain') + self.em.setmimetype(b'text/plain') if len(self.contents) == 0: eof = rclexecm.RclExecM.eofnext else: @@ -121,44 +125,44 @@ class ICalSimpleSplitter: # Note that if an 'interesting' element is nested inside another one, # it will not be extracted (stay as text in external event). This is # not an issue and I don't think it can happen with the current list - interesting = ('VTODO', 'VEVENT', 'VJOURNAL') + interesting = (b'VTODO', b'VEVENT', b'VJOURNAL') def splitcalendar(self, fin): - curblkname = '' - curblk = '' + curblkname = b'' + curblk = b'' lo = [] for line in fin: line = line.rstrip() - if line == '': + if line == b'': continue if curblkname: - curblk = curblk + line + "\n" + curblk = curblk + line + b'\n' - l = line.split(":") + l = line.split(b':') if len(l) < 2: continue # If not currently inside a block and we see an # 'interesting' BEGIN, start block - if curblkname == '' and l[0].upper() == "BEGIN" : + if curblkname == b'' and l[0].upper() == b'BEGIN': name = l[1].upper() if name in ICalSimpleSplitter.interesting: curblkname = name - curblk = curblk + line + "\n" + curblk = curblk + line + b'\n' # If currently accumulating block lines, check for end - if curblkname and l[0].upper() == "END" and \ + if curblkname and l[0].upper() == b'END' and \ l[1].upper() == curblkname: lo.append(curblk) - curblkname = '' - curblk = '' + curblkname = b'' + curblk = b'' if curblk: lo.append(curblk) - curblkname = '' - curblk = '' + curblkname = b'' + curblk = b'' return lo diff --git a/src/filters/rclimg b/src/filters/rclimg index e6c4bedc..7ad11452 100755 --- a/src/filters/rclimg +++ b/src/filters/rclimg @@ -147,6 +147,9 @@ if ($@) { exit(1); } +binmode(STDIN) || die "cannot binmode STDIN"; +binmode(STDOUT) || die "cannot binmode STDOUT"; + #print STDERR "RCLIMG: Starting\n"; $| = 1; while (1) { diff --git a/src/filters/rclimg.py b/src/filters/rclimg.py new file mode 100755 index 00000000..8892a9ae --- /dev/null +++ b/src/filters/rclimg.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python + +# Python-based Image Tag extractor for Recoll. This is less thorough +# than the Perl-based rclimg script, but useful if you don't want to +# have to install Perl (e.g. on Windows). +# +# Uses pyexiv2. Also tried Pillow, found it useless for tags. +# +from __future__ import print_function + +import sys +import os +import rclexecm +import re + +try: + import pyexiv2 +except: + print("RECFILTERROR HELPERNOTFOUND python:pyexiv2") + sys.exit(1); + +khexre = re.compile('.*\.0[xX][0-9a-fA-F]+$') + +pyexiv2_titles = { + 'Xmp.dc.subject', + 'Xmp.lr.hierarchicalSubject', + 'Xmp.MicrosoftPhoto.LastKeywordXMP', + } + +# Keys for which we set meta tags +meta_pyexiv2_keys = { + 'Xmp.dc.subject', + 'Xmp.lr.hierarchicalSubject', + 'Xmp.MicrosoftPhoto.LastKeywordXMP', + 'Xmp.digiKam.TagsList', + 'Exif.Photo.DateTimeDigitized', + 'Exif.Photo.DateTimeOriginal', + 'Exif.Image.DateTime', + } + +exiv2_dates = ['Exif.Photo.DateTimeOriginal', + 'Exif.Image.DateTime', 'Exif.Photo.DateTimeDigitized'] + +class ImgTagExtractor: + def __init__(self, em): + self.em = em + self.currentindex = 0 + + def extractone(self, params): + #self.em.rclog("extractone %s" % params["filename:"]) + ok = False + if "filename:" not in params: + self.em.rclog("extractone: no file name") + return (ok, docdata, "", rclexecm.RclExecM.eofnow) + filename = params["filename:"] + + try: + metadata = pyexiv2.ImageMetadata(filename) + metadata.read() + keys = metadata.exif_keys + metadata.iptc_keys + metadata.xmp_keys + mdic = {} + for k in keys: + # we skip numeric keys and undecoded makernote data + if k != 'Exif.Photo.MakerNote' and not khexre.match(k): + mdic[k] = str(metadata[k].raw_value) + except Exception as err: + self.em.rclog("extractone: extract failed: [%s]" % err) + return (ok, "", "", rclexecm.RclExecM.eofnow) + + docdata = b'\n' + + ttdata = set() + for k in pyexiv2_titles: + if k in mdic: + ttdata.add(self.em.htmlescape(mdic[k])) + if ttdata: + title = "" + for v in ttdata: + v = v.replace('[', '').replace(']', '').replace("'", "") + title += v + " " + docdata += rclexecm.makebytes("" + title + "\n") + + for k in exiv2_dates: + if k in mdic: + # Recoll wants: %Y-%m-%d %H:%M:%S. + # We get 2014:06:27 14:58:47 + dt = mdic[k].replace(":", "-", 2) + docdata += b'\n' + break + + for k,v in mdic.items(): + if k == 'Xmp.digiKam.TagsList': + docdata += b'\n' + + docdata += b'\n' + for k,v in mdic.items(): + docdata += rclexecm.makebytes(k + " : " + \ + self.em.htmlescape(mdic[k]) + "
            \n") + docdata += b'' + + self.em.setmimetype("text/html") + + return (True, docdata, "", rclexecm.RclExecM.eofnext) + + ###### File type handler api, used by rclexecm ----------> + def openfile(self, params): + self.currentindex = 0 + return True + + def getipath(self, params): + return self.extractone(params) + + def getnext(self, params): + if self.currentindex >= 1: + return (False, "", "", rclexecm.RclExecM.eofnow) + else: + ret= self.extractone(params) + self.currentindex += 1 + return ret + +if __name__ == '__main__': + proto = rclexecm.RclExecM() + extract = ImgTagExtractor(proto) + rclexecm.main(proto, extract) diff --git a/src/filters/rclinfo b/src/filters/rclinfo index c6b8a8b1..7defcd97 100755 --- a/src/filters/rclinfo +++ b/src/filters/rclinfo @@ -3,10 +3,11 @@ # Read a file in GNU info format and output its nodes as subdocs, # interfacing with recoll execm +from __future__ import print_function import rclexecm import sys -import os.path +import os import subprocess # Prototype for the html document we're returning. Info files are @@ -16,24 +17,12 @@ import subprocess # Some info source docs contain charset info like: # @documentencoding ISO-2022-JP # But this seems to be absent from outputs. -htmltemplate = ''' - - - %s - - - -
            -   %s
            -   
            - -''' # RclExecm interface class InfoExtractor: def __init__(self, em): self.file = "" - self.contents = [] + self.contents = [] self.em = em def extractone(self, index): @@ -43,8 +32,15 @@ class InfoExtractor: nodename, docdata = self.contents[index] nodename = self.em.htmlescape(nodename) docdata = self.em.htmlescape(docdata) - - docdata = htmltemplate % (nodename, docdata) + # strange whitespace to avoid changing the module tests (same as old) + docdata = b'\n\n \n ' + \ + nodename + \ + b'\n' + \ + b' \n' + \ + b' \n \n' + \ + b'
            \n   ' + \
            +                  docdata + \
            +                  b'\n   
            \n\n' iseof = rclexecm.RclExecM.noteof if self.currentindex >= len(self.contents) -1: @@ -60,19 +56,18 @@ class InfoExtractor: self.em.rclog("Openfile: %s is not a file" % self.file) return False - cmd = "info --subnodes -o - -f " + self.file - nullstream = open("/dev/null", 'w') + cmd = b'info --subnodes -o - -f ' + self.file + nullstream = open(os.devnull, 'w') try: infostream = subprocess.Popen(cmd, shell=True, bufsize=1, stderr=nullstream, stdout=subprocess.PIPE).stdout - except Exception, e: + except Exception as e: # Consider this as permanently fatal. self.em.rclog("Openfile: exec info: %s" % str(e)) - print "RECFILTERROR HELPERNOTFOUND info" + print("RECFILTERROR HELPERNOTFOUND info") sys.exit(1); - self.currentindex = -1 self.contents = InfoSimpleSplitter().splitinfo(self.file, infostream) @@ -117,9 +112,9 @@ class InfoSimpleSplitter: index = 0 listout = [] node_dict = {} - node = "" + node = b'' infofile = os.path.basename(filename) - nodename = "Unknown" + nodename = b'Unknown' for line in fin: @@ -128,41 +123,41 @@ class InfoSimpleSplitter: # beginning with spaces (it's a bug probably, only seen it once) # Maybe we'd actually be better off directly interpreting the # info files - if gotblankline and line.lstrip(" ").startswith("File: "): + if gotblankline and line.lstrip(b' ').startswith(b'File: '): prevnodename = nodename - line = line.rstrip("\n\r") - pairs = line.split(",") - up = "Top" + line = line.rstrip(b'\n\r') + pairs = line.split(b',') + up = b'Top' nodename = str(index) try: for pair in pairs: - name, value = pair.split(':') - name = name.strip(" ") - value = value.strip(" ") - if name == "Node": + name, value = pair.split(b':') + name = name.strip(b' ') + value = value.strip(b' ') + if name == b'Node': nodename = value - if name == "Up": + if name == b'Up': up = value - if name == "File": + if name == b'File': infofile = value - except: - print >> sys.stderr, "rclinfo: bad line in %s: [%s]\n" % \ - (infofile, line) + except Exception as err: + print("rclinfo: bad line in %s: [%s] %s\n" % \ + (infofile, line, err), file = sys.stderr) nodename = prevnodename node += line continue - if node_dict.has_key(nodename): - print >> sys.stderr, "Info file", filename, \ - "Dup node: ", nodename + if nodename in node_dict: + print("Info file %s Dup node: %s" % (filename, nodename), \ + file=sys.stderr) node_dict[nodename] = up if index != 0: listout.append((prevnodename, node)) - node = "" + node = b'' index += 1 - if line.rstrip("\n\r") == '': + if line.rstrip(b'\n\r') == b'': gotblankline = 1 else: gotblankline = 0 @@ -170,7 +165,7 @@ class InfoSimpleSplitter: node += line # File done, add last dangling node - if node != "": + if node != b'': listout.append((nodename, node)) # Compute node paths (concatenate "Up" values), to be used @@ -178,34 +173,34 @@ class InfoSimpleSplitter: # the info file tree is bad listout1 = [] for nodename, node in listout: - title = "" + title = b'' loop = 0 error = 0 - while nodename != "Top": - title = nodename + " / " + title - if node_dict.has_key(nodename): + while nodename != b'Top': + title = nodename + b' / ' + title + if nodename in node_dict: nodename = node_dict[nodename] else: - print >> sys.stderr, \ + print( "Infofile: node's Up does not exist: file %s, path %s, up [%s]" % \ - (infofile, title, nodename) + (infofile, title, nodename), sys.stderr) error = 1 break loop += 1 if loop > 50: - print >> sys.stderr, "Infofile: bad tree (looping)", \ - infofile + print("Infofile: bad tree (looping) %s" % infofile, \ + file = sys.stderr) error = 1 break if error: continue - if title == "": + if title == b'': title = infofile else: - title = infofile + " / " + title - title = title.rstrip(" / ") + title = infofile + b' / ' + title + title = title.rstrip(b' / ') listout1.append((title, node)) return listout1 diff --git a/src/filters/rclkar b/src/filters/rclkar index 83c0207c..940f13d0 100755 --- a/src/filters/rclkar +++ b/src/filters/rclkar @@ -1,6 +1,8 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 # Read a .kar midi karaoke file and translate to recoll indexable format +# This does not work with Python3 yet because python:midi doesn't +from __future__ import print_function import rclexecm import sys @@ -15,9 +17,9 @@ except: pass try: - import midi + from midi import midi except: - print "RECFILTERROR HELPERNOTFOUND python:midi" + print("RECFILTERROR HELPERNOTFOUND python:midi") sys.exit(1); try: @@ -106,12 +108,12 @@ class KarTextExtractor: if data: try: data = data.decode(self.encoding, 'ignore') - except Exception, err: + except Exception as err: self.em.rclog("Decode failed: " + str(err)) return "" try: data = data.encode('utf-8') - except Exception, err: + except Exception as err: self.em.rclog("Encode failed: " + str(err)) return "" @@ -127,7 +129,7 @@ class KarTextExtractor: just one our users could use if there is trouble with guessing encodings''' - rexp = r'\(([^\)]+)\)\.[a-zA-Z]+$' + rexp = b'''\(([^\)]+)\)\.[a-zA-Z]+$''' m = re.search(rexp, fn) if m: return m.group(1) @@ -165,7 +167,7 @@ class KarTextExtractor: if count > 0: confidence = 1.0 encoding = code - except Exception, err: + except Exception as err: self.em.rclog("stopwords-based classifier failed: %s" % err) return (encoding, confidence) @@ -177,7 +179,7 @@ class KarTextExtractor: docdata = "" ok = False - if not params.has_key("filename:"): + if "filename:" not in params: self.em.rclog("extractone: no mime or file name") return (ok, docdata, "", rclexecm.RclExecM.eofnow) filename = params["filename:"] @@ -191,7 +193,7 @@ class KarTextExtractor: self.encoding = "" # Mimetype not used for now - if not params.has_key("mimetype:"): + if "mimetype:" not in params: mimetype = 'audio/x-midi' else: mimetype = params["mimetype:"] @@ -199,8 +201,8 @@ class KarTextExtractor: # Read in and midi-decode the file try: stream = midi.read_midifile(filename) - except Exception, err: - self.em.rclog("extractone: midi extract failed: [%s]" % err) + except Exception as err: + self.em.rclog("extractone: read_midifile failed: [%s]" % err) return (ok, docdata, "", rclexecm.RclExecM.eofnow) title = None diff --git a/src/filters/rcllatinclass.py b/src/filters/rcllatinclass.py index d038ff64..fa9504b9 100755 --- a/src/filters/rcllatinclass.py +++ b/src/filters/rcllatinclass.py @@ -13,13 +13,18 @@ epsilon with dasia (in unicode but not iso). Can this be replaced by either epsi with acute accent ? """ +from __future__ import print_function + import sys -import string +PY3 = sys.version > '3' +if not PY3: + import string import glob import os import os.path from zipfile import ZipFile + class European8859TextClassifier: def __init__(self, langzip=""): """langzip contains text files. Each text file is named like lang_code.txt @@ -31,9 +36,12 @@ class European8859TextClassifier: self.readlanguages(langzip) # Table to translate from punctuation to spaces - self.punct = '''*?[].@+-,#_$%&={};.,:!"''' + "'\n\r" - spaces = len(self.punct) * " " - self.spacetable = string.maketrans(self.punct, spaces) + self.punct = b'''0123456789<>/*?[].@+-,#_$%&={};.,:!"''' + b"'\n\r" + spaces = len(self.punct) * b' ' + if PY3: + self.spacetable = bytes.maketrans(self.punct, spaces) + else: + self.spacetable = string.maketrans(self.punct, spaces) def readlanguages(self, langzip): """Extract the stop words lists from the zip file. @@ -51,7 +59,7 @@ class European8859TextClassifier: text = zip.read(fn) words = text.split() for word in words: - if self.allwords.has_key(word): + if word in self.allwords: self.allwords[word].append((lang, code)) else: self.allwords[word] = [(lang, code)] @@ -62,7 +70,7 @@ class European8859TextClassifier: # Limit to reasonable size. if len(rawtext) > 10000: - i = rawtext.find(" ", 9000) + i = rawtext.find(b' ', 9000) if i == -1: i = 9000 rawtext = rawtext[0:i] @@ -77,9 +85,9 @@ class European8859TextClassifier: dict = {} for w in words: dict[w] = dict.get(w, 0) + 1 - lfreq = [a[0] for a in sorted(dict.iteritems(), \ + lfreq = [a[0] for a in sorted(dict.items(), \ key=lambda entry: entry[1], reverse=True)[0:ntest]] - #print lfreq + #print(lfreq) # Build a dict (lang,code)->matchcount langstats = {} @@ -89,9 +97,9 @@ class European8859TextClassifier: langstats[lc] = langstats.get(lc, 0) + 1 # Get a list of (lang,code) sorted by match count - lcfreq = sorted(langstats.iteritems(), \ + lcfreq = sorted(langstats.items(), \ key=lambda entry: entry[1], reverse=True) - #print lcfreq[0:3] + #print(lcfreq[0:3]) if len(lcfreq) != 0: lc,maxcount = lcfreq[0] maxlang = lc[0] @@ -109,7 +117,7 @@ class European8859TextClassifier: if __name__ == "__main__": - f = open(sys.argv[1]) + f = open(sys.argv[1], "rb") rawtext = f.read() f.close() @@ -117,7 +125,7 @@ if __name__ == "__main__": lang,code,count = classifier.classify(rawtext) if count > 0: - print "%s %s %d" % (code, lang, count) + print("%s %s %d" % (code, lang, count)) else: - print "UNKNOWN UNKNOWN 0" + print("UNKNOWN UNKNOWN 0") diff --git a/src/filters/rclmpdf b/src/filters/rclmpdf deleted file mode 100755 index d3d72da6..00000000 --- a/src/filters/rclmpdf +++ /dev/null @@ -1,237 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2014 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 -# (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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -# Recoll PDF extractor, with support for attachments - -import os -import sys -import re -import rclexecm -import subprocess -import distutils.spawn -import tempfile -import atexit -import signal - -tmpdir = None - -def finalcleanup(): - if tmpdir: - vacuumdir(tmpdir) - os.rmdir(tmpdir) - -def signal_handler(signal, frame): - sys.exit(1) - -atexit.register(finalcleanup) - -signal.signal(signal.SIGHUP, signal_handler) -signal.signal(signal.SIGINT, signal_handler) -signal.signal(signal.SIGQUIT, signal_handler) -signal.signal(signal.SIGTERM, signal_handler) - -def vacuumdir(dir): - if dir: - for fn in os.listdir(dir): - path = os.path.join(dir, fn) - if os.path.isfile(path): - os.unlink(path) - return True - -class PDFExtractor: - def __init__(self, em): - self.currentindex = 0 - self.pdftotext = "" - self.pdftk = "" - self.em = em - self.attextractdone = False - - # Extract all attachments if any into temporary directory - def extractAttach(self): - if self.attextractdone: - return True - self.attextractdone = True - - global tmpdir - if not tmpdir or not self.pdftk: - return False - - try: - vacuumdir(tmpdir) - subprocess.check_call([self.pdftk, self.filename, "unpack_files", - "output", tmpdir]) - self.attachlist = sorted(os.listdir(tmpdir)) - return True - except Exception, e: - self.em.rclog("extractAttach: failed: %s" % e) - return False - - def extractone(self, ipath): - #self.em.rclog("extractone: [%s]" % ipath) - if not self.attextractdone: - if not self.extractAttach(): - return (False, "", "", rclexecm.RclExecM.eofnow) - path = os.path.join(tmpdir, ipath) - if os.path.isfile(path): - f = open(path) - docdata = f.read(); - f.close() - if self.currentindex == len(self.attachlist) - 1: - eof = rclexecm.RclExecM.eofnext - else: - eof = rclexecm.RclExecM.noteof - return (True, docdata, ipath, eof) - - # pdftotext (used to?) badly escape text inside the header - # fields. We do it here. This is not an html parser, and depends a - # lot on the actual format output by pdftotext. - def _fixhtml(self, input): - #print input - inheader = False - inbody = False - didcs = False - output = '' - cont = '' - for line in input.split('\n'): - line = cont + line - cont = '' - if re.search('', line): - inheader = False - if re.search('
            ', line): - inbody = False - if inheader: - if not didcs: - output += '\n' - didcs = True - - m = re.search(r'(.*)(.*)(<\/title>.*)', line) - if not m: - m = re.search(r'(.*content=")(.*)(".*/>.*)', line) - if m: - line = m.group(1) + self.em.htmlescape(m.group(2)) + \ - m.group(3) - - # Recoll treats "Subject" as a "title" element - # (based on emails). The PDF "Subject" metadata - # field is more like an HTML "description" - line = re.sub('name="Subject"', 'name="Description"', line, 1) - - elif inbody: - # Remove end-of-line hyphenation. It's not clear that - # we should do this as pdftotext without the -layout - # option does it ? - #if re.search(r'[-]$', line): - #m = re.search(r'(.*)[ \t]([^ \t]+)$', line) - #if m: - #line = m.group(1) - #cont = m.group(2).rstrip('-') - line = self.em.htmlescape(line) - - if re.search('<head>', line): - inheader = True - if re.search('<pre>', line): - inbody = True - - output += line + '\n' - - return output - - def _selfdoc(self): - self.em.setmimetype('text/html') - - if self.attextractdone and len(self.attachlist) == 0: - eof = rclexecm.RclExecM.eofnext - else: - eof = rclexecm.RclExecM.noteof - - data = subprocess.check_output([self.pdftotext, "-htmlmeta", "-enc", - "UTF-8", "-eol", "unix", "-q", - self.filename, "-"]) - data = self._fixhtml(data) - #self.em.rclog("%s" % data) - return (True, data, "", eof) - - ###### File type handler api, used by rclexecm ----------> - def openfile(self, params): - self.filename = params["filename:"] - #self.em.rclog("openfile: [%s]" % self.filename) - self.currentindex = -1 - self.attextractdone = False - - if self.pdftotext == "": - self.pdftotext = distutils.spawn.find_executable("pdftotext") - if self.pdftotext is None: - print("RECFILTERROR HELPERNOTFOUND pdftotext") - sys.exit(1); - - if self.pdftk == "": - self.pdftk = distutils.spawn.find_executable("pdftk") - - if self.pdftk: - global tmpdir - if tmpdir: - if not vacuumdir(tmpdir): - self.em.rclog("openfile: vacuumdir %s failed" % tmpdir) - return False - else: - tmpdir = tempfile.mkdtemp(prefix='rclmpdf') - - preview = os.environ.get("RECOLL_FILTER_FORPREVIEW", "no") - if preview != "yes": - # When indexing, extract attachments at once. This - # will be needed anyway and it allows generating an - # eofnext error instead of waiting for actual eof, - # which avoids a bug in recollindex up to 1.20 - self.extractAttach() - - return True - - def getipath(self, params): - ipath = params["ipath:"] - ok, data, ipath, eof = self.extractone(ipath) - return (ok, data, ipath, eof) - - def getnext(self, params): - if self.currentindex == -1: - #self.em.rclog("getnext: current -1") - self.currentindex = 0 - return self._selfdoc() - else: - self.em.setmimetype('') - - if not self.attextractdone: - if not self.extractAttach(): - return (False, "", "", rclexecm.RclExecM.eofnow) - - if self.currentindex >= len(self.attachlist): - return (False, "", "", rclexecm.RclExecM.eofnow) - try: - ok, data, ipath, eof = \ - self.extractone(self.attachlist[self.currentindex]) - self.currentindex += 1 - - #self.em.rclog("getnext: returning ok for [%s]" % ipath) - return (ok, data, ipath, eof) - except: - return (False, "", "", rclexecm.RclExecM.eofnow) - - -# Main program: create protocol handler and extractor and run them -proto = rclexecm.RclExecM() -extract = PDFExtractor(proto) -rclexecm.main(proto, extract) diff --git a/src/filters/rclnull b/src/filters/rclnull deleted file mode 100755 index eba02f4a..00000000 --- a/src/filters/rclnull +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -# It may make sense in some cases to set this null filter (no output) -# instead of using recoll_noindex or leaving the default filter in -# case one doesn't want to install it: this will avoid endless retries -# to reindex the affected files, as recoll will think it has succeeded -# indexing them. Downside: the files won't be indexed when one -# actually installs the real filter, will need a -z - -exit 0 diff --git a/src/filters/rclopxml b/src/filters/rclopxml deleted file mode 100755 index 13ba54a2..00000000 --- a/src/filters/rclopxml +++ /dev/null @@ -1,238 +0,0 @@ -#!/bin/sh -# @(#$Id: rclopxml,v 1.3 2008-10-08 08:27:34 dockes Exp $ (C) 2004 J.F.Dockes -#================================================================ -# Extract text from an openxml msword file (will be extended for spreadsheets) -# TODO: Also process docProps/core.xml for attributes, and word/endnotes.xml -#================================================================ - -# set variables -LANG=C ; export LANG -LC_ALL=C ; export LC_ALL -progname=rclopxml -filetype=openxml - - -#RECFILTCOMMONCODE -############################################################################## -# !! Leave the previous line unmodified!! Code imported from the -# recfiltcommon file - -# Utility code common to all shell filters. This could be sourced at run -# time, but it's slightly more efficient to include the code in the -# filters at build time (with a sed script). - -# Describe error in a way that can be interpreted by our caller -senderror() -{ - echo RECFILTERROR $* - # Also alert on stderr just in case - echo ":2:$progname::: $*" 1>&2 - exit 1 -} - -iscmd() -{ - cmd=$1 - case $cmd in - */*) - if test -x $cmd -a ! -d $cmd ; then return 0; else return 1; fi ;; - *) - oldifs=$IFS; IFS=":"; set -- $PATH; IFS=$oldifs - for d in $*;do test -x $d/$cmd -a ! -d $d/$cmd && return 0;done - return 1 ;; - esac -} - -checkcmds() -{ - for cmd in $*;do - if iscmd $cmd - then - a=1 - else - senderror HELPERNOTFOUND $cmd - fi - done -} - -# show help message -if test $# -ne 1 -o "$1" = "--help" -then - echo "Convert a $filetype file to HTML text for Recoll indexing." - echo "Usage: $progname [infile]" - exit 1 -fi - -infile="$1" - -# check the input file existence (may be '-' for stdin) -if test "X$infile" != X- -a ! -f "$infile" -then - senderror INPUTNOSUCHFILE "$infile" -fi - -# protect access to our temp files and directories -umask 77 - -############################################################################## -# !! Leave the following line unmodified ! -#ENDRECFILTCOMMONCODE - -checkcmds xsltproc unzip - -# We need a temporary directory -if test z"$RECOLL_TMPDIR" != z; then - ttdir=$RECOLL_TMPDIR -elif test z"$TMPDIR" != z ; then - ttdir=$TMPDIR -else - ttdir=/tmp -fi -tmpdir=$ttdir/rclopxml_tmp$$ -mkdir $tmpdir || exit 1 -mkdir $tmpdir/rclopxmltmp || exit 1 - -cleanup() -{ - # Note that we're using a constant part (rclopxmltmp), that hopefully - # guarantees that we can't do big mistakes here. - rm -rf $tmpdir/rclopxmltmp - rmdir $tmpdir -} - -trap cleanup EXIT HUP QUIT INT TERM - -# Unzip the input file and change to the unzipped directory -unzip -q -d $tmpdir/rclopxmltmp "$infile" -cd $tmpdir/rclopxmltmp - -echo '<html> -<head>' - -xsltproc --novalid --nonet - docProps/core.xml <<EOF -<?xml version="1.0"?> -<xsl:stylesheet - xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" - xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:dcterms="http://purl.org/dc/terms/" - xmlns:dcmitype="http://purl.org/dc/dcmitype/" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - -<!-- <xsl:output method="text"/> --> - <xsl:output omit-xml-declaration="yes"/> - - <xsl:template match="cp:coreProperties"> - <xsl:text> </xsl:text> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> - <xsl:text> </xsl:text> - <xsl:apply-templates/> - </xsl:template> - - <xsl:template match="dc:creator"> - <meta> - <xsl:attribute name="name"> - <!-- <xsl:value-of select="name()"/> pour sortir tous les meta avec - le meme nom que dans le xml (si on devenait dc-natif) --> - <xsl:text>author</xsl:text> - </xsl:attribute> - <xsl:attribute name="content"> - <xsl:value-of select="."/> - </xsl:attribute> - </meta> - <xsl:text> </xsl:text> - </xsl:template> - - <xsl:template match="dcterms:modified"> - <meta> - <xsl:attribute name="name"> - <xsl:text>date</xsl:text> - </xsl:attribute> - <xsl:attribute name="content"> - <xsl:value-of select="."/> - </xsl:attribute> - </meta> - <xsl:text> </xsl:text> - </xsl:template> - - <xsl:template match="*"> - </xsl:template> - -</xsl:stylesheet> -EOF - -echo '</head> -<body>' - -filename='' -if test -f word/document.xml ; then - filenames=word/document.xml - tagmatch="w:p" - xmlns_decls=' - xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" - xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:o="urn:schemas-microsoft-com:office:office" - xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" - xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" - xmlns:v="urn:schemas-microsoft-com:vml" - xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" - xmlns:w10="urn:schemas-microsoft-com:office:word" - xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" - xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" - ' - -elif test -f xl/sharedStrings.xml ; then - filenames=xl/sharedStrings.xml - tagmatch='x:t' - xmlns_decls=' - xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" - xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main" - ' - -elif test -f ppt/slides/slide1.xml ; then - filenames=`echo ppt/slides/slide*.xml` - tagmatch='a:t' - xmlns_decls=' - xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" - xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" - xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" - xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" - ' -# I want to suppress text output for all except a:t, don't know how to do it -# help ! At least get rid of these: - moretemplates=' - <xsl:template match="p:attrName"> - </xsl:template> -' -else - # ?? - exit 1 -fi - - -for filename in $filenames;do -xsltproc --novalid --nonet - $filename <<EOF -<?xml version="1.0"?> -<xsl:stylesheet $xmlns_decls > - - <xsl:output omit-xml-declaration="yes"/> - - <xsl:template match="/"> - <div> - <xsl:apply-templates/> - </div> -</xsl:template> - - <xsl:template match="$tagmatch"> - <p> - <xsl:value-of select="."/> - </p> - </xsl:template> - - $moretemplates - -</xsl:stylesheet> -EOF -done - -echo '</html>' diff --git a/src/filters/rclopxml.py b/src/filters/rclopxml.py new file mode 100755 index 00000000..60d951e5 --- /dev/null +++ b/src/filters/rclopxml.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python +# Copyright (C) 2015 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 +# (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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +###################################### +from __future__ import print_function + +import sys +import rclexecm +import rclxslt +import fnmatch +from zipfile import ZipFile + +meta_stylesheet = '''<?xml version="1.0"?> +<xsl:stylesheet + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" + xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:dcterms="http://purl.org/dc/terms/" + xmlns:dcmitype="http://purl.org/dc/dcmitype/" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + +<!-- <xsl:output method="text"/> --> + <xsl:output omit-xml-declaration="yes"/> + + <xsl:template match="cp:coreProperties"> + <xsl:text> </xsl:text> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <xsl:text> </xsl:text> + <xsl:apply-templates/> + </xsl:template> + + <xsl:template match="dc:creator"> + <meta> + <xsl:attribute name="name"> + <!-- <xsl:value-of select="name()"/> pour sortir tous les meta avec + le meme nom que dans le xml (si on devenait dc-natif) --> + <xsl:text>author</xsl:text> + </xsl:attribute> + <xsl:attribute name="content"> + <xsl:value-of select="."/> + </xsl:attribute> + </meta> + <xsl:text> </xsl:text> + </xsl:template> + + <xsl:template match="dcterms:modified"> + <meta> + <xsl:attribute name="name"> + <xsl:text>date</xsl:text> + </xsl:attribute> + <xsl:attribute name="content"> + <xsl:value-of select="."/> + </xsl:attribute> + </meta> + <xsl:text> </xsl:text> + </xsl:template> + + <xsl:template match="*"> + </xsl:template> + +</xsl:stylesheet> +''' + +word_tagmatch = 'w:p' +word_xmlns_decls = '''xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" +xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006" +xmlns:o="urn:schemas-microsoft-com:office:office" +xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" +xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" +xmlns:v="urn:schemas-microsoft-com:vml" +xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" +xmlns:w10="urn:schemas-microsoft-com:office:word" +xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" +xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" +''' +word_moretemplates = '' + + +xl_tagmatch = 'x:t' +xl_xmlns_decls='''xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" +xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main" + ''' +xl_moretemplates = '' + +pp_tagmatch = 'a:t' +pp_xmlns_decls = '''xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" +xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" +xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" +xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" +''' +# I want to suppress text output for all except a:t, don't know how to do it +# help ! At least get rid of these: +pp_moretemplates = '''<xsl:template match="p:attrName"> +</xsl:template> +''' + +content_stylesheet = '''<?xml version="1.0"?> +<xsl:stylesheet @XMLNS_DECLS@ > + + <xsl:output omit-xml-declaration="yes"/> + + <xsl:template match="/"> + <div> + <xsl:apply-templates/> + </div> +</xsl:template> + + <xsl:template match="@TAGMATCH@"> + <p> + <xsl:value-of select="."/> + </p> + </xsl:template> + +@MORETEMPLATES@ + +</xsl:stylesheet> +''' + +class OXExtractor: + def __init__(self, em): + self.em = em + self.currentindex = 0 + + # Replace values inside data style sheet, depending on type of doc + def computestylesheet(self, nm): + decls = globals()[nm + '_xmlns_decls'] + stylesheet = content_stylesheet.replace('@XMLNS_DECLS@', decls) + tagmatch = globals()[nm + '_tagmatch'] + stylesheet = stylesheet.replace('@TAGMATCH@', tagmatch) + moretmpl = globals()[nm + '_moretemplates'] + stylesheet = stylesheet.replace('@MORETEMPLATES@', moretmpl) + + return stylesheet + + def extractone(self, params): + if "filename:" not in params: + self.em.rclog("extractone: no mime or file name") + return (False, "", "", rclexecm.RclExecM.eofnow) + fn = params["filename:"] + + try: + f = open(fn, 'rb') + zip = ZipFile(f) + except Exception as err: + self.em.rclog("unzip failed: " + str(err)) + return (False, "", "", rclexecm.RclExecM.eofnow) + + docdata = b'<html><head>' + + try: + metadata = zip.read("docProps/core.xml") + if metadata: + res = rclxslt.apply_sheet_data(meta_stylesheet, metadata) + docdata += res + except Exception as err: + # To be checked. I'm under the impression that I get this when + # nothing matches? + self.em.rclog("no/bad metadata in %s: %s" % (fn, err)) + pass + + docdata += b'</head><body>' + + try: + content= zip.read('word/document.xml') + stl = self.computestylesheet('word') + docdata += rclxslt.apply_sheet_data(stl, content) + except: + pass + + try: + content = zip.read('xl/sharedStrings.xml') + stl = self.computestylesheet('xl') + docdata += rclxslt.apply_sheet_data(stl, content) + except: + pass + + try: + stl = self.computestylesheet('pp') + # Note that we'd need a numeric sort really (else we get slide1 + # slide11 slide2) + for fn in sorted(zip.namelist()): + if fnmatch.fnmatch(fn, 'ppt/slides/slide*.xml'): + content = zip.read(fn) + docdata += rclxslt.apply_sheet_data(stl, content) + except: + pass + + docdata += b'</body></html>' + + return (True, docdata, "", rclexecm.RclExecM.eofnext) + + + ###### File type handler api, used by rclexecm ----------> + def openfile(self, params): + self.currentindex = 0 + return True + + def getipath(self, params): + return self.extractone(params) + + def getnext(self, params): + if self.currentindex >= 1: + return (False, "", "", rclexecm.RclExecM.eofnow) + else: + ret= self.extractone(params) + self.currentindex += 1 + return ret + +if __name__ == '__main__': + proto = rclexecm.RclExecM() + extract = OXExtractor(proto) + rclexecm.main(proto, extract) diff --git a/src/filters/rclpdf b/src/filters/rclpdf deleted file mode 100755 index 3e11ea6d..00000000 --- a/src/filters/rclpdf +++ /dev/null @@ -1,351 +0,0 @@ -#!/bin/bash -#================================================================ -# Copyright (C) 2015 J.F. Dockes -# There used to be Estraier content in there, but I quite believe that is not -# the case any more. -# This file is licensed under the GPL v2 -#================================================================ -# Convert a pdf file to HTML. -# -# We use pdftotext from the xpdf/poppler-utils package. -# -# pdftotext sometimes outputs unescaped text inside HTML text sections. -# We try to correct. -# -# If pdftotext produces no text and tesseract is available, we try to -# perform OCR. As this can be very slow and the result not always -# good, we only do this if a file named $RECOLL_CONFDIR/ocrpdf exists -# -# We guess the OCR language in order of preference: -# - From the content of a ".ocrpdflang" file if it exists in the same -# directory as the PDF -# - From an RECOLL_TESSERACT_LANG environment variable -# - From the content of $RECOLL_CONFDIR/ocrpdf -# - Default to "eng" -# -# Uncomment the following if you get better results without. The -# pdftotext manual says that the option is no longer recommended The -# difference in output seems mostly the removal of soft-hyphens when -# -raw is not set -# optionraw=-raw - -# set variables -progname="rclpdf" -filetype=pdf - - -#RECFILTCOMMONCODE -############################################################################## -# !! Leave the previous line unmodified!! Code imported from the -# recfiltcommon file - -# Utility code common to all shell filters. This could be sourced at run -# time, but it's slightly more efficient to include the code in the -# filters at build time (with a sed script). - -# Describe error in a way that can be interpreted by our caller -senderror() -{ - echo RECFILTERROR $* - # Also alert on stderr just in case - echo ":2:$progname::: $*" 1>&2 - exit 1 -} - -iscmd() -{ - cmd=$1 - case $cmd in - */*) - if test -x $cmd -a ! -d $cmd ; then return 0; else return 1; fi ;; - *) - oldifs=$IFS; IFS=":"; set -- $PATH; IFS=$oldifs - for d in $*;do test -x $d/$cmd -a ! -d $d/$cmd && return 0;done - return 1 ;; - esac -} - -checkcmds() -{ - for cmd in $*;do - if iscmd $cmd - then - a=1 - else - senderror HELPERNOTFOUND $cmd - fi - done -} - -# show help message -if test $# -ne 1 -o "$1" = "--help" -then - echo "Convert a $filetype file to HTML text for Recoll indexing." - echo "Usage: $progname [infile]" - exit 1 -fi - -infile="$1" - -# check the input file existence (may be '-' for stdin) -if test "X$infile" != X- -a ! -f "$infile" -then - senderror INPUTNOSUCHFILE "$infile" -fi - -# protect access to our temp files and directories -umask 77 - -############################################################################## -# !! Leave the following line unmodified ! -#ENDRECFILTCOMMONCODE - -checkcmds pdftotext iconv awk - -ocrpossible=0 -if iscmd tesseract; then - if iscmd pdftoppm; then - ocrpossible=1 - fi -fi -confdir=${RECOLL_CONFDIR:-~/.recoll} -test ! -f "$confdir/ocrpdf" && ocrpossible=0 - -tmpdir= - -cleanup() -{ - # Note that we're using a constant part (rclpdftmp), that hopefully - # guarantees that we can't do big mistakes with the -rf here. - if test ! -z "$tmpdir"; then - rm -rf $tmpdir/rclpdftmp - rmdir $tmpdir - fi -} - -trap cleanup EXIT HUP QUIT INT TERM - -runpdftotext() -{ - # Test poppler version: at some point before 0.24, poppler began - # to properly escape text inside the header (but not the body). - XYZ=`pdftotext -v 2>&1 | awk '/pdftotext/{print $3}'` - MAJOR=`echo $XYZ | cut -d. -f 1` - MINOR=`echo $XYZ | cut -d. -f 2` - escapeheader=1 - escapebody=1 - if test "$MAJOR" -gt 0 ; then - escapeheader=0 - elif test "$MINOR" -ge 24; then - escapeheader=0; - fi - - # Run pdftotext and fix the result (add a charset tag and fix the - # html escaping). The escaping is a half-hearted job. We do try to - # fix some header fields, only for those which are single-line. - pdftotext $optionraw -htmlmeta -enc UTF-8 -eol unix -q "$infile" - | - iconv -f UTF-8 -t UTF-8 -c -s | - awk -v escapeheader=$escapeheader -v escapebody=$escapebody 'BEGIN'\ -' { - inbodypre = 0 - cont = "" -} -function escapehtml(s) -{ - gsub(/&/, "\\&", s) - gsub(/</, "\\<", s) - gsub(/>/, "\\>", s) - gsub(/"/, "\\"", s) - return s -} -{ - $0 = cont $0 - cont = "" - # Insert charset meta tag at end of header - if(inbodypre == 0 && $0 ~ /<\/head>/) { - match($0, /<\/head>/) - part1 = substr($0, 0, RSTART-1) - part2 = substr($0, RSTART, length($0)) - charsetmeta = "<meta http-equiv=\"Content-Type\" "\ - "content=\"text/html; charset=UTF-8\">" - $0 = part1 charsetmeta "\n" part2 - } - if(inbodypre == 0 && $0 ~ /<title>.*<\/title>/){ - match($0, /<title>.*<\/title>/) - part1 = substr($0, 0, RSTART-1) - mid = substr($0, RSTART, RLENGTH) - part2 = substr($0, RSTART + RLENGTH, length($0)) - gsub(/<title>/, "", mid) - gsub(/<\/title>/, "", mid) - if (escapeheader) { - mid = escapehtml(mid) - } - mid = "<title>" mid "" - $0 = part1 mid part2 - } - # This matches all single-line meta fields - if(inbodypre == 0 && $0 ~ /content=".*"\/>/){ - match($0, /content=".*"\/>/) - part1 = substr($0, 0, RSTART-1) - mid = substr($0, RSTART, RLENGTH) - part2 = substr($0, RSTART + RLENGTH, length($0)) - gsub(/content="/, "", mid) - gsub(/"\/>/, "", mid) - if (escapeheader) { - mid = escapehtml(mid) - } - mid = "content=\"" mid "\"/>" - $0 = part1 mid part2 - } - - # Recoll treats "Subject" as a "title" element (based on emails). The PDF - # "Subject" metadata field is more like an HTML "description" - if(inbodypre == 0 && $0 ~ /"){ - # Begin of body text. - inbodypre++ - print $0 - next - } else if ($0 ~ /<\/pre>/){ - inbodypre-- - print $0 - next - } else if ($0 ~ /[­-]$/) { - # Note : soft-hyphen is iso8859 0xad - # Break at last whitespace - match($0, "[ \t][^ \t]+$") - line = substr($0, 0, RSTART) - cont = substr($0, RSTART, RLENGTH-1) - $0 = line - # print "LINE [" $0 "] CONT[" cont "]" - } - if(inbodypre > 0 && escapebody){ - $0 = escapehtml($0) - } - print $0 -} -' -} - -# If we're not equipped for ocr, just run pdftotext to stdout -if test $ocrpossible -eq 0; then - runpdftotext - exit $? -fi - - -# tesseract is installed, prepare for running it. -# We need to check the pdftotext output, but we don't want to run -# it twice. Use a temporary file. -if test z"$RECOLL_TMPDIR" != z; then - ttdir=$RECOLL_TMPDIR -elif test z"$TMPDIR" != z ; then - ttdir=$TMPDIR -else - ttdir=/tmp -fi -tmpdir=$ttdir/rclpdf_tmp$$ -mkdir $tmpdir || senderror mkdir $tmpdir failed -mkdir $tmpdir/rclpdftmp || senderror mkdir $tmpdir/rclpdftmp failed - -# Run pdftotext into the temp file -pdftxtfile=$tmpdir/rclpdftmp/pdftxtfile -runpdftotext > $pdftxtfile - -# If text is big, or small but not only tags and empty lines, output -# it. Given the contents check which we perform, a file in which the -# only text content is metadata (pdf description field), will be run -# through OCR, which is not necessarily what we would want. It would -# be possible to detect the situation if this proved an issue. -txtsize=`ls -l $pdftxtfile | awk '{print $5}'` -txtempty=0 -# Use grep to check if there is regular text in there. Only do it on -# small outputs -if test $txtsize -lt 5000 ; then - realtext=`egrep -v '^[[:space:]]*$|^[[:space:]]*<.*>[[:space:]]*$' $pdftxtfile` - test -z "$realtext" && txtempty=1 -fi - -if test $txtempty -eq 0; then - # pdftotext produced actual output, use it. No OCR - cat $pdftxtfile - exit 0 -fi - -# PDF has no text content and tesseract is available. Give it a try -pdflangfile=`dirname "$infile"`/.ocrpdflang -if test -f "$pdflangfile"; then - tesseractlang=`cat "$pdflangfile"` -fi - -# Try to guess tesseract language. This should depend on the input -# file, but we have no general way to determine it. So use the -# environment and hope for the best. -if test -z "$tesseractlang"; then - tesseractlang=${RECOLL_TESSERACT_LANG} - if test -z "$tesseractlang"; then - # Half assed trial to guess from LANG then default to english - localelang=`echo $LANG | awk -F_ '{print $1}'` - # echo localelang "$localelang" >&2 - case "$localelang" in - en) tesseractlang=eng;; - de) tesseractlang=deu;; - fr) tesseractlang=fra;; - # Someone will have to add more tesseract language codes here. - esac - - test -z "$tessractlang" && tesseractlang=`cat "$confdir/ocrpdf"` - - test -z "$tesseractlang" && tesseractlang="eng" - fi -fi - -# echo tesseractlang "$tesseractlang" >&2 - -TESSERRORFILE="$tmpdir/rclpdftmp/tesserrorfile" -TMPFILE="$tmpdir/rclpdftmp/ocrXXXXXX" - -# split pdf-pages -ERR_MSG=$(pdftoppm -r 300 "$infile" "$TMPFILE" 2>&1) -if [ $? -ne 0 ] ; then - senderror "pdftoppm: $ERR_MSG" -fi - -for i in $TMPFILE* ; do - if [ -s "$i" ] ; then - - tesseract $i $i -l $tesseractlang > $TESSERRORFILE 2>&1 - TESSERR=$? - # ignore tesseract start message - LINECOUNT=$(wc -l < $TESSERRORFILE) - if [ $TESSERR -ne 0 -o $LINECOUNT -gt 1 ] ; then - echo "tesseract-error $TESSERR page $i in $infile" >&2 - # sort "compacts" leptonica-output - cat $TESSERRORFILE | sort -u >&2 - fi - # else - # debugging purpose - # SICFILE=$(mktemp -p $tmpdir -t sicXXXXXX) - # echo "no pdftoppm in $infile cp to $SICFILE" >&2 - # cp -a $infile $SICFILE - # fi - fi -done - -# don't output "empty" HTML-Files -CHARS=$(cat "$TMPFILE"*.txt 2>/dev/null | wc -m) -if [ "$CHARS" -gt 0 ] ; then - echo "
            " 
            -    cat "$TMPFILE"*.txt | \
            -        awk '{
            -  gsub(/&/, "\\&", $0)
            -  gsub(//, "\\>", $0)
            -  print $0
            -}
            -'
            -    echo "
            " -fi \ No newline at end of file diff --git a/src/filters/rclpdf.py b/src/filters/rclpdf.py new file mode 100755 index 00000000..9fe11246 --- /dev/null +++ b/src/filters/rclpdf.py @@ -0,0 +1,386 @@ +#!/usr/bin/env python +# Copyright (C) 2014 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 +# (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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# Recoll PDF extractor, with support for attachments +# +# pdftotext sometimes outputs unescaped text inside HTML text sections. +# We try to correct. +# +# If pdftotext produces no text and tesseract is available, we try to +# perform OCR. As this can be very slow and the result not always +# good, we only do this if a file named $RECOLL_CONFDIR/ocrpdf exists +# +# We guess the OCR language in order of preference: +# - From the content of a ".ocrpdflang" file if it exists in the same +# directory as the PDF +# - From an RECOLL_TESSERACT_LANG environment variable +# - From the content of $RECOLL_CONFDIR/ocrpdf +# - Default to "eng" + +from __future__ import print_function + +import os +import sys +import re +import rclexecm +import subprocess +import tempfile +import atexit +import signal +import rclconfig +import glob + +tmpdir = None + +def finalcleanup(): + if tmpdir: + vacuumdir(tmpdir) + os.rmdir(tmpdir) + +def signal_handler(signal, frame): + sys.exit(1) + +atexit.register(finalcleanup) + +# Not all signals necessary exist on all systems, use catch +try: signal.signal(signal.SIGHUP, signal_handler) +except: pass +try: signal.signal(signal.SIGINT, signal_handler) +except: pass +try: signal.signal(signal.SIGQUIT, signal_handler) +except: pass +try: signal.signal(signal.SIGTERM, signal_handler) +except: pass + +def vacuumdir(dir): + if dir: + for fn in os.listdir(dir): + path = os.path.join(dir, fn) + if os.path.isfile(path): + os.unlink(path) + return True + +class PDFExtractor: + def __init__(self, em): + self.currentindex = 0 + self.pdftotext = None + self.em = em + + self.confdir = rclconfig.RclConfig().getConfDir() + cf_doocr = rclconfig.RclConfig().getConfParam("pdfocr") + cf_attach = rclconfig.RclConfig().getConfParam("pdfattach") + + self.pdftotext = rclexecm.which("pdftotext") + if not self.pdftotext: + self.pdftotext = rclexecm.which("poppler/pdftotext") + + # Check if we need to escape portions of text where old + # versions of pdftotext output raw HTML special characters. + self.needescape = True + try: + version = subprocess.check_output([self.pdftotext, "-v"], + stderr=subprocess.STDOUT) + major,minor,rev = version.split()[2].split('.') + # Don't know exactly when this changed but it's fixed in + # jessie 0.26.5 + if int(major) > 0 or int(minor) >= 26: + self.needescape = False + except: + pass + + # See if we'll try to perform OCR. Need the commands and the + # either the presence of a file in the config dir (historical) + # or a set config variable. + self.ocrpossible = False + if cf_doocr or os.path.isfile(os.path.join(self.confdir, "ocrpdf")): + self.tesseract = rclexecm.which("tesseract") + if self.tesseract: + self.pdftoppm = rclexecm.which("pdftoppm") + if self.pdftoppm: + self.ocrpossible = True + self.maybemaketmpdir() + # self.em.rclog("OCRPOSSIBLE: %d" % self.ocrpossible) + + # Pdftk is optionally used to extract attachments. This takes + # a hit on perfmance even in the absence of any attachments, + # so it can be disabled in the configuration. + self.attextractdone = False + self.attachlist = [] + if cf_attach: + self.pdftk = rclexecm.which("pdftk") + else: + self.pdftk = None + if self.pdftk: + self.maybemaketmpdir() + + # Extract all attachments if any into temporary directory + def extractAttach(self): + if self.attextractdone: + return True + self.attextractdone = True + + global tmpdir + if not tmpdir or not self.pdftk: + # no big deal + return True + + try: + vacuumdir(tmpdir) + subprocess.check_call([self.pdftk, self.filename, "unpack_files", + "output", tmpdir]) + self.attachlist = sorted(os.listdir(tmpdir)) + return True + except Exception as e: + self.em.rclog("extractAttach: failed: %s" % e) + # Return true anyway, pdf attachments are no big deal + return True + + def extractone(self, ipath): + #self.em.rclog("extractone: [%s]" % ipath) + if not self.attextractdone: + if not self.extractAttach(): + return (False, "", "", rclexecm.RclExecM.eofnow) + path = os.path.join(tmpdir, ipath) + if os.path.isfile(path): + f = open(path) + docdata = f.read(); + f.close() + if self.currentindex == len(self.attachlist) - 1: + eof = rclexecm.RclExecM.eofnext + else: + eof = rclexecm.RclExecM.noteof + return (True, docdata, ipath, eof) + + + # Try to guess tesseract language. This should depend on the input + # file, but we have no general way to determine it. So use the + # environment and hope for the best. + def guesstesseractlang(self): + tesseractlang = "" + pdflangfile = os.path.join(os.path.dirname(self.filename), ".ocrpdflang") + if os.path.isfile(pdflangfile): + tesseractlang = open(pdflangfile, "r").read().strip() + if tesseractlang: + return tesseractlang + + tesseractlang = os.environ.get("RECOLL_TESSERACT_LANG", ""); + if tesseractlang: + return tesseractlang + + tesseractlang = \ + open(os.path.join(self.confdir, "ocrpdf"), "r").read().strip() + if tesseractlang: + return tesseractlang + + # Half-assed trial to guess from LANG then default to english + localelang = os.environ.get("LANG", "").split("_")[0] + if localelang == "en": + tesseractlang = "eng" + elif localelang == "de": + tesseractlang = "deu" + elif localelang == "fr": + tesseractlang = "fra" + if tesseractlang: + return tesseractlang + + if not tesseractlang: + tesseractlang = "eng" + return tesseractlang + + # PDF has no text content and tesseract is available. Give OCR a try + def ocrpdf(self): + + global tmpdir + if not tmpdir: + return "" + + tesseractlang = self.guesstesseractlang() + # self.em.rclog("tesseractlang %s" % tesseractlang) + + tesserrorfile = os.path.join(tmpdir, "tesserrorfile") + tmpfile = os.path.join(tmpdir, "ocrXXXXXX") + + # Split pdf pages + try: + vacuumdir(tmpdir) + subprocess.check_call([self.pdftoppm, "-r", "300", self.filename, + tmpfile]) + except Exception as e: + self.em.rclog("pdftoppm failed: %s" % e) + return "" + + files = glob.glob(tmpfile + "*") + for f in files: + try: + out = subprocess.check_output([self.tesseract, f, f, "-l", + tesseractlang], + stderr = subprocess.STDOUT) + except Exception as e: + self.em.rclog("tesseract failed: %s" % e) + + errlines = out.split('\n') + if len(errlines) > 2: + self.em.rclog("Tesseract error: %s" % out) + + # Concatenate the result files + files = glob.glob(tmpfile + "*" + ".txt") + data = "" + for f in files: + data += open(f, "r").read() + + if not data: + return "" + return '''
            ''' + \
            +        self.em.htmlescape(data) + \
            +        '''
            ''' + + # pdftotext (used to?) badly escape text inside the header + # fields. We do it here. This is not an html parser, and depends a + # lot on the actual format output by pdftotext. + # We also determine if the doc has actual content, for triggering OCR + def _fixhtml(self, input): + #print input + inheader = False + inbody = False + didcs = False + output = b'' + isempty = True + for line in input.split(b'\n'): + if re.search(b'', line): + inheader = False + if re.search(b'', line): + inbody = False + if inheader: + if not didcs: + output += b'\n' + didcs = True + if self.needescape: + m = re.search(b'''(.*)(.*)(<\/title>.*)''', line) + if not m: + m = re.search(b'''(.*content=")(.*)(".*/>.*)''', line) + if m: + line = m.group(1) + self.em.htmlescape(m.group(2)) + \ + m.group(3) + + # Recoll treats "Subject" as a "title" element + # (based on emails). The PDF "Subject" metadata + # field is more like an HTML "description" + line = re.sub(b'name="Subject"', b'name="Description"', line, 1) + + elif inbody: + s = line[0:1] + if s != "\x0c" and s != "<": + isempty = False + # We used to remove end-of-line hyphenation (and join + # lines), but but it's not clear that we should do + # this as pdftotext without the -layout option does it ? + line = self.em.htmlescape(line) + + if re.search(b'<head>', line): + inheader = True + if re.search(b'<pre>', line): + inbody = True + + output += line + b'\n' + + return output, isempty + + def _selfdoc(self): + self.em.setmimetype('text/html') + + if self.attextractdone and len(self.attachlist) == 0: + eof = rclexecm.RclExecM.eofnext + else: + eof = rclexecm.RclExecM.noteof + + data = subprocess.check_output([self.pdftotext, "-htmlmeta", "-enc", + "UTF-8", "-eol", "unix", "-q", + self.filename, "-"]) + + data, isempty = self._fixhtml(data) + #self.em.rclog("ISEMPTY: %d : data: \n%s" % (isempty, data)) + if isempty and self.ocrpossible: + data = self.ocrpdf() + return (True, data, "", eof) + + def maybemaketmpdir(self): + global tmpdir + if tmpdir: + if not vacuumdir(tmpdir): + self.em.rclog("openfile: vacuumdir %s failed" % tmpdir) + return False + else: + tmpdir = tempfile.mkdtemp(prefix='rclmpdf') + + ###### File type handler api, used by rclexecm ----------> + def openfile(self, params): + self.filename = params["filename:"] + #self.em.rclog("openfile: [%s]" % self.filename) + self.currentindex = -1 + self.attextractdone = False + + if not self.pdftotext: + print("RECFILTERROR HELPERNOTFOUND pdftotext") + sys.exit(1); + + if self.pdftk: + preview = os.environ.get("RECOLL_FILTER_FORPREVIEW", "no") + if preview != "yes": + # When indexing, extract attachments at once. This + # will be needed anyway and it allows generating an + # eofnext error instead of waiting for actual eof, + # which avoids a bug in recollindex up to 1.20 + self.extractAttach() + else: + self.attextractdone = True + return True + + def getipath(self, params): + ipath = params["ipath:"] + ok, data, ipath, eof = self.extractone(ipath) + return (ok, data, ipath, eof) + + def getnext(self, params): + # self.em.rclog("getnext: current %d" % self.currentindex) + if self.currentindex == -1: + self.currentindex = 0 + return self._selfdoc() + else: + self.em.setmimetype('') + + if not self.attextractdone: + if not self.extractAttach(): + return (False, "", "", rclexecm.RclExecM.eofnow) + + if self.currentindex >= len(self.attachlist): + return (False, "", "", rclexecm.RclExecM.eofnow) + try: + ok, data, ipath, eof = \ + self.extractone(self.attachlist[self.currentindex]) + self.currentindex += 1 + + #self.em.rclog("getnext: returning ok for [%s]" % ipath) + return (ok, data, ipath, eof) + except: + return (False, "", "", rclexecm.RclExecM.eofnow) + + +# Main program: create protocol handler and extractor and run them +proto = rclexecm.RclExecM() +extract = PDFExtractor(proto) +rclexecm.main(proto, extract) diff --git a/src/filters/rclppt b/src/filters/rclppt deleted file mode 100755 index 467acab6..00000000 --- a/src/filters/rclppt +++ /dev/null @@ -1,110 +0,0 @@ -#!/bin/sh -# @(#$Id: rclppt,v 1.4 2008-10-08 08:27:34 dockes Exp $ (C) 2004 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 -# (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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -#================================================================ -# Handle powerpoint files for recoll. -# Use unoconv, this is very slow, but catppt just can't handle the majority -# of semi-modern ppt files - -#================================================================ - -# set variables -LANG=C ; export LANG -LC_ALL=C ; export LC_ALL -progname="rclppt" -filetype=powerpoint - -#RECFILTCOMMONCODE -############################################################################## -# !! Leave the previous line unmodified!! Code imported from the -# recfiltcommon file - -# Utility code common to all shell filters. This could be sourced at run -# time, but it's slightly more efficient to include the code in the -# filters at build time (with a sed script). - -# Describe error in a way that can be interpreted by our caller -senderror() -{ - echo RECFILTERROR $* - # Also alert on stderr just in case - echo ":2:$progname::: $*" 1>&2 - exit 1 -} - -iscmd() -{ - cmd=$1 - case $cmd in - */*) - if test -x $cmd -a ! -d $cmd ; then return 0; else return 1; fi ;; - *) - oldifs=$IFS; IFS=":"; set -- $PATH; IFS=$oldifs - for d in $*;do test -x $d/$cmd -a ! -d $d/$cmd && return 0;done - return 1 ;; - esac -} - -checkcmds() -{ - for cmd in $*;do - if iscmd $cmd - then - a=1 - else - senderror HELPERNOTFOUND $cmd - fi - done -} - -# show help message -if test $# -ne 1 -o "$1" = "--help" -then - echo "Convert a $filetype file to HTML text for Recoll indexing." - echo "Usage: $progname [infile]" - exit 1 -fi - -infile="$1" - -# check the input file existence (may be '-' for stdin) -if test "X$infile" != X- -a ! -f "$infile" -then - senderror INPUTNOSUCHFILE "$infile" -fi - -# protect access to our temp files and directories -umask 77 - -############################################################################## -# !! Leave the following line unmodified ! -#ENDRECFILTCOMMONCODE - -filtersdir=`dirname $0` -checkcmds $filtersdir/ppt-dump.py - -mso="$filtersdir/ppt-dump.py --no-struct-output --dump-text" - -cat <<EOF -<html><head> -<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> -</head><body><pre> -EOF - -$mso "$infile"| sed -e 's/</</g' -e 's/&/&/g' - -echo '</pre></body></html>' diff --git a/src/filters/rclppt.py b/src/filters/rclppt.py new file mode 100755 index 00000000..d86cc897 --- /dev/null +++ b/src/filters/rclppt.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python2 + +# Recoll PPT text extractor +# Mso-dumper is not compatible with Python3. We use sys.executable to +# start the actual extractor, so we need to use python2 too. + +from __future__ import print_function + +import rclexecm +import rclexec1 +import re +import sys +import os + +class PPTProcessData: + def __init__(self, em): + self.em = em + self.out = "" + self.gotdata = 0 + + def takeLine(self, line): + if not self.gotdata: + self.out += '''<html><head>''' + \ + '''<meta http-equiv="Content-Type" ''' + \ + '''content="text/html;charset=UTF-8">''' + \ + '''</head><body><pre>''' + self.gotdata = True + self.out += self.em.htmlescape(line) + "<br>\n" + + def wrapData(self): + return self.out + '''</pre></body></html>''' + +class PPTFilter: + def __init__(self, em): + self.em = em + self.ntry = 0 + + def reset(self): + self.ntry = 0 + pass + + def getCmd(self, fn): + if self.ntry: + return ([], None) + self.ntry = 1 + cmd = rclexecm.which("ppt-dump.py") + if cmd: + # ppt-dump.py often exits 1 with valid data. Ignore exit value + return ([sys.executable, cmd, "--no-struct-output", "--dump-text"], + PPTProcessData(self.em), rclexec1.Executor.opt_ignxval) + else: + return ([], None) + +if __name__ == '__main__': + if not rclexecm.which("ppt-dump.py"): + print("RECFILTERROR HELPERNOTFOUND ppt-dump.py") + sys.exit(1) + proto = rclexecm.RclExecM() + filter = PPTFilter(proto) + extract = rclexec1.Executor(proto, filter) + rclexecm.main(proto, extract) diff --git a/src/filters/rclpython b/src/filters/rclpython index 990d03b5..362d8a4e 100755 --- a/src/filters/rclpython +++ b/src/filters/rclpython @@ -22,6 +22,8 @@ # - parse script encoding and allow output in any encoding by using unicode # as intermediate +from __future__ import print_function + __version__ = '0.3' __date__ = '2005-07-04' __license__ = 'GPL' @@ -29,9 +31,26 @@ __author__ = 'J # Imports -import cgi, string, sys, cStringIO +import cgi, string, sys +PY2 = sys.version < '3' +if PY2: + import cStringIO +else: + import io import keyword, token, tokenize +if PY2: + def makebytes(data): + if isinstance(data, unicode): + return data.encode("UTF-8") + else: + return data +else: + def makebytes(data): + if isinstance(data, bytes): + return data + else: + return data.encode("UTF-8") ############################################################################# ### Python Source Parser (does Hilighting) @@ -57,7 +76,7 @@ _HTML_HEADER = """\ <html> <head> <title>%%(title)s - + @@ -114,7 +133,7 @@ class Parser: def __init__(self, raw, out=sys.stdout): """ Store the source text. """ - self.raw = string.strip(string.expandtabs(raw)) + self.raw = raw.expandtabs().strip() self.out = out def format(self): @@ -124,35 +143,44 @@ class Parser: self.lines = [0, 0] pos = 0 while 1: - pos = string.find(self.raw, '\n', pos) + 1 + pos = self.raw.find(b'\n', pos) + 1 if not pos: break self.lines.append(pos) self.lines.append(len(self.raw)) # parse the source and write it self.pos = 0 - text = cStringIO.StringIO(self.raw) - self.out.write(self.stylesheet) - self.out.write('
            \n')
            +        if PY2:
            +            text = cStringIO.StringIO(self.raw)
            +        else:
            +            text = io.BytesIO(self.raw)
            +        self.out.write(makebytes(self.stylesheet))
            +        self.out.write(b'
            \n')
                     try:
            -            tokenize.tokenize(text.readline, self)
            -        except tokenize.TokenError, ex:
            +            if PY2:
            +                tokenize.tokenize(text.readline, self)
            +            else:
            +                for a,b,c,d,e in tokenize.tokenize(text.readline):
            +                    self(a,b,c,d,e)
            +        except tokenize.TokenError as ex:
                         msg = ex[0]
                         line = ex[1][0]
                         self.out.write("

            ERROR: %s

            %s\n" % ( msg, self.raw[self.lines[line]:])) - except IndentationError, ex: + except IndentationError as ex: msg = ex[0] self.out.write("

            ERROR: %s

            \n" % (msg)) - self.out.write('\n
            ') + self.out.write(b'\n
            ') - def __call__(self, toktype, toktext, (srow,scol), (erow,ecol), line): + def __call__(self, toktype, toktext, startpos, endpos, line): """ Token handler. """ if 0: - print "type", toktype, token.tok_name[toktype], "text", toktext, - print "start", srow,scol, "end", erow,ecol, "
            " - + print("type %s %s text %s start %s %s end %s %s
            \n" % \ + (toktype, token.tok_name[toktype], toktext, \ + srow, scol,erow,ecol)) + srow, scol = startpos + erow, ecol = endpos # calculate new positions oldpos = self.pos newpos = self.lines[srow] + scol @@ -160,7 +188,7 @@ class Parser: # handle newlines if toktype in [token.NEWLINE, tokenize.NL]: - self.out.write('\n') + self.out.write(b'\n') return # send the original whitespace, if needed @@ -180,9 +208,9 @@ class Parser: css_class = _css_classes.get(toktype, 'text') # send text - self.out.write('' % (css_class,)) - self.out.write(cgi.escape(toktext)) - self.out.write('') + self.out.write(makebytes('' % (css_class,))) + self.out.write(makebytes(cgi.escape(toktext))) + self.out.write(b'') def colorize_file(file=None, outstream=sys.stdout, standalone=True): @@ -205,7 +233,7 @@ def colorize_file(file=None, outstream=sys.stdout, standalone=True): filename = 'STREAM' elif file is not None: try: - sourcefile = open(file) + sourcefile = open(file, 'rb') filename = basename(file) except IOError: raise SystemExit("File %s unknown." % file) @@ -215,22 +243,26 @@ def colorize_file(file=None, outstream=sys.stdout, standalone=True): source = sourcefile.read() if standalone: - outstream.write(_HTML_HEADER % {'title': filename}) + outstream.write(makebytes(_HTML_HEADER % {'title': filename})) Parser(source, out=outstream).format() if standalone: - outstream.write(_HTML_FOOTER) + outstream.write(makebytes(_HTML_FOOTER)) if file: sourcefile.close() if __name__ == "__main__": import os + if PY2: + out = sys.stdout + else: + out = sys.stdout.buffer if os.environ.get('PATH_TRANSLATED'): filepath = os.environ.get('PATH_TRANSLATED') - print 'Content-Type: text/html; charset="iso-8859-1"\n' - colorize_file(filepath) + print('Content-Type: text/html; charset="iso-8859-1"\n') + colorize_file(filepath, out) elif len(sys.argv) > 1: filepath = sys.argv[1] - colorize_file(filepath) + colorize_file(filepath, out) else: colorize_file() diff --git a/src/filters/rclrar b/src/filters/rclrar index b661f510..f11c2a39 100755 --- a/src/filters/rclrar +++ b/src/filters/rclrar @@ -18,12 +18,14 @@ # Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +from __future__ import print_function + import sys import rclexecm try: from rarfile import RarFile except: - print "RECFILTERROR HELPERNOTFOUND python:rarfile" + print("RECFILTERROR HELPERNOTFOUND python:rarfile") sys.exit(1); # Requires RarFile python module. Try "sudo pip install rarfile" @@ -43,7 +45,7 @@ class RarExtractor: try: rarinfo = self.rar.getinfo(ipath) isdir = rarinfo.isdir() - except Exception, err: + except Exception as err: self.em.rclog("extractone: getinfo failed: [%s]" % err) return (True, docdata, ipath, false) @@ -56,7 +58,7 @@ class RarExtractor: else: docdata = self.rar.read(ipath) ok = True - except Exception, err: + except Exception as err: self.em.rclog("extractone: failed: [%s]" % err) ok = False else: @@ -67,9 +69,7 @@ class RarExtractor: iseof = rclexecm.RclExecM.noteof if self.currentindex >= len(self.rar.namelist()) -1: iseof = rclexecm.RclExecM.eofnext - if isinstance(ipath, unicode): - ipath = ipath.encode("utf-8") - return (ok, docdata, ipath, iseof) + return (ok, docdata, rclexecm.makebytes(ipath), iseof) ###### File type handler api, used by rclexecm ----------> def openfile(self, params): @@ -89,7 +89,7 @@ class RarExtractor: try: ipath = ipath.decode("utf-8") return self.extractone(ipath) - except Exception, err: + except Exception as err: return (ok, data, ipath, eof) def getnext(self, params): diff --git a/src/filters/rclrtf b/src/filters/rclrtf deleted file mode 100755 index abfe6e2f..00000000 --- a/src/filters/rclrtf +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/sh -# @(#$Id: rclrtf,v 1.5 2007-06-08 13:51:09 dockes Exp $ (C) 2004 J.F.Dockes -# Some inspiration from estraier -#================================================================ -# convert rtf to html, by executing the unrtf program: -# http://www.gnu.org/software/unrtf/unrtf.html -#================================================================ - -# set variables -LANG=C ; export LANG -LC_ALL=C ; export LC_ALL -progname="rclrtl" -filetype=rtf - -#RECFILTCOMMONCODE -############################################################################## -# !! Leave the previous line unmodified!! Code imported from the -# recfiltcommon file - -# Utility code common to all shell filters. This could be sourced at run -# time, but it's slightly more efficient to include the code in the -# filters at build time (with a sed script). - -# Describe error in a way that can be interpreted by our caller -senderror() -{ - echo RECFILTERROR $* - # Also alert on stderr just in case - echo ":2:$progname::: $*" 1>&2 - exit 1 -} - -iscmd() -{ - cmd=$1 - case $cmd in - */*) - if test -x $cmd -a ! -d $cmd ; then return 0; else return 1; fi ;; - *) - oldifs=$IFS; IFS=":"; set -- $PATH; IFS=$oldifs - for d in $*;do test -x $d/$cmd -a ! -d $d/$cmd && return 0;done - return 1 ;; - esac -} - -checkcmds() -{ - for cmd in $*;do - if iscmd $cmd - then - a=1 - else - senderror HELPERNOTFOUND $cmd - fi - done -} - -# show help message -if test $# -ne 1 -o "$1" = "--help" -then - echo "Convert a $filetype file to HTML text for Recoll indexing." - echo "Usage: $progname [infile]" - exit 1 -fi - -infile="$1" - -# check the input file existence (may be '-' for stdin) -if test "X$infile" != X- -a ! -f "$infile" -then - senderror INPUTNOSUCHFILE "$infile" -fi - -# protect access to our temp files and directories -umask 77 - -############################################################################## -# !! Leave the following line unmodified ! -#ENDRECFILTCOMMONCODE - -checkcmds awk unrtf - -# output the result -# The strange 'BEGIN' setup is to prevent 'file' from thinking this file -# is an awk program -# The thing about the charset is that unrtf outputs a garbled one. -unrtf --nopict --html "$infile" 2> /dev/null | -awk 'BEGIN'\ -' { - gothead = 0 -} -/<\/head>/{ - if (gothead == 0) { - printf("\n") - gothead = 1 - } -} -/''') + self.patcharset = re.compile(b'''^' + b'\n' + self.out += line + b'\n' + self.gothead = 1 + elif not self.patcharset.search(line): + self.out += line + b'\n' + else: + self.out += line + b'\n' + + def wrapData(self): + return self.out + +class RTFFilter: + def __init__(self, em): + self.em = em + self.ntry = 0 + + def reset(self): + self.ntry = 0 + + def getCmd(self, fn): + if self.ntry: + return ([], None) + self.ntry = 1 + cmd = rclexecm.which("unrtf") + if cmd: + return ([cmd, "--nopict", "--html"], RTFProcessData(self.em)) + else: + return ([], None) + +if __name__ == '__main__': + if not rclexecm.which("unrtf"): + print("RECFILTERROR HELPERNOTFOUND unrtf") + sys.exit(1) + proto = rclexecm.RclExecM() + filter = RTFFilter(proto) + extract = rclexec1.Executor(proto, filter) + rclexecm.main(proto, extract) diff --git a/src/filters/rclsoff b/src/filters/rclsoff deleted file mode 100755 index c7e2046f..00000000 --- a/src/filters/rclsoff +++ /dev/null @@ -1,225 +0,0 @@ -#!/bin/sh -# @(#$Id: rclsoff,v 1.12 2008-10-08 08:27:34 dockes Exp $ (C) 2004 J.F.Dockes -# Parts taken from Estraier: -#================================================================ -# Estraier: a personal full-text search system -# Copyright (C) 2003-2004 Mikio Hirabayashi -#================================================================ -#================================================================ -# Extract text from an openoffice/soffice file -# -#================================================================ - -# set variables -LANG=C ; export LANG -LC_ALL=C ; export LC_ALL -progname="rclsoff" -filetype=openoffice - - -#RECFILTCOMMONCODE -############################################################################## -# !! Leave the previous line unmodified!! Code imported from the -# recfiltcommon file - -# Utility code common to all shell filters. This could be sourced at run -# time, but it's slightly more efficient to include the code in the -# filters at build time (with a sed script). - -# Describe error in a way that can be interpreted by our caller -senderror() -{ - echo RECFILTERROR $* - # Also alert on stderr just in case - echo ":2:$progname::: $*" 1>&2 - exit 1 -} - -iscmd() -{ - cmd=$1 - case $cmd in - */*) - if test -x $cmd -a ! -d $cmd ; then return 0; else return 1; fi ;; - *) - oldifs=$IFS; IFS=":"; set -- $PATH; IFS=$oldifs - for d in $*;do test -x $d/$cmd -a ! -d $d/$cmd && return 0;done - return 1 ;; - esac -} - -checkcmds() -{ - for cmd in $*;do - if iscmd $cmd - then - a=1 - else - senderror HELPERNOTFOUND $cmd - fi - done -} - -# show help message -if test $# -ne 1 -o "$1" = "--help" -then - echo "Convert a $filetype file to HTML text for Recoll indexing." - echo "Usage: $progname [infile]" - exit 1 -fi - -infile="$1" - -# check the input file existence (may be '-' for stdin) -if test "X$infile" != X- -a ! -f "$infile" -then - senderror INPUTNOSUCHFILE "$infile" -fi - -# protect access to our temp files and directories -umask 77 - -############################################################################## -# !! Leave the following line unmodified ! -#ENDRECFILTCOMMONCODE - -checkcmds xsltproc unzip - -# We need a temporary directory -if test z"$RECOLL_TMPDIR" != z; then - ttdir=$RECOLL_TMPDIR -elif test z"$TMPDIR" != z ; then - ttdir=$TMPDIR -else - ttdir=/tmp -fi -tmpdir=$ttdir/rclsoff_tmp$$ -mkdir $tmpdir || exit 1 -mkdir $tmpdir/rclsofftmp || exit 1 - -cleanup() -{ - # Note that we're using a constant part (rclsofftmp), that hopefully - # guarantees that we can't do big mistakes here. - rm -rf $tmpdir/rclsofftmp - rmdir $tmpdir -} - -trap cleanup EXIT HUP QUIT INT TERM - -# Unzip the input file and change to the unzipped directory -unzip -q -d $tmpdir/rclsofftmp "$infile" -cd $tmpdir/rclsofftmp - -echo ' -' - -xsltproc --novalid --nonet - meta.xml < - - - - - - - - - - - - - - <xsl:value-of select="."/> - - - - - - abstract - - - - - - - - - - keywords - - - - - - - - - - author - - - - - - - - - - keywords - - - - - - - - -EOF - -echo '' - -xsltproc --novalid --nonet - content.xml < - - - - - -

            - -
            - - -

            - -
            - - - - - - -
            -
            - - - - - -
            -EOF -echo '' -cd / -exit 0 diff --git a/src/filters/rclsoff.py b/src/filters/rclsoff.py new file mode 100755 index 00000000..67e08014 --- /dev/null +++ b/src/filters/rclsoff.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python +# Copyright (C) 2014 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 +# (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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +###################################### + +from __future__ import print_function + +import sys +import rclexecm +import rclxslt +from zipfile import ZipFile + +stylesheet_meta = ''' + + + + + + + + + + + + + + <xsl:value-of select="."/> + + + + + + abstract + + + + + + + + + + keywords + + + + + + + + + + author + + + + + + + + + + keywords + + + + + + + + +''' + +stylesheet_content = ''' + + + + + +

            + +
            + + +

            + +
            + + + + + + +
            +
            + + + + + +
            +''' + +class OOExtractor: + def __init__(self, em): + self.em = em + self.currentindex = 0 + + def extractone(self, params): + if "filename:" not in params: + self.em.rclog("extractone: no mime or file name") + return (False, "", "", rclexecm.RclExecM.eofnow) + fn = params["filename:"] + + try: + zip = ZipFile(fn.decode('UTF-8')) + except Exception as err: + self.em.rclog("unzip failed: %s" % err) + return (False, "", "", rclexecm.RclExecM.eofnow) + + docdata = b'' + + try: + metadata = zip.read("meta.xml") + if metadata: + res = rclxslt.apply_sheet_data(stylesheet_meta, metadata) + docdata += res + except: + # To be checked. I'm under the impression that I get this when + # nothing matches? + #self.em.rclog("no/bad metadata in %s" % fn) + pass + + try: + content = zip.read("content.xml") + if content: + res = rclxslt.apply_sheet_data(stylesheet_content, content) + docdata += res + docdata += b'' + except Exception as err: + self.em.rclog("bad data in %s: %s" % (fn, err)) + return (False, "", "", rclexecm.RclExecM.eofnow) + + return (True, docdata, "", rclexecm.RclExecM.eofnext) + + ###### File type handler api, used by rclexecm ----------> + def openfile(self, params): + self.currentindex = 0 + return True + + def getipath(self, params): + return self.extractone(params) + + def getnext(self, params): + if self.currentindex >= 1: + return (False, "", "", rclexecm.RclExecM.eofnow) + else: + ret= self.extractone(params) + self.currentindex += 1 + return ret + +if __name__ == '__main__': + proto = rclexecm.RclExecM() + extract = OOExtractor(proto) + rclexecm.main(proto, extract) diff --git a/src/filters/rclsvg b/src/filters/rclsvg deleted file mode 100755 index be5d75b5..00000000 --- a/src/filters/rclsvg +++ /dev/null @@ -1,161 +0,0 @@ -#!/bin/sh - -#================================================================ -# Extract text from a Scalable Vector Graphics file -#================================================================ - -# set variables -LANG=C ; export LANG -LC_ALL=C ; export LC_ALL -progname="rclsvg" -filetype=svg - - -#RECFILTCOMMONCODE -############################################################################## -# !! Leave the previous line unmodified!! Code imported from the -# recfiltcommon file - -# Utility code common to all shell filters. This could be sourced at run -# time, but it's slightly more efficient to include the code in the -# filters at build time (with a sed script). - -# Describe error in a way that can be interpreted by our caller -senderror() -{ - echo RECFILTERROR $* - # Also alert on stderr just in case - echo ":2:$progname::: $*" 1>&2 - exit 1 -} - -iscmd() -{ - cmd=$1 - case $cmd in - */*) - if test -x $cmd -a ! -d $cmd ; then return 0; else return 1; fi ;; - *) - oldifs=$IFS; IFS=":"; set -- $PATH; IFS=$oldifs - for d in $*;do test -x $d/$cmd -a ! -d $d/$cmd && return 0;done - return 1 ;; - esac -} - -checkcmds() -{ - for cmd in $*;do - if iscmd $cmd - then - a=1 - else - senderror HELPERNOTFOUND $cmd - fi - done -} - -# show help message -if test $# -ne 1 -o "$1" = "--help" -then - echo "Convert a $filetype file to HTML text for Recoll indexing." - echo "Usage: $progname [infile]" - exit 1 -fi - -infile="$1" - -# check the input file existence (may be '-' for stdin) -if test "X$infile" != X- -a ! -f "$infile" -then - senderror INPUTNOSUCHFILE "$infile" -fi - -# protect access to our temp files and directories -umask 77 - -############################################################################## -# !! Leave the following line unmodified ! -#ENDRECFILTCOMMONCODE - -checkcmds xsltproc - -xsltproc --novalid --nonet - "$infile" < - - - - - - - - - - - - - - - - - - - - - - keywords - - - - - - - - - - author - - - - - - - - - - keywords - - - - - - - - - - description - - - - - - - - - <xsl:value-of select="."/> - - - - -

            - -
            - -
            -EOF - -exit 0 diff --git a/src/filters/rclsvg.py b/src/filters/rclsvg.py new file mode 100755 index 00000000..ef99664b --- /dev/null +++ b/src/filters/rclsvg.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python +# Copyright (C) 2014 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 +# (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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +###################################### + +from __future__ import print_function + +import sys +import rclexecm +import rclxslt + +stylesheet_all = ''' + + + + + + + + + + + + + + + + + + + + + + keywords + + + + + + + + + + author + + + + + + + + + + keywords + + + + + + + + + + description + + + + + + + + + <xsl:value-of select="."/> + + + + +

            + +
            + +
            +''' + +class SVGExtractor: + def __init__(self, em): + self.em = em + self.currentindex = 0 + + def extractone(self, params): + if "filename:" not in params: + self.em.rclog("extractone: no mime or file name") + return (False, "", "", rclexecm.RclExecM.eofnow) + fn = params["filename:"] + + try: + data = open(fn, 'rb').read() + docdata = rclxslt.apply_sheet_data(stylesheet_all, data) + except Exception as err: + self.em.rclog("%s: bad data: " % (fn, err)) + return (False, "", "", rclexecm.RclExecM.eofnow) + + return (True, docdata, "", rclexecm.RclExecM.eofnext) + + ###### File type handler api, used by rclexecm ----------> + def openfile(self, params): + self.currentindex = 0 + return True + + def getipath(self, params): + return self.extractone(params) + + def getnext(self, params): + if self.currentindex >= 1: + return (False, "", "", rclexecm.RclExecM.eofnow) + else: + ret= self.extractone(params) + self.currentindex += 1 + return ret + +if __name__ == '__main__': + proto = rclexecm.RclExecM() + extract = SVGExtractor(proto) + rclexecm.main(proto, extract) diff --git a/src/filters/rcltar b/src/filters/rcltar index 3d6508e0..74aaecbd 100755 --- a/src/filters/rcltar +++ b/src/filters/rcltar @@ -6,12 +6,14 @@ # It works not only for tar-files, but automatically for gzipped and # bzipped tar-files at well. +from __future__ import print_function + import rclexecm try: import tarfile except: - print "RECFILTERROR HELPERNOTFOUND python:tarfile" + print("RECFILTERROR HELPERNOTFOUND python:tarfile") sys.exit(1); class TarExtractor: @@ -21,32 +23,32 @@ class TarExtractor: self.namen = [] def extractone(self, ipath): - docdata = "" + docdata = b'' try: info = self.tar.getmember(ipath) if info.size > self.em.maxmembersize: # skip - docdata = "" + docdata = b'' self.em.rclog("extractone: entry %s size %d too big" % (ipath, info.size)) - docdata = "" # raise TarError("Member too big") + docdata = b'' # raise TarError("Member too big") else: docdata = self.tar.extractfile(ipath).read() ok = True - except Exception, err: + except Exception as err: ok = False iseof = rclexecm.RclExecM.noteof if self.currentindex >= len(self.namen) -1: iseof = rclexecm.RclExecM.eofnext - if isinstance(ipath, unicode): - ipath = ipath.encode("utf-8") - return (ok, docdata, ipath, iseof) + return (ok, docdata, rclexecm.makebytes(ipath), iseof) def openfile(self, params): self.currentindex = -1 try: - self.tar = tarfile.open(name=params["filename:"],mode='r') - self.namen = [ y.name for y in filter(lambda z:z.isfile(),self.tar.getmembers())] + self.tar = tarfile.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 [z for z in self.tar.getmembers() if z.isfile()]] + return True except: return False @@ -59,7 +61,7 @@ class TarExtractor: try: ipath = ipath.decode("utf-8") return self.extractone(ipath) - except Exception, err: + except Exception as err: return (ok, data, ipath, eof) def getnext(self, params): diff --git a/src/filters/rcltext b/src/filters/rcltext deleted file mode 100755 index 05a27d7e..00000000 --- a/src/filters/rcltext +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/sh -# @(#$Id: rcltext,v 1.1 2008-09-12 11:30:03 dockes Exp $ (C) 2004 J.F.Dockes -# Parts taken from Estraier: -#================================================================ -# Estraier: a personal full-text search system -# Copyright (C) 2003-2004 Mikio Hirabayashi -#================================================================ -#================================================================ -# Wrap generic text (ie: program text) in html -# Assumes ascii or iso-8859-1 -#================================================================ - -# set variables -LANG=C ; export LANG -LC_ALL=C ; export LC_ALL -progname="rcltext" -filetype=text - - -#RECFILTCOMMONCODE -############################################################################## -# !! Leave the previous line unmodified!! Code imported from the -# recfiltcommon file - -# Utility code common to all shell filters. This could be sourced at run -# time, but it's slightly more efficient to include the code in the -# filters at build time (with a sed script). - -# Describe error in a way that can be interpreted by our caller -senderror() -{ - echo RECFILTERROR $* - # Also alert on stderr just in case - echo ":2:$progname::: $*" 1>&2 - exit 1 -} - -iscmd() -{ - cmd=$1 - case $cmd in - */*) - if test -x $cmd -a ! -d $cmd ; then return 0; else return 1; fi ;; - *) - oldifs=$IFS; IFS=":"; set -- $PATH; IFS=$oldifs - for d in $*;do test -x $d/$cmd -a ! -d $d/$cmd && return 0;done - return 1 ;; - esac -} - -checkcmds() -{ - for cmd in $*;do - if iscmd $cmd - then - a=1 - else - senderror HELPERNOTFOUND $cmd - fi - done -} - -# show help message -if test $# -ne 1 -o "$1" = "--help" -then - echo "Convert a $filetype file to HTML text for Recoll indexing." - echo "Usage: $progname [infile]" - exit 1 -fi - -infile="$1" - -# check the input file existence (may be '-' for stdin) -if test "X$infile" != X- -a ! -f "$infile" -then - senderror INPUTNOSUCHFILE "$infile" -fi - -# protect access to our temp files and directories -umask 77 - -############################################################################## -# !! Leave the following line unmodified ! -#ENDRECFILTCOMMONCODE - -checkcmds sed -echo '
            '
            -
            -sed -e 's/\&/\&/g' -e 's/'
            diff --git a/src/filters/rcltext.py b/src/filters/rcltext.py
            new file mode 100755
            index 00000000..f449dfe6
            --- /dev/null
            +++ b/src/filters/rcltext.py
            @@ -0,0 +1,54 @@
            +#!/usr/bin/env python
            +
            +# Wrapping a text file. Recoll does it internally in most cases, but
            +# this is for use by another filter.
            +
            +from __future__ import print_function
            +
            +import rclexecm
            +import sys
            +
            +class TxtDump:
            +    def __init__(self, em):
            +        self.em = em
            +
            +    def extractone(self, params):
            +        #self.em.rclog("extractone %s %s" % (params["filename:"], \
            +        #params["mimetype:"]))
            +        if not "filename:" in params:
            +            self.em.rclog("extractone: no file name")
            +            return (False, "", "", rclexecm.RclExecM.eofnow)
            +
            +        fn = params["filename:"]
            +        # No charset, so recoll will have to use its config to guess it
            +        txt = b'
            '
            +        try:
            +            f = open(fn, "rb")
            +            txt += self.em.htmlescape(f.read())
            +        except Exception as err:
            +            self.em.rclog("TxtDump: %s : %s" % (fn, err))
            +            return (False, "", "", rclexecm.RclExecM.eofnow)
            +            
            +        txt += b'
            ' + return (True, txt, "", rclexecm.RclExecM.eofnext) + + ###### File type handler api, used by rclexecm ----------> + def openfile(self, params): + self.currentindex = 0 + return True + + def getipath(self, params): + return self.extractone(params) + + def getnext(self, params): + if self.currentindex >= 1: + return (False, "", "", rclexecm.RclExecM.eofnow) + else: + ret= self.extractone(params) + self.currentindex += 1 + return ret + +if __name__ == '__main__': + proto = rclexecm.RclExecM() + extract = TxtDump(proto) + rclexecm.main(proto, extract) diff --git a/src/filters/rcltxtlines.py b/src/filters/rcltxtlines.py new file mode 100755 index 00000000..2e7b5468 --- /dev/null +++ b/src/filters/rcltxtlines.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +"""Index text lines as document (execm handler sample). This exists +to demonstrate the execm interface and is not meant to be useful or +efficient""" +from __future__ import print_function + +import sys +import os + +import rclexecm + +# Here try to import your document module if you need one. There is +# not much risk of 'sys' missing, but this shows what you should do if +# something is not there: the data will go to the 'missing' file, which +# can be displayed by the GUI as a list of MIME type and missing +# helpers. +try: + import sys +except: + print("RECFILTERROR HELPERNOTFOUND python:sys") + sys.exit(1); + +# Our class. +class rclTXTLINES: + def __init__(self, em): + # Store a ref to our execm object so that we can use its services. + self.em = em + + # This is called once for every processed file during indexing, or + # query preview. For multi-document files, it usually creates some + # kind of table of contents, and resets the current index in it, + # because we don't know at this point if this is for indexing + # (will walk all entries) or previewing (will request + # one). Actually we could know from the environment but it's just + # simpler this way in general. Note that there is no close call, + # openfile() will just be called repeatedly during indexing, and + # should clear any existing state + def openfile(self, params): + """Open the text file, create a contents array""" + self.currentindex = -1 + try: + f = open(params["filename:"].decode('UTF-8'), "r") + except Exception as err: + self.em.rclog("openfile: open failed: [%s]" % err) + return False + self.lines = f.readlines() + return True + + # This is called during indexing to walk the contents. The first + # time, we return a 'self' document, which may be empty (e.g. for + # a tar file), or might contain data (e.g. for an email body, + # further docs being the attachments), and may also be the only + # document returned (for single document files). + def getnext(self, params): + + # Self doc. Here empty. + # + # This could also be the only entry if this file type holds a + # single document. We return eofnext in this case + # + # !Note that the self doc has an *empty* ipath + if self.currentindex == -1: + self.currentindex = 0 + if len(self.lines) == 0: + eof = rclexecm.RclExecM.eofnext + else: + eof = rclexecm.RclExecM.noteof + return (True, "", "", eof) + + + if self.currentindex >= len(self.lines): + return (False, "", "", rclexecm.RclExecM.eofnow) + else: + ret= self.extractone(self.currentindex) + self.currentindex += 1 + return ret + + # This is called for query preview to request one specific (or the + # only) entry. Here our internal paths are stringified line + # numbers, but they could be tar archive paths or whatever we + # returned during indexing. + def getipath(self, params): + return self.extractone(int(params["ipath:"])) + + # Most handlers factorize common code from getipath() and + # getnext() in an extractone() method, but this is not part of the + # interface. + def extractone(self, lno): + """Extract one line from the text file""" + + # Need to specify the MIME type here. This would not be + # necessary if the ipath was a file name with a usable + # extension. + self.em.setmimetype("text/plain") + + # Warning of upcoming eof saves one roundtrip + iseof = rclexecm.RclExecM.noteof + if lno == len(self.lines) - 1: + iseof = rclexecm.RclExecM.eofnext + + try: + # Return the doc data and internal path (here stringified + # line number). If we're called from getipath(), the + # returned ipath is not that useful of course. + return (True, self.lines[lno], str(lno), iseof) + except Exception as err: + self.em.rclog("extractone: failed: [%s]" % err) + return (False, "", lno, iseof) + + +# Initialize: create our protocol handler, the filetype-specific +# object, link them and run. +proto = rclexecm.RclExecM() +extract = rclTXTLINES(proto) +rclexecm.main(proto, extract) diff --git a/src/filters/rcluncomp.py b/src/filters/rcluncomp.py new file mode 100644 index 00000000..438dab62 --- /dev/null +++ b/src/filters/rcluncomp.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +from __future__ import print_function + +import rclexecm +import sys +import os +import shutil +import platform +import subprocess +import glob + +ftrace = sys.stderr +#ftrace = open("C:/Users/Bill/log-uncomp.txt", "w") + +sysplat = platform.system() +if sysplat != "Windows": + print("rcluncomp.py: only for Windows", file = ftrace) + sys.exit(1) + +try: + import msvcrt + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) +except Exception as err: + print("setmode binary failed: %s" % str(err), file = ftrace) + +sevenz = rclexecm.which("7z") +if not sevenz: + print("rcluncomp.py: can't find 7z exe. Maybe set recollhelperpath " \ + "in recoll.conf ?", file=ftrace) + sys.exit(2) + +# Params: uncompression program, input file name, temp directory. +# We ignore the uncomp program, and always use 7z on Windows + +infile = sys.argv[2] +outdir = sys.argv[3] +# print("rcluncomp.py infile [%s], outdir [%s]" % (infile, outdir), file = ftrace) + +# There is apparently no way to suppress 7z output. Hopefully the +# possible deadlock described by the subprocess module doc can't occur +# here because there is little data printed. AFAIK nothing goes to stderr anyway +try: + cmd = [sevenz, "e", "-bd", "-y", "-o" + outdir, infile] + subprocess.check_output(cmd, stderr = subprocess.PIPE) + outputname = glob.glob(os.path.join(outdir, "*")) + # There should be only one file in there.. + print(outputname[0]) +except Exception as err: + print("%s" % (str(err),), file = ftrace) + sys.exit(4) + +sys.exit(0) diff --git a/src/filters/rclwar b/src/filters/rclwar index 8fe46638..8b0dc35f 100755 --- a/src/filters/rclwar +++ b/src/filters/rclwar @@ -2,6 +2,8 @@ # WAR web archive filter for recoll. War file are gzipped tar files +from __future__ import print_function + import rclexecm import tarfile @@ -15,7 +17,7 @@ class WarExtractor: member = self.tar.extractfile(tarinfo) docdata = member.read() ok = True - except Exception, err: + except Exception as err: self.em.rclog("extractone: failed: [%s]" % err) ok = False return (ok, docdata, tarinfo.name, rclexecm.RclExecM.noteof) @@ -26,7 +28,7 @@ class WarExtractor: try: self.tar = tarfile.open(params["filename:"]) return True - except Exception, err: + except Exception as err: self.em.rclog(str(err)) return False @@ -34,7 +36,7 @@ class WarExtractor: ipath = params["ipath:"] try: tarinfo = self.tar.getmember(ipath) - except Exception, err: + except Exception as err: self.em.rclog(str(err)) return (False, "", ipath, rclexecm.RclExecM.noteof) return self.extractone(tarinfo) diff --git a/src/filters/rclxls b/src/filters/rclxls deleted file mode 100755 index eb5d4904..00000000 --- a/src/filters/rclxls +++ /dev/null @@ -1,116 +0,0 @@ -#!/bin/sh -# @(#$Id: rclxls,v 1.5 2008-10-08 08:27:34 dockes Exp $ (C) 2004 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 -# (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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -#================================================================ -# Handle excel files for recoll. -#================================================================ -# set variables -LANG=C ; export LANG -LC_ALL=C ; export LC_ALL -progname="rclxls" -filetype=excel - - - -#RECFILTCOMMONCODE -############################################################################## -# !! Leave the previous line unmodified!! Code imported from the -# recfiltcommon file - -# Utility code common to all shell filters. This could be sourced at run -# time, but it's slightly more efficient to include the code in the -# filters at build time (with a sed script). - -# Describe error in a way that can be interpreted by our caller -senderror() -{ - echo RECFILTERROR $* - # Also alert on stderr just in case - echo ":2:$progname::: $*" 1>&2 - exit 1 -} - -iscmd() -{ - cmd=$1 - case $cmd in - */*) - if test -x $cmd -a ! -d $cmd ; then return 0; else return 1; fi ;; - *) - oldifs=$IFS; IFS=":"; set -- $PATH; IFS=$oldifs - for d in $*;do test -x $d/$cmd -a ! -d $d/$cmd && return 0;done - return 1 ;; - esac -} - -checkcmds() -{ - for cmd in $*;do - if iscmd $cmd - then - a=1 - else - senderror HELPERNOTFOUND $cmd - fi - done -} - -# show help message -if test $# -ne 1 -o "$1" = "--help" -then - echo "Convert a $filetype file to HTML text for Recoll indexing." - echo "Usage: $progname [infile]" - exit 1 -fi - -infile="$1" - -# check the input file existence (may be '-' for stdin) -if test "X$infile" != X- -a ! -f "$infile" -then - senderror INPUTNOSUCHFILE "$infile" -fi - -# protect access to our temp files and directories -umask 77 - -############################################################################## -# !! Leave the following line unmodified ! -#ENDRECFILTCOMMONCODE - -top=`dirname $0` -XLSDUMP="$top/xls-dump.py" -XMLTOCSV="$top/xlsxmltocsv.py" - -checkcmds $XLSDUMP $XLSTOCSV - -# output the result -echo '' -#echo '' "$title" '' -echo '' -echo '' -echo '
            '
            -
            -$XLSDUMP --dump-mode=canonical-xml --utf-8 --catch "$infile" | \
            -   $XMLTOCSV | \
            -   sed -e 's/'
            -echo ''
            -
            -# exit normally
            -exit 0
            diff --git a/src/filters/rclxls.py b/src/filters/rclxls.py
            new file mode 100755
            index 00000000..82fc2379
            --- /dev/null
            +++ b/src/filters/rclxls.py
            @@ -0,0 +1,80 @@
            +#!/usr/bin/env python2
            +
            +# Extractor for Excel files.
            +# Mso-dumper is not compatible with Python3. We use sys.executable to
            +# start the actual extractor, so we need to use python2 too.
            +
            +import rclexecm
            +import rclexec1
            +import xlsxmltocsv
            +import re
            +import sys
            +import os
            +import xml.sax
            +
            +class XLSProcessData:
            +    def __init__(self, em, ishtml = False):
            +        self.em = em
            +        self.out = ""
            +        self.gotdata = 0
            +        self.xmldata = ""
            +        self.ishtml = ishtml
            +        
            +    def takeLine(self, line):
            +        if self.ishtml:
            +            self.out += line + "\n"
            +            return
            +        if not self.gotdata:
            +            self.out += '''''' + \
            +                        '''''' + \
            +                        '''
            '''
            +            self.gotdata = True
            +        self.xmldata += line
            +
            +    def wrapData(self):
            +        if self.ishtml:
            +            return self.out
            +        handler =  xlsxmltocsv.XlsXmlHandler()
            +        data = xml.sax.parseString(self.xmldata, handler)
            +        self.out += self.em.htmlescape(handler.output)
            +        return self.out + '''
            ''' + +class XLSFilter: + def __init__(self, em): + self.em = em + self.ntry = 0 + + def reset(self): + self.ntry = 0 + pass + + def getCmd(self, fn): + if self.ntry: + return ([], None) + self.ntry = 1 + # Some HTML files masquerade as XLS + try: + data = open(fn, 'rb').read(512) + if data.find('html') != -1 or data.find('HTML') != -1: + return ("cat", XLSProcessData(self.em, True)) + except Exception as err: + self.em.rclog("Error reading %s:%s" % (fn, str(err))) + pass + cmd = rclexecm.which("xls-dump.py") + if cmd: + # xls-dump.py often exits 1 with valid data. Ignore exit value + return ([sys.executable, cmd, "--dump-mode=canonical-xml", \ + "--utf-8", "--catch"], + XLSProcessData(self.em), rclexec1.Executor.opt_ignxval) + else: + return ([], None) + +if __name__ == '__main__': + if not rclexecm.which("xls-dump.py"): + print("RECFILTERROR HELPERNOTFOUND ppt-dump.py") + sys.exit(1) + proto = rclexecm.RclExecM() + filter = XLSFilter(proto) + extract = rclexec1.Executor(proto, filter) + rclexecm.main(proto, extract) diff --git a/src/filters/rclxml b/src/filters/rclxml deleted file mode 100755 index 62d7846d..00000000 --- a/src/filters/rclxml +++ /dev/null @@ -1,119 +0,0 @@ -#!/bin/sh - -#================================================================ -# Extract text from a generic XML file (Justus Piater) -#================================================================ - -# set variables -LANG=C ; export LANG -LC_ALL=C ; export LC_ALL -progname="rclxml" -filetype=xml - - -#RECFILTCOMMONCODE -############################################################################## -# !! Leave the previous line unmodified!! Code imported from the -# recfiltcommon file - -# Utility code common to all shell filters. This could be sourced at run -# time, but it's slightly more efficient to include the code in the -# filters at build time (with a sed script). - -# Describe error in a way that can be interpreted by our caller -senderror() -{ - echo RECFILTERROR $* - # Also alert on stderr just in case - echo ":2:$progname::: $*" 1>&2 - exit 1 -} - -iscmd() -{ - cmd=$1 - case $cmd in - */*) - if test -x $cmd -a ! -d $cmd ; then return 0; else return 1; fi ;; - *) - oldifs=$IFS; IFS=":"; set -- $PATH; IFS=$oldifs - for d in $*;do test -x $d/$cmd -a ! -d $d/$cmd && return 0;done - return 1 ;; - esac -} - -checkcmds() -{ - for cmd in $*;do - if iscmd $cmd - then - a=1 - else - senderror HELPERNOTFOUND $cmd - fi - done -} - -# show help message -if test $# -ne 1 -o "$1" = "--help" -then - echo "Convert a $filetype file to HTML text for Recoll indexing." - echo "Usage: $progname [infile]" - exit 1 -fi - -infile="$1" - -# check the input file existence (may be '-' for stdin) -if test "X$infile" != X- -a ! -f "$infile" -then - senderror INPUTNOSUCHFILE "$infile" -fi - -# protect access to our temp files and directories -umask 77 - -############################################################################## -# !! Leave the following line unmodified ! -#ENDRECFILTCOMMONCODE - -checkcmds xsltproc - -xsltproc --novalid --nonet - "$infile" < - - - - - - - - - - <xsl:value-of select="//*[local-name() = 'title'][1]"/> - - - - - - - - - - - -

            - - -
            -
            - - - - - -
            -EOF - -exit 0 diff --git a/src/filters/rclxml.py b/src/filters/rclxml.py new file mode 100755 index 00000000..1fd993f2 --- /dev/null +++ b/src/filters/rclxml.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# Copyright (C) 2014 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 +# (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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +###################################### + +import sys +import rclexecm +import rclxslt + +stylesheet_all = ''' + + + + + + + + + + <xsl:value-of select="//*[local-name() = 'title'][1]"/> + + + + + + + + + + + +

            + + +
            +
            + + + + + +
            +''' + +class XMLExtractor: + def __init__(self, em): + self.em = em + self.currentindex = 0 + + def extractone(self, params): + if "filename:" not in params: + self.em.rclog("extractone: no mime or file name") + return (False, "", "", rclexecm.RclExecM.eofnow) + fn = params["filename:"] + + try: + data = open(fn, 'rb').read() + docdata = rclxslt.apply_sheet_data(stylesheet_all, data) + except Exception as err: + self.em.rclog("%s: bad data: " % (fn, err)) + return (False, "", "", rclexecm.RclExecM.eofnow) + + return (True, docdata, "", rclexecm.RclExecM.eofnext) + + ###### File type handler api, used by rclexecm ----------> + def openfile(self, params): + self.currentindex = 0 + return True + + def getipath(self, params): + return self.extractone(params) + + def getnext(self, params): + if self.currentindex >= 1: + return (False, "", "", rclexecm.RclExecM.eofnow) + else: + ret= self.extractone(params) + self.currentindex += 1 + return ret + +if __name__ == '__main__': + proto = rclexecm.RclExecM() + extract = XMLExtractor(proto) + rclexecm.main(proto, extract) diff --git a/src/filters/rclxmp.py b/src/filters/rclxmp.py new file mode 100755 index 00000000..9dce6741 --- /dev/null +++ b/src/filters/rclxmp.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# Copyright (C) 2016 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 +# (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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# Code to extract XMP tags using libexempi and python-xmp +from __future__ import print_function + +can_xmp = True +try: + import libxmp.utils +except: + can_xmp = False + +import re +import sys + +xmp_index_re = re.compile('''\[[0-9+]\]$''') +#xmp_index_re = re.compile('''3''') + +def rclxmp_enabled(): + return can_xmp + +def rclxmp(filename): + if not can_xmp: + return None, "python-xmp not accessible" + errstr = "" + try: + xmp = libxmp.utils.file_to_dict(filename) + except Exception as e: + errstr = str(e) + + if errstr: + return None, errstr + + out = {} + for ns in xmp.keys(): + for entry in xmp[ns]: + if entry[1]: + k = xmp_index_re.sub('', entry[0]) + if k.find("/") != -1: + continue + if k in out: + out[k] += " " + entry[1] + else: + out[k] = entry[1] + return out, "" + +if __name__ == "__main__": + d, err = rclxmp(sys.argv[1]) + if d: + print("Data: %s" % d) + else: + print("Error: %s" % err) + + diff --git a/src/filters/rclxslt.py b/src/filters/rclxslt.py new file mode 100644 index 00000000..2441294e --- /dev/null +++ b/src/filters/rclxslt.py @@ -0,0 +1,70 @@ +# Copyright (C) 2014 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 +# (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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +###################################### + +# Helper module for xslt-based filters + +from __future__ import print_function + +import sys + +PY2 = sys.version < '3' + +if PY2: + try: + import libxml2 + import libxslt + libxml2.substituteEntitiesDefault(1) + except: + print("RECFILTERROR HELPERNOTFOUND python:libxml2/python:libxslt1") + sys.exit(1); + def apply_sheet_data(sheet, data): + styledoc = libxml2.parseMemory(sheet, len(sheet)) + style = libxslt.parseStylesheetDoc(styledoc) + doc = libxml2.parseMemory(data, len(data)) + result = style.applyStylesheet(doc, None) + res = style.saveResultToString(result) + style.freeStylesheet() + doc.freeDoc() + result.freeDoc() + return res + def apply_sheet_file(sheet, fn): + styledoc = libxml2.parseMemory(sheet, len(sheet)) + style = libxslt.parseStylesheetDoc(styledoc) + doc = libxml2.parseFile(fn) + result = style.applyStylesheet(doc, None) + res = style.saveResultToString(result) + style.freeStylesheet() + doc.freeDoc() + result.freeDoc() + return res +else: + try: + from lxml import etree + except: + print("RECFILTERROR HELPERNOTFOUND python3:lxml") + sys.exit(1); + def apply_sheet_data(sheet, data): + styledoc = etree.fromstring(sheet) + transform = etree.XSLT(styledoc) + doc = etree.fromstring(data) + return etree.tostring(transform(doc)) + def apply_sheet_file(sheet, fn): + styledoc = etree.fromstring(sheet) + transform = etree.XSLT(styledoc) + doc = etree.parse(fn) + return etree.tostring(transform(doc)) + diff --git a/src/filters/rclzip b/src/filters/rclzip index a3afb06e..2131bf1d 100755 --- a/src/filters/rclzip +++ b/src/filters/rclzip @@ -1,8 +1,27 @@ #!/usr/bin/env python +# Copyright (C) 2014 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 +# (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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# -# Zip file filter for Recoll +# Zip file extractor for Recoll + +from __future__ import print_function import os +import posixpath import fnmatch import rclexecm from zipfile import ZipFile @@ -71,16 +90,24 @@ class ZipExtractor: #raise BadZipfile() else: docdata = self.zip.read(ipath) + try: + # We are assuming here that the zip uses forward slash + # separators, which is not necessarily the case. At + # worse, we'll get a wrong or no file name, which is + # no big deal (the ipath is the important data + # element). + filename = posixpath.basename(ipath) + self.em.setfield("filename", filename) + except: + pass ok = True - except Exception, err: + except Exception as err: self.em.rclog("extractone: failed: [%s]" % err) ok = False iseof = rclexecm.RclExecM.noteof if self.currentindex >= len(self.zip.namelist()) -1: iseof = rclexecm.RclExecM.eofnext - if isinstance(ipath, unicode): - ipath = ipath.encode("utf-8") - return (ok, docdata, ipath, iseof) + return (ok, docdata, rclexecm.makebytes(ipath), iseof) ###### File type handler api, used by rclexecm ----------> def openfile(self, params): @@ -96,9 +123,16 @@ class ZipExtractor: self.skiplist = skipped.split(" ") try: - self.zip = ZipFile(filename) + if rclexecm.PY3: + # Note: python3 ZipFile wants an str file name, which + # is wrong: file names are binary. But it accepts an + # open file, and open() has no such restriction + f = open(filename, 'rb') + self.zip = ZipFile(f) + else: + self.zip = ZipFile(filename) return True - except Exception, err: + except Exception as err: self.em.rclog("openfile: failed: [%s]" % err) return False @@ -111,7 +145,7 @@ class ZipExtractor: try: ipath = ipath.decode("utf-8") return self.extractone(ipath) - except Exception, err: + except Exception as err: return (ok, data, ipath, eof) def getnext(self, params): diff --git a/src/filters/xls-dump.py b/src/filters/xls-dump.py index d826654f..15613f35 100755 --- a/src/filters/xls-dump.py +++ b/src/filters/xls-dump.py @@ -1,10 +1,14 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # +# mso-dumper is not compatible with python3 + +from __future__ import print_function + import sys, os.path, optparse sys.path.append(sys.path[0]+"/msodump.zip") @@ -97,7 +101,7 @@ class XLDumper(object): node.prettyPrint(sys.stdout, docroot, utf8 = self.params.utf8) except Exception as err: - print >> sys.stderr, "xls-dump.py: error: %s" % err + print("xls-dump.py: error: %s" % err, file=sys.stderr) sys.exit(1) def dump (self): diff --git a/src/filters/xlsxmltocsv.py b/src/filters/xlsxmltocsv.py index f8a6d654..7fa12e58 100755 --- a/src/filters/xlsxmltocsv.py +++ b/src/filters/xlsxmltocsv.py @@ -1,9 +1,33 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 +# Copyright (C) 2015 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 +# (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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# Transform XML output from xls-dump.py into csv format. +# +# Note: this would be difficult to make compatible with python 3 <= 3.4 +# because of the use of % interpolation on what should be bytes. +# The python2 restriction is not a big issue at this point because +# msodumper is not compatible with python3 anyway +# % interpolation for bytes is planned for python 3.5, at which point +# porting this module will become trivial. + +from __future__ import print_function import sys import xml.sax -sys.path.append(sys.path[0]+"/msodump.zip") -from msodumper.globals import error dtt = True @@ -15,22 +39,25 @@ else: dquote = '"' class XlsXmlHandler(xml.sax.handler.ContentHandler): + def __init__(self): + self.output = "" + def startElement(self, name, attrs): if name == "worksheet": if "name" in attrs: - print("%s" % attrs["name"].encode("UTF-8")) + self.output += "%s\n" % attrs["name"].encode("UTF-8") elif name == "row": self.cells = dict() elif name == "label-cell" or name == "number-cell": if "value" in attrs: value = attrs["value"].encode("UTF-8") else: - value = unicode() + value = b'' if "col" in attrs: self.cells[int(attrs["col"])] = value else: #?? - sys.stdout.write("%s%s"%(value.encode("UTF-8"),sepstring)) + self.output += "%s%s" % (value.encode("UTF-8"), sepstring) elif name == "formula-cell": if "formula-result" in attrs and "col" in attrs: self.cells[int(attrs["col"])] = \ @@ -39,18 +66,22 @@ class XlsXmlHandler(xml.sax.handler.ContentHandler): def endElement(self, name, ): if name == "row": curidx = 0 - for idx, value in self.cells.iteritems(): - sys.stdout.write(sepstring * (idx - curidx)) - sys.stdout.write('%s%s%s' % (dquote, value, dquote)) + for idx, value in self.cells.items(): + self.output += sepstring * (idx - curidx) + self.output += "%s%s%s" % (dquote, value, dquote) curidx = idx - sys.stdout.write("\n") + self.output += "\n" elif name == "worksheet": - print("") + self.output += "\n" -try: - xml.sax.parse(sys.stdin, XlsXmlHandler()) -except BaseException as err: - error("xml-parse: %s\n" % (str(sys.exc_info()[:2]),)) - sys.exit(1) -sys.exit(0) +if __name__ == '__main__': + try: + handler = XlsXmlHandler() + xml.sax.parse(sys.stdin, handler) + print(handler.output) + except BaseException as err: + print("xml-parse: %s\n" % (str(sys.exc_info()[:2]),), file=sys.stderr) + sys.exit(1) + + sys.exit(0) diff --git a/src/index/Makefile b/src/index/Makefile index f6b4e7e1..2c6721b5 100644 --- a/src/index/Makefile +++ b/src/index/Makefile @@ -1,42 +1,18 @@ -depth = .. -include $(depth)/mk/sysconf - -PROGS = recollindex -SRCS = recollindex.cpp rclmonrcv.cpp rclmonprc.cpp - -all: depend librecoll $(PROGS) - -RECOLLINDEX_OBJS= recollindex.o rclmonrcv.o rclmonprc.o -recollindex : $(RECOLLINDEX_OBJS) - $(CXX) $(ALL_CXXFLAGS) $(RECOLL_LDFLAGS) $(LDFLAGS) -o recollindex \ - $(RECOLLINDEX_OBJS) \ - $(BSTATIC) $(LIBRECOLL) $(LIBXAPIAN) $(LIBXAPIANSTATICEXTRA) \ - $(LIBICONV) $(BDYNAMIC) \ - $(LIBFAM) \ - $(X_LIBS) $(X_PRE_LIBS) $(X_LIBX11) $(X_EXTRA_LIBS) \ - $(LIBSYS) $(LIBTHREADS) -recollindex.o : recollindex.cpp - $(CXX) $(ALL_CXXFLAGS) -c -o recollindex.o $< -rclmonrcv.o : rclmonrcv.cpp - $(CXX) $(ALL_CXXFLAGS) -c -o rclmonrcv.o $< -rclmonprc.o : rclmonprc.cpp - $(CXX) $(ALL_CXXFLAGS) -c -o rclmonprc.o $< +PROGS = subtreelist mimetype +all: $(PROGS) SUBTREELIST_OBJS= subtreelist.o subtreelist : $(SUBTREELIST_OBJS) $(CXX) $(ALL_CXXFLAGS) -o subtreelist $(SUBTREELIST_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBXAPIAN) $(LIBSYS) + $(LIBRECOLL) subtreelist.o : subtreelist.cpp $(CXX) $(ALL_CXXFLAGS) -DTEST_SUBTREELIST -c subtreelist.cpp MIMETYPE_OBJS= trmimetype.o mimetype : $(MIMETYPE_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o mimetype $(MIMETYPE_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) $(ALL_CXXFLAGS) -o mimetype $(MIMETYPE_OBJS) $(LIBRECOLL) trmimetype.o : mimetype.cpp $(CXX) $(ALL_CXXFLAGS) -DTEST_MIMETYPE -c -o trmimetype.o \ mimetype.cpp -include $(depth)/mk/commontargets - -include alldeps +include ../utils/utmkdefs.mk diff --git a/src/index/beaglequeue.cpp b/src/index/beaglequeue.cpp index 2a2af9ac..5012d6fa 100644 --- a/src/index/beaglequeue.cpp +++ b/src/index/beaglequeue.cpp @@ -16,14 +16,15 @@ */ #include "autoconfig.h" -#include -#include #include #include +#include "safesysstat.h" +#include "safeunistd.h" #include "cstr.h" #include "pathut.h" -#include "debuglog.h" +#include "rclutil.h" +#include "log.h" #include "fstreewalk.h" #include "beaglequeue.h" #include "beaglequeuecache.h" @@ -43,9 +44,6 @@ using namespace std; -#include - - // Beagle creates a file named .xxx (where xxx is the name for the main file // in the queue), to hold external metadata (http or created by Beagle). // This class reads the .xxx, dotfile, and turns it into an Rcl::Doc holder @@ -64,7 +62,7 @@ public: m_input.getline(cline, LL-1); if (!m_input.good()) { if (m_input.bad()) { - LOGERR(("beagleDotFileRead: input.bad()\n")); + LOGERR("beagleDotFileRead: input.bad()\n" ); } return false; } @@ -74,7 +72,7 @@ public: ll--; } line.assign(cline, ll); - LOGDEB2(("BeagleDotFile:readLine: [%s]\n", line.c_str())); + LOGDEB2("BeagleDotFile:readLine: [" << (line) << "]\n" ); return true; } @@ -85,7 +83,7 @@ public: m_input.open(m_fn.c_str(), ios::in); if (!m_input.good()) { - LOGERR(("BeagleDotFile: open failed for [%s]\n", m_fn.c_str())); + LOGERR("BeagleDotFile: open failed for [" << (m_fn) << "]\n" ); return false; } @@ -187,7 +185,7 @@ BeagleQueueIndexer::BeagleQueueIndexer(RclConfig *cnf, Rcl::Db *db, BeagleQueueIndexer::~BeagleQueueIndexer() { - LOGDEB(("BeagleQueueIndexer::~\n")); + LOGDEB("BeagleQueueIndexer::~\n" ); deleteZ(m_cache); } @@ -204,12 +202,12 @@ bool BeagleQueueIndexer::indexFromCache(const string& udi) string hittype; if (!m_cache || !m_cache->getFromCache(udi, dotdoc, data, &hittype)) { - LOGERR(("BeagleQueueIndexer::indexFromCache: cache failed\n")); + LOGERR("BeagleQueueIndexer::indexFromCache: cache failed\n" ); return false; } if (hittype.empty()) { - LOGERR(("BeagleIndexer::index: cc entry has no hit type\n")); + LOGERR("BeagleIndexer::index: cc entry has no hit type\n" ); return false; } @@ -226,11 +224,11 @@ bool BeagleQueueIndexer::indexFromCache(const string& udi) try { fis = interner.internfile(doc); } catch (CancelExcept) { - LOGERR(("BeagleQueueIndexer: interrupted\n")); + LOGERR("BeagleQueueIndexer: interrupted\n" ); return false; } if (fis != FileInterner::FIDone) { - LOGERR(("BeagleQueueIndexer: bad status from internfile\n")); + LOGERR("BeagleQueueIndexer: bad status from internfile\n" ); return false; } @@ -259,15 +257,14 @@ bool BeagleQueueIndexer::index() { if (!m_db) return false; - LOGDEB(("BeagleQueueIndexer::processqueue: [%s]\n", m_queuedir.c_str())); + LOGDEB("BeagleQueueIndexer::processqueue: [" << (m_queuedir) << "]\n" ); m_config->setKeyDir(m_queuedir); - if (!makepath(m_queuedir)) { - LOGERR(("BeagleQueueIndexer:: can't create queuedir [%s] errno %d\n", - m_queuedir.c_str(), errno)); + if (!path_makepath(m_queuedir, 0700)) { + LOGERR("BeagleQueueIndexer:: can't create queuedir [" << (m_queuedir) << "] errno " << (errno) << "\n" ); return false; } if (!m_cache || !m_cache->cc()) { - LOGERR(("BeagleQueueIndexer: cache initialization failed\n")); + LOGERR("BeagleQueueIndexer: cache initialization failed\n" ); return false; } CirCache *cc = m_cache->cc(); @@ -285,7 +282,7 @@ bool BeagleQueueIndexer::index() do { string udi; if (!cc->getCurrentUdi(udi)) { - LOGERR(("BeagleQueueIndexer:: cache file damaged\n")); + LOGERR("BeagleQueueIndexer:: cache file damaged\n" ); break; } if (udi.empty()) @@ -298,7 +295,7 @@ bool BeagleQueueIndexer::index() indexFromCache(udi); updstatus(udi); } catch (CancelExcept) { - LOGERR(("BeagleQueueIndexer: interrupted\n")); + LOGERR("BeagleQueueIndexer: interrupted\n" ); return false; } } @@ -310,17 +307,17 @@ bool BeagleQueueIndexer::index() FsTreeWalker walker(FsTreeWalker::FtwNoRecurse); walker.addSkippedName(".*"); FsTreeWalker::Status status = walker.walk(m_queuedir, *this); - LOGDEB(("BeagleQueueIndexer::processqueue: done: status %d\n", status)); + LOGDEB("BeagleQueueIndexer::processqueue: done: status " << (status) << "\n" ); return true; } // Index a list of files (sent by the real time monitor) bool BeagleQueueIndexer::indexFiles(list& files) { - LOGDEB(("BeagleQueueIndexer::indexFiles\n")); + LOGDEB("BeagleQueueIndexer::indexFiles\n" ); if (!m_db) { - LOGERR(("BeagleQueueIndexer::indexfiles no db??\n")); + LOGERR("BeagleQueueIndexer::indexfiles no db??\n" ); return false; } for (list::iterator it = files.begin(); it != files.end();) { @@ -329,8 +326,7 @@ bool BeagleQueueIndexer::indexFiles(list& files) } string father = path_getfather(*it); if (father.compare(m_queuedir)) { - LOGDEB(("BeagleQueueIndexer::indexfiles: skipping [%s] (nq)\n", - it->c_str())); + LOGDEB("BeagleQueueIndexer::indexfiles: skipping [" << *it << "] (nq)\n" ); it++; continue; } // Pb: we are often called with the dot file, before the @@ -345,14 +341,12 @@ bool BeagleQueueIndexer::indexFiles(list& files) it++; continue; } struct stat st; - if (lstat(it->c_str(), &st) != 0) { - LOGERR(("BeagleQueueIndexer::indexfiles: cant stat [%s]\n", - it->c_str())); + if (path_fileprops(*it, &st) != 0) { + LOGERR("BeagleQueueIndexer::indexfiles: cant stat [" << *it << "]\n" ); it++; continue; } if (!S_ISREG(st.st_mode)) { - LOGDEB(("BeagleQueueIndexer::indexfiles: skipping [%s] (nr)\n", - it->c_str())); + LOGDEB("BeagleQueueIndexer::indexfiles: skipping [" << *it << "] (nr)\n" ); it++; continue; } @@ -380,7 +374,7 @@ BeagleQueueIndexer::processone(const string &path, string dotpath = path_cat(path_getfather(path), string(".") + path_getsimple(path)); - LOGDEB(("BeagleQueueIndexer: prc1: [%s]\n", path.c_str())); + LOGDEB("BeagleQueueIndexer: prc1: [" << (path) << "]\n" ); BeagleDotFile dotfile(m_config, dotpath); Rcl::Doc dotdoc; @@ -394,7 +388,7 @@ BeagleQueueIndexer::processone(const string &path, udipath = path_cat(dotdoc.meta[Rcl::Doc::keybght], url_gpath(dotdoc.url)); make_udi(udipath, cstr_null, udi); - LOGDEB(("BeagleQueueIndexer: prc1: udi [%s]\n", udi.c_str())); + LOGDEB("BeagleQueueIndexer: prc1: udi [" << (udi) << "]\n" ); char ascdate[30]; sprintf(ascdate, "%ld", long(stp->st_mtime)); @@ -404,9 +398,7 @@ BeagleQueueIndexer::processone(const string &path, if (dotdoc.fmtime.empty()) dotdoc.fmtime = ascdate; - char cbuf[100]; - sprintf(cbuf, "%lld", (long long)stp->st_size); - dotdoc.pcbytes = cbuf; + dotdoc.pcbytes = lltodecstr(stp->st_size); // Document signature for up to date checks: none. dotdoc.sig.clear(); @@ -428,11 +420,11 @@ BeagleQueueIndexer::processone(const string &path, try { fis = interner.internfile(doc); } catch (CancelExcept) { - LOGERR(("BeagleQueueIndexer: interrupted\n")); + LOGERR("BeagleQueueIndexer: interrupted\n" ); goto out; } if (fis != FileInterner::FIDone && fis != FileInterner::FIAgain) { - LOGERR(("BeagleQueueIndexer: bad status from internfile\n")); + LOGERR("BeagleQueueIndexer: bad status from internfile\n" ); // TOBEDONE: internfile can return FIAgain here if it is // paging a big text file, we should loop. Means we're // only indexing the first page for text/plain files @@ -444,9 +436,7 @@ BeagleQueueIndexer::processone(const string &path, doc.fmtime = ascdate; dotdoc.fmtime = doc.fmtime; - char cbuf[100]; - sprintf(cbuf, "%lld", (long long)stp->st_size); - doc.pcbytes = cbuf; + doc.pcbytes = lltodecstr(stp->st_size); // Document signature for up to date checks: none. doc.sig.clear(); doc.url = dotdoc.url; @@ -467,12 +457,11 @@ BeagleQueueIndexer::processone(const string &path, string fdata; file_to_string(path, fdata); if (!m_cache || !m_cache->cc()) { - LOGERR(("BeagleQueueIndexer: cache initialization failed\n")); + LOGERR("BeagleQueueIndexer: cache initialization failed\n" ); goto out; } if (!m_cache->cc()->put(udi, &dotfile.m_fields, fdata, 0)) { - LOGERR(("BeagleQueueIndexer::prc1: cache_put failed; %s\n", - m_cache->cc()->getReason().c_str())); + LOGERR("BeagleQueueIndexer::prc1: cache_put failed; " << (m_cache->cc()->getReason()) << "\n" ); goto out; } } @@ -485,3 +474,4 @@ out: } return FsTreeWalker::FtwOk; } + diff --git a/src/index/bglfetcher.cpp b/src/index/bglfetcher.cpp index 9a927766..5fc6f744 100644 --- a/src/index/bglfetcher.cpp +++ b/src/index/bglfetcher.cpp @@ -14,48 +14,41 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef HAVE_CONFIG_H #include "autoconfig.h" -#endif -#include -#include +#include -#include "debuglog.h" #include "rcldoc.h" - #include "fetcher.h" #include "bglfetcher.h" -#include "debuglog.h" -#include "ptmutex.h" +#include "log.h" #include "beaglequeuecache.h" // We use a single beagle cache object to access beagle data. We protect it // against multiple thread access. -static PTMutexInit o_beagler_mutex; +static std::mutex o_beagler_mutex; bool BGLDocFetcher::fetch(RclConfig* cnf, const Rcl::Doc& idoc, RawDoc& out) { string udi; if (!idoc.getmeta(Rcl::Doc::keyudi, &udi) || udi.empty()) { - LOGERR(("BGLDocFetcher:: no udi in idoc\n")); + LOGERR("BGLDocFetcher:: no udi in idoc\n" ); return false; } Rcl::Doc dotdoc; { - PTMutexLocker locker(o_beagler_mutex); + std::unique_lock locker(o_beagler_mutex); // Retrieve from our webcache (beagle data). The beagler // object is created at the first call of this routine and // deleted when the program exits. static BeagleQueueCache o_beagler(cnf); if (!o_beagler.getFromCache(udi, dotdoc, out.data)) { - LOGINFO(("BGLDocFetcher::fetch: failed for [%s]\n", udi.c_str())); + LOGINFO("BGLDocFetcher::fetch: failed for [" << (udi) << "]\n" ); return false; } } if (dotdoc.mimetype.compare(idoc.mimetype)) { - LOGINFO(("BGLDocFetcher:: udi [%s], mimetp mismatch: in: [%s], bgl " - "[%s]\n", idoc.mimetype.c_str(), dotdoc.mimetype.c_str())); + LOGINFO("BGLDocFetcher:: udi [" << (udi) << "], mimetp mismatch: in: [" << (idoc.mimetype) << "], bgl [" << (dotdoc.mimetype) << "]\n" ); } out.kind = RawDoc::RDK_DATA; return true; @@ -68,3 +61,4 @@ bool BGLDocFetcher::makesig(RclConfig* cnf, const Rcl::Doc& idoc, string& sig) return true; } + diff --git a/src/index/checkretryfailed.cpp b/src/index/checkretryfailed.cpp index b818749c..e85cc45f 100644 --- a/src/index/checkretryfailed.cpp +++ b/src/index/checkretryfailed.cpp @@ -21,18 +21,20 @@ #include "rclconfig.h" #include "execmd.h" -#include "debuglog.h" +#include "log.h" #include "checkretryfailed.h" using namespace std; bool checkRetryFailed(RclConfig *conf, bool record) { +#ifdef _WIN32 + return true; +#else string cmd; if (!conf->getConfParam("checkneedretryindexscript", cmd)) { - LOGDEB(("checkRetryFailed: 'checkneedretryindexscript' " - "not set in config\n")); + LOGDEB("checkRetryFailed: 'checkneedretryindexscript' not set in config\n" ); // We could toss a dice ? Say no retry in this case. return false; } @@ -51,4 +53,6 @@ bool checkRetryFailed(RclConfig *conf, bool record) return true; } return false; +#endif } + diff --git a/src/index/exefetcher.cpp b/src/index/exefetcher.cpp new file mode 100644 index 00000000..cc5ea2e7 --- /dev/null +++ b/src/index/exefetcher.cpp @@ -0,0 +1,124 @@ +/* Copyright (C) 2016 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include "autoconfig.h" + +#include +#include +#include + +#include "exefetcher.h" + +#include "log.h" +#include "pathut.h" +#include "rclconfig.h" +#include "execmd.h" +#include "rcldoc.h" + +using namespace std; + +class EXEDocFetcher::Internal { +public: + string bckid; + vector sfetch; + vector smkid; + bool docmd(const vector& cmd, const Rcl::Doc& idoc, string& out) { + ExecCmd ecmd; + // We're always called for preview (or Open) + ecmd.putenv("RECOLL_FILTER_FORPREVIEW=yes"); + string udi; + idoc.getmeta(Rcl::Doc::keyudi, &udi); + vector args(cmd); + args.push_back(udi); + args.push_back(idoc.url); + args.push_back(idoc.ipath); + int status = ecmd.doexec1(args, 0, &out); + if (status == 0) { + LOGDEB("EXEDocFetcher::Internal: got [" << (out) << "]\n" ); + return true; + } else { + LOGERR("EXEDOcFetcher::fetch: " << (bckid) << ": " << (stringsToString(cmd)) << " failed for " << (udi) << " " << (idoc.url) << " " << (idoc.ipath) << "\n" ); + return false; + } + } +}; + +EXEDocFetcher::EXEDocFetcher(const EXEDocFetcher::Internal& _m) +{ + m = new Internal(_m); + LOGDEB("EXEDocFetcher::EXEDocFetcher: fetch is " << (stringsToString(m->sfetch)) << "\n" ); +} + +bool EXEDocFetcher::fetch(RclConfig* cnf, const Rcl::Doc& idoc, RawDoc& out) +{ + out.kind = RawDoc::RDK_DATADIRECT; + return m->docmd(m->sfetch, idoc, out.data); +} + +bool EXEDocFetcher::makesig(RclConfig* cnf, const Rcl::Doc& idoc, string& sig) +{ + return m->docmd(m->smkid, idoc, sig); +} + +// Lookup bckid in the config and create an appropriate fetcher. +EXEDocFetcher *exeDocFetcherMake(RclConfig *config, const string& bckid) +{ + EXEDocFetcher *fetcher = 0; + + // The config we only read once, not gonna change. + static ConfSimple *bconf; + if (!bconf) { + string bconfname = path_cat(config->getConfDir(), "backends"); + LOGDEB("exeDocFetcherMake: using config in " << (bconfname) << "\n" ); + bconf = new ConfSimple(bconfname.c_str(), true); + if (!bconf->ok()) { + delete bconf; + bconf = 0; + LOGDEB("exeDocFetcherMake: bad/no config: " << (bconfname) << "\n" ); + return 0; + } + } + + EXEDocFetcher::Internal m; + m.bckid = bckid; + + string sfetch; + if (!bconf->get("fetch", sfetch, bckid) || sfetch.empty()) { + LOGERR("exeDocFetcherMake: no 'fetch' for [" << (bckid) << "]\n" ); + return 0; + } + stringToStrings(sfetch, m.sfetch); + // We look up the command as we do for filters for now + m.sfetch[0] = config->findFilter(m.sfetch[0]); + if (!path_isabsolute(m.sfetch[0])) { + LOGERR("exeDocFetcherMake: " << (m.sfetch[0]) << " not found in exec path or filters dir\n" ); + return 0; + } + + string smkid; + if (!bconf->get("makesig", smkid, bckid) || smkid.empty()) { + LOGDEB("exeDocFetcherMake: no 'makesig' for [" << (bckid) << "]\n" ); + return 0; + } + stringToStrings(smkid, m.smkid); + m.smkid[0] = config->findFilter(m.smkid[0]); + if (!path_isabsolute(m.smkid[0])) { + LOGERR("exeDocFetcherMake: " << (m.smkid[0]) << " not found in exec path or filters dir\n" ); + return 0; + } + return new EXEDocFetcher(m); +} + diff --git a/src/index/exefetcher.h b/src/index/exefetcher.h new file mode 100644 index 00000000..30e43d3d --- /dev/null +++ b/src/index/exefetcher.h @@ -0,0 +1,44 @@ +/* Copyright (C) 2012 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _EXEFETCHER_H_INCLUDED_ +#define _EXEFETCHER_H_INCLUDED_ + +#include "fetcher.h" + +class RclConfig; + +/** + * A fetcher which works by executing external programs, defined in a + * configuration file: + */ +class EXEDocFetcher : public DocFetcher { + class Internal; + EXEDocFetcher(const Internal&); + virtual ~EXEDocFetcher() {} + + virtual bool fetch(RclConfig* cnf, const Rcl::Doc& idoc, RawDoc& out); + /** Calls stat to retrieve file signature data */ + virtual bool makesig(RclConfig* cnf, const Rcl::Doc& idoc, std::string& sig); + friend EXEDocFetcher *exeDocFetcherMake(RclConfig *, const std::string&); +private: + Internal *m; +}; + +// Lookup bckid in the config and create an appropriate fetcher. +EXEDocFetcher *exeDocFetcherMake(RclConfig *config, const std::string& bckid); + +#endif /* _EXEFETCHER_H_INCLUDED_ */ diff --git a/src/index/fetcher.cpp b/src/index/fetcher.cpp index d6106480..99d60735 100644 --- a/src/index/fetcher.cpp +++ b/src/index/fetcher.cpp @@ -14,30 +14,37 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef HAVE_CONFIG_H #include "autoconfig.h" -#endif -#include "debuglog.h" + +#include "log.h" +#include "rclconfig.h" #include "fetcher.h" #include "fsfetcher.h" #include "bglfetcher.h" +#include "exefetcher.h" -DocFetcher *docFetcherMake(const Rcl::Doc& idoc) +DocFetcher *docFetcherMake(RclConfig *config, const Rcl::Doc& idoc) { if (idoc.url.empty()) { - LOGERR(("docFetcherMakeg:: no url in doc!\n")); + LOGERR("docFetcherMakeg:: no url in doc!\n" ); return 0; } string backend; idoc.getmeta(Rcl::Doc::keybcknd, &backend); if (backend.empty() || !backend.compare("FS")) { return new FSDocFetcher; +#ifndef DISABLE_WEB_INDEXER } else if (!backend.compare("BGL")) { return new BGLDocFetcher; +#endif } else { - LOGERR(("DocFetcherFactory: unknown backend [%s]\n", backend.c_str())); - return 0; + DocFetcher *f = exeDocFetcherMake(config, backend); + if (!f) { + LOGERR("DocFetcherFactory: unknown backend [" << (backend) << "]\n" ); + } + return f; } } + diff --git a/src/index/fetcher.h b/src/index/fetcher.h index 8ac83544..418aa604 100644 --- a/src/index/fetcher.h +++ b/src/index/fetcher.h @@ -17,7 +17,7 @@ #ifndef _FETCHER_H_INCLUDED_ #define _FETCHER_H_INCLUDED_ -#include +#include "safesysstat.h" #include #include "rcldoc.h" @@ -42,7 +42,7 @@ public: /** A RawDoc is the data for a document-holding entity either as a memory block, or pointed to by a file name */ struct RawDoc { - enum RawDocKind {RDK_FILENAME, RDK_DATA}; + enum RawDocKind {RDK_FILENAME, RDK_DATA, RDK_DATADIRECT}; RawDocKind kind; std::string data; // Doc data or file name struct stat st; // Only used if RDK_FILENAME @@ -71,6 +71,6 @@ public: }; /** Return an appropriate fetcher object given the backend string identifier */ -DocFetcher *docFetcherMake(const Rcl::Doc& idoc); +DocFetcher *docFetcherMake(RclConfig *config, const Rcl::Doc& idoc); #endif /* _FETCHER_H_INCLUDED_ */ diff --git a/src/index/fsfetcher.cpp b/src/index/fsfetcher.cpp index f1dc6f13..a699863b 100644 --- a/src/index/fsfetcher.cpp +++ b/src/index/fsfetcher.cpp @@ -14,20 +14,17 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef HAVE_CONFIG_H #include "autoconfig.h" -#endif -#include #include +#include "safesysstat.h" -#include "debuglog.h" +#include "log.h" #include "cstr.h" - #include "fetcher.h" #include "fsfetcher.h" #include "fsindexer.h" -#include "debuglog.h" +#include "pathut.h" using std::string; @@ -35,19 +32,17 @@ static bool urltopath(RclConfig* cnf, const Rcl::Doc& idoc, string& fn, struct stat& st) { // The url has to be like file:// - if (idoc.url.find(cstr_fileu) != 0) { - LOGERR(("FSDocFetcher::fetch/sig: non fs url: [%s]\n", - idoc.url.c_str())); + fn = fileurltolocalpath(idoc.url); + if (fn.empty()) { + LOGERR("FSDocFetcher::fetch/sig: non fs url: [" << (idoc.url) << "]\n" ); return false; } - fn = idoc.url.substr(7, string::npos); cnf->setKeyDir(path_getfather(fn)); bool follow = false; cnf->getConfParam("followLinks", &follow); - if ((follow ? stat(fn.c_str(), &st) : lstat(fn.c_str(), &st))< 0) { - LOGERR(("FSDocFetcher::fetch: stat errno %d for [%s]\n", - errno, fn.c_str())); + if (path_fileprops(fn, &st, follow) < 0) { + LOGERR("FSDocFetcher::fetch: stat errno " << (errno) << " for [" << (fn) << "]\n" ); return false; } return true; @@ -73,3 +68,4 @@ bool FSDocFetcher::makesig(RclConfig* cnf, const Rcl::Doc& idoc, string& sig) return true; } + diff --git a/src/index/fsindexer.cpp b/src/index/fsindexer.cpp index 74947229..3f43c2d8 100644 --- a/src/index/fsindexer.cpp +++ b/src/index/fsindexer.cpp @@ -17,10 +17,9 @@ #include "autoconfig.h" #include -#include -#include #include #include +#include "safesysstat.h" #include #include @@ -29,6 +28,7 @@ #include "cstr.h" #include "pathut.h" +#include "rclutil.h" #include "conftree.h" #include "rclconfig.h" #include "fstreewalk.h" @@ -37,15 +37,16 @@ #include "indexer.h" #include "fsindexer.h" #include "transcode.h" -#include "debuglog.h" +#include "log.h" #include "internfile.h" #include "smallut.h" +#include "chrono.h" #include "wipedir.h" #include "fileudi.h" #include "cancelcheck.h" #include "rclinit.h" -#include "execmd.h" #include "extrameta.h" +#include "utf8fn.h" using namespace std; @@ -85,13 +86,13 @@ extern void *FsIndexerInternfileWorker(void*); // main thread either before or after the exciting part class FSIFIMissingStore : public FIMissingStore { #ifdef IDX_THREADS - PTMutexInit m_mutex; + std::mutex m_mutex; #endif public: virtual void addMissing(const string& prog, const string& mt) { #ifdef IDX_THREADS - PTMutexLocker locker(m_mutex); + std::unique_lock locker(m_mutex); #endif FIMissingStore::addMissing(prog, mt); } @@ -106,19 +107,18 @@ FsIndexer::FsIndexer(RclConfig *cnf, Rcl::Db *db, DbIxStatusUpdater *updfunc) m_dwqueue("Split", cnf->getThrConf(RclConfig::ThrSplit).first) #endif // IDX_THREADS { - LOGDEB1(("FsIndexer::FsIndexer\n")); + LOGDEB1("FsIndexer::FsIndexer\n" ); m_havelocalfields = m_config->hasNameAnywhere("localfields"); m_config->getConfParam("detectxattronly", &m_detectxattronly); #ifdef IDX_THREADS m_stableconfig = new RclConfig(*m_config); - m_loglevel = DebugLog::getdbl()->getlevel(); m_haveInternQ = m_haveSplitQ = false; int internqlen = cnf->getThrConf(RclConfig::ThrIntern).first; int internthreads = cnf->getThrConf(RclConfig::ThrIntern).second; if (internqlen >= 0) { if (!m_iwqueue.start(internthreads, FsIndexerInternfileWorker, this)) { - LOGERR(("FsIndexer::FsIndexer: intern worker start failed\n")); + LOGERR("FsIndexer::FsIndexer: intern worker start failed\n" ); return; } m_haveInternQ = true; @@ -127,32 +127,28 @@ FsIndexer::FsIndexer(RclConfig *cnf, Rcl::Db *db, DbIxStatusUpdater *updfunc) int splitthreads = cnf->getThrConf(RclConfig::ThrSplit).second; if (splitqlen >= 0) { if (!m_dwqueue.start(splitthreads, FsIndexerDbUpdWorker, this)) { - LOGERR(("FsIndexer::FsIndexer: split worker start failed\n")); + LOGERR("FsIndexer::FsIndexer: split worker start failed\n" ); return; } m_haveSplitQ = true; } - LOGDEB(("FsIndexer: threads: haveIQ %d iql %d iqts %d " - "haveSQ %d sql %d sqts %d\n", m_haveInternQ, internqlen, - internthreads, m_haveSplitQ, splitqlen, splitthreads)); + LOGDEB("FsIndexer: threads: haveIQ " << (m_haveInternQ) << " iql " << (internqlen) << " iqts " << (internthreads) << " haveSQ " << (m_haveSplitQ) << " sql " << (splitqlen) << " sqts " << (splitthreads) << "\n" ); #endif // IDX_THREADS } FsIndexer::~FsIndexer() { - LOGDEB1(("FsIndexer::~FsIndexer()\n")); + LOGDEB1("FsIndexer::~FsIndexer()\n" ); #ifdef IDX_THREADS void *status; if (m_haveInternQ) { status = m_iwqueue.setTerminateAndWait(); - LOGDEB0(("FsIndexer: internfile wrkr status: %ld (1->ok)\n", - long(status))); + LOGDEB0("FsIndexer: internfile wrkr status: " << (status) << " (1->ok)\n" ); } if (m_haveSplitQ) { status = m_dwqueue.setTerminateAndWait(); - LOGDEB0(("FsIndexer: dbupd worker status: %ld (1->ok)\n", - long(status))); + LOGDEB0("FsIndexer: dbupd worker status: " << (status) << " (1->ok)\n" ); } delete m_stableconfig; #endif // IDX_THREADS @@ -165,7 +161,7 @@ bool FsIndexer::init() if (m_tdl.empty()) { m_tdl = m_config->getTopdirs(); if (m_tdl.empty()) { - LOGERR(("FsIndexers: no topdirs list defined\n")); + LOGERR("FsIndexers: no topdirs list defined\n" ); return false; } } @@ -183,9 +179,8 @@ bool FsIndexer::index(int flags) if (m_updater) { #ifdef IDX_THREADS - PTMutexLocker locker(m_updater->m_mutex); + std::unique_lock locker(m_updater->m_mutex); #endif - m_updater->status.reset(); m_updater->status.dbtotdocs = m_db->docCnt(); } @@ -197,8 +192,8 @@ bool FsIndexer::index(int flags) for (vector::const_iterator it = m_tdl.begin(); it != m_tdl.end(); it++) { - LOGDEB(("FsIndexer::index: Indexing %s into %s\n", it->c_str(), - getDbDir().c_str())); + LOGDEB("FsIndexer::index: Indexing " << *it << " into " << + getDbDir() << "\n"); // Set the current directory in config so that subsequent // getConfParams() will get local values @@ -220,8 +215,8 @@ bool FsIndexer::index(int flags) // Walk the directory tree if (m_walker.walk(*it, *this) != FsTreeWalker::FtwOk) { - LOGERR(("FsIndexer::index: error while indexing %s: %s\n", - it->c_str(), m_walker.getReason().c_str())); + LOGERR("FsIndexer::index: error while indexing " << *it << + ": " << m_walker.getReason() << "\n"); return false; } } @@ -238,12 +233,11 @@ bool FsIndexer::index(int flags) string missing; m_missing->getMissingDescription(missing); if (!missing.empty()) { - LOGINFO(("FsIndexer::index missing helper program(s):\n%s\n", - missing.c_str())); + LOGINFO("FsIndexer::index missing helper program(s):\n" << (missing) << "\n" ); } m_config->storeMissingHelperDesc(missing); } - LOGINFO(("fsindexer index time: %d mS\n", chron.ms())); + LOGINFO("fsindexer index time: " << (chron.millis()) << " mS\n" ); return true; } @@ -260,12 +254,11 @@ static bool matchesSkipped(const vector& tdl, string canonpath = path_canon(path); string mpath = canonpath; string topdir; - while (mpath.length() > 1) { + while (!path_isroot(mpath)) { // we assume root not in skipped paths. for (vector::const_iterator it = tdl.begin(); it != tdl.end(); it++) { // the topdirs members are already canonized. - LOGDEB2(("matchesSkipped: comparing ancestor [%s] to " - "topdir [%s]\n", mpath.c_str(), it->c_str())); + LOGDEB2("matchesSkipped: comparing ancestor [" << (mpath) << "] to topdir [" << (it) << "]\n" ); if (!mpath.compare(*it)) { topdir = *it; goto goodpath; @@ -273,8 +266,7 @@ static bool matchesSkipped(const vector& tdl, } if (walker.inSkippedPaths(mpath, false)) { - LOGDEB(("FsIndexer::indexFiles: skipping [%s] (skpp)\n", - path.c_str())); + LOGDEB("FsIndexer::indexFiles: skipping [" << (path) << "] (skpp)\n" ); return true; } @@ -282,19 +274,18 @@ static bool matchesSkipped(const vector& tdl, mpath = path_getfather(mpath); // getfather normally returns a path ending with /, canonic // paths don't (except for '/' itself). - if (!mpath.empty() && mpath[mpath.size()-1] == '/') + if (!path_isroot(mpath) && mpath[mpath.size()-1] == '/') mpath.erase(mpath.size()-1); // should not be necessary, but lets be prudent. If the // path did not shorten, something is seriously amiss // (could be an assert actually) if (mpath.length() >= len) { - LOGERR(("FsIndexer::indexFile: internal Error: path [%s] did not " - "shorten\n", mpath.c_str())); + LOGERR("FsIndexer::indexFile: internal Error: path [" << (mpath) << "] did not shorten\n" ); return true; } } // We get there if neither topdirs nor skippedPaths tests matched - LOGDEB(("FsIndexer::indexFiles: skipping [%s] (ntd)\n", path.c_str())); + LOGDEB("FsIndexer::indexFiles: skipping [" << (path) << "] (ntd)\n" ); return true; goodpath: @@ -304,8 +295,7 @@ goodpath: while (mpath.length() >= topdir.length() && mpath.length() > 1) { string fn = path_getsimple(mpath); if (walker.inSkippedNames(fn)) { - LOGDEB(("FsIndexer::indexFiles: skipping [%s] (skpn)\n", - path.c_str())); + LOGDEB("FsIndexer::indexFiles: skipping [" << (path) << "] (skpn)\n" ); return true; } @@ -329,9 +319,9 @@ goodpath: */ bool FsIndexer::indexFiles(list& files, int flags) { - LOGDEB(("FsIndexer::indexFiles\n")); + LOGDEB("FsIndexer::indexFiles\n" ); m_noretryfailed = (flags & ConfIndexer::IxFNoRetryFailed) != 0; - int ret = false; + bool ret = false; if (!init()) return false; @@ -347,7 +337,7 @@ bool FsIndexer::indexFiles(list& files, int flags) walker.setSkippedPaths(m_config->getSkippedPaths()); for (list::iterator it = files.begin(); it != files.end(); ) { - LOGDEB2(("FsIndexer::indexFiles: [%s]\n", it->c_str())); + LOGDEB2("FsIndexer::indexFiles: [" << (it) << "]\n" ); m_config->setKeyDir(path_getfather(*it)); if (m_havelocalfields) @@ -365,18 +355,17 @@ bool FsIndexer::indexFiles(list& files, int flags) } struct stat stb; - int ststat = follow ? stat(it->c_str(), &stb) : - lstat(it->c_str(), &stb); + int ststat = path_fileprops(*it, &stb, follow); if (ststat != 0) { - LOGERR(("FsIndexer::indexFiles: lstat(%s): %s", it->c_str(), - strerror(errno))); + LOGERR("FsIndexer::indexFiles: (l)stat " << *it << ": " << + strerror(errno) << "\n"); it++; continue; } if (processone(*it, &stb, FsTreeWalker::FtwRegular) != FsTreeWalker::FtwOk) { - LOGERR(("FsIndexer::indexFiles: processone failed\n")); + LOGERR("FsIndexer::indexFiles: processone failed\n" ); goto out; } it = files.erase(it); @@ -394,11 +383,11 @@ out: // Purge possible orphan documents if (ret == true) { - LOGDEB(("Indexfiles: purging orphans\n")); + LOGDEB("Indexfiles: purging orphans\n" ); const vector& purgecandidates = m_purgeCandidates.getCandidates(); for (vector::const_iterator it = purgecandidates.begin(); it != purgecandidates.end(); it++) { - LOGDEB(("Indexfiles: purging orphans for %s\n", it->c_str())); + LOGDEB("Indexfiles: purging orphans for " << *it << "\n"); m_db->purgeOrphans(*it); } #ifdef IDX_THREADS @@ -406,7 +395,7 @@ out: #endif // IDX_THREADS } - LOGDEB(("FsIndexer::indexFiles: done\n")); + LOGDEB("FsIndexer::indexFiles: done\n" ); return ret; } @@ -414,7 +403,7 @@ out: /** Purge docs for given files out of the database */ bool FsIndexer::purgeFiles(list& files) { - LOGDEB(("FsIndexer::purgeFiles\n")); + LOGDEB("FsIndexer::purgeFiles\n" ); bool ret = false; if (!init()) return false; @@ -426,7 +415,7 @@ bool FsIndexer::purgeFiles(list& files) // found or deleted, false only in case of actual error bool existed; if (!m_db->purgeFile(udi, &existed)) { - LOGERR(("FsIndexer::purgeFiles: Database error\n")); + LOGERR("FsIndexer::purgeFiles: Database error\n" ); goto out; } // If we actually deleted something, take it off the list @@ -446,14 +435,14 @@ out: m_dwqueue.waitIdle(); m_db->waitUpdIdle(); #endif // IDX_THREADS - LOGDEB(("FsIndexer::purgeFiles: done\n")); + LOGDEB("FsIndexer::purgeFiles: done\n" ); return ret; } // Local fields can be set for fs subtrees in the configuration file void FsIndexer::localfieldsfromconf() { - LOGDEB1(("FsIndexer::localfieldsfromconf\n")); + LOGDEB1("FsIndexer::localfieldsfromconf\n" ); string sfields; m_config->getConfParam("localfields", sfields); @@ -473,8 +462,7 @@ void FsIndexer::localfieldsfromconf() it != nmlst.end(); it++) { string nm = m_config->fieldCanon(*it); attrs.get(*it, m_localfields[nm]); - LOGDEB2(("FsIndexer::localfieldsfromconf: [%s]->[%s]\n", - nm.c_str(), m_localfields[nm].c_str())); + LOGDEB2("FsIndexer::localfieldsfromconf: [" << (nm) << "]->[" << (m_localfields[nm]) << "]\n" ); } } @@ -491,11 +479,8 @@ void FsIndexer::setlocalfields(const map& fields, Rcl::Doc& doc) void FsIndexer::makesig(const struct stat *stp, string& out) { - char cbuf[100]; - sprintf(cbuf, "%lld" "%ld", (long long)stp->st_size, - o_uptodate_test_use_mtime ? - (long)stp->st_mtime : (long)stp->st_ctime); - out = cbuf; + out = lltodecstr(stp->st_size) + + lltodecstr(o_uptodate_test_use_mtime ? stp->st_mtime : stp->st_ctime); } #ifdef IDX_THREADS @@ -508,7 +493,6 @@ void *FsIndexerDbUpdWorker(void * fsp) recoll_threadinit(); FsIndexer *fip = (FsIndexer*)fsp; WorkQueue *tqp = &fip->m_dwqueue; - DebugLog::getdbl()->setloglevel(fip->m_loglevel); DbUpdTask *tsk; for (;;) { @@ -517,9 +501,9 @@ void *FsIndexerDbUpdWorker(void * fsp) tqp->workerExit(); return (void*)1; } - LOGDEB0(("FsIndexerDbUpdWorker: task ql %d\n", int(qsz))); + LOGDEB0("FsIndexerDbUpdWorker: task ql " << (int(qsz)) << "\n" ); if (!fip->m_db->addOrUpdate(tsk->udi, tsk->parent_udi, tsk->doc)) { - LOGERR(("FsIndexerDbUpdWorker: addOrUpdate failed\n")); + LOGERR("FsIndexerDbUpdWorker: addOrUpdate failed\n" ); tqp->workerExit(); return (void*)0; } @@ -532,7 +516,6 @@ void *FsIndexerInternfileWorker(void * fsp) recoll_threadinit(); FsIndexer *fip = (FsIndexer*)fsp; WorkQueue *tqp = &fip->m_iwqueue; - DebugLog::getdbl()->setloglevel(fip->m_loglevel); RclConfig myconf(*(fip->m_stableconfig)); InternfileTask *tsk = 0; @@ -541,15 +524,15 @@ void *FsIndexerInternfileWorker(void * fsp) tqp->workerExit(); return (void*)1; } - LOGDEB0(("FsIndexerInternfileWorker: task fn %s\n", tsk->fn.c_str())); + LOGDEB0("FsIndexerInternfileWorker: task fn " << (tsk->fn) << "\n" ); if (fip->processonefile(&myconf, tsk->fn, &tsk->statbuf, tsk->localfields) != FsTreeWalker::FtwOk) { - LOGERR(("FsIndexerInternfileWorker: processone failed\n")); + LOGERR("FsIndexerInternfileWorker: processone failed\n" ); tqp->workerExit(); return (void*)0; } - LOGDEB1(("FsIndexerInternfileWorker: done fn %s\n", tsk->fn.c_str())); + LOGDEB1("FsIndexerInternfileWorker: done fn " << (tsk->fn) << "\n" ); delete tsk; } } @@ -572,7 +555,7 @@ FsIndexer::processone(const std::string &fn, const struct stat *stp, { if (m_updater) { #ifdef IDX_THREADS - PTMutexLocker locker(m_updater->m_mutex); + std::unique_lock locker(m_updater->m_mutex); #endif if (!m_updater->update()) { return FsTreeWalker::FtwStop; @@ -610,28 +593,6 @@ FsIndexer::processone(const std::string &fn, const struct stat *stp, return processonefile(m_config, fn, stp, m_localfields); } -// File name transcoded to utf8 for indexing. If this fails, the file -// name won't be indexed, no big deal Note that we used to do the full -// path here, but I ended up believing that it made more sense to use -// only the file name The charset is used is the one from the locale. -static string compute_utf8fn(RclConfig *config, const string& fn) -{ - string charset = config->getDefCharset(true); - string utf8fn; - int ercnt; - if (!transcode(path_getsimple(fn), utf8fn, charset, "UTF-8", &ercnt)) { - LOGERR(("processone: fn transcode failure from [%s] to UTF-8: %s\n", - charset.c_str(), path_getsimple(fn).c_str())); - } else if (ercnt) { - LOGDEB(("processone: fn transcode %d errors from [%s] to UTF-8: %s\n", - ercnt, charset.c_str(), path_getsimple(fn).c_str())); - } - LOGDEB2(("processone: fn transcoded from [%s] to [%s] (%s->%s)\n", - path_getsimple(fn).c_str(), utf8fn.c_str(), charset.c_str(), - "UTF-8")); - return utf8fn; -} - FsTreeWalker::Status FsIndexer::processonefile(RclConfig *config, const std::string &fn, const struct stat *stp, @@ -670,8 +631,7 @@ FsIndexer::processonefile(RclConfig *config, bool xattronly = m_detectxattronly && !m_db->inFullReset() && existingDoc && needupdate && (stp->st_mtime < stp->st_ctime); - LOGDEB(("processone: needupdate %d noretry %d existing %d oldsig [%s]\n", - needupdate, m_noretryfailed, existingDoc, oldsig.c_str())); + LOGDEB("processone: needupdate " << (needupdate) << " noretry " << (m_noretryfailed) << " existing " << (existingDoc) << " oldsig [" << (oldsig) << "]\n" ); // If noretryfailed is set, check for a file which previously // failed to index, and avoid re-processing it @@ -681,17 +641,17 @@ FsIndexer::processonefile(RclConfig *config, // actually changed, we always retry (maybe it was fixed) string nold = oldsig.substr(0, oldsig.size()-1); if (!nold.compare(sig)) { - LOGDEB(("processone: not retrying previously failed file\n")); + LOGDEB("processone: not retrying previously failed file\n" ); m_db->setExistingFlags(udi, existingDoc); needupdate = false; } } if (!needupdate) { - LOGDEB0(("processone: up to date: %s\n", fn.c_str())); + LOGDEB0("processone: up to date: " << (fn) << "\n" ); if (m_updater) { #ifdef IDX_THREADS - PTMutexLocker locker(m_updater->m_mutex); + std::unique_lock locker(m_updater->m_mutex); #endif // Status bar update, abort request etc. m_updater->status.fn = fn; @@ -703,10 +663,12 @@ FsIndexer::processonefile(RclConfig *config, return FsTreeWalker::FtwOk; } - LOGDEB0(("processone: processing: [%s] %s\n", - displayableBytes(stp->st_size).c_str(), fn.c_str())); + LOGDEB0("processone: processing: [" << + displayableBytes(off_t(stp->st_size)) << "] " << fn << "\n"); - string utf8fn = compute_utf8fn(config, fn); + // Note that we used to do the full path here, but I ended up + // believing that it made more sense to use only the file name + string utf8fn = compute_utf8fn(config, fn, true); // parent_udi is initially the same as udi, it will be used if there // are subdocs. @@ -736,7 +698,7 @@ FsIndexer::processonefile(RclConfig *config, try { fis = interner.internfile(doc); } catch (CancelExcept) { - LOGERR(("fsIndexer::processone: interrupted\n")); + LOGERR("fsIndexer::processone: interrupted\n" ); return FsTreeWalker::FtwStop; } @@ -773,7 +735,7 @@ FsIndexer::processonefile(RclConfig *config, if (doc.fmtime.empty()) doc.fmtime = ascdate; if (doc.url.empty()) - doc.url = cstr_fileu + fn; + doc.url = path_pathtofileurl(fn); const string *fnp = 0; if (doc.ipath.empty()) { if (!doc.peekmeta(Rcl::Doc::keyfn, &fnp) || fnp->empty()) @@ -782,9 +744,7 @@ FsIndexer::processonefile(RclConfig *config, // Set container file name for all docs, top or subdoc doc.meta[Rcl::Doc::keytcfn] = utf8fn; - char cbuf[100]; - sprintf(cbuf, "%lld", (long long)stp->st_size); - doc.pcbytes = cbuf; + doc.pcbytes = lltodecstr(stp->st_size); // Document signature for up to date checks. All subdocs inherit the // file's. doc.sig = sig; @@ -809,7 +769,7 @@ FsIndexer::processonefile(RclConfig *config, DbUpdTask *tp = new DbUpdTask(udi, doc.ipath.empty() ? cstr_null : parent_udi, doc); if (!m_dwqueue.put(tp)) { - LOGERR(("processonefile: wqueue.put failed\n")); + LOGERR("processonefile: wqueue.put failed\n" ); return FsTreeWalker::FtwError; } } else { @@ -825,14 +785,20 @@ FsIndexer::processonefile(RclConfig *config, // Tell what we are doing and check for interrupt request if (m_updater) { #ifdef IDX_THREADS - PTMutexLocker locker(m_updater->m_mutex); + std::unique_lock locker(m_updater->m_mutex); #endif ++(m_updater->status.docsdone); if (m_updater->status.dbtotdocs < m_updater->status.docsdone) m_updater->status.dbtotdocs = m_updater->status.docsdone; m_updater->status.fn = fn; - if (!doc.ipath.empty()) + if (!doc.ipath.empty()) { m_updater->status.fn += "|" + doc.ipath; + } else { + if (fis == FileInterner::FIError) { + ++(m_updater->status.fileerrors); + } + ++(m_updater->status.filesdone); + } if (!m_updater->update()) { return FsTreeWalker::FtwStop; } @@ -842,8 +808,7 @@ FsIndexer::processonefile(RclConfig *config, // If this doc existed and it's a container, recording for // possible subdoc purge (this will be used only if we don't do a // db-wide purge, e.g. if we're called from indexfiles()). - LOGDEB2(("processOnefile: existingDoc %d hadNonNullIpath %d\n", - existingDoc, hadNonNullIpath)); + LOGDEB2("processOnefile: existingDoc " << (existingDoc) << " hadNonNullIpath " << (hadNonNullIpath) << "\n" ); if (existingDoc && hadNonNullIpath) { m_purgeCandidates.record(parent_udi); } @@ -856,7 +821,7 @@ FsIndexer::processonefile(RclConfig *config, // If xattronly is set, ONLY the extattr metadata is valid and will be used // by the following step. if (xattronly || hadNullIpath == false) { - LOGDEB(("Creating empty doc for file or pure xattr update\n")); + LOGDEB("Creating empty doc for file or pure xattr update\n" ); Rcl::Doc fileDoc; if (xattronly) { map xfields; @@ -869,12 +834,10 @@ FsIndexer::processonefile(RclConfig *config, fileDoc.meta[Rcl::Doc::keytcfn] = utf8fn; fileDoc.haschildren = true; fileDoc.mimetype = mimetype; - fileDoc.url = cstr_fileu + fn; + fileDoc.url = path_pathtofileurl(fn); if (m_havelocalfields) setlocalfields(localfields, fileDoc); - char cbuf[100]; - sprintf(cbuf, "%lld", (long long)stp->st_size); - fileDoc.pcbytes = cbuf; + fileDoc.pcbytes = lltodecstr(stp->st_size); } fileDoc.sig = sig; @@ -894,3 +857,4 @@ FsIndexer::processonefile(RclConfig *config, return FsTreeWalker::FtwOk; } + diff --git a/src/index/fsindexer.h b/src/index/fsindexer.h index 42d65e03..358dd488 100644 --- a/src/index/fsindexer.h +++ b/src/index/fsindexer.h @@ -18,11 +18,11 @@ #define _fsindexer_h_included_ #include +#include #include "indexer.h" #include "fstreewalk.h" #ifdef IDX_THREADS -#include "ptmutex.h" #include "workqueue.h" #endif // IDX_THREADS @@ -93,7 +93,7 @@ class FsIndexer : public FsTreeWalkerCB { if (!dorecord) return; #ifdef IDX_THREADS - PTMutexLocker locker(mutex); + std::unique_lock locker(mutex); #endif udis.push_back(udi); } @@ -103,7 +103,7 @@ class FsIndexer : public FsTreeWalkerCB { } private: #ifdef IDX_THREADS - PTMutexInit mutex; + std::mutex mutex; #endif bool dorecord; std::vector udis; @@ -142,7 +142,6 @@ class FsIndexer : public FsTreeWalkerCB { #ifdef IDX_THREADS friend void *FsIndexerDbUpdWorker(void*); friend void *FsIndexerInternfileWorker(void*); - int m_loglevel; WorkQueue m_iwqueue; WorkQueue m_dwqueue; bool m_haveInternQ; diff --git a/src/index/indexer.cpp b/src/index/indexer.cpp index de706d07..ff336cb3 100644 --- a/src/index/indexer.cpp +++ b/src/index/indexer.cpp @@ -14,23 +14,22 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef HAVE_CONFIG_H #include "autoconfig.h" -#endif #include -#include -#include #include #include #include "cstr.h" -#include "debuglog.h" +#include "log.h" #include "indexer.h" #include "fsindexer.h" +#ifndef DISABLE_WEB_INDEXER #include "beaglequeue.h" +#endif #include "mimehandler.h" +#include "pathut.h" #ifdef RCL_USE_ASPELL #include "rclaspell.h" @@ -47,7 +46,9 @@ ConfIndexer::ConfIndexer(RclConfig *cnf, DbIxStatusUpdater *updfunc) ConfIndexer::~ConfIndexer() { deleteZ(m_fsindexer); +#ifndef DISABLE_WEB_INDEXER deleteZ(m_beagler); +#endif } // Determine if this is likely the first time that the user runs @@ -58,17 +59,14 @@ ConfIndexer::~ConfIndexer() bool ConfIndexer::runFirstIndexing() { // Indexing status file existing and not empty ? - struct stat st; - if (stat(m_config->getIdxStatusFile().c_str(), &st) == 0 && - st.st_size > 0) { - LOGDEB0(("ConfIndexer::runFirstIndexing: no: status file not empty\n")); - exit(1); + if (path_filesize(m_config->getIdxStatusFile()) > 0) { + LOGDEB0("ConfIndexer::runFirstIndexing: no: status file not empty\n" ); return false; } // And only do this if the user has kept the default topdirs (~). - vectortdl = m_config->getTopdirs(); + vector tdl = m_config->getTopdirs(); if (tdl.size() != 1 || tdl[0].compare(path_canon(path_tildexpand("~")))) { - LOGDEB0(("ConfIndexer::runFirstIndexing: no: not home only\n")); + LOGDEB0("ConfIndexer::runFirstIndexing: no: not home only\n" ); return false; } return true; @@ -76,7 +74,7 @@ bool ConfIndexer::runFirstIndexing() bool ConfIndexer::firstFsIndexingSequence() { - LOGDEB(("ConfIndexer::firstFsIndexingSequence\n")); + LOGDEB("ConfIndexer::firstFsIndexingSequence\n" ); deleteZ(m_fsindexer); m_fsindexer = new FsIndexer(m_config, &m_db, m_updater); if (!m_fsindexer) { @@ -94,8 +92,7 @@ bool ConfIndexer::index(bool resetbefore, ixType typestorun, int flags) { Rcl::Db::OpenMode mode = resetbefore ? Rcl::Db::DbTrunc : Rcl::Db::DbUpd; if (!m_db.open(mode)) { - LOGERR(("ConfIndexer: error opening database %s : %s\n", - m_config->getDbDir().c_str(), m_db.getReason().c_str())); + LOGERR("ConfIndexer: error opening database " << (m_config->getDbDir()) << " : " << (m_db.getReason()) << "\n" ); return false; } @@ -111,7 +108,7 @@ bool ConfIndexer::index(bool resetbefore, ixType typestorun, int flags) return false; } } - +#ifndef DISABLE_WEB_INDEXER if (m_dobeagle && (typestorun & IxTBeagleQueue)) { deleteZ(m_beagler); m_beagler = new BeagleQueueIndexer(m_config, &m_db, m_updater); @@ -120,7 +117,7 @@ bool ConfIndexer::index(bool resetbefore, ixType typestorun, int flags) return false; } } - +#endif if (typestorun == IxTAll) { // Get rid of all database entries that don't exist in the // filesystem anymore. Only if all *configured* indexers ran. @@ -137,21 +134,23 @@ bool ConfIndexer::index(bool resetbefore, ixType typestorun, int flags) if (m_updater) m_updater->update(DbIxStatus::DBIXS_CLOSING, string()); if (!m_db.close()) { - LOGERR(("ConfIndexer::index: error closing database in %s\n", - m_config->getDbDir().c_str())); + LOGERR("ConfIndexer::index: error closing database in " << (m_config->getDbDir()) << "\n" ); return false; } if (m_updater && !m_updater->update(DbIxStatus::DBIXS_CLOSING, string())) return false; - createStemmingDatabases(); + bool ret = true; + if (!createStemmingDatabases()) { + ret = false; + } if (m_updater && !m_updater->update(DbIxStatus::DBIXS_CLOSING, string())) return false; - createAspellDict(); + ret = ret && createAspellDict(); clearMimeHandlerCache(); if (m_updater) m_updater->update(DbIxStatus::DBIXS_DONE, string()); - return true; + return ret; } bool ConfIndexer::indexFiles(list& ifiles, int flag) @@ -165,8 +164,7 @@ bool ConfIndexer::indexFiles(list& ifiles, int flag) myfiles.sort(); if (!m_db.open(Rcl::Db::DbUpd)) { - LOGERR(("ConfIndexer: indexFiles error opening database %s\n", - m_config->getDbDir().c_str())); + LOGERR("ConfIndexer: indexFiles error opening database " << (m_config->getDbDir()) << "\n" ); return false; } m_config->setKeyDir(cstr_null); @@ -175,8 +173,8 @@ bool ConfIndexer::indexFiles(list& ifiles, int flag) m_fsindexer = new FsIndexer(m_config, &m_db, m_updater); if (m_fsindexer) ret = m_fsindexer->indexFiles(myfiles, flag); - LOGDEB2(("ConfIndexer::indexFiles: fsindexer returned %d, " - "%d files remainining\n", ret, myfiles.size())); + LOGDEB2("ConfIndexer::indexFiles: fsindexer returned " << (ret) << ", " << (myfiles.size()) << " files remainining\n" ); +#ifndef DISABLE_WEB_INDEXER if (m_dobeagle && !myfiles.empty() && !(flag & IxFNoWeb)) { if (!m_beagler) @@ -187,11 +185,10 @@ bool ConfIndexer::indexFiles(list& ifiles, int flag) ret = false; } } - +#endif // The close would be done in our destructor, but we want status here if (!m_db.close()) { - LOGERR(("ConfIndexer::index: error closing database in %s\n", - m_config->getDbDir().c_str())); + LOGERR("ConfIndexer::index: error closing database in " << (m_config->getDbDir()) << "\n" ); return false; } ifiles = myfiles; @@ -215,8 +212,7 @@ bool ConfIndexer::docsToPaths(vector &docs, vector &paths) // Filesystem document. The url has to be like file:// if (idoc.url.find(cstr_fileu) != 0) { - LOGERR(("idx::docsToPaths: FS backend and non fs url: [%s]\n", - idoc.url.c_str())); + LOGERR("idx::docsToPaths: FS backend and non fs url: [" << (idoc.url) << "]\n" ); continue; } paths.push_back(idoc.url.substr(7, string::npos)); @@ -248,8 +244,7 @@ bool ConfIndexer::purgeFiles(std::list &files, int flag) myfiles.sort(); if (!m_db.open(Rcl::Db::DbUpd)) { - LOGERR(("ConfIndexer: purgeFiles error opening database %s\n", - m_config->getDbDir().c_str())); + LOGERR("ConfIndexer: purgeFiles error opening database " << (m_config->getDbDir()) << "\n" ); return false; } bool ret = false; @@ -259,6 +254,7 @@ bool ConfIndexer::purgeFiles(std::list &files, int flag) if (m_fsindexer) ret = m_fsindexer->purgeFiles(myfiles); +#ifndef DISABLE_WEB_INDEXER if (m_dobeagle && !myfiles.empty() && !(flag & IxFNoWeb)) { if (!m_beagler) m_beagler = new BeagleQueueIndexer(m_config, &m_db, m_updater); @@ -268,11 +264,11 @@ bool ConfIndexer::purgeFiles(std::list &files, int flag) ret = false; } } +#endif // The close would be done in our destructor, but we want status here if (!m_db.close()) { - LOGERR(("ConfIndexer::purgefiles: error closing database in %s\n", - m_config->getDbDir().c_str())); + LOGERR("ConfIndexer::purgefiles: error closing database in " << (m_config->getDbDir()) << "\n" ); return false; } return ret; @@ -283,9 +279,10 @@ bool ConfIndexer::purgeFiles(std::list &files, int flag) bool ConfIndexer::createStemmingDatabases() { string slangs; + bool ret = true; if (m_config->getConfParam("indexstemminglanguages", slangs)) { if (!m_db.open(Rcl::Db::DbUpd)) { - LOGERR(("ConfIndexer::createStemmingDb: could not open db\n")) + LOGERR("ConfIndexer::createStemmingDb: could not open db\n" ); return false; } vector langs; @@ -299,10 +296,10 @@ bool ConfIndexer::createStemmingDatabases() if (find(langs.begin(), langs.end(), *it) == langs.end()) m_db.deleteStemDb(*it); } - m_db.createStemDbs(langs); + ret = ret && m_db.createStemDbs(langs); } m_db.close(); - return true; + return ret; } bool ConfIndexer::createStemDb(const string &lang) @@ -318,7 +315,7 @@ bool ConfIndexer::createStemDb(const string &lang) // module, either from a configuration variable or the NLS environment. bool ConfIndexer::createAspellDict() { - LOGDEB2(("ConfIndexer::createAspellDict()\n")); + LOGDEB2("ConfIndexer::createAspellDict()\n" ); #ifdef RCL_USE_ASPELL // For the benefit of the real-time indexer, we only initialize // noaspell from the configuration once. It can then be set to @@ -333,22 +330,20 @@ bool ConfIndexer::createAspellDict() return true; if (!m_db.open(Rcl::Db::DbRO)) { - LOGERR(("ConfIndexer::createAspellDict: could not open db\n")); + LOGERR("ConfIndexer::createAspellDict: could not open db\n" ); return false; } Aspell aspell(m_config); string reason; if (!aspell.init(reason)) { - LOGERR(("ConfIndexer::createAspellDict: aspell init failed: %s\n", - reason.c_str())); + LOGERR("ConfIndexer::createAspellDict: aspell init failed: " << (reason) << "\n" ); noaspell = true; return false; } - LOGDEB(("ConfIndexer::createAspellDict: creating dictionary\n")); + LOGDEB("ConfIndexer::createAspellDict: creating dictionary\n" ); if (!aspell.buildDict(m_db, reason)) { - LOGERR(("ConfIndexer::createAspellDict: aspell buildDict failed: %s\n", - reason.c_str())); + LOGERR("ConfIndexer::createAspellDict: aspell buildDict failed: " << (reason) << "\n" ); noaspell = true; return false; } @@ -360,3 +355,4 @@ vector ConfIndexer::getStemmerNames() { return Rcl::Db::getStemmerNames(); } + diff --git a/src/index/indexer.h b/src/index/indexer.h index 4c024bac..fa7f0067 100644 --- a/src/index/indexer.h +++ b/src/index/indexer.h @@ -16,25 +16,21 @@ */ #ifndef _INDEXER_H_INCLUDED_ #define _INDEXER_H_INCLUDED_ +#include "rclconfig.h" #include #include #include #include +#include -#ifndef NO_NAMESPACES using std::string; using std::list; using std::map; using std::vector; -#endif -#include "rclconfig.h" #include "rcldb.h" #include "rcldoc.h" -#ifdef IDX_THREADS -#include "ptmutex.h" -#endif class FsIndexer; class BeagleQueueIndexer; @@ -49,12 +45,16 @@ class DbIxStatus { string fn; // Last file processed int docsdone; // Documents actually updated int filesdone; // Files tested (updated or not) + int fileerrors; // Failed files (e.g.: missing input handler). int dbtotdocs; // Doc count in index at start - void reset() - { + // Total files in index.This is actually difficult to compute from + // the index so it's preserved from last indexing + int totfiles; + + void reset() { phase = DBIXS_FILES; fn.erase(); - docsdone = filesdone = dbtotdocs = 0; + docsdone = filesdone = fileerrors = dbtotdocs = totfiles = 0; } DbIxStatus() {reset();} }; @@ -64,7 +64,7 @@ class DbIxStatus { class DbIxStatusUpdater { public: #ifdef IDX_THREADS - PTMutexInit m_mutex; + std::mutex m_mutex; #endif DbIxStatus status; virtual ~DbIxStatusUpdater(){} @@ -73,7 +73,7 @@ class DbIxStatusUpdater { virtual bool update(DbIxStatus::Phase phase, const string& fn) { #ifdef IDX_THREADS - PTMutexLocker lock(m_mutex); + std::unique_lock lock(m_mutex); #endif status.phase = phase; status.fn = fn; diff --git a/src/index/mimetype.cpp b/src/index/mimetype.cpp index a295ec0e..cce8aff3 100644 --- a/src/index/mimetype.cpp +++ b/src/index/mimetype.cpp @@ -18,7 +18,7 @@ #ifndef TEST_MIMETYPE #include "autoconfig.h" -#include +#include "safesysstat.h" #include #include @@ -26,7 +26,7 @@ using namespace std; #include "mimetype.h" -#include "debuglog.h" +#include "log.h" #include "execmd.h" #include "rclconfig.h" #include "smallut.h" @@ -44,7 +44,8 @@ using namespace std; /// So we first call the internal file identifier, which currently /// only knows about mail, but in which we can add the more /// current/interesting file types. -/// As a last resort we execute 'file' (except if forbidden by config) +/// As a last resort we execute 'file' or its configured replacement +/// (except if forbidden by config) static string mimetypefromdata(RclConfig *cfg, const string &fn, bool usfc) { @@ -54,20 +55,37 @@ static string mimetypefromdata(RclConfig *cfg, const string &fn, bool usfc) #ifdef USE_SYSTEM_FILE_COMMAND if (usfc && mime.empty()) { // Last resort: use "file -i", or its configured replacement. - vector cmd = create_vector(FILE_PROG) ("-i") (fn); + + // 'file' fallback if the configured command (default: + // xdg-mime) is not found + static const vector tradfilecmd = {{FILE_PROG}, {"-i"}, {fn}}; + + vector cmd; string scommand; if (cfg->getConfParam("systemfilecommand", scommand)) { + LOGDEB2("mimetype: syscmd from config: " << (scommand) << "\n" ); stringToStrings(scommand, cmd); + string exe; + if (cmd.empty()) { + cmd = tradfilecmd; + } else if (!ExecCmd::which(cmd[0], exe)) { + cmd = tradfilecmd; + } else { + cmd[0] = exe; + } cmd.push_back(fn); + } else { + LOGDEB("mimetype:systemfilecommand not found, using " << (stringsToString(tradfilecmd)) << "\n" ); + cmd = tradfilecmd; } string result; if (!ExecCmd::backtick(cmd, result)) { - LOGERR(("mimetypefromdata: exec %s failed\n", FILE_PROG)); + LOGERR("mimetypefromdata: exec " << (stringsToString(cmd)) << " failed\n" ); return string(); } - LOGDEB2(("mimetype: [%s] \"file\" output [%s]\n", - result.c_str(), fn.c_str())); + trimstring(result, " \t\n\r"); + LOGDEB2("mimetype: systemfilecommand output [" << (result) << "]\n" ); // The normal output from "file -i" looks like the following: // thefilename.xxx: text/plain; charset=us-ascii @@ -75,8 +93,7 @@ static string mimetypefromdata(RclConfig *cfg, const string &fn, bool usfc) // mimetype.cpp: text/x-c charset=us-ascii // And sometimes we only get the mime type. This apparently happens // when 'file' believes that the file name is binary - - trimstring(result, " \t\n\r"); + // xdg-mime only outputs the MIME type. // If there is no colon and there is a slash, this is hopefuly // the mime type @@ -89,8 +106,7 @@ static string mimetypefromdata(RclConfig *cfg, const string &fn, bool usfc) if (result.find(fn) != 0) { // Garbage "file" output. Maybe the result of a charset // conversion attempt? - LOGERR(("mimetype: can't interpret 'file' output: [%s]\n", - result.c_str())); + LOGERR("mimetype: can't interpret 'file' output: [" << (result) << "]\n" ); return string(); } result = result.substr(fn.size()); @@ -140,24 +156,26 @@ string mimetype(const string &fn, const struct stat *stp, string mtype; +#ifndef _WIN32 // Extended attribute has priority on everything, as per: // http://freedesktop.org/wiki/CommonExtendedAttributes if (pxattr::get(fn, "mime_type", &mtype)) { - LOGDEB0(("Mimetype: 'mime_type' xattr : [%s]\n", mtype.c_str())); + LOGDEB0("Mimetype: 'mime_type' xattr : [" << (mtype) << "]\n" ); if (mtype.empty()) { - LOGDEB0(("Mimetype: getxattr() returned empty mime type !\n")); + LOGDEB0("Mimetype: getxattr() returned empty mime type !\n" ); } else { return mtype; } } +#endif if (cfg == 0) { - LOGERR(("Mimetype: null config ??\n")); + LOGERR("Mimetype: null config ??\n" ); return mtype; } if (cfg->inStopSuffixes(fn)) { - LOGDEB(("mimetype: fn [%s] in stopsuffixes\n", fn.c_str())); + LOGDEB("mimetype: fn [" << (fn) << "] in stopsuffixes\n" ); return mtype; } @@ -177,7 +195,6 @@ string mimetype(const string &fn, const struct stat *stp, if (mtype.empty() && stp) mtype = mimetypefromdata(cfg, fn, usfc); - out: return mtype; } @@ -185,12 +202,13 @@ string mimetype(const string &fn, const struct stat *stp, #else // TEST-> #include -#include +#include "safesysstat.h" #include #include -#include "debuglog.h" +#include "log.h" + #include "rclconfig.h" #include "rclinit.h" #include "mimetype.h" @@ -224,3 +242,4 @@ int main(int argc, const char **argv) #endif // TEST + diff --git a/src/index/mimetype.h b/src/index/mimetype.h index 3d659b80..ae30b012 100644 --- a/src/index/mimetype.h +++ b/src/index/mimetype.h @@ -17,10 +17,10 @@ #ifndef _MIMETYPE_H_INCLUDED_ #define _MIMETYPE_H_INCLUDED_ +#include "safesysstat.h" #include class RclConfig; -struct stat; /** * Try to determine a mime type for file. diff --git a/src/index/rclmon.h b/src/index/rclmon.h index 2393f0fa..6e6df5b4 100644 --- a/src/index/rclmon.h +++ b/src/index/rclmon.h @@ -32,6 +32,7 @@ #include #include #include +#include #include "rclconfig.h" @@ -78,16 +79,12 @@ class RclMonEventQueue { public: RclMonEventQueue(); ~RclMonEventQueue(); - /** Unlock queue and wait until there are new events. - * Returns with the queue locked */ - bool wait(int secs = -1, bool *timedout = 0); - /** Unlock queue */ - bool unlock(); - /** Lock queue. */ - bool lock(); - /** Lock queue and add event. */ + /** Wait for event or timeout. Returns with the queue locked */ + std::unique_lock wait(int secs = -1, bool *timedout = 0); + /** Add event. */ bool pushEvent(const RclMonEvent &ev); - void setTerminate(); /* To all threads: end processing */ + /** To all threads: end processing */ + void setTerminate(); bool ok(); bool empty(); RclMonEvent pop(); diff --git a/src/index/rclmonprc.cpp b/src/index/rclmonprc.cpp index 3cacb241..833ed18b 100644 --- a/src/index/rclmonprc.cpp +++ b/src/index/rclmonprc.cpp @@ -24,34 +24,37 @@ * initialization function. */ -#include -#include -#include #include -#include #include +#include "safeunistd.h" #include #include #include #include #include +#include +#include +#include +#include + using std::list; using std::vector; -#include "debuglog.h" +#include "log.h" #include "rclmon.h" -#include "debuglog.h" +#include "log.h" + #include "execmd.h" #include "recollindex.h" #include "pathut.h" +#ifndef _WIN32 #include "x11mon.h" +#endif #include "subtreelist.h" typedef unsigned long mttcast; -static pthread_t rcv_thrid; - // Seconds between auxiliary db (stem, spell) updates: static const int dfltauxinterval = 60 *60; static int auxinterval = dfltauxinterval; @@ -134,13 +137,13 @@ public: vector m_delaypats; RclConfig *m_config; bool m_ok; - pthread_mutex_t m_mutex; - pthread_cond_t m_cond; + + std::mutex m_mutex; + std::condition_variable m_cond; + RclEQData() - : m_config(0), m_ok(false) + : m_config(0), m_ok(true) { - if (!pthread_mutex_init(&m_mutex, 0) && !pthread_cond_init(&m_cond, 0)) - m_ok = true; } void readDelayPats(int dfltsecs); DelayPat searchDelayPats(const string& path) @@ -167,7 +170,7 @@ void RclEQData::readDelayPats(int dfltsecs) vector dplist; if (!stringToStrings(patstring, dplist)) { - LOGERR(("rclEQData: bad pattern list: [%s]\n", patstring.c_str())); + LOGERR("rclEQData: bad pattern list: [" << (patstring) << "]\n" ); return; } @@ -182,8 +185,7 @@ void RclEQData::readDelayPats(int dfltsecs) dp.seconds = dfltsecs; } m_delaypats.push_back(dp); - LOGDEB2(("rclmon::readDelayPats: add [%s] %d\n", - dp.pattern.c_str(), dp.seconds)); + LOGDEB2("rclmon::readDelayPats: add [" << (dp.pattern) << "] " << (dp.seconds) << "\n" ); } } @@ -192,8 +194,8 @@ void RclEQData::readDelayPats(int dfltsecs) // when necessary. void RclEQData::delayInsert(const queue_type::iterator &qit) { - MONDEB(("RclEQData::delayInsert: minclock %lu\n", - (mttcast)qit->second.m_minclock)); + MONDEB("RclEQData::delayInsert: minclock " << qit->second.m_minclock << + std::endl); for (delays_type::iterator dit = m_delays.begin(); dit != m_delays.end(); dit++) { queue_type::iterator qit1 = *dit; @@ -222,64 +224,33 @@ void RclMonEventQueue::setopts(int opts) } /** Wait until there is something to process on the queue, or timeout. - * Must be called with the queue locked + * returns a queue lock */ -bool RclMonEventQueue::wait(int seconds, bool *top) +std::unique_lock RclMonEventQueue::wait(int seconds, bool *top) { - MONDEB(("RclMonEventQueue::wait\n")); + std::unique_lock lock(m_data->m_mutex); + + MONDEB("RclMonEventQueue::wait, seconds: " << seconds << std::endl); if (!empty()) { - MONDEB(("RclMonEventQueue:: imm return\n")); - return true; + MONDEB("RclMonEventQueue:: immediate return\n"); + return lock; } int err; if (seconds > 0) { - struct timespec to; - to.tv_sec = time(0L) + seconds; - to.tv_nsec = 0; if (top) *top = false; - if ((err = - pthread_cond_timedwait(&m_data->m_cond, &m_data->m_mutex, &to))) { - if (err == ETIMEDOUT) { - *top = true; - MONDEB(("RclMonEventQueue:: timeout\n")); - return true; - } - LOGERR(("RclMonEventQueue::wait:pthread_cond_timedwait failed" - "with err %d\n", err)); - return false; - } + if (m_data->m_cond.wait_for(lock, std::chrono::seconds(seconds)) == + std::cv_status::timeout) { + *top = true; + MONDEB("RclMonEventQueue:: timeout\n"); + return lock; + } } else { - if ((err = pthread_cond_wait(&m_data->m_cond, &m_data->m_mutex))) { - LOGERR(("RclMonEventQueue::wait: pthread_cond_wait failed" - "with err %d\n", err)); - return false; - } + m_data->m_cond.wait(lock); } - MONDEB(("RclMonEventQueue:: normal return\n")); - return true; -} - -bool RclMonEventQueue::lock() -{ - MONDEB(("RclMonEventQueue:: lock\n")); - if (pthread_mutex_lock(&m_data->m_mutex)) { - LOGERR(("RclMonEventQueue::lock: pthread_mutex_lock failed\n")); - return false; - } - MONDEB(("RclMonEventQueue:: lock return\n")); - return true; -} - -bool RclMonEventQueue::unlock() -{ - MONDEB(("RclMonEventQueue:: unlock\n")); - if (pthread_mutex_unlock(&m_data->m_mutex)) { - LOGERR(("RclMonEventQueue::lock: pthread_mutex_unlock failed\n")); - return false; - } - return true; + MONDEB("RclMonEventQueue:: non-timeout return\n"); + return lock; } void RclMonEventQueue::setConfig(RclConfig *cnf) @@ -298,15 +269,15 @@ RclConfig *RclMonEventQueue::getConfig() bool RclMonEventQueue::ok() { if (m_data == 0) { - LOGINFO(("RclMonEventQueue: not ok: bad state\n")); + LOGINFO("RclMonEventQueue: not ok: bad state\n" ); return false; } if (stopindexing) { - LOGINFO(("RclMonEventQueue: not ok: stop request\n")); + LOGINFO("RclMonEventQueue: not ok: stop request\n" ); return false; } if (!m_data->m_ok) { - LOGINFO(("RclMonEventQueue: not ok: queue terminated\n")); + LOGINFO("RclMonEventQueue: not ok: queue terminated\n" ); return false; } return true; @@ -314,37 +285,36 @@ bool RclMonEventQueue::ok() void RclMonEventQueue::setTerminate() { - MONDEB(("RclMonEventQueue:: setTerminate\n")); - lock(); + MONDEB("RclMonEventQueue:: setTerminate\n"); + std::unique_lock lock(m_data->m_mutex); m_data->m_ok = false; - pthread_cond_broadcast(&m_data->m_cond); - unlock(); + m_data->m_cond.notify_all(); } // Must be called with the queue locked bool RclMonEventQueue::empty() { if (m_data == 0) { - MONDEB(("RclMonEventQueue::empty(): true (m_data==0)\n")); + MONDEB("RclMonEventQueue::empty(): true (m_data==0)\n"); return true; } if (!m_data->m_iqueue.empty()) { - MONDEB(("RclMonEventQueue::empty(): false (m_iqueue not empty)\n")); + MONDEB("RclMonEventQueue::empty(): false (m_iqueue not empty)\n"); return true; } if (m_data->m_dqueue.empty()) { - MONDEB(("RclMonEventQueue::empty(): true (m_Xqueue both empty)\n")); + MONDEB("RclMonEventQueue::empty(): true (m_Xqueue both empty)\n"); return true; } // Only dqueue has events. Have to check the delays (only the // first, earliest one): queue_type::iterator qit = *(m_data->m_delays.begin()); if (qit->second.m_minclock > time(0)) { - MONDEB(("RclMonEventQueue::empty(): true (no delay ready %lu)\n", - (mttcast)qit->second.m_minclock)); + MONDEB("RclMonEventQueue::empty(): true (no delay ready " << + qit->second.m_minclock << ")\n"); return true; } - MONDEB(("RclMonEventQueue::empty(): returning false (delay expired)\n")); + MONDEB("RclMonEventQueue::empty(): returning false (delay expired)\n"); return false; } @@ -354,15 +324,15 @@ bool RclMonEventQueue::empty() RclMonEvent RclMonEventQueue::pop() { time_t now = time(0); - MONDEB(("RclMonEventQueue::pop(), now %lu\n", (mttcast)now)); + MONDEB("RclMonEventQueue::pop(), now " << now << std::endl); // Look at the delayed events, get rid of the expired/unactive // ones, possibly return an expired/needidx one. while (!m_data->m_delays.empty()) { delays_type::iterator dit = m_data->m_delays.begin(); queue_type::iterator qit = *dit; - MONDEB(("RclMonEventQueue::pop(): in delays: evt minclock %lu\n", - (mttcast)qit->second.m_minclock)); + MONDEB("RclMonEventQueue::pop(): in delays: evt minclock " << + qit->second.m_minclock << std::endl); if (qit->second.m_minclock <= now) { if (qit->second.m_needidx) { RclMonEvent ev = qit->second; @@ -401,8 +371,8 @@ RclMonEvent RclMonEventQueue::pop() // special processing to limit their reindexing rate. bool RclMonEventQueue::pushEvent(const RclMonEvent &ev) { - MONDEB(("RclMonEventQueue::pushEvent for %s\n", ev.m_path.c_str())); - lock(); + MONDEB("RclMonEventQueue::pushEvent for " << ev.m_path << std::endl); + std::unique_lock lock(m_data->m_mutex); DelayPat pat = m_data->searchDelayPats(ev.m_path); if (pat.seconds != 0) { @@ -434,15 +404,14 @@ bool RclMonEventQueue::pushEvent(const RclMonEvent &ev) m_data->m_iqueue[ev.m_path] = ev; } - pthread_cond_broadcast(&m_data->m_cond); - unlock(); + m_data->m_cond.notify_all(); return true; } static bool checkfileanddelete(const string& fname) { bool ret; - ret = access(fname.c_str(), 0) == 0; + ret = path_exists(fname); unlink(fname.c_str()); return ret; } @@ -484,20 +453,13 @@ bool startMonitor(RclConfig *conf, int opts) if (!conf->getConfParam("monixinterval", &ixinterval)) ixinterval = dfltixinterval; - rclEQ.setConfig(conf); rclEQ.setopts(opts); - if (pthread_create(&rcv_thrid, 0, &rclMonRcvRun, &rclEQ) != 0) { - LOGERR(("startMonitor: cant create event-receiving thread\n")); - return false; - } - - if (!rclEQ.lock()) { - LOGERR(("startMonitor: cant lock queue ???\n")); - return false; - } - LOGDEB(("start_monitoring: entering main loop\n")); + std::thread treceive(rclMonRcvRun, &rclEQ); + treceive.detach(); + + LOGDEB("start_monitoring: entering main loop\n" ); bool timedout; time_t lastauxtime = time(0); @@ -506,57 +468,62 @@ bool startMonitor(RclConfig *conf, int opts) list modified; list deleted; + ; + // Set a relatively short timeout for better monitoring of exit requests - while (rclEQ.wait(2, &timedout)) { - // Queue is locked. + while (true) { + { + std::unique_lock lock = rclEQ.wait(2, &timedout); - // x11IsAlive() can't be called from ok() because both threads call it - // and Xlib is not multithreaded. - bool x11dead = !(opts & RCLMON_NOX11) && !x11IsAlive(); - if (x11dead) - LOGDEB(("RclMonprc: x11 is dead\n")); - if (!rclEQ.ok() || x11dead) { - rclEQ.unlock(); - break; - } + // x11IsAlive() can't be called from ok() because both + // threads call it and Xlib is not multithreaded. +#ifndef _WIN32 + bool x11dead = !(opts & RCLMON_NOX11) && !x11IsAlive(); + if (x11dead) + LOGDEB("RclMonprc: x11 is dead\n" ); +#else + bool x11dead = false; +#endif + if (!rclEQ.ok() || x11dead) { + break; + } - // Process event queue - for (;;) { - // Retrieve event - RclMonEvent ev = rclEQ.pop(); - if (ev.m_path.empty()) - break; - switch (ev.evtype()) { - case RclMonEvent::RCLEVT_MODIFY: - case RclMonEvent::RCLEVT_DIRCREATE: - LOGDEB0(("Monitor: Modify/Check on %s\n", ev.m_path.c_str())); - modified.push_back(ev.m_path); - break; - case RclMonEvent::RCLEVT_DELETE: - LOGDEB0(("Monitor: Delete on %s\n", ev.m_path.c_str())); - // If this is for a directory (which the caller should - // tell us because he knows), we should purge the db - // of all the subtree, because on a directory rename, - // inotify will only generate one event for the - // renamed top, not the subentries. This is relatively - // complicated to do though, and we currently do not - // do it, and just wait for a restart to do a full run and - // purge. - deleted.push_back(ev.m_path); - if (ev.evflags() & RclMonEvent::RCLEVT_ISDIR) { - vector paths; - if (subtreelist(conf, ev.m_path, paths)) { - deleted.insert(deleted.end(), - paths.begin(), paths.end()); - } - } - break; - default: - LOGDEB(("Monitor: got Other on [%s]\n", ev.m_path.c_str())); - } - } - // Unlock queue before processing lists - rclEQ.unlock(); + // Process event queue + for (;;) { + // Retrieve event + RclMonEvent ev = rclEQ.pop(); + if (ev.m_path.empty()) + break; + switch (ev.evtype()) { + case RclMonEvent::RCLEVT_MODIFY: + case RclMonEvent::RCLEVT_DIRCREATE: + LOGDEB0("Monitor: Modify/Check on " << ev.m_path << "\n"); + modified.push_back(ev.m_path); + break; + case RclMonEvent::RCLEVT_DELETE: + LOGDEB0("Monitor: Delete on " << (ev.m_path) << "\n" ); + // If this is for a directory (which the caller should + // tell us because he knows), we should purge the db + // of all the subtree, because on a directory rename, + // inotify will only generate one event for the + // renamed top, not the subentries. This is relatively + // complicated to do though, and we currently do not + // do it, and just wait for a restart to do a full run and + // purge. + deleted.push_back(ev.m_path); + if (ev.evflags() & RclMonEvent::RCLEVT_ISDIR) { + vector paths; + if (subtreelist(conf, ev.m_path, paths)) { + deleted.insert(deleted.end(), + paths.begin(), paths.end()); + } + } + break; + default: + LOGDEB("Monitor: got Other on [" << (ev.m_path) << "]\n" ); + } + } + } // Process. We don't do this every time but let the lists accumulate // a little, this saves processing. Start at once if list is big. @@ -599,22 +566,25 @@ bool startMonitor(RclConfig *conf, int opts) // Check for a config change if (!(opts & RCLMON_NOCONFCHECK) && o_reexec && conf->sourceChanged()) { - LOGDEB(("Rclmonprc: config changed, reexecuting myself\n")); + LOGDEB("Rclmonprc: config changed, reexecuting myself\n" ); // We never want to have a -n option after a config // change. -n was added by the reexec after the initial // pass even if it was not given on the command line o_reexec->removeArg("-n"); o_reexec->reexec(); } - // Lock queue before waiting again - rclEQ.lock(); } - LOGDEB(("Rclmonprc: calling queue setTerminate\n")); + LOGDEB("Rclmonprc: calling queue setTerminate\n" ); rclEQ.setTerminate(); - // Wait for receiver thread before returning - pthread_join(rcv_thrid, 0); - LOGDEB(("Monitor: returning\n")); + + // We used to wait for the receiver thread here before returning, + // but this is not useful and may waste time / risk problems + // during our limited time window for exiting. To be reviewed if + // we ever need several monitor invocations in the same process + // (can't foresee any reason why we'd want to do this). + LOGDEB("Monitor: returning\n" ); return true; } #endif // RCL_MONITOR + diff --git a/src/index/rclmonrcv.cpp b/src/index/rclmonrcv.cpp index bda85e8f..ba8a302c 100644 --- a/src/index/rclmonrcv.cpp +++ b/src/index/rclmonrcv.cpp @@ -16,13 +16,15 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include -#include +#include "autoconfig.h" + #include #include #include +#include "safesysstat.h" +#include "safeunistd.h" -#include "debuglog.h" +#include "log.h" #include "rclmon.h" #include "rclinit.h" #include "fstreewalk.h" @@ -74,15 +76,13 @@ public: FsTreeWalker& walker) : m_config(conf), m_mon(mon), m_queue(queue), m_walker(walker) {} - virtual ~WalkCB() - {} + virtual ~WalkCB() {} virtual FsTreeWalker::Status processone(const string &fn, const struct stat *st, - FsTreeWalker::CbFlag flg) - { - MONDEB(("rclMonRcvRun: processone %s m_mon %p m_mon->ok %d\n", - fn.c_str(), m_mon, m_mon?m_mon->ok():0)); + FsTreeWalker::CbFlag flg) { + MONDEB("rclMonRcvRun: processone " << fn << " m_mon " << m_mon << + " m_mon->ok " << (m_mon ? m_mon->ok() : false) << std::endl); if (flg == FsTreeWalker::FtwDirEnter || flg == FsTreeWalker::FtwDirReturn) { @@ -100,7 +100,7 @@ public: if (ev.m_etyp != RclMonEvent::RCLEVT_NONE) m_queue->pushEvent(ev); } else { - MONDEB(("rclMonRcvRun: no event pending\n")); + MONDEB("rclMonRcvRun: no event pending\n"); break; } } @@ -141,41 +141,31 @@ private: FsTreeWalker& m_walker; }; -/** Main thread routine: create watches, then forever wait for and queue events */ +// Main thread routine: create watches, then forever wait for and queue events void *rclMonRcvRun(void *q) { RclMonEventQueue *queue = (RclMonEventQueue *)q; - LOGDEB(("rclMonRcvRun: running\n")); + LOGDEB("rclMonRcvRun: running\n"); recoll_threadinit(); // Make a local copy of the configuration as it doesn't like // concurrent accesses. It's ok to copy it here as the other // thread will not work before we have sent events. RclConfig lconfig(*queue->getConfig()); - string loglevel; - lconfig.getConfParam(string("daemloglevel"), loglevel); - if (loglevel.empty()) - lconfig.getConfParam(string("loglevel"), loglevel); - if (!loglevel.empty()) { - int lev = atoi(loglevel.c_str()); - DebugLog::getdbl()->setloglevel(lev); - } - // Create the fam/whatever interface object RclMonitor *mon; if ((mon = makeMonitor()) == 0) { - LOGERR(("rclMonRcvRun: makeMonitor failed\n")); + LOGERR("rclMonRcvRun: makeMonitor failed\n"); queue->setTerminate(); return 0; } - // Get top directories from config vector tdl = lconfig.getTopdirs(); if (tdl.empty()) { - LOGERR(("rclMonRcvRun:: top directory list (topdirs param.) not" - "found in config or Directory list parse error")); + LOGERR("rclMonRcvRun:: top directory list (topdirs param.) not found " + "in configuration or topdirs list parse error"); queue->setTerminate(); return 0; } @@ -184,7 +174,7 @@ void *rclMonRcvRun(void *q) FsTreeWalker walker; walker.setSkippedPaths(lconfig.getDaemSkippedPaths()); WalkCB walkcb(&lconfig, mon, queue, walker); - for (vector::iterator it = tdl.begin(); it != tdl.end(); it++) { + for (auto it = tdl.begin(); it != tdl.end(); it++) { lconfig.setKeyDir(*it); // Adjust the follow symlinks options bool follow; @@ -194,15 +184,29 @@ void *rclMonRcvRun(void *q) } else { walker.setOpts(FsTreeWalker::FtwOptNone); } - LOGDEB(("rclMonRcvRun: walking %s\n", it->c_str())); - if (walker.walk(*it, walkcb) != FsTreeWalker::FtwOk) { - LOGERR(("rclMonRcvRun: tree walk failed\n")); - goto terminate; - } - if (walker.getErrCnt() > 0) { - LOGINFO(("rclMonRcvRun: fs walker errors: %s\n", - walker.getReason().c_str())); - } + // We have to special-case regular files which are part of the topdirs + // list because we the tree walker only adds watches for directories + struct stat st; + if (path_fileprops(*it, &st, follow) != 0) { + LOGERR("rclMonRcvRun: stat failed for " << *it << "\n"); + continue; + } + if (S_ISDIR(st.st_mode)) { + LOGDEB("rclMonRcvRun: walking " << *it << "\n"); + if (walker.walk(*it, walkcb) != FsTreeWalker::FtwOk) { + LOGERR("rclMonRcvRun: tree walk failed\n"); + goto terminate; + } + if (walker.getErrCnt() > 0) { + LOGINFO("rclMonRcvRun: fs walker errors: " << + walker.getReason() << "\n"); + } + } else { + if (!mon->addWatch(*it, false)) { + LOGERR("rclMonRcvRun: addWatch failed for " << *it << + " errno " << mon->saved_errno << std::endl); + } + } } { @@ -211,7 +215,7 @@ void *rclMonRcvRun(void *q) if (doweb) { string webqueuedir = lconfig.getWebQueueDir(); if (!mon->addWatch(webqueuedir, true)) { - LOGERR(("rclMonRcvRun: addwatch (webqueuedir) failed\n")); + LOGERR("rclMonRcvRun: addwatch (webqueuedir) failed\n"); if (mon->saved_errno != EACCES && mon->saved_errno != ENOENT) goto terminate; } @@ -219,7 +223,8 @@ void *rclMonRcvRun(void *q) } // Forever wait for monitoring events and add them to queue: - MONDEB(("rclMonRcvRun: waiting for events. q->ok() %d\n", queue->ok())); + MONDEB("rclMonRcvRun: waiting for events. q->ok(): " << queue->ok() << + std::endl); while (queue->ok() && mon->ok()) { RclMonEvent ev; // Note: I could find no way to get the select @@ -247,16 +252,15 @@ void *rclMonRcvRun(void *q) // it seems that fam/gamin is doing the job for us so // that we are generating double events here (no big // deal as prc will sort/merge). - LOGDEB(("rclMonRcvRun: walking new dir %s\n", - ev.m_path.c_str())); + LOGDEB("rclMonRcvRun: walking new dir " << ev.m_path << "\n"); if (walker.walk(ev.m_path, walkcb) != FsTreeWalker::FtwOk) { - LOGERR(("rclMonRcvRun: walking new dir %s: %s\n", - ev.m_path.c_str(), walker.getReason().c_str())); + LOGERR("rclMonRcvRun: walking new dir " << ev.m_path << + " : " << walker.getReason() << "\n"); goto terminate; } if (walker.getErrCnt() > 0) { - LOGINFO(("rclMonRcvRun: fs walker errors: %s\n", - walker.getReason().c_str())); + LOGINFO("rclMonRcvRun: fs walker errors: " << + walker.getReason() << "\n"); } } @@ -267,7 +271,7 @@ void *rclMonRcvRun(void *q) terminate: queue->setTerminate(); - LOGINFO(("rclMonRcvRun: monrcv thread routine returning\n")); + LOGINFO("rclMonRcvRun: monrcv thread routine returning\n"); return 0; } @@ -276,7 +280,7 @@ terminate: bool eraseWatchSubTree(map& idtopath, const string& top) { bool found = false; - MONDEB(("Clearing map for [%s]\n", top.c_str())); + MONDEB("Clearing map for [" << top << "]\n"); map::iterator it = idtopath.begin(); while (it != idtopath.end()) { if (it->second.find(top) == 0) { @@ -298,7 +302,6 @@ bool eraseWatchSubTree(map& idtopath, const string& top) #include #include #include -#include #include /** FAM based monitor class. We have to keep a record of FAM watch @@ -352,7 +355,7 @@ RclFAM::RclFAM() : m_ok(false) { if (FAMOpen2(&m_conn, "Recoll")) { - LOGERR(("RclFAM::RclFAM: FAMOpen2 failed, errno %d\n", errno)); + LOGERR("RclFAM::RclFAM: FAMOpen2 failed, errno " << errno << "\n"); return; } m_ok = true; @@ -375,7 +378,7 @@ bool RclFAM::addWatch(const string& path, bool isdir) return false; bool ret = false; - MONDEB(("RclFAM::addWatch: adding %s\n", path.c_str())); + MONDEB("RclFAM::addWatch: adding " << path << std::endl); // It happens that the following call block forever. // We'd like to be able to at least terminate on a signal here, but @@ -383,7 +386,7 @@ bool RclFAM::addWatch(const string& path, bool isdir) // to unblock signals. SIGALRM is not used by the main thread, so at least // ensure that we exit after gamin gets stuck. if (setjmp(jbuf)) { - LOGERR(("RclFAM::addWatch: timeout talking to FAM\n")); + LOGERR("RclFAM::addWatch: timeout talking to FAM\n"); return false; } signal(SIGALRM, onalrm); @@ -391,12 +394,12 @@ bool RclFAM::addWatch(const string& path, bool isdir) FAMRequest req; if (isdir) { if (FAMMonitorDirectory(&m_conn, path.c_str(), &req, 0) != 0) { - LOGERR(("RclFAM::addWatch: FAMMonitorDirectory failed\n")); + LOGERR("RclFAM::addWatch: FAMMonitorDirectory failed\n"); goto out; } } else { if (FAMMonitorFile(&m_conn, path.c_str(), &req, 0) != 0) { - LOGERR(("RclFAM::addWatch: FAMMonitorFile failed\n")); + LOGERR("RclFAM::addWatch: FAMMonitorFile failed\n"); goto out; } } @@ -414,14 +417,14 @@ bool RclFAM::getEvent(RclMonEvent& ev, int msecs) { if (!ok()) return false; - MONDEB(("RclFAM::getEvent:\n")); + MONDEB("RclFAM::getEvent:\n"); fd_set readfds; int fam_fd = FAMCONNECTION_GETFD(&m_conn); FD_ZERO(&readfds); FD_SET(fam_fd, &readfds); - MONDEB(("RclFAM::getEvent: select. fam_fd is %d\n", fam_fd)); + MONDEB("RclFAM::getEvent: select. fam_fd is " << fam_fd << std::endl); // Fam / gamin is sometimes a bit slow to send events. Always add // a little timeout, because if we fail to retrieve enough events, // we risk deadlocking in addwatch() @@ -434,16 +437,16 @@ bool RclFAM::getEvent(RclMonEvent& ev, int msecs) } int ret; if ((ret=select(fam_fd+1, &readfds, 0, 0, msecs >= 0 ? &timeout : 0)) < 0) { - LOGERR(("RclFAM::getEvent: select failed, errno %d\n", errno)); + LOGERR("RclFAM::getEvent: select failed, errno " << errno << "\n"); close(); return false; } else if (ret == 0) { // timeout - MONDEB(("RclFAM::getEvent: select timeout\n")); + MONDEB("RclFAM::getEvent: select timeout\n"); return false; } - MONDEB(("RclFAM::getEvent: select returned %d\n", ret)); + MONDEB("RclFAM::getEvent: select returned " << ret << std::endl); if (!FD_ISSET(fam_fd, &readfds)) return false; @@ -454,29 +457,29 @@ bool RclFAM::getEvent(RclMonEvent& ev, int msecs) // around the issue, but we did not need this in the past and this // is most weird. if (FAMPending(&m_conn) <= 0) { - MONDEB(("RclFAM::getEvent: FAMPending says no events\n")); + MONDEB("RclFAM::getEvent: FAMPending says no events\n"); return false; } - MONDEB(("RclFAM::getEvent: call FAMNextEvent\n")); + MONDEB("RclFAM::getEvent: call FAMNextEvent\n"); FAMEvent fe; if (FAMNextEvent(&m_conn, &fe) < 0) { - LOGERR(("RclFAM::getEvent: FAMNextEvent failed, errno %d\n", errno)); + LOGERR("RclFAM::getEvent: FAMNextEvent: errno " << errno << "\n"); close(); return false; } - MONDEB(("RclFAM::getEvent: FAMNextEvent returned\n")); + MONDEB("RclFAM::getEvent: FAMNextEvent returned\n"); map::const_iterator it; - if ((fe.filename[0] != '/') && + if ((!path_isabsolute(fe.filename)) && (it = m_idtopath.find(fe.fr.reqnum)) != m_idtopath.end()) { ev.m_path = path_cat(it->second, fe.filename); } else { ev.m_path = fe.filename; } - MONDEB(("RclFAM::getEvent: %-12s %s\n", - event_name(fe.code), ev.m_path.c_str())); + MONDEB("RclFAM::getEvent: " << event_name(fe.code) < " " << + ev.m_path << std::endl); switch (fe.code) { case FAMCreated: @@ -510,7 +513,7 @@ bool RclFAM::getEvent(RclMonEvent& ev, int msecs) // Have to return something, this is different from an empty queue, // esp if we are trying to empty it... if (fe.code != FAMEndExist) - LOGDEB(("RclFAM::getEvent: got other event %d!\n", fe.code)); + LOGDEB("RclFAM::getEvent: got other event " << fe.code << "!\n"); ev.m_etyp = RclMonEvent::RCLEVT_NONE; break; } @@ -531,7 +534,7 @@ public: : m_ok(false), m_fd(-1), m_evp(0), m_ep(0) { if ((m_fd = inotify_init()) < 0) { - LOGERR(("RclIntf:: inotify_init failed, errno %d\n", errno)); + LOGERR("RclIntf:: inotify_init failed, errno " << errno << "\n"); return; } m_ok = true; @@ -597,7 +600,7 @@ bool RclIntf::addWatch(const string& path, bool) { if (!ok()) return false; - MONDEB(("RclIntf::addWatch: adding %s\n", path.c_str())); + MONDEB("RclIntf::addWatch: adding " << path << std::endl); // CLOSE_WRITE is covered through MODIFY. CREATE is needed for mkdirs uint32_t mask = IN_MODIFY | IN_CREATE | IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE @@ -615,11 +618,11 @@ bool RclIntf::addWatch(const string& path, bool) int wd; if ((wd = inotify_add_watch(m_fd, path.c_str(), mask)) < 0) { saved_errno = errno; - LOGERR(("RclIntf::addWatch: inotify_add_watch failed. errno %d\n", - saved_errno)); + LOGERR("RclIntf::addWatch: inotify_add_watch failed. errno " << + saved_errno << "\n"); if (errno == ENOSPC) { - LOGERR(("RclIntf::addWatch: ENOSPC error may mean that you need" - "increase the inotify kernel constants. See inotify(7)\n")); + LOGERR("RclIntf::addWatch: ENOSPC error may mean that you should " + "increase the inotify kernel constants. See inotify(7)\n"); } return false; } @@ -634,7 +637,7 @@ bool RclIntf::getEvent(RclMonEvent& ev, int msecs) if (!ok()) return false; ev.m_etyp = RclMonEvent::RCLEVT_NONE; - MONDEB(("RclIntf::getEvent:\n")); + MONDEB("RclIntf::getEvent:\n"); if (m_evp == 0) { fd_set readfds; @@ -646,24 +649,25 @@ bool RclIntf::getEvent(RclMonEvent& ev, int msecs) timeout.tv_usec = (msecs % 1000) * 1000; } int ret; - MONDEB(("RclIntf::getEvent: select\n")); - if ((ret=select(m_fd + 1, &readfds, 0, 0, msecs >= 0 ? &timeout : 0)) < 0) { - LOGERR(("RclIntf::getEvent: select failed, errno %d\n", errno)); + MONDEB("RclIntf::getEvent: select\n"); + if ((ret = select(m_fd + 1, &readfds, 0, 0, msecs >= 0 ? &timeout : 0)) + < 0) { + LOGERR("RclIntf::getEvent: select failed, errno " << errno << "\n"); close(); return false; } else if (ret == 0) { - MONDEB(("RclIntf::getEvent: select timeout\n")); + MONDEB("RclIntf::getEvent: select timeout\n"); // timeout return false; } - MONDEB(("RclIntf::getEvent: select returned\n")); + MONDEB("RclIntf::getEvent: select returned\n"); if (!FD_ISSET(m_fd, &readfds)) return false; int rret; if ((rret=read(m_fd, m_evbuf, sizeof(m_evbuf))) <= 0) { - LOGERR(("RclIntf::getEvent: read failed, %d->%d errno %d\n", - sizeof(m_evbuf), rret, errno)); + LOGERR("RclIntf::getEvent: read failed, " << sizeof(m_evbuf) << + "->" << rret << " errno " << errno << "\n"); close(); return false; } @@ -680,7 +684,7 @@ bool RclIntf::getEvent(RclMonEvent& ev, int msecs) map::const_iterator it; if ((it = m_idtopath.find(evp->wd)) == m_idtopath.end()) { - LOGERR(("RclIntf::getEvent: unknown wd %d\n", evp->wd)); + LOGERR("RclIntf::getEvent: unknown wd " << evp->wd << "\n"); return true; } ev.m_path = it->second; @@ -689,8 +693,8 @@ bool RclIntf::getEvent(RclMonEvent& ev, int msecs) ev.m_path = path_cat(ev.m_path, evp->name); } - MONDEB(("RclIntf::getEvent: %-12s %s\n", - event_name(evp->mask), ev.m_path.c_str())); + MONDEB("RclIntf::getEvent: " << event_name(evp->mask) << " " << + ev.m_path << std::endl); if ((evp->mask & IN_MOVED_FROM) && (evp->mask & IN_ISDIR)) { // We get this when a directory is renamed. Erase the subtree @@ -720,13 +724,13 @@ bool RclIntf::getEvent(RclMonEvent& ev, int msecs) } } else if (evp->mask & (IN_IGNORED)) { if (!m_idtopath.erase(evp->wd)) { - LOGDEB0(("Got IGNORE event for unknown watch\n")); + LOGDEB0("Got IGNORE event for unknown watch\n"); } else { eraseWatchSubTree(m_idtopath, ev.m_path); } } else { - LOGDEB(("RclIntf::getEvent: unhandled event %s 0x%x %s\n", - event_name(evp->mask), evp->mask, ev.m_path.c_str())); + LOGDEB("RclIntf::getEvent: unhandled event " << event_name(evp->mask) << + " " << evp->mask << " " << ev.m_path << "\n"); return true; } return true; @@ -747,8 +751,9 @@ static RclMonitor *makeMonitor() return new RclFAM; #endif #endif - LOGINFO(("RclMonitor: neither Inotify nor Fam was compiled as " - "file system change notification interface\n")); + LOGINFO("RclMonitor: neither Inotify nor Fam was compiled as file system " + "change notification interface\n"); return 0; } #endif // RCL_MONITOR + diff --git a/src/index/recollindex.cpp b/src/index/recollindex.cpp index 7044d103..c1a86acd 100644 --- a/src/index/recollindex.cpp +++ b/src/index/recollindex.cpp @@ -14,16 +14,20 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef HAVE_CONFIG_H #include "autoconfig.h" -#endif #include #include #include +#include +#ifndef _WIN32 #include #include -#include +#else +#include +#endif +#include "safefcntl.h" +#include "safeunistd.h" #include #include @@ -32,19 +36,25 @@ using namespace std; -#include "debuglog.h" +#include "log.h" #include "rclinit.h" #include "indexer.h" #include "smallut.h" +#include "chrono.h" #include "pathut.h" +#include "rclutil.h" #include "rclmon.h" #include "x11mon.h" #include "cancelcheck.h" #include "rcldb.h" +#ifndef DISABLE_WEB_INDEXER #include "beaglequeue.h" +#endif #include "recollindex.h" #include "fsindexer.h" +#ifndef _WIN32 #include "rclionice.h" +#endif #include "execmd.h" #include "checkretryfailed.h" @@ -71,7 +81,7 @@ static int op_flags; #define OPT_r 0x40000 #define OPT_k 0x80000 #define OPT_E 0x100000 - +#define OPT_K 0x200000 ReExec *o_reexec; // Globals for atexit cleanup @@ -81,6 +91,7 @@ static ConfIndexer *confindexer; static void cleanup() { deleteZ(confindexer); + recoll_exitready(); } // Global stop request flag. This is checked in a number of place in the @@ -93,56 +104,61 @@ int stopindexing; class MyUpdater : public DbIxStatusUpdater { public: MyUpdater(const RclConfig *config) - : m_prevphase(DbIxStatus::DBIXS_NONE) - { - m_fd = open(config->getIdxStatusFile().c_str(), - O_WRONLY|O_CREAT|O_TRUNC, 0600); - if (m_fd < 0) - LOGERR(("Can't open/create status file: [%s]\n", - config->getIdxStatusFile().c_str())); + : m_file(config->getIdxStatusFile().c_str()), + m_prevphase(DbIxStatus::DBIXS_NONE) { + // The total number of files included in the index is actually + // difficult to compute from the index itself. For display + // purposes, we save it in the status file from indexing to + // indexing (mostly...) + string stf; + if (m_file.get("totfiles", stf)) { + status.totfiles = atoi(stf.c_str()); + } } virtual bool update() { - // Update the status file. Avoid doing it too often - if (status.phase != m_prevphase || m_chron.millis() > 300) { + // Update the status file. Avoid doing it too often. Always do + // it at the end (status DONE) + if (status.phase == DbIxStatus::DBIXS_DONE || + status.phase != m_prevphase || m_chron.millis() > 300) { + if (status.totfiles < status.filesdone || + status.phase == DbIxStatus::DBIXS_DONE) { + status.totfiles = status.filesdone; + } m_prevphase = status.phase; m_chron.restart(); - lseek(m_fd, 0, 0); - int fd1 = dup(m_fd); - FILE *fp = fdopen(fd1, "w"); - fprintf(fp, "phase = %d\n", int(status.phase)); - fprintf(fp, "docsdone = %d\n", status.docsdone); - fprintf(fp, "filesdone = %d\n", status.filesdone); - fprintf(fp, "dbtotdocs = %d\n", status.dbtotdocs); - fprintf(fp, "fn = %s\n", status.fn.c_str()); - if (ftruncate(m_fd, off_t(ftell(fp))) < 0) { - // ? kill compiler warning about ignoring ftruncate return - LOGDEB(("Status update: ftruncate failed\n")); - } - // Flush data and closes fd1. m_fd still valid - fclose(fp); + m_file.holdWrites(true); + m_file.set("phase", int(status.phase)); + m_file.set("docsdone", status.docsdone); + m_file.set("filesdone", status.filesdone); + m_file.set("fileerrors", status.fileerrors); + m_file.set("dbtotdocs", status.dbtotdocs); + m_file.set("totfiles", status.totfiles); + m_file.set("fn", status.fn); + m_file.holdWrites(false); } if (stopindexing) { return false; } +#ifndef DISABLE_X11MON // If we are in the monitor, we also need to check X11 status // during the initial indexing pass (else the user could log // out and the indexing would go on, not good (ie: if the user // logs in again, the new recollindex will fail). if ((op_flags & OPT_m) && !(op_flags & OPT_x) && !x11IsAlive()) { - LOGDEB(("X11 session went away during initial indexing pass\n")); + LOGDEB("X11 session went away during initial indexing pass\n" ); stopindexing = true; return false; } - +#endif return true; } private: - int m_fd; + ConfSimple m_file; Chrono m_chron; DbIxStatus::Phase m_prevphase; }; @@ -151,7 +167,7 @@ static MyUpdater *updater; static void sigcleanup(int sig) { fprintf(stderr, "Got signal, registering stop request\n"); - LOGDEB(("Got signal, registering stop request\n")); + LOGDEB("Got signal, registering stop request\n" ); CancelCheck::instance().setCancel(); stopindexing = 1; } @@ -171,36 +187,57 @@ static void makeIndexerOrExit(RclConfig *config, bool inPlaceReset) void rclIxIonice(const RclConfig *config) { +#ifndef _WIN32 string clss, classdata; if (!config->getConfParam("monioniceclass", clss) || clss.empty()) clss = "3"; config->getConfParam("monioniceclassdata", classdata); rclionice(clss, classdata); +#endif } class MakeListWalkerCB : public FsTreeWalkerCB { public: - MakeListWalkerCB(list& files) - : m_files(files) + MakeListWalkerCB(list& files, const vector& selpats) + : m_files(files), m_pats(selpats) { } virtual FsTreeWalker::Status processone(const string& fn, const struct stat *, FsTreeWalker::CbFlag flg) { - if (flg == FsTreeWalker::FtwDirEnter || flg == FsTreeWalker::FtwRegular) - m_files.push_back(fn); + if (flg== FsTreeWalker::FtwDirEnter || flg == FsTreeWalker::FtwRegular){ + if (m_pats.empty()) { + cerr << "Selecting " << fn << endl; + m_files.push_back(fn); + } else { + for (vector::const_iterator it = m_pats.begin(); + it != m_pats.end(); it++) { + if (fnmatch(it->c_str(), fn.c_str(), 0) == 0) { + m_files.push_back(fn); + break; + } + } + } + } return FsTreeWalker::FtwOk; } list& m_files; + const vector& m_pats; }; -// Build a list of things to index and call indexfiles. -bool recursive_index(RclConfig *config, const string& top) +// Build a list of things to index, then call purgefiles and/or +// indexfiles. This is basically the same as find xxx | recollindex +// -i [-e] without the find (so, simpler but less powerfull) +bool recursive_index(RclConfig *config, const string& top, + const vector& selpats) { list files; - MakeListWalkerCB cb(files); + MakeListWalkerCB cb(files, selpats); FsTreeWalker walker; walker.walk(top, cb); + if (op_flags & OPT_e) { + purgefiles(config, files); + } return indexfiles(config, files); } @@ -217,9 +254,13 @@ bool indexfiles(RclConfig *config, list &filenames) if (filenames.empty()) return true; makeIndexerOrExit(config, (op_flags & OPT_Z) != 0); - return confindexer->indexFiles(filenames, (op_flags&OPT_f) ? - ConfIndexer::IxFIgnoreSkip : - ConfIndexer::IxFNone); + // The default is to retry failed files + int indexerFlags = ConfIndexer::IxFNone; + if (op_flags & OPT_K) + indexerFlags |= ConfIndexer::IxFNoRetryFailed; + if (op_flags & OPT_f) + indexerFlags |= ConfIndexer::IxFIgnoreSkip; + return confindexer->indexFiles(filenames, indexerFlags); } // Delete a list of files. Same comments about call contexts as indexfiles. @@ -261,48 +302,29 @@ static bool checktopdirs(RclConfig *config, vector& nonexist) vector tdl; if (!config->getConfParam("topdirs", &tdl)) { cerr << "No 'topdirs' parameter in configuration\n"; - LOGERR(("recollindex:No 'topdirs' parameter in configuration\n"));; + LOGERR("recollindex:No 'topdirs' parameter in configuration\n" );; return false; } for (vector::iterator it = tdl.begin(); it != tdl.end(); it++) { *it = path_tildexpand(*it); - if (!it->size() || (*it)[0] != '/') { + if (!it->size() || !path_isabsolute(*it)) { if ((*it)[0] == '~') { cerr << "Tilde expansion failed: " << *it << endl; - LOGERR(("recollindex: tilde expansion failed: %s\n", - it->c_str())); + LOGERR("recollindex: tilde expansion failed: " << *it << "\n" ); } else { cerr << "Not an absolute path: " << *it << endl; - LOGERR(("recollindex: not an absolute path: %s\n", - it->c_str())); + LOGERR("recollindex: not an absolute path: " << *it << "\n" ); } return false; } - if (access(it->c_str(), 0) < 0) { + if (!path_exists(*it)) { nonexist.push_back(*it); } } - // Check skippedPaths too, but only the user part (shallow==true), not - // the default values (e.g. /media, which might not exist). - if (config->getConfParam("skippedPaths", &tdl, true)) { - for (vector::iterator it = tdl.begin(); it != tdl.end(); it++) { - *it = path_tildexpand(*it); - if (access(it->c_str(), 0) < 0) { - nonexist.push_back(*it); - } - } - } + // We'd like to check skippedPaths too, but these are wildcard exprs, so reasonably can't - if (config->getConfParam("daemSkippedPaths", &tdl, true)) { - for (vector::iterator it = tdl.begin(); it != tdl.end(); it++) { - *it = path_tildexpand(*it); - if (access(it->c_str(), 0) < 0) { - nonexist.push_back(*it); - } - } - } return true; } @@ -333,8 +355,11 @@ static const char usage [] = "recollindex -i [-f] [-Z] \n" " Index individual files. No database purge or stem database updates\n" " -f : ignore skippedPaths and skippedNames while doing this\n" -"recollindex -r [-f] [-Z] \n" -" Recursive partial reindex\n" +"recollindex -r [-K] [-f] [-Z] [-p pattern] \n" +" Recursive partial reindex. \n" +" -p : filter file names, multiple instances are allowed, e.g.: \n" +" -p *.odt -p *.pdf\n" +" -K : skip previously failed files (they are retried by default)\n" "recollindex -l\n" " List available stemming languages\n" "recollindex -s \n" @@ -383,12 +408,15 @@ int main(int argc, char **argv) { string a_config; int sleepsecs = 60; + vector selpatterns; // The reexec struct is used by the daemon to shed memory after // the initial indexing pass and to restart when the configuration // changes +#ifndef _WIN32 o_reexec = new ReExec; o_reexec->init(argc, argv); +#endif thisprog = argv[0]; argc--; argv++; @@ -413,9 +441,13 @@ int main(int argc, char **argv) case 'h': op_flags |= OPT_h; break; case 'i': op_flags |= OPT_i; break; case 'k': op_flags |= OPT_k; break; + case 'K': op_flags |= OPT_K; break; case 'l': op_flags |= OPT_l; break; case 'm': op_flags |= OPT_m; break; case 'n': op_flags |= OPT_n; break; + case 'p': if (argc < 2) Usage(); + selpatterns.push_back(*(++argv)); + argc--; goto b1; case 'r': op_flags |= OPT_r; break; case 's': op_flags |= OPT_s; break; #ifdef RCL_USE_ASPELL @@ -446,18 +478,20 @@ int main(int argc, char **argv) Usage(); if ((op_flags & OPT_Z) && (op_flags & (OPT_m))) Usage(); - if ((op_flags & OPT_E) && (op_flags & ~OPT_E)) { + if ((op_flags & OPT_E) && (op_flags & ~(OPT_E|OPT_c))) { Usage(); } string reason; RclInitFlags flags = (op_flags & OPT_m) && !(op_flags&OPT_D) ? - RCLINIT_DAEMON : RCLINIT_NONE; + RCLINIT_DAEMON : RCLINIT_IDX; config = recollinit(flags, cleanup, sigcleanup, reason, &a_config); if (config == 0 || !config->ok()) { cerr << "Configuration problem: " << reason << endl; exit(1); } +#ifndef _WIN32 o_reexec->atexit(cleanup); +#endif vector nonexist; if (!checktopdirs(config, nonexist)) @@ -481,18 +515,14 @@ int main(int argc, char **argv) string rundir; config->getConfParam("idxrundir", rundir); if (!rundir.compare("tmp")) { - LOGINFO(("recollindex: changing current directory to [%s]\n", - tmplocation().c_str())); + LOGINFO("recollindex: changing current directory to [" << (tmplocation()) << "]\n" ); if (chdir(tmplocation().c_str()) < 0) { - LOGERR(("chdir(%s) failed, errno %d\n", - tmplocation().c_str(), errno)); + LOGERR("chdir(" << (tmplocation()) << ") failed, errno " << (errno) << "\n" ); } } else if (!rundir.empty()) { - LOGINFO(("recollindex: changing current directory to [%s]\n", - rundir.c_str())); + LOGINFO("recollindex: changing current directory to [" << (rundir) << "]\n" ); if (chdir(rundir.c_str()) < 0) { - LOGERR(("chdir(%s) failed, errno %d\n", - rundir.c_str(), errno)); + LOGERR("chdir(" << (rundir) << ") failed, errno " << (errno) << "\n" ); } } @@ -515,15 +545,24 @@ int main(int argc, char **argv) // Log something at LOGINFO to reset the trace file. Else at level // 3 it's not even truncated if all docs are up to date. - LOGINFO(("recollindex: starting up\n")); - + LOGINFO("recollindex: starting up\n" ); +#ifndef _WIN32 if (setpriority(PRIO_PROCESS, 0, 20) != 0) { - LOGINFO(("recollindex: can't setpriority(), errno %d\n", errno)); + LOGINFO("recollindex: can't setpriority(), errno " << (errno) << "\n" ); } // Try to ionice. This does not work on all platforms rclIxIonice(config); +#endif - if (op_flags & (OPT_i|OPT_e)) { + if (op_flags & OPT_r) { + if (argc != 1) + Usage(); + string top = *argv++; argc--; + bool status = recursive_index(config, top, selpatterns); + if (confindexer && !confindexer->getReason().empty()) + cerr << confindexer->getReason() << endl; + exit(status ? 0 : 1); + } else if (op_flags & (OPT_i|OPT_e)) { lockorexit(&pidfile); list filenames; @@ -555,14 +594,6 @@ int main(int argc, char **argv) if (confindexer && !confindexer->getReason().empty()) cerr << confindexer->getReason() << endl; exit(status ? 0 : 1); - } else if (op_flags & OPT_r) { - if (argc != 1) - Usage(); - string top = *argv++; argc--; - bool status = recursive_index(config, top); - if (confindexer && !confindexer->getReason().empty()) - cerr << confindexer->getReason() << endl; - exit(status ? 0 : 1); } else if (op_flags & OPT_l) { if (argc != 0) Usage(); @@ -589,42 +620,44 @@ int main(int argc, char **argv) Usage(); lockorexit(&pidfile); if (!(op_flags&OPT_D)) { - LOGDEB(("recollindex: daemonizing\n")); + LOGDEB("recollindex: daemonizing\n" ); +#ifndef _WIN32 if (daemon(0,0) != 0) { fprintf(stderr, "daemon() failed, errno %d\n", errno); - LOGERR(("daemon() failed, errno %d\n", errno)); + LOGERR("daemon() failed, errno " << (errno) << "\n" ); exit(1); } +#endif } // Need to rewrite pid, it changed pidfile.write_pid(); - +#ifndef _WIN32 // Not too sure if I have to redo the nice thing after daemon(), // can't hurt anyway (easier than testing on all platforms...) if (setpriority(PRIO_PROCESS, 0, 20) != 0) { - LOGINFO(("recollindex: can't setpriority(), errno %d\n", errno)); + LOGINFO("recollindex: can't setpriority(), errno " << (errno) << "\n" ); } // Try to ionice. This does not work on all platforms rclIxIonice(config); +#endif if (sleepsecs > 0) { - LOGDEB(("recollindex: sleeping %d\n", sleepsecs)); + LOGDEB("recollindex: sleeping " << (sleepsecs) << "\n" ); for (int i = 0; i < sleepsecs; i++) { sleep(1); // Check that x11 did not go away while we were sleeping. if (!(op_flags & OPT_x) && !x11IsAlive()) { - LOGDEB(("X11 session went away during initial sleep period\n")); + LOGDEB("X11 session went away during initial sleep period\n" ); exit(0); } } } if (!(op_flags & OPT_n)) { makeIndexerOrExit(config, inPlaceReset); - LOGDEB(("Recollindex: initial indexing pass before monitoring\n")); + LOGDEB("Recollindex: initial indexing pass before monitoring\n" ); if (!confindexer->index(rezero, ConfIndexer::IxTAll, indexerFlags) || stopindexing) { - LOGERR(("recollindex, initial indexing pass failed, " - "not going into monitor mode\n")); + LOGERR("recollindex, initial indexing pass failed, not going into monitor mode\n" ); exit(1); } else { // Record success of indexing pass with failed files retries. @@ -633,13 +666,15 @@ int main(int argc, char **argv) } } deleteZ(confindexer); +#ifndef _WIN32 o_reexec->insertArgs(vector(1, "-n")); - LOGINFO(("recollindex: reexecuting with -n after initial full pass\n")); + LOGINFO("recollindex: reexecuting with -n after initial full pass\n" ); // Note that -n will be inside the reexec when we come // back, but the monitor will explicitely strip it before // starting a config change exec to ensure that we do a // purging pass in this case. o_reexec->reexec(); +#endif } if (updater) { updater->status.phase = DbIxStatus::DBIXS_MONITOR; @@ -666,7 +701,6 @@ int main(int argc, char **argv) makeIndexerOrExit(config, inPlaceReset); bool status = confindexer->index(rezero, ConfIndexer::IxTAll, indexerFlags); - // Record success of indexing pass with failed files retries. if (status && !(indexerFlags & ConfIndexer::IxFNoRetryFailed)) { checkRetryFailed(config, true); @@ -684,3 +718,4 @@ int main(int argc, char **argv) return !status; } } + diff --git a/src/index/subtreelist.cpp b/src/index/subtreelist.cpp index 9396ec3a..c7fe1ecc 100644 --- a/src/index/subtreelist.cpp +++ b/src/index/subtreelist.cpp @@ -16,28 +16,29 @@ */ #ifndef TEST_SUBTREELIST +#include "autoconfig.h" + +#include #include "cstr.h" -#include "refcntr.h" #include "rcldb.h" #include "searchdata.h" #include "rclquery.h" #include "subtreelist.h" -#include "debuglog.h" +#include "log.h" bool subtreelist(RclConfig *config, const string& top, vector& paths) { - LOGDEB(("subtreelist: top: [%s]\n", top.c_str())); + LOGDEB("subtreelist: top: [" << (top) << "]\n" ); Rcl::Db rcldb(config); if (!rcldb.open(Rcl::Db::DbRO)) { - LOGERR(("subtreelist: can't open database in [%s]: %s\n", - config->getDbDir().c_str(), rcldb.getReason().c_str())); + LOGERR("subtreelist: can't open database in [" << (config->getDbDir()) << "]: " << (rcldb.getReason()) << "\n" ); return false; } Rcl::SearchData *sd = new Rcl::SearchData(Rcl::SCLT_OR, cstr_null); - RefCntr rq(sd); + std::shared_ptr rq(sd); sd->addClause(new Rcl::SearchDataClausePath(top, false)); @@ -61,7 +62,6 @@ bool subtreelist(RclConfig *config, const string& top, #include #include -#include #include #include @@ -128,3 +128,4 @@ int main(int argc, char **argv) exit(0); } #endif + diff --git a/src/internfile/Filter.h b/src/internfile/Filter.h index c0b309f3..742567ef 100644 --- a/src/internfile/Filter.h +++ b/src/internfile/Filter.h @@ -108,7 +108,7 @@ namespace Dijon */ virtual bool set_document_data(const std::string& mtype, const char *data_ptr, - unsigned int data_length) = 0; + size_t data_length) = 0; /** (Re)initializes the filter with the given data. * Call next_document() to position the filter onto the first document. @@ -140,7 +140,7 @@ namespace Dijon stat() calls The value is stored inside metaData, docsize key */ - virtual void set_docsize(size_t size) = 0; + virtual void set_docsize(off_t size) = 0; // Going from one nested document to the next. diff --git a/src/internfile/Makefile b/src/internfile/Makefile index aa6cb90f..8520ef50 100644 --- a/src/internfile/Makefile +++ b/src/internfile/Makefile @@ -1,25 +1,20 @@ -depth = .. -include $(depth)/mk/sysconf - -# Only test executables get build in here PROGS = mh_mbox internfile -all: librecoll $(PROGS) +all: $(PROGS) -INTERNFILE_OBJS= trinternfile.o $(BIGLIB) +INTERNFILE_OBJS= trinternfile.o internfile : $(INTERNFILE_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o internfile $(INTERNFILE_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) $(ALL_CXXFLAGS) -o internfile $(INTERNFILE_OBJS) $(LIBRECOLL) trinternfile.o : internfile.cpp $(CXX) $(ALL_CXXFLAGS) -DTEST_INTERNFILE -c -o trinternfile.o \ internfile.cpp MH_MBOX_OBJS= trmh_mbox.o $(BIGLIB) mh_mbox : $(MH_MBOX_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o mh_mbox $(MH_MBOX_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) $(ALL_CXXFLAGS) -o mh_mbox $(MH_MBOX_OBJS) $(LIBRECOLL) trmh_mbox.o : mh_mbox.cpp $(CXX) $(ALL_CXXFLAGS) -DTEST_MH_MBOX -c -o trmh_mbox.o \ mh_mbox.cpp -include $(depth)/mk/commontargets +include ../utils/utmkdefs.mk + diff --git a/src/internfile/extrameta.cpp b/src/internfile/extrameta.cpp index 3466c1a6..12070172 100644 --- a/src/internfile/extrameta.cpp +++ b/src/internfile/extrameta.cpp @@ -21,7 +21,7 @@ #include "rclconfig.h" #include "pxattr.h" -#include "debuglog.h" +#include "log.h" #include "cstr.h" #include "rcldoc.h" #include "execmd.h" @@ -33,8 +33,7 @@ static void docfieldfrommeta(RclConfig* cfg, const string& name, const string &value, Rcl::Doc& doc) { string fieldname = cfg->fieldCanon(name); - LOGDEB0(("Internfile:: setting [%s] from cmd/xattr value [%s]\n", - fieldname.c_str(), value.c_str())); + LOGDEB0("Internfile:: setting [" << (fieldname) << "] from cmd/xattr value [" << (value) << "]\n" ); if (fieldname == cstr_dj_keymd) { doc.dmtime = value; } else { @@ -45,12 +44,16 @@ static void docfieldfrommeta(RclConfig* cfg, const string& name, void reapXAttrs(const RclConfig* cfg, const string& path, map& xfields) { - LOGDEB2(("reapXAttrs: [%s]\n", path.c_str())); - + LOGDEB2("reapXAttrs: [" << (path) << "]\n" ); +#ifndef _WIN32 // Retrieve xattrs names from files and mapping table from config vector xnames; if (!pxattr::list(path, &xnames)) { - LOGERR(("FileInterner::reapXattrs: pxattr::list: errno %d\n", errno)); + if (errno == ENOTSUP) { + LOGDEB("FileInterner::reapXattrs: pxattr::list: errno " << (errno) << "\n" ); + } else { + LOGERR("FileInterner::reapXattrs: pxattr::list: errno " << (errno) << "\n" ); + } return; } const map& xtof = cfg->getXattrToField(); @@ -71,14 +74,14 @@ void reapXAttrs(const RclConfig* cfg, const string& path, } string value; if (!pxattr::get(path, *it, &value, pxattr::PXATTR_NOFOLLOW)) { - LOGERR(("FileInterner::reapXattrs: pxattr::get failed" - "for %s, errno %d\n", (*it).c_str(), errno)); + LOGERR("FileInterner::reapXattrs: pxattr::get failedfor " << ((*it)) << ", errno " << (errno) << "\n" ); continue; } // Encode should we ? xfields[key] = value; - LOGDEB2(("reapXAttrs: [%s] -> [%s]\n", key.c_str(), value.c_str())); + LOGDEB2("reapXAttrs: [" << (key) << "] -> [" << (value) << "]\n" ); } +#endif } void docFieldsFromXattrs(RclConfig *cfg, const map& xfields, @@ -96,7 +99,7 @@ void reapMetaCmds(RclConfig* cfg, const string& path, const vector& reapers = cfg->getMDReapers(); if (reapers.empty()) return; - map smap = create_map('f', path); + map smap = {{'f', path}}; for (vector::const_iterator rp = reapers.begin(); rp != reapers.end(); rp++) { vector cmd; @@ -143,3 +146,4 @@ void docFieldsFromMetaCmds(RclConfig *cfg, const map& cfields, } } } + diff --git a/src/internfile/indextext.h b/src/internfile/indextext.h index 7e700dcb..3fc9fe88 100644 --- a/src/internfile/indextext.h +++ b/src/internfile/indextext.h @@ -18,9 +18,6 @@ #define _INDEXTEXT_H_INCLUDED_ /* Note: this only exists to help with using myhtmlparse.cc */ -// Minimize changes to myhtmlparse.cpp -#include "debuglog.h" - #include // lets hope that the charset includes ascii values... diff --git a/src/internfile/internfile.cpp b/src/internfile/internfile.cpp index e34168cb..55a1339e 100644 --- a/src/internfile/internfile.cpp +++ b/src/internfile/internfile.cpp @@ -19,24 +19,23 @@ #include "autoconfig.h" #include -#include -#include -#include -#include #include +#include "safefcntl.h" +#include +#include "safesysstat.h" +#include "safeunistd.h" #include #include #include -#ifndef NO_NAMESPACES + using namespace std; -#endif /* NO_NAMESPACES */ #include "cstr.h" #include "internfile.h" #include "rcldoc.h" #include "mimetype.h" -#include "debuglog.h" +#include "log.h" #include "mimehandler.h" #include "execmd.h" #include "pathut.h" @@ -77,8 +76,7 @@ static string colon_restore(const string& in) // (ie message having a given attachment) bool FileInterner::getEnclosingUDI(const Rcl::Doc &doc, string& udi) { - LOGDEB(("FileInterner::getEnclosingUDI(): url [%s] ipath [%s]\n", - doc.url.c_str(), doc.ipath.c_str())); + LOGDEB("FileInterner::getEnclosingUDI(): url [" << (doc.url) << "] ipath [" << (doc.ipath) << "]\n" ); string eipath = doc.ipath; string::size_type colon; if (eipath.empty()) @@ -103,6 +101,12 @@ string FileInterner::getLastIpathElt(const string& ipath) } } +bool FileInterner::ipathContains(const string& parent, const string& child) +{ + return child.find(parent) == 0 && + child.find(cstr_isep, parent.size()) == parent.size(); +} + // Constructor: identify the input file, possibly create an // uncompressed temporary copy, and create the top filter for the // uncompressed file type. @@ -114,9 +118,9 @@ FileInterner::FileInterner(const string &fn, const struct stat *stp, RclConfig *cnf, int flags, const string *imime) : m_ok(false), m_missingdatap(0), m_uncomp((flags & FIF_forPreview) != 0) { - LOGDEB0(("FileInterner::FileInterner(fn=%s)\n", fn.c_str())); + LOGDEB0("FileInterner::FileInterner(fn=" << (fn) << ")\n" ); if (fn.empty()) { - LOGERR(("FileInterner::FileInterner: empty file name!\n")); + LOGERR("FileInterner::FileInterner: empty file name!\n" ); return; } initcommon(cnf, flags); @@ -127,7 +131,7 @@ void FileInterner::init(const string &f, const struct stat *stp, RclConfig *cnf, int flags, const string *imime) { if (f.empty()) { - LOGERR(("FileInterner::init: empty file name!\n")); + LOGERR("FileInterner::init: empty file name!\n" ); return; } m_fn = f; @@ -152,13 +156,14 @@ void FileInterner::init(const string &f, const struct stat *stp, RclConfig *cnf, // (e.g. the beagle indexer sets it). if (flags & FIF_doUseInputMimetype) { if (!imime) { - LOGERR(("FileInterner:: told to use null imime\n")); + LOGERR("FileInterner:: told to use null imime\n" ); return; } l_mime = *imime; } else { - LOGDEB(("FileInterner::init fn [%s] mime [%s] preview %d\n", - f.c_str(), imime?imime->c_str() : "(null)", m_forPreview)); + LOGDEB("FileInterner::init fn [" << f << "] mime [" << + (imime?imime->c_str() : "(null)") << "] preview " << m_forPreview + << "\n" ); // Run mime type identification in any case (see comment above). l_mime = mimetype(m_fn, stp, m_cfg, usfci); @@ -170,7 +175,7 @@ void FileInterner::init(const string &f, const struct stat *stp, RclConfig *cnf, l_mime = *imime; } - size_t docsize = stp->st_size; + off_t docsize = stp->st_size; if (!l_mime.empty()) { // Has mime: check for a compressed file. If so, create a @@ -185,14 +190,12 @@ void FileInterner::init(const string &f, const struct stat *stp, RclConfig *cnf, if (!m_uncomp.uncompressfile(m_fn, ucmd, m_tfile)) { return; } - LOGDEB1(("FileInterner:: after ucomp: tfile %s\n", - m_tfile.c_str())); + LOGDEB1("FileInterner:: after ucomp: tfile " << (m_tfile) << "\n" ); m_fn = m_tfile; // Stat the uncompressed file, mainly to get the size struct stat ucstat; - if (stat(m_fn.c_str(), &ucstat) != 0) { - LOGERR(("FileInterner: can't stat the uncompressed file" - "[%s] errno %d\n", m_fn.c_str(), errno)); + if (path_fileprops(m_fn, &ucstat) != 0) { + LOGERR("FileInterner: can't stat the uncompressed file[" << (m_fn) << "] errno " << (errno) << "\n" ); return; } else { docsize = ucstat.st_size; @@ -201,8 +204,7 @@ void FileInterner::init(const string &f, const struct stat *stp, RclConfig *cnf, if (l_mime.empty() && imime) l_mime = *imime; } else { - LOGINFO(("FileInterner:: %s over size limit %d kbs\n", - m_fn.c_str(), maxkbs)); + LOGINFO("FileInterner:: " << (m_fn) << " over size limit " << (maxkbs) << " kbs\n" ); } } } @@ -210,17 +212,16 @@ void FileInterner::init(const string &f, const struct stat *stp, RclConfig *cnf, if (l_mime.empty()) { // No mime type. We let it through as config may warrant that // we index all file names - LOGDEB0(("FileInterner:: no mime: [%s]\n", m_fn.c_str())); + LOGDEB0("FileInterner:: no mime: [" << (m_fn) << "]\n" ); } // Look for appropriate handler (might still return empty) m_mimetype = l_mime; RecollFilter *df = getMimeHandler(l_mime, m_cfg, !m_forPreview); - if (!df or df->is_unknown()) { + if (!df || df->is_unknown()) { // No real handler for this type, for now :( - LOGDEB(("FileInterner:: unprocessed mime: [%s] [%s]\n", - l_mime.c_str(), f.c_str())); + LOGDEB("FileInterner:: unprocessed mime: [" << (l_mime) << "] [" << (f) << "]\n" ); if (!df) return; } @@ -240,12 +241,12 @@ void FileInterner::init(const string &f, const struct stat *stp, RclConfig *cnf, df->set_docsize(docsize); if (!df->set_document_file(l_mime, m_fn)) { delete df; - LOGERR(("FileInterner:: error converting %s\n", m_fn.c_str())); + LOGERR("FileInterner:: error converting " << (m_fn) << "\n" ); return; } m_handlers.push_back(df); - LOGDEB(("FileInterner:: init ok %s [%s]\n", l_mime.c_str(), m_fn.c_str())); + LOGDEB("FileInterner:: init ok " << (l_mime) << " [" << (m_fn) << "]\n" ); m_ok = true; } @@ -254,7 +255,7 @@ FileInterner::FileInterner(const string &data, RclConfig *cnf, int flags, const string& imime) : m_ok(false), m_missingdatap(0), m_uncomp((flags & FIF_forPreview) != 0) { - LOGDEB0(("FileInterner::FileInterner(data)\n")); + LOGDEB0("FileInterner::FileInterner(data)\n" ); initcommon(cnf, flags); init(data, cnf, flags, imime); } @@ -263,7 +264,7 @@ void FileInterner::init(const string &data, RclConfig *cnf, int flags, const string& imime) { if (imime.empty()) { - LOGERR(("FileInterner: inmemory constructor needs input mime type\n")); + LOGERR("FileInterner: inmemory constructor needs input mime type\n" ); return; } m_mimetype = imime; @@ -274,7 +275,7 @@ void FileInterner::init(const string &data, RclConfig *cnf, if (!df) { // No handler for this type, for now :( if indexallfilenames // is set in the config, this normally wont happen (we get mh_unknown) - LOGDEB(("FileInterner:: unprocessed mime [%s]\n", m_mimetype.c_str())); + LOGDEB("FileInterner:: unprocessed mime [" << (m_mimetype) << "]\n" ); return; } df->set_property(Dijon::Filter::OPERATING_MODE, @@ -288,15 +289,14 @@ void FileInterner::init(const string &data, RclConfig *cnf, result = df->set_document_data(m_mimetype, data.c_str(), data.length()); } else if (df->is_data_input_ok(Dijon::Filter::DOCUMENT_FILE_NAME)) { TempFile temp = dataToTempFile(data, m_mimetype); - if (temp.isNotNull() && + if (temp && (result = df->set_document_file(m_mimetype, temp->filename()))) { m_tmpflgs[m_handlers.size()] = true; m_tempfiles.push_back(temp); } } if (!result) { - LOGINFO(("FileInterner:: set_doc failed inside for mtype %s\n", - m_mimetype.c_str())); + LOGINFO("FileInterner:: set_doc failed inside for mtype " << (m_mimetype) << "\n" ); delete df; return; } @@ -314,22 +314,23 @@ void FileInterner::initcommon(RclConfig *cnf, int flags) m_tmpflgs[i] = false; m_targetMType = cstr_textplain; m_cfg->getConfParam("noxattrfields", &m_noxattrs); + m_direct = false; } FileInterner::FileInterner(const Rcl::Doc& idoc, RclConfig *cnf, int flags) : m_ok(false), m_missingdatap(0), m_uncomp(((flags & FIF_forPreview) != 0)) { - LOGDEB0(("FileInterner::FileInterner(idoc)\n")); + LOGDEB0("FileInterner::FileInterner(idoc)\n" ); initcommon(cnf, flags); - DocFetcher *fetcher = docFetcherMake(idoc); + DocFetcher *fetcher = docFetcherMake(cnf, idoc); if (fetcher == 0) { - LOGERR(("FileInterner:: no backend\n")); + LOGERR("FileInterner:: no backend\n" ); return; } DocFetcher::RawDoc rawdoc; if (!fetcher->fetch(cnf, idoc, rawdoc)) { - LOGERR(("FileInterner:: fetcher failed\n")); + LOGERR("FileInterner:: fetcher failed\n" ); return; } switch (rawdoc.kind) { @@ -339,17 +340,21 @@ FileInterner::FileInterner(const Rcl::Doc& idoc, RclConfig *cnf, int flags) case DocFetcher::RawDoc::RDK_DATA: init(rawdoc.data, cnf, flags, idoc.mimetype); break; + case DocFetcher::RawDoc::RDK_DATADIRECT: + init(rawdoc.data, cnf, flags, idoc.mimetype); + m_direct = true; + break; default: - LOGERR(("FileInterner::FileInterner(idoc): bad rawdoc kind ??\n")); + LOGERR("FileInterner::FileInterner(idoc): bad rawdoc kind ??\n" ); } return; } bool FileInterner::makesig(RclConfig *cnf, const Rcl::Doc& idoc, string& sig) { - DocFetcher *fetcher = docFetcherMake(idoc); + DocFetcher *fetcher = docFetcherMake(cnf, idoc); if (fetcher == 0) { - LOGERR(("FileInterner::makesig no backend for doc\n")); + LOGERR("FileInterner::makesig no backend for doc\n" ); return false; } @@ -376,14 +381,12 @@ TempFile FileInterner::dataToTempFile(const string& dt, const string& mt) // Create temp file with appropriate suffix for mime type TempFile temp(new TempFileInternal(m_cfg->getSuffixFromMimeType(mt))); if (!temp->ok()) { - LOGERR(("FileInterner::dataToTempFile: cant create tempfile: %s\n", - temp->getreason().c_str())); + LOGERR("FileInterner::dataToTempFile: cant create tempfile: " << (temp->getreason()) << "\n" ); return TempFile(); } string reason; if (!stringtofile(dt, temp->filename(), reason)) { - LOGERR(("FileInterner::dataToTempFile: stringtofile: %s\n", - reason.c_str())); + LOGERR("FileInterner::dataToTempFile: stringtofile: " << (reason) << "\n" ); return TempFile(); } return temp; @@ -394,7 +397,7 @@ TempFile FileInterner::dataToTempFile(const string& dt, const string& mt) // RECFILTERROR HELPERNOTFOUND program1 [program2 ...] void FileInterner::checkExternalMissing(const string& msg, const string& mt) { - LOGDEB2(("checkExternalMissing: [%s]\n", msg.c_str())); + LOGDEB2("checkExternalMissing: [" << (msg) << "]\n" ); if (m_missingdatap && msg.find("RECFILTERROR") == 0) { vector verr; stringToStrings(msg, verr); @@ -486,10 +489,10 @@ static inline bool getKeyValue(const map& docdata, it = docdata.find(key); if (it != docdata.end()) { value = it->second; - LOGDEB2(("getKeyValue: [%s]->[%s]\n", key.c_str(), value.c_str())); + LOGDEB2("getKeyValue: [" << (key) << "]->[" << (value) << "]\n" ); return true; } - LOGDEB2(("getKeyValue: no value for [%s]\n", key.c_str())); + LOGDEB2("getKeyValue: no value for [" << (key) << "]\n" ); return false; } @@ -498,7 +501,7 @@ bool FileInterner::dijontorcl(Rcl::Doc& doc) RecollFilter *df = m_handlers.back(); if (df == 0) { //?? - LOGERR(("FileInterner::dijontorcl: null top handler ??\n")); + LOGERR("FileInterner::dijontorcl: null top handler ??\n" ); return false; } const map& docdata = df->get_meta_data(); @@ -514,9 +517,9 @@ bool FileInterner::dijontorcl(Rcl::Doc& doc) // point if the last container filter is directly // returning text/plain content, so that there is no // ipath-less filter at the top - char cbuf[30]; - sprintf(cbuf, "%d", int(doc.text.length())); - doc.fbytes = cbuf; + lltodecstr(doc.text.length(), doc.fbytes); + LOGDEB("FileInterner::dijontorcl: fbytes->" << doc.fbytes << + endl); } } else if (it->first == cstr_dj_keymd) { doc.dmtime = it->second; @@ -533,6 +536,8 @@ bool FileInterner::dijontorcl(Rcl::Doc& doc) it->first == cstr_dj_keycharset) { // don't need/want these. } else { + LOGDEB2("dijontorcl: " << m_cfg->fieldCanon(it->first) << " -> " << + it->second << endl); doc.addmeta(m_cfg->fieldCanon(it->first), it->second); } } @@ -550,6 +555,10 @@ bool FileInterner::dijontorcl(Rcl::Doc& doc) // doc with an ipath, not the last one which is usually text/plain We // also set the author and modification time from the last doc which // has them. +// +// The stack can contain objects with an ipath element (corresponding +// to actual embedded documents), and, at the top, elements without an +// ipath element, corresponding to format translations of the last doc. // // The docsize is fetched from the first element without an ipath // (first non container). If the last element directly returns @@ -561,7 +570,7 @@ bool FileInterner::dijontorcl(Rcl::Doc& doc) // actually complicated. void FileInterner::collectIpathAndMT(Rcl::Doc& doc) const { - LOGDEB2(("FileInterner::collectIpathAndMT\n")); + LOGDEB2("FileInterner::collectIpathAndMT\n" ); bool hasipath = false; if (!m_noxattrs) { @@ -577,29 +586,38 @@ void FileInterner::collectIpathAndMT(Rcl::Doc& doc) const for (vector::const_iterator hit = m_handlers.begin(); hit != m_handlers.end(); hit++) { const map& docdata = (*hit)->get_meta_data(); - if (getKeyValue(docdata, cstr_dj_keyipath, ipathel)) { - if (!ipathel.empty()) { - // We have a non-empty ipath - hasipath = true; - getKeyValue(docdata, cstr_dj_keymt, doc.mimetype); - getKeyValue(docdata, cstr_dj_keyfn, doc.meta[Rcl::Doc::keyfn]); - } else { - if (doc.fbytes.empty()) - getKeyValue(docdata, cstr_dj_keydocsize, doc.fbytes); - } - doc.ipath += colon_hide(ipathel) + cstr_isep; - } else { - if (doc.fbytes.empty()) - getKeyValue(docdata, cstr_dj_keydocsize, doc.fbytes); - doc.ipath += cstr_isep; - } - getKeyValue(docdata, cstr_dj_keyauthor, doc.meta[Rcl::Doc::keyau]); - getKeyValue(docdata, cstr_dj_keymd, doc.dmtime); + ipathel.clear(); + getKeyValue(docdata, cstr_dj_keyipath, ipathel); + if (!ipathel.empty()) { + // Non-empty ipath. This stack element is for an + // actual embedded document, not a format translation. + hasipath = true; + getKeyValue(docdata, cstr_dj_keymt, doc.mimetype); + getKeyValue(docdata, cstr_dj_keyfn, doc.meta[Rcl::Doc::keyfn]); + } else { + if (doc.fbytes.empty()) { + lltodecstr((*hit)->get_docsize(), doc.fbytes); + LOGDEB("collectIpath..: fbytes->" << doc.fbytes << endl); + } + } + doc.ipath += colon_hide(ipathel) + cstr_isep; + // We set the author field from the innermost doc which has + // one: allows finding, e.g. an image attachment having no + // metadata by a search on the sender name. Only do this for + // actually embedded documents (avoid replacing values from + // metacmds for the topmost one). For a topmost doc, author + // will be merged by dijontorcl() later on. About same for + // dmtime, but an external value will be replaced, not + // augmented if dijontorcl() finds an internal value. + if (hasipath) { + getKeyValue(docdata, cstr_dj_keyauthor, doc.meta[Rcl::Doc::keyau]); + getKeyValue(docdata, cstr_dj_keymd, doc.dmtime); + } } // Trim empty tail elements in ipath. if (hasipath) { - LOGDEB2(("IPATH [%s]\n", doc.ipath.c_str())); + LOGDEB2("IPATH [" << (doc.ipath) << "]\n" ); string::size_type sit = doc.ipath.find_last_not_of(cstr_isep); if (sit == string::npos) doc.ipath.erase(); @@ -615,7 +633,7 @@ void FileInterner::popHandler() { if (m_handlers.empty()) return; - int i = m_handlers.size() - 1; + size_t i = m_handlers.size() - 1; if (m_tmpflgs[i]) { m_tempfiles.pop_back(); m_tmpflgs[i] = false; @@ -635,8 +653,7 @@ int FileInterner::addHandler() getKeyValue(docdata, cstr_dj_keycharset, charset); getKeyValue(docdata, cstr_dj_keymt, mimetype); - LOGDEB(("FileInterner::addHandler: next_doc is %s target [%s]\n", - mimetype.c_str(), m_targetMType.c_str())); + LOGDEB("FileInterner::addHandler: next_doc is " << (mimetype) << " target [" << (m_targetMType) << "]\n" ); // If we find a document of the target type (text/plain in // general), we're done decoding. If we hit text/plain, we're done @@ -644,7 +661,7 @@ int FileInterner::addHandler() if (!stringicmp(mimetype, m_targetMType) || !stringicmp(mimetype, cstr_textplain)) { m_reachedMType = mimetype; - LOGDEB1(("FileInterner::addHandler: target reached\n")); + LOGDEB1("FileInterner::addHandler: target reached\n" ); return ADD_BREAK; } @@ -652,16 +669,15 @@ int FileInterner::addHandler() if (m_handlers.size() >= MAXHANDLERS) { // Stack too big. Skip this and go on to check if there is // something else in the current back() - LOGERR(("FileInterner::addHandler: stack too high\n")); + LOGERR("FileInterner::addHandler: stack too high\n" ); return ADD_CONTINUE; } - RecollFilter *newflt = getMimeHandler(mimetype, m_cfg); + RecollFilter *newflt = getMimeHandler(mimetype, m_cfg, !m_forPreview); if (!newflt) { // If we can't find a handler, this doc can't be handled // but there can be other ones so we go on - LOGINFO(("FileInterner::addHandler: no filter for [%s]\n", - mimetype.c_str())); + LOGINFO("FileInterner::addHandler: no filter for [" << (mimetype) << "]\n" ); return ADD_CONTINUE; } newflt->set_property(Dijon::Filter::OPERATING_MODE, @@ -687,7 +703,7 @@ int FileInterner::addHandler() setres = newflt->set_document_data(mimetype,txt->c_str(),txt->length()); } else if (newflt->is_data_input_ok(Dijon::Filter::DOCUMENT_FILE_NAME)) { TempFile temp = dataToTempFile(*txt, mimetype); - if (temp.isNotNull() && + if (temp && (setres = newflt->set_document_file(mimetype, temp->filename()))) { m_tmpflgs[m_handlers.size()] = true; m_tempfiles.push_back(temp); @@ -700,8 +716,7 @@ int FileInterner::addHandler() } } if (!setres) { - LOGINFO(("FileInterner::addHandler: set_doc failed inside %s " - " for mtype %s\n", m_fn.c_str(), mimetype.c_str())); + LOGINFO("FileInterner::addHandler: set_doc failed inside " << (m_fn) << " for mtype " << (mimetype) << "\n" ); delete newflt; if (m_forPreview) return ADD_ERROR; @@ -709,7 +724,7 @@ int FileInterner::addHandler() } // add handler and go on, maybe this one will give us text... m_handlers.push_back(newflt); - LOGDEB1(("FileInterner::addHandler: added\n")); + LOGDEB1("FileInterner::addHandler: added\n" ); return ADD_OK; } @@ -719,21 +734,19 @@ void FileInterner::processNextDocError(Rcl::Doc &doc) collectIpathAndMT(doc); m_reason = m_handlers.back()->get_error(); checkExternalMissing(m_reason, doc.mimetype); - LOGERR(("FileInterner::internfile: next_document error " - "[%s%s%s] %s %s\n", m_fn.c_str(), doc.ipath.empty() ? "" : "|", - doc.ipath.c_str(), doc.mimetype.c_str(), m_reason.c_str())); + LOGERR("FileInterner::internfile: next_document error [" << (m_fn) << "" << (doc.ipath.empty() ? "" : "|") << "" << (doc.ipath) << "] " << (doc.mimetype) << " " << (m_reason) << "\n" ); } FileInterner::Status FileInterner::internfile(Rcl::Doc& doc, const string& ipath) { - LOGDEB(("FileInterner::internfile. ipath [%s]\n", ipath.c_str())); + LOGDEB("FileInterner::internfile. ipath [" << (ipath) << "]\n" ); // Get rid of possible image tempfile from older call - m_imgtmp.release(); + m_imgtmp.reset(); if (m_handlers.size() < 1) { // Just means the constructor failed - LOGDEB(("FileInterner::internfile: no handler: constructor failed\n")); + LOGDEB("FileInterner::internfile: no handler: constructor failed\n" ); return FIError; } @@ -743,8 +756,7 @@ FileInterner::Status FileInterner::internfile(Rcl::Doc& doc, const string& ipath // We set the ipath for the first handler here, others are set // when they're pushed on the stack vector vipath; - int vipathidx = 0; - if (!ipath.empty()) { + if (!ipath.empty() && !m_direct) { vector lipath; stringToTokens(ipath, lipath, cstr_isep, true); for (vector::iterator it = lipath.begin(); @@ -753,7 +765,7 @@ FileInterner::Status FileInterner::internfile(Rcl::Doc& doc, const string& ipath } vipath.insert(vipath.begin(), lipath.begin(), lipath.end()); if (!m_handlers.back()->skip_to_document(vipath[m_handlers.size()-1])){ - LOGERR(("FileInterner::internfile: can't skip\n")); + LOGERR("FileInterner::internfile: can't skip\n" ); return FIError; } } @@ -769,7 +781,7 @@ FileInterner::Status FileInterner::internfile(Rcl::Doc& doc, const string& ipath while (!m_handlers.empty()) { CancelCheck::instance().checkCancel(); if (loop++ > 1000) { - LOGERR(("FileInterner:: looping!\n")); + LOGERR("FileInterner:: looping!\n" ); return FIError; } // If there are no more docs at the current top level we pop and @@ -781,7 +793,7 @@ FileInterner::Status FileInterner::internfile(Rcl::Doc& doc, const string& ipath if (m_forPreview) { m_reason += "Requested document does not exist. "; m_reason += m_handlers.back()->get_error(); - LOGERR(("FileInterner: requested document does not exist\n")); + LOGERR("FileInterner: requested document does not exist\n" ); return FIError; } popHandler(); @@ -796,7 +808,7 @@ FileInterner::Status FileInterner::internfile(Rcl::Doc& doc, const string& ipath if (m_forPreview) { m_reason += "Requested document does not exist. "; m_reason += m_handlers.back()->get_error(); - LOGERR(("FileInterner: requested document does not exist\n")); + LOGERR("FileInterner: requested document does not exist\n" ); return FIError; } popHandler(); @@ -807,20 +819,20 @@ FileInterner::Status FileInterner::internfile(Rcl::Doc& doc, const string& ipath // handler to stack. switch (addHandler()) { case ADD_OK: // Just go through: handler has been stacked, use it - LOGDEB2(("addHandler returned OK\n")); + LOGDEB2("addHandler returned OK\n" ); break; case ADD_CONTINUE: // forget this doc and retrieve next from current handler // (ipath stays same) - LOGDEB2(("addHandler returned CONTINUE\n")); + LOGDEB2("addHandler returned CONTINUE\n" ); continue; case ADD_BREAK: // Stop looping: doc type ok, need complete its processing // and return it - LOGDEB2(("addHandler returned BREAK\n")); + LOGDEB2("addHandler returned BREAK\n" ); goto breakloop; // when you have to you have to case ADD_ERROR: - LOGDEB2(("addHandler returned ERROR\n")); + LOGDEB2("addHandler returned ERROR\n" ); return FIError; } @@ -834,14 +846,14 @@ FileInterner::Status FileInterner::internfile(Rcl::Doc& doc, const string& ipath if (!ipath.empty()) { if (m_handlers.size() <= vipath.size() && !m_handlers.back()->skip_to_document(vipath[m_handlers.size()-1])) { - LOGERR(("FileInterner::internfile: can't skip\n")); + LOGERR("FileInterner::internfile: can't skip\n" ); return FIError; } } } breakloop: if (m_handlers.empty()) { - LOGDEB(("FileInterner::internfile: conversion ended with no doc\n")); + LOGDEB("FileInterner::internfile: conversion ended with no doc\n" ); return FIError; } @@ -878,19 +890,13 @@ FileInterner::Status FileInterner::internfile(Rcl::Doc& doc, const string& ipath return FIAgain; } -// Temporary while we fix backend things -static string urltolocalpath(string url) -{ - return url.substr(7, string::npos); -} - bool FileInterner::tempFileForMT(TempFile& otemp, RclConfig* cnf, const string& mimetype) { TempFile temp(new TempFileInternal( cnf->getSuffixFromMimeType(mimetype))); if (!temp->ok()) { - LOGERR(("FileInterner::interntofile: can't create temp file\n")); + LOGERR("FileInterner::interntofile: can't create temp file\n" ); return false; } otemp = temp; @@ -916,7 +922,7 @@ bool FileInterner::tempFileForMT(TempFile& otemp, RclConfig* cnf, bool FileInterner::idocToFile(TempFile& otemp, const string& tofile, RclConfig *cnf, const Rcl::Doc& idoc) { - LOGDEB(("FileInterner::idocToFile\n")); + LOGDEB("FileInterner::idocToFile\n" ); if (idoc.ipath.empty()) { return topdocToFile(otemp, tofile, cnf, idoc); @@ -933,14 +939,14 @@ bool FileInterner::idocToFile(TempFile& otemp, const string& tofile, bool FileInterner::topdocToFile(TempFile& otemp, const string& tofile, RclConfig *cnf, const Rcl::Doc& idoc) { - DocFetcher *fetcher = docFetcherMake(idoc); + DocFetcher *fetcher = docFetcherMake(cnf, idoc); if (fetcher == 0) { - LOGERR(("FileInterner::idocToFile no backend\n")); + LOGERR("FileInterner::idocToFile no backend\n" ); return false; } DocFetcher::RawDoc rawdoc; if (!fetcher->fetch(cnf, idoc, rawdoc)) { - LOGERR(("FileInterner::idocToFile fetcher failed\n")); + LOGERR("FileInterner::idocToFile fetcher failed\n" ); return false; } const char *filename = ""; @@ -957,20 +963,18 @@ bool FileInterner::topdocToFile(TempFile& otemp, const string& tofile, switch (rawdoc.kind) { case DocFetcher::RawDoc::RDK_FILENAME: if (!copyfile(rawdoc.data.c_str(), filename, reason)) { - LOGERR(("FileInterner::idocToFile: copyfile: %s\n", - reason.c_str())); + LOGERR("FileInterner::idocToFile: copyfile: " << (reason) << "\n" ); return false; } break; case DocFetcher::RawDoc::RDK_DATA: if (!stringtofile(rawdoc.data, filename, reason)) { - LOGERR(("FileInterner::idocToFile: stringtofile: %s\n", - reason.c_str())); + LOGERR("FileInterner::idocToFile: stringtofile: " << (reason) << "\n" ); return false; } break; default: - LOGERR(("FileInterner::FileInterner(idoc): bad rawdoc kind ??\n")); + LOGERR("FileInterner::FileInterner(idoc): bad rawdoc kind ??\n" ); } if (tofile.empty()) @@ -982,13 +986,13 @@ bool FileInterner::interntofile(TempFile& otemp, const string& tofile, const string& ipath, const string& mimetype) { if (!ok()) { - LOGERR(("FileInterner::interntofile: constructor failed\n")); + LOGERR("FileInterner::interntofile: constructor failed\n" ); return false; } Rcl::Doc doc; Status ret = internfile(doc, ipath); if (ret == FileInterner::FIError) { - LOGERR(("FileInterner::interntofile: internfile() failed\n")); + LOGERR("FileInterner::interntofile: internfile() failed\n" ); return false; } @@ -1015,8 +1019,7 @@ bool FileInterner::interntofile(TempFile& otemp, const string& tofile, } string reason; if (!stringtofile(doc.text, filename, reason)) { - LOGERR(("FileInterner::interntofile: stringtofile : %s\n", - reason.c_str())); + LOGERR("FileInterner::interntofile: stringtofile : " << (reason) << "\n" ); return false; } @@ -1027,16 +1030,15 @@ bool FileInterner::interntofile(TempFile& otemp, const string& tofile, bool FileInterner::isCompressed(const string& fn, RclConfig *cnf) { - LOGDEB(("FileInterner::isCompressed: [%s]\n", fn.c_str())); + LOGDEB("FileInterner::isCompressed: [" << (fn) << "]\n" ); struct stat st; - if (stat(fn.c_str(), &st) < 0) { - LOGERR(("FileInterner::isCompressed: can't stat [%s]\n", fn.c_str())); + if (path_fileprops(fn, &st) < 0) { + LOGERR("FileInterner::isCompressed: can't stat [" << (fn) << "]\n" ); return false; } string l_mime = mimetype(fn, &st, cnf, true); if (l_mime.empty()) { - LOGERR(("FileInterner::isUncompressed: can't get mime for [%s]\n", - fn.c_str())); + LOGERR("FileInterner::isUncompressed: can't get mime for [" << (fn) << "]\n" ); return false; } @@ -1051,17 +1053,15 @@ bool FileInterner::isCompressed(const string& fn, RclConfig *cnf) bool FileInterner::maybeUncompressToTemp(TempFile& temp, const string& fn, RclConfig *cnf, const Rcl::Doc& doc) { - LOGDEB(("FileInterner::maybeUncompressToTemp: [%s]\n", fn.c_str())); + LOGDEB("FileInterner::maybeUncompressToTemp: [" << (fn) << "]\n" ); struct stat st; - if (stat(fn.c_str(), &st) < 0) { - LOGERR(("FileInterner::maybeUncompressToTemp: can't stat [%s]\n", - fn.c_str())); + if (path_fileprops(fn.c_str(), &st) < 0) { + LOGERR("FileInterner::maybeUncompressToTemp: can't stat [" << (fn) << "]\n" ); return false; } string l_mime = mimetype(fn, &st, cnf, true); if (l_mime.empty()) { - LOGERR(("FileInterner::maybeUncompress.: can't id. mime for [%s]\n", - fn.c_str())); + LOGERR("FileInterner::maybeUncompress.: can't id. mime for [" << (fn) << "]\n" ); return false; } @@ -1073,14 +1073,13 @@ bool FileInterner::maybeUncompressToTemp(TempFile& temp, const string& fn, int maxkbs = -1; if (cnf->getConfParam("compressedfilemaxkbs", &maxkbs) && maxkbs >= 0 && int(st.st_size / 1024) > maxkbs) { - LOGINFO(("FileInterner:: %s over size limit %d kbs\n", - fn.c_str(), maxkbs)); + LOGINFO("FileInterner:: " << (fn) << " over size limit " << (maxkbs) << " kbs\n" ); return false; } temp = TempFile(new TempFileInternal(cnf->getSuffixFromMimeType(doc.mimetype))); if (!temp->ok()) { - LOGERR(("FileInterner: cant create temporary file")); + LOGERR("FileInterner: cant create temporary file" ); return false; } @@ -1095,9 +1094,7 @@ bool FileInterner::maybeUncompressToTemp(TempFile& temp, const string& fn, // uncompressed file, hopefully staying on the same dev. string reason; if (!renameormove(uncomped.c_str(), temp->filename(), reason)) { - LOGERR(("FileInterner::maybeUncompress: move [%s] -> [%s] " - "failed: %s\n", - uncomped.c_str(), temp->filename(), reason.c_str())); + LOGERR("FileInterner::maybeUncompress: move [" << (uncomped) << "] -> [" << (temp->filename()) << "] failed: " << (reason) << "\n" ); return false; } return true; @@ -1109,11 +1106,12 @@ bool FileInterner::maybeUncompressToTemp(TempFile& temp, const string& fn, #include #include #include -#include +#include "safesysstat.h" using namespace std; -#include "debuglog.h" +#include "log.h" + #include "rclinit.h" #include "internfile.h" #include "rclconfig.h" @@ -1214,3 +1212,4 @@ int main(int argc, char **argv) } #endif // TEST_INTERNFILE + diff --git a/src/internfile/internfile.h b/src/internfile/internfile.h index 69d88b47..5e7c6c4b 100644 --- a/src/internfile/internfile.h +++ b/src/internfile/internfile.h @@ -206,6 +206,9 @@ class FileInterner { /** Return last element in ipath, like basename */ static std::string getLastIpathElt(const std::string& ipath); + /** Check that 2nd param is child of first */ + static bool ipathContains(const std::string& parent, + const std::string& child); /** * Build sig for doc coming from rcldb. This is here because we know how * to query the right backend. Used to check up-to-dateness at query time */ @@ -272,7 +275,8 @@ class FileInterner { Uncomp m_uncomp; bool m_noxattrs; // disable xattrs usage - + bool m_direct; // External app did the extraction + // Pseudo-constructors void init(const string &fn, const struct stat *stp, RclConfig *cnf, int flags, const string *mtype = 0); diff --git a/src/internfile/mh_exec.cpp b/src/internfile/mh_exec.cpp index 94ccdbc8..30708e11 100644 --- a/src/internfile/mh_exec.cpp +++ b/src/internfile/mh_exec.cpp @@ -17,48 +17,59 @@ #include "autoconfig.h" #include -#include +#include +#include "safesyswait.h" #include -using namespace std; #include "cstr.h" #include "execmd.h" #include "mh_exec.h" #include "mh_html.h" -#include "debuglog.h" +#include "log.h" #include "cancelcheck.h" #include "smallut.h" #include "md5ut.h" #include "rclconfig.h" -// This is called periodically by ExeCmd when it is waiting for data, -// or when it does receive some. We may choose to interrupt the -// command. -class MEAdv : public ExecCmdAdvise { -public: - MEAdv(int maxsecs) : m_filtermaxseconds(maxsecs) {m_start = time(0L);} - void newData(int n) { - LOGDEB1(("MHExec:newData(%d)\n", n)); - if (m_filtermaxseconds > 0 && - time(0L) - m_start > m_filtermaxseconds) { - LOGERR(("MimeHandlerExec: filter timeout (%d S)\n", - m_filtermaxseconds)); - CancelCheck::instance().setCancel(); - } - // If a cancel request was set by the signal handler (or by us - // just above), this will raise an exception. Another approach - // would be to call ExeCmd::setCancel(). - CancelCheck::instance().checkCancel(); - } - time_t m_start; - int m_filtermaxseconds; -}; +using namespace std; +MimeHandlerExec::MimeHandlerExec(RclConfig *cnf, const std::string& id) + : RecollFilter(cnf, id), missingHelper(false), m_filtermaxseconds(900), + m_filtermaxmbytes(0) +{ + m_config->getConfParam("filtermaxseconds", &m_filtermaxseconds); + m_config->getConfParam("filtermaxmbytes", &m_filtermaxmbytes); +} + +MEAdv::MEAdv(int maxsecs) + : m_filtermaxseconds(maxsecs) +{ + m_start = time(0L); +} +void MEAdv::reset() +{ + m_start = time(0L); + +} + +void MEAdv::newData(int n) +{ + LOGDEB2("MHExec:newData(" << (n) << ")\n" ); + if (m_filtermaxseconds > 0 && + time(0L) - m_start > m_filtermaxseconds) { + LOGERR("MimeHandlerExec: filter timeout (" << (m_filtermaxseconds) << " S)\n" ); + throw HandlerTimeout(); + } + // If a cancel request was set by the signal handler (or by us + // just above), this will raise an exception. Another approach + // would be to call ExeCmd::setCancel(). + CancelCheck::instance().checkCancel(); +} bool MimeHandlerExec::skip_to_document(const string& ipath) { - LOGDEB(("MimeHandlerExec:skip_to_document: [%s]\n", ipath.c_str())); + LOGDEB("MimeHandlerExec:skip_to_document: [" << (ipath) << "]\n" ); m_ipath = ipath; return true; } @@ -71,18 +82,13 @@ bool MimeHandlerExec::next_document() return false; m_havedoc = false; if (missingHelper) { - LOGDEB(("MimeHandlerExec::next_document(): helper known missing\n")); + LOGDEB("MimeHandlerExec::next_document(): helper known missing\n" ); return false; } - int filtermaxseconds = 900; - m_config->getConfParam("filtermaxseconds", &filtermaxseconds); - int filtermaxmbytes = 0; - m_config->getConfParam("filtermaxmbytes", &filtermaxmbytes); - if (params.empty()) { // Hu ho - LOGERR(("MimeHandlerExec::mkDoc: empty params\n")); + LOGERR("MimeHandlerExec::mkDoc: empty params\n" ); m_reason = "RECFILTERROR BADCONFIG"; return false; } @@ -100,24 +106,26 @@ bool MimeHandlerExec::next_document() string& output = m_metaData[cstr_dj_keycontent]; output.erase(); ExecCmd mexec; - MEAdv adv(filtermaxseconds); + MEAdv adv(m_filtermaxseconds); mexec.setAdvise(&adv); mexec.putenv("RECOLL_CONFDIR", m_config->getConfDir()); mexec.putenv(m_forPreview ? "RECOLL_FILTER_FORPREVIEW=yes" : "RECOLL_FILTER_FORPREVIEW=no"); - mexec.setrlimit_as(filtermaxmbytes); + mexec.setrlimit_as(m_filtermaxmbytes); int status; try { status = mexec.doexec(cmd, myparams, 0, &output); + } catch (HandlerTimeout) { + LOGERR("MimeHandlerExec: handler timeout\n" ); + status = 0x110f; } catch (CancelExcept) { - LOGERR(("MimeHandlerExec: cancelled\n")); + LOGERR("MimeHandlerExec: cancelled\n" ); status = 0x110f; } if (status) { - LOGERR(("MimeHandlerExec: command status 0x%x for %s\n", - status, cmd.c_str())); + LOGERR("MimeHandlerExec: command status 0x" << (status) << " for " << (cmd) << "\n" ); if (WIFEXITED(status) && WEXITSTATUS(status) == 127) { // That's how execmd signals a failed exec (most probably // a missing command). Let'hope no filter uses the same value as @@ -185,10 +193,10 @@ void MimeHandlerExec::finaldetails() if (MD5File(m_fn, md5, &reason)) { m_metaData[cstr_dj_keymd5] = MD5HexPrint(md5, xmd5); } else { - LOGERR(("MimeHandlerExec: cant compute md5 for [%s]: %s\n", - m_fn.c_str(), reason.c_str())); + LOGERR("MimeHandlerExec: cant compute md5 for [" << (m_fn) << "]: " << (reason) << "\n" ); } } handle_cs(m_metaData[cstr_dj_keymt]); } + diff --git a/src/internfile/mh_exec.h b/src/internfile/mh_exec.h index 0f9be27c..15e359b4 100644 --- a/src/internfile/mh_exec.h +++ b/src/internfile/mh_exec.h @@ -19,11 +19,12 @@ #include #include -using std::vector; -using std::string; #include "mimehandler.h" +#include "execmd.h" +class HandlerTimeout {}; + /** * Turn external document into internal one by executing an external filter. * @@ -45,28 +46,33 @@ class MimeHandlerExec : public RecollFilter { // Parameters: this has been built by our creator, from config file // data. We always add the file name at the end before actual execution - vector params; + std::vector params; // Filter output type. The default for ext. filters is to output html, // but some don't, in which case the type is defined in the config. - string cfgFilterOutputMtype; + std::string cfgFilterOutputMtype; // Output character set if the above type is not text/html. For // those filters, the output charset has to be known: ie set by a command // line option. - string cfgFilterOutputCharset; + std::string cfgFilterOutputCharset; bool missingHelper; + // Resource management values + int m_filtermaxseconds; + int m_filtermaxmbytes; //////////////// - MimeHandlerExec(RclConfig *cnf, const string& id) - : RecollFilter(cnf, id), missingHelper(false) - {} - virtual bool set_document_file(const string& mt, const string &file_path) { + MimeHandlerExec(RclConfig *cnf, const std::string& id); + + virtual bool set_document_file(const std::string& mt, + const std::string &file_path) { RecollFilter::set_document_file(mt, file_path); m_fn = file_path; m_havedoc = true; return true; } + virtual bool next_document(); - virtual bool skip_to_document(const string& ipath); + virtual bool skip_to_document(const std::string& ipath); + virtual void clear() { m_fn.erase(); m_ipath.erase(); @@ -74,17 +80,36 @@ class MimeHandlerExec : public RecollFilter { } protected: - string m_fn; - string m_ipath; + std::string m_fn; + std::string m_ipath; // Set up the character set metadata fields and possibly transcode // text/plain output. // @param charset when called from mh_execm, a possible explicit // value from the filter (else the data will come from the config) - virtual void handle_cs(const string& mt, const string& charset = string()); + virtual void handle_cs(const std::string& mt, + const std::string& charset = std::string()); private: virtual void finaldetails(); }; + +// This is called periodically by ExeCmd when it is waiting for data, +// or when it does receive some. We may choose to interrupt the +// command. +class MEAdv : public ExecCmdAdvise { +public: + MEAdv(int maxsecs = 900); + // Reset start time to now + void reset(); + void setmaxsecs(int maxsecs) { + m_filtermaxseconds = maxsecs; + } + void newData(int n); +private: + time_t m_start; + int m_filtermaxseconds; +}; + #endif /* _MH_EXEC_H_INCLUDED_ */ diff --git a/src/internfile/mh_execm.cpp b/src/internfile/mh_execm.cpp index 74a7f911..c3db6a18 100644 --- a/src/internfile/mh_execm.cpp +++ b/src/internfile/mh_execm.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2005 J.F.Dockes + /* Copyright (C) 2005 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 @@ -23,7 +23,7 @@ using namespace std; #include "cstr.h" #include "mh_execm.h" #include "mh_html.h" -#include "debuglog.h" +#include "log.h" #include "cancelcheck.h" #include "smallut.h" #include "md5ut.h" @@ -32,14 +32,14 @@ using namespace std; #include "idfile.h" #include -#include +#include "safesyswait.h" bool MimeHandlerExecMultiple::startCmd() { - LOGDEB(("MimeHandlerExecMultiple::startCmd\n")); + LOGDEB("MimeHandlerExecMultiple::startCmd\n" ); if (params.empty()) { // Hu ho - LOGERR(("MHExecMultiple::startCmd: empty params\n")); + LOGERR("MHExecMultiple::startCmd: empty params\n" ); m_reason = "RECFILTERROR BADCONFIG"; return false; } @@ -47,9 +47,6 @@ bool MimeHandlerExecMultiple::startCmd() // Command name string cmd = params.front(); - int filtermaxmbytes = 0; - m_config->getConfParam("filtermaxmbytes", &filtermaxmbytes); - m_maxmemberkb = 50000; m_config->getConfParam("membermaxkbs", &m_maxmemberkb); ostringstream oss; @@ -60,7 +57,9 @@ bool MimeHandlerExecMultiple::startCmd() m_cmd.putenv(m_forPreview ? "RECOLL_FILTER_FORPREVIEW=yes" : "RECOLL_FILTER_FORPREVIEW=no"); - m_cmd.setrlimit_as(filtermaxmbytes); + m_cmd.setrlimit_as(m_filtermaxmbytes); + m_adv.setmaxsecs(m_filtermaxseconds); + m_cmd.setAdvise(&m_adv); // Build parameter list: delete cmd name vectormyparams(params.begin() + 1, params.end()); @@ -87,12 +86,15 @@ bool MimeHandlerExecMultiple::readDataElement(string& name, string &data) // Read name and length if (m_cmd.getline(ibuf) <= 0) { - LOGERR(("MHExecMultiple: getline error\n")); + LOGERR("MHExecMultiple: getline error\n" ); return false; } + + LOGDEB1("MHEM:rde: line [" << (ibuf) << "]\n" ); + // Empty line (end of message) ? if (!ibuf.compare("\n")) { - LOGDEB(("MHExecMultiple: Got empty line\n")); + LOGDEB("MHExecMultiple: Got empty line\n" ); name.clear(); return true; } @@ -110,8 +112,7 @@ bool MimeHandlerExecMultiple::readDataElement(string& name, string &data) vector tokens; stringToTokens(ibuf, tokens); if (tokens.size() != 2) { - LOGERR(("MHExecMultiple: bad line in filter output: [%s]\n", - ibuf.c_str())); + LOGERR("MHExecMultiple: bad line in filter output: [" << (ibuf) << "]\n" ); return false; } vector::iterator it = tokens.begin(); @@ -119,13 +120,12 @@ bool MimeHandlerExecMultiple::readDataElement(string& name, string &data) string& slen = *it; int len; if (sscanf(slen.c_str(), "%d", &len) != 1) { - LOGERR(("MHExecMultiple: bad line in filter output: [%s]\n", - ibuf.c_str())); + LOGERR("MHExecMultiple: bad line in filter output: [" << (ibuf) << "]\n" ); return false; } if (len / 1024 > m_maxmemberkb) { - LOGERR(("MHExecMultiple: data len > maxmemberkb\n")); + LOGERR("MHExecMultiple: data len > maxmemberkb\n" ); return false; } @@ -142,36 +142,49 @@ bool MimeHandlerExecMultiple::readDataElement(string& name, string &data) // Read element data datap->erase(); if (len > 0 && m_cmd.receive(*datap, len) != len) { - LOGERR(("MHExecMultiple: expected %d bytes of data, got %d\n", - len, datap->length())); + LOGERR("MHExecMultiple: expected " << (len) << " bytes of data, got " << (datap->length()) << "\n" ); return false; } - LOGDEB1(("MHExecMe:rdDtElt got: name [%s] len %d value [%s]\n", - name.c_str(), len, datap->size() > 100 ? - (datap->substr(0, 100) + " ...").c_str() : datap->c_str())); + LOGDEB1("MHExecMe:rdDtElt got: name [" << name << "] len " << len << + "value [" << (datap->size() > 100 ? + (datap->substr(0, 100) + " ...") : datap) << endl); return true; } bool MimeHandlerExecMultiple::next_document() { - LOGDEB(("MimeHandlerExecMultiple::next_document(): [%s]\n", m_fn.c_str())); + LOGDEB("MimeHandlerExecMultiple::next_document(): [" << (m_fn) << "]\n" ); if (m_havedoc == false) return false; if (missingHelper) { - LOGDEB(("MHExecMultiple::next_document(): helper known missing\n")); + LOGDEB("MHExecMultiple::next_document(): helper known missing\n" ); return false; } - if (m_cmd.getChildPid() < 0 && !startCmd()) { + if (m_cmd.getChildPid() <= 0 && !startCmd()) { return false; } + m_metaData.clear(); + // Send request to child process. This maybe the first/only // request for a given file, or a continuation request. We send an // empty file name in the latter case. + // We also compute the file md5 before starting the extraction: + // under Windows, we may not be able to do it while the file + // is opened by the filter ostringstream obuf; + string file_md5; if (m_filefirst) { + if (!m_forPreview) { + string md5, xmd5, reason; + if (MD5File(m_fn, md5, &reason)) { + file_md5 = MD5HexPrint(md5, xmd5); + } else { + LOGERR("MimeHandlerExecM: cant compute md5 for [" << (m_fn) << "]: " << (reason) << "\n" ); + } + } obuf << "FileName: " << m_fn.length() << "\n" << m_fn; // m_filefirst is set to true by set_document_file() m_filefirst = false; @@ -179,8 +192,7 @@ bool MimeHandlerExecMultiple::next_document() obuf << "Filename: " << 0 << "\n"; } if (!m_ipath.empty()) { - LOGDEB(("next_doc: sending len %d val [%s]\n", m_ipath.length(), - m_ipath.c_str())); + LOGDEB("next_doc: sending len " << (m_ipath.length()) << " val [" << (m_ipath) << "]\n" ); obuf << "Ipath: " << m_ipath.length() << "\n" << m_ipath; } if (!m_dfltInputCharset.empty()) { @@ -191,12 +203,14 @@ bool MimeHandlerExecMultiple::next_document() obuf << "\n"; if (m_cmd.send(obuf.str()) < 0) { m_cmd.zapChild(); - LOGERR(("MHExecMultiple: send error\n")); + LOGERR("MHExecMultiple: send error\n" ); return false; } + m_adv.reset(); + // Read answer (multiple elements) - LOGDEB1(("MHExecMultiple: reading answer\n")); + LOGDEB1("MHExecMultiple: reading answer\n" ); bool eofnext_received = false; bool eofnow_received = false; bool fileerror_received = false; @@ -206,37 +220,52 @@ bool MimeHandlerExecMultiple::next_document() string charset; for (int loop=0;;loop++) { string name, data; - if (!readDataElement(name, data)) { + try { + if (!readDataElement(name, data)) { + m_cmd.zapChild(); + return false; + } + } catch (HandlerTimeout) { + LOGINFO("MHExecMultiple: timeout\n" ); + m_cmd.zapChild(); + return false; + } catch (CancelExcept) { + LOGINFO("MHExecMultiple: interrupt\n" ); m_cmd.zapChild(); return false; } if (name.empty()) break; if (!stringlowercmp("eofnext:", name)) { - LOGDEB(("MHExecMultiple: got EOFNEXT\n")); + LOGDEB("MHExecMultiple: got EOFNEXT\n" ); eofnext_received = true; } else if (!stringlowercmp("eofnow:", name)) { - LOGDEB(("MHExecMultiple: got EOFNOW\n")); + LOGDEB("MHExecMultiple: got EOFNOW\n" ); eofnow_received = true; } else if (!stringlowercmp("fileerror:", name)) { - LOGDEB(("MHExecMultiple: got FILEERROR\n")); + LOGDEB("MHExecMultiple: got FILEERROR\n" ); fileerror_received = true; } else if (!stringlowercmp("subdocerror:", name)) { - LOGDEB(("MHExecMultiple: got SUBDOCERROR\n")); + LOGDEB("MHExecMultiple: got SUBDOCERROR\n" ); subdocerror_received = true; } else if (!stringlowercmp("ipath:", name)) { ipath = data; - LOGDEB(("MHExecMultiple: got ipath [%s]\n", data.c_str())); + LOGDEB("MHExecMultiple: got ipath [" << (data) << "]\n" ); } else if (!stringlowercmp("charset:", name)) { charset = data; - LOGDEB(("MHExecMultiple: got charset [%s]\n", data.c_str())); + LOGDEB("MHExecMultiple: got charset [" << (data) << "]\n" ); } else if (!stringlowercmp("mimetype:", name)) { mtype = data; - LOGDEB(("MHExecMultiple: got mimetype [%s]\n", data.c_str())); + LOGDEB("MHExecMultiple: got mimetype [" << (data) << "]\n" ); + } else { + string nm = stringtolower((const string&)name); + trimstring(nm, ":"); + LOGDEB("MHExecMultiple: got [" << (nm) << "] -> [" << (data) << "]\n" ); + m_metaData[nm] += data; } - if (loop == 10) { + if (loop == 20) { // ?? - LOGERR(("MHExecMultiple: filter sent too many parameters\n")); + LOGERR("MHExecMultiple: filter sent too many parameters\n" ); return false; } } @@ -254,8 +283,7 @@ bool MimeHandlerExecMultiple::next_document() // this was wrong. Empty documents can be found ie in zip files and should // not be interpreted as eof. if (m_metaData[cstr_dj_keycontent].empty()) { - LOGDEB0(("MHExecMultiple: got empty document inside [%s]: [%s]\n", - m_fn.c_str(), ipath.c_str())); + LOGDEB0("MHExecMultiple: got empty document inside [" << (m_fn) << "]: [" << (ipath) << "]\n" ); } if (!ipath.empty()) { @@ -265,8 +293,7 @@ bool MimeHandlerExecMultiple::next_document() // string which we can use to compute a mime type m_metaData[cstr_dj_keyipath] = ipath; if (mtype.empty()) { - LOGDEB0(("MHExecMultiple: no mime type from filter, " - "using ipath for a guess\n")); + LOGDEB0("MHExecMultiple: no mime type from filter, using ipath for a guess\n" ); mtype = mimetype(ipath, 0, m_config, false); if (mtype.empty()) { // mimetype() won't call idFile when there is no file. Do it @@ -275,7 +302,7 @@ bool MimeHandlerExecMultiple::next_document() // Note this happens for example for directory zip members // We could recognize them by the end /, but wouldn't know // what to do with them anyway. - LOGINFO(("MHExecMultiple: cant guess mime type\n")); + LOGINFO("MHExecMultiple: cant guess mime type\n" ); mtype = "application/octet-stream"; } } @@ -291,14 +318,8 @@ bool MimeHandlerExecMultiple::next_document() m_metaData[cstr_dj_keymt] = mtype.empty() ? "text/html" : mtype; m_metaData.erase(cstr_dj_keyipath); if (!m_forPreview) { - string md5, xmd5, reason; - if (MD5File(m_fn, md5, &reason)) { - m_metaData[cstr_dj_keymd5] = MD5HexPrint(md5, xmd5); - } else { - LOGERR(("MimeHandlerExecM: cant compute md5 for [%s]: %s\n", - m_fn.c_str(), reason.c_str())); - } - } + m_metaData[cstr_dj_keymd5] = file_md5; + } } handle_cs(m_metaData[cstr_dj_keymt], charset); @@ -306,10 +327,7 @@ bool MimeHandlerExecMultiple::next_document() if (eofnext_received) m_havedoc = false; - LOGDEB0(("MHExecMultiple: returning %d bytes of content," - " mtype [%s] charset [%s]\n", - m_metaData[cstr_dj_keycontent].size(), - m_metaData[cstr_dj_keymt].c_str(), - m_metaData[cstr_dj_keycharset].c_str())); + LOGDEB0("MHExecMultiple: returning " << (m_metaData[cstr_dj_keycontent].size()) << " bytes of content, mtype [" << (m_metaData[cstr_dj_keymt]) << "] charset [" << (m_metaData[cstr_dj_keycharset]) << "]\n" ); return true; } + diff --git a/src/internfile/mh_execm.h b/src/internfile/mh_execm.h index c2b29242..e5f2bd8c 100644 --- a/src/internfile/mh_execm.h +++ b/src/internfile/mh_execm.h @@ -120,6 +120,7 @@ private: bool readDataElement(string& name, string& data); bool m_filefirst; int m_maxmemberkb; + MEAdv m_adv; }; #endif /* _MH_EXECM_H_INCLUDED_ */ diff --git a/src/internfile/mh_html.cpp b/src/internfile/mh_html.cpp index 2750146d..bc0ec594 100644 --- a/src/internfile/mh_html.cpp +++ b/src/internfile/mh_html.cpp @@ -17,7 +17,7 @@ #include "cstr.h" #include "mimehandler.h" -#include "debuglog.h" +#include "log.h" #include "readfile.h" #include "transcode.h" #include "mimeparse.h" @@ -36,11 +36,11 @@ using namespace std; bool MimeHandlerHtml::set_document_file(const string& mt, const string &fn) { - LOGDEB0(("textHtmlToDoc: %s\n", fn.c_str())); + LOGDEB0("textHtmlToDoc: " << (fn) << "\n" ); RecollFilter::set_document_file(mt, fn); string otext; if (!file_to_string(fn, otext)) { - LOGINFO(("textHtmlToDoc: cant read: %s\n", fn.c_str())); + LOGINFO("textHtmlToDoc: cant read: " << (fn) << "\n" ); return false; } m_filename = fn; @@ -73,14 +73,12 @@ bool MimeHandlerHtml::next_document() m_filename.erase(); string charset = m_dfltInputCharset; - LOGDEB(("MHHtml::next_doc.: default supposed input charset: [%s]\n", - charset.c_str())); + LOGDEB("MHHtml::next_doc.: default supposed input charset: [" << (charset) << "]\n" ); // Override default input charset if someone took care to set one: map::const_iterator it = m_metaData.find(cstr_dj_keycharset); if (it != m_metaData.end() && !it->second.empty()) { charset = it->second; - LOGDEB(("MHHtml: next_doc.: input charset from ext. metadata: [%s]\n", - charset.c_str())); + LOGDEB("MHHtml: next_doc.: input charset from ext. metadata: [" << (charset) << "]\n" ); } // - We first try to convert from the supposed charset @@ -93,14 +91,13 @@ bool MimeHandlerHtml::next_document() MyHtmlParser result; for (int pass = 0; pass < 2; pass++) { string transcoded; - LOGDEB(("Html::mkDoc: pass %d\n", pass)); + LOGDEB("Html::mkDoc: pass " << (pass) << "\n" ); MyHtmlParser p; // Try transcoding. If it fails, use original text. int ecnt; if (!transcode(m_html, transcoded, charset, "UTF-8", &ecnt)) { - LOGDEB(("textHtmlToDoc: transcode failed from cs '%s' to UTF-8 for" - "[%s]", charset.c_str(), fn.empty()?"unknown":fn.c_str())); + LOGDEB("textHtmlToDoc: transcode failed from cs '" << (charset) << "' to UTF-8 for[" << (fn.empty()?"unknown":fn) << "]" ); transcoded = m_html; // We don't know the charset, at all p.reset_charsets(); @@ -108,11 +105,9 @@ bool MimeHandlerHtml::next_document() } else { if (ecnt) { if (pass == 0) { - LOGDEB(("textHtmlToDoc: init transcode had %d errors for " - "[%s]\n", ecnt, fn.empty()?"unknown":fn.c_str())); + LOGDEB("textHtmlToDoc: init transcode had " << (ecnt) << " errors for [" << (fn.empty()?"unknown":fn) << "]\n" ); } else { - LOGERR(("textHtmlToDoc: final transcode had %d errors for " - "[%s]\n", ecnt, fn.empty()?"unknown":fn.c_str())); + LOGERR("textHtmlToDoc: final transcode had " << (ecnt) << " errors for [" << (fn.empty()?"unknown":fn) << "]\n" ); } } // charset has the putative source charset, transcoded is now @@ -150,16 +145,15 @@ bool MimeHandlerHtml::next_document() break; } - LOGDEB(("textHtmlToDoc: charset [%s] doc charset [%s]\n", - charset.c_str(), result.get_charset().c_str())); + LOGDEB("textHtmlToDoc: charset [" << (charset) << "] doc charset [" << (result.get_charset()) << "]\n" ); if (!result.get_charset().empty() && !samecharset(result.get_charset(), result.fromcharset)) { - LOGDEB(("textHtmlToDoc: reparse for charsets\n")); + LOGDEB("textHtmlToDoc: reparse for charsets\n" ); // Set the origin charset as specified in document before // transcoding again charset = result.get_charset(); } else { - LOGERR(("textHtmlToDoc:: error: non charset exception\n")); + LOGERR("textHtmlToDoc:: error: non charset exception\n" ); return false; } } @@ -181,3 +175,4 @@ bool MimeHandlerHtml::next_document() } return true; } + diff --git a/src/internfile/mh_mail.cpp b/src/internfile/mh_mail.cpp index 23870e07..e54812fa 100644 --- a/src/internfile/mh_mail.cpp +++ b/src/internfile/mh_mail.cpp @@ -14,13 +14,15 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#include "autoconfig.h" #include #include #include -#include +#include "safeunistd.h" #include #include +#include "safesysstat.h" #include #include @@ -31,7 +33,7 @@ #include "transcode.h" #include "mimeparse.h" #include "mh_mail.h" -#include "debuglog.h" +#include "log.h" #include "smallut.h" #include "mh_html.h" #include "rclconfig.h" @@ -87,7 +89,7 @@ void MimeHandlerMail::clear() bool MimeHandlerMail::set_document_file(const string& mt, const string &fn) { - LOGDEB(("MimeHandlerMail::set_document_file(%s)\n", fn.c_str())); + LOGDEB("MimeHandlerMail::set_document_file(" << (fn) << ")\n" ); RecollFilter::set_document_file(mt, fn); if (m_fd >= 0) { close(m_fd); @@ -101,14 +103,12 @@ bool MimeHandlerMail::set_document_file(const string& mt, const string &fn) if (MD5File(fn, md5, &reason)) { m_metaData[cstr_dj_keymd5] = MD5HexPrint(md5, xmd5); } else { - LOGERR(("MimeHandlerMail: cant md5 [%s]: %s\n", fn.c_str(), - reason.c_str())); + LOGERR("MimeHandlerMail: cant md5 [" << (fn) << "]: " << (reason) << "\n" ); } } m_fd = open(fn.c_str(), 0); if (m_fd < 0) { - LOGERR(("MimeHandlerMail::set_document_file: open(%s) errno %d\n", - fn.c_str(), errno)); + LOGERR("MimeHandlerMail::set_document_file: open(" << (fn) << ") errno " << (errno) << "\n" ); return false; } #if defined O_NOATIME && O_NOATIME != 0 @@ -120,8 +120,7 @@ bool MimeHandlerMail::set_document_file(const string& mt, const string &fn) m_bincdoc = new Binc::MimeDocument; m_bincdoc->parseFull(m_fd); if (!m_bincdoc->isHeaderParsed() && !m_bincdoc->isAllParsed()) { - LOGERR(("MimeHandlerMail::mkDoc: mime parse error for %s\n", - fn.c_str())); + LOGERR("MimeHandlerMail::mkDoc: mime parse error for " << (fn) << "\n" ); return false; } m_havedoc = true; @@ -131,8 +130,8 @@ bool MimeHandlerMail::set_document_file(const string& mt, const string &fn) bool MimeHandlerMail::set_document_string(const string& mt, const string &msgtxt) { - LOGDEB1(("MimeHandlerMail::set_document_string\n")); - LOGDEB2(("Message text: [%s]\n", msgtxt.c_str())); + LOGDEB1("MimeHandlerMail::set_document_string\n" ); + LOGDEB2("Message text: [" << (msgtxt) << "]\n" ); RecollFilter::set_document_string(mt, msgtxt); delete m_stream; @@ -143,19 +142,17 @@ bool MimeHandlerMail::set_document_string(const string& mt, } if ((m_stream = new stringstream(msgtxt)) == 0 || !m_stream->good()) { - LOGERR(("MimeHandlerMail::set_document_string: stream create error." - "msgtxt.size() %d\n", int(msgtxt.size()))); + LOGERR("MimeHandlerMail::set_document_string: stream create error.msgtxt.size() " << (int(msgtxt.size())) << "\n" ); return false; } delete m_bincdoc; if ((m_bincdoc = new Binc::MimeDocument) == 0) { - LOGERR(("MimeHandlerMail::set_doc._string: new Binc:Document failed." - " Out of memory?")); + LOGERR("MimeHandlerMail::set_doc._string: new Binc:Document failed. Out of memory?" ); return false; } m_bincdoc->parseFull(*m_stream); if (!m_bincdoc->isHeaderParsed() && !m_bincdoc->isAllParsed()) { - LOGERR(("MimeHandlerMail::set_document_string: mime parse error\n")); + LOGERR("MimeHandlerMail::set_document_string: mime parse error\n" ); return false; } m_havedoc = true; @@ -164,14 +161,14 @@ bool MimeHandlerMail::set_document_string(const string& mt, bool MimeHandlerMail::skip_to_document(const string& ipath) { - LOGDEB(("MimeHandlerMail::skip_to_document(%s)\n", ipath.c_str())); + LOGDEB("MimeHandlerMail::skip_to_document(" << (ipath) << ")\n" ); if (m_idx == -1) { // No decoding done yet. If ipath is null need do nothing if (ipath.empty() || ipath == "-1") return true; // ipath points to attachment: need to decode message if (!next_document()) { - LOGERR(("MimeHandlerMail::skip_to_doc: next_document failed\n")); + LOGERR("MimeHandlerMail::skip_to_doc: next_document failed\n" ); return false; } } @@ -181,8 +178,7 @@ bool MimeHandlerMail::skip_to_document(const string& ipath) bool MimeHandlerMail::next_document() { - LOGDEB(("MimeHandlerMail::next_document m_idx %d m_havedoc %d\n", - m_idx, m_havedoc)); + LOGDEB("MimeHandlerMail::next_document m_idx " << (m_idx) << " m_havedoc " << (m_havedoc) << "\n" ); if (!m_havedoc) return false; bool res = false; @@ -190,8 +186,7 @@ bool MimeHandlerMail::next_document() if (m_idx == -1) { m_metaData[cstr_dj_keymt] = cstr_textplain; res = processMsg(m_bincdoc, 0); - LOGDEB1(("MimeHandlerMail::next_document: mt %s, att cnt %d\n", - m_metaData[cstr_dj_keymt].c_str(), m_attachments.size())); + LOGDEB1("MimeHandlerMail::next_document: mt " << (m_metaData[cstr_dj_keymt]) << ", att cnt " << (m_attachments.size()) << "\n" ); const string& txt = m_metaData[cstr_dj_keycontent]; if (m_startoftext < txt.size()) m_metaData[cstr_dj_keyabstract] = @@ -226,16 +221,16 @@ static bool decodeBody(const string& cte, // Content transfer encoding if (!stringlowercmp("quoted-printable", cte)) { if (!qp_decode(body, decoded)) { - LOGERR(("decodeBody: quoted-printable decoding failed !\n")); - LOGDEB((" Body: \n%s\n", body.c_str())); + LOGERR("decodeBody: quoted-printable decoding failed !\n" ); + LOGDEB(" Body: \n" << (body) << "\n" ); return false; } *respp = &decoded; } else if (!stringlowercmp("base64", cte)) { if (!base64_decode(body, decoded)) { // base64 encoding errors are actually relatively common - LOGERR(("decodeBody: base64 decoding failed !\n")); - LOGDEB((" Body: \n%s\n", body.c_str())); + LOGERR("decodeBody: base64 decoding failed !\n" ); + LOGDEB(" Body: \n" << (body) << "\n" ); return false; } *respp = &decoded; @@ -245,7 +240,7 @@ static bool decodeBody(const string& cte, // Content transfer encoding bool MimeHandlerMail::processAttach() { - LOGDEB(("MimeHandlerMail::processAttach() m_idx %d\n", m_idx)); + LOGDEB("MimeHandlerMail::processAttach() m_idx " << (m_idx) << "\n" ); if (!m_havedoc) return false; if (m_idx >= (int)m_attachments.size()) { @@ -259,10 +254,7 @@ bool MimeHandlerMail::processAttach() m_metaData[cstr_dj_keycharset] = att->m_charset; m_metaData[cstr_dj_keyfn] = att->m_filename; m_metaData[cstr_dj_keytitle] = att->m_filename + " (" + m_subject + ")"; - LOGDEB1((" processAttach:ct [%s] cs [%s] fn [%s]\n", - att->m_contentType.c_str(), - att->m_charset.c_str(), - att->m_filename.c_str())); + LOGDEB1(" processAttach:ct [" << (att->m_contentType) << "] cs [" << (att->m_charset) << "] fn [" << (att->m_filename) << "]\n" ); // Erase current content and replace m_metaData[cstr_dj_keycontent] = string(); @@ -313,11 +305,10 @@ bool MimeHandlerMail::processAttach() // text bool MimeHandlerMail::processMsg(Binc::MimePart *doc, int depth) { - LOGDEB2(("MimeHandlerMail::processMsg: depth %d\n", depth)); + LOGDEB2("MimeHandlerMail::processMsg: depth " << (depth) << "\n" ); if (depth++ >= maxdepth) { // Have to stop somewhere - LOGINFO(("MimeHandlerMail::processMsg: maxdepth %d exceeded\n", - maxdepth)); + LOGINFO("MimeHandlerMail::processMsg: maxdepth " << (maxdepth) << " exceeded\n" ); // Return true anyway, better to index partially than not at all return true; } @@ -369,7 +360,7 @@ bool MimeHandlerMail::processMsg(Binc::MimePart *doc, int depth) m_metaData[cstr_dj_keymd] = ascuxtime; } else { // Leave mtime field alone, ftime will be used instead. - LOGDEB(("rfc2822Date...: failed: [%s]\n", decoded.c_str())); + LOGDEB("rfc2822Date...: failed: [" << (decoded) << "]\n" ); } } if (preview()) @@ -403,12 +394,10 @@ bool MimeHandlerMail::processMsg(Binc::MimePart *doc, int depth) text += '\n'; m_startoftext = text.size(); - LOGDEB2(("MimeHandlerMail::processMsg:ismultipart %d mime subtype '%s'\n", - doc->isMultipart(), doc->getSubType().c_str())); + LOGDEB2("MimeHandlerMail::processMsg:ismultipart " << (doc->isMultipart()) << " mime subtype '" << (doc->getSubType()) << "'\n" ); walkmime(doc, depth); - LOGDEB2(("MimeHandlerMail::processMsg:text:[%s]\n", - m_metaData[cstr_dj_keycontent].c_str())); + LOGDEB2("MimeHandlerMail::processMsg:text:[" << (m_metaData[cstr_dj_keycontent]) << "]\n" ); return true; } @@ -424,17 +413,16 @@ bool MimeHandlerMail::processMsg(Binc::MimePart *doc, int depth) // message/rfc822 may also be of interest. void MimeHandlerMail::walkmime(Binc::MimePart* doc, int depth) { - LOGDEB2(("MimeHandlerMail::walkmime: depth %d\n", depth)); + LOGDEB2("MimeHandlerMail::walkmime: depth " << (depth) << "\n" ); if (depth++ >= maxdepth) { - LOGINFO(("walkmime: max depth (%d) exceeded\n", maxdepth)); + LOGINFO("walkmime: max depth (" << (maxdepth) << ") exceeded\n" ); return; } string& out = m_metaData[cstr_dj_keycontent]; if (doc->isMultipart()) { - LOGDEB2(("walkmime: ismultipart %d subtype '%s'\n", - doc->isMultipart(), doc->getSubType().c_str())); + LOGDEB2("walkmime: ismultipart " << (doc->isMultipart()) << " subtype '" << (doc->getSubType()) << "'\n" ); // We only handle alternative, related and mixed (no digests). std::vector::iterator it; @@ -457,22 +445,22 @@ void MimeHandlerMail::walkmime(Binc::MimePart* doc, int depth) // Get and parse content-type header Binc::HeaderItem hi; if (!it->h.getFirstHeader("Content-Type", hi)) { - LOGDEB(("walkmime:no ctent-type header for part %d\n", i)); + LOGDEB("walkmime:no ctent-type header for part " << (i) << "\n" ); continue; } MimeHeaderValue content_type; parseMimeHeaderValue(hi.getValue(), content_type); - LOGDEB2(("walkmime: C-type: %s\n",content_type.value.c_str())); + LOGDEB2("walkmime: C-type: " << (content_type.value) << "\n" ); if (!stringlowercmp(cstr_textplain, content_type.value)) ittxt = it; else if (!stringlowercmp("text/html", content_type.value)) ithtml = it; } if (ittxt != doc->members.end()) { - LOGDEB2(("walkmime: alternative: chose text/plain part\n")) + LOGDEB2("walkmime: alternative: chose text/plain part\n" ); walkmime(&(*ittxt), depth); } else if (ithtml != doc->members.end()) { - LOGDEB2(("walkmime: alternative: chose text/html part\n")) + LOGDEB2("walkmime: alternative: chose text/html part\n" ); walkmime(&(*ithtml), depth); } } @@ -488,7 +476,7 @@ void MimeHandlerMail::walkmime(Binc::MimePart* doc, int depth) if (doc->h.getFirstHeader("Content-Type", hi)) { ctt = hi.getValue(); } - LOGDEB2(("walkmime:content-type: %s\n", ctt.c_str())); + LOGDEB2("walkmime:content-type: " << (ctt) << "\n" ); MimeHeaderValue content_type; parseMimeHeaderValue(ctt, content_type); @@ -499,7 +487,7 @@ void MimeHandlerMail::walkmime(Binc::MimePart* doc, int depth) } MimeHeaderValue content_disposition; parseMimeHeaderValue(ctd, content_disposition); - LOGDEB2(("Content_disposition:[%s]\n", content_disposition.value.c_str())); + LOGDEB2("Content_disposition:[" << (content_disposition.value) << "]\n" ); string dispindic; if (stringlowercmp("inline", content_disposition.value)) dispindic = "Attachment"; @@ -519,7 +507,7 @@ void MimeHandlerMail::walkmime(Binc::MimePart* doc, int depth) } if (doc->isMessageRFC822()) { - LOGDEB2(("walkmime: message/RFC822 part\n")); + LOGDEB2("walkmime: message/RFC822 part\n" ); // The first part is the already parsed message. Call // processMsg instead of walkmime so that mail headers get @@ -540,7 +528,7 @@ void MimeHandlerMail::walkmime(Binc::MimePart* doc, int depth) } // "Simple" part. - LOGDEB2(("walkmime: simple part\n")); + LOGDEB2("walkmime: simple part\n" ); // Normally the default charset is us-ascii. But it happens that 8 // bit chars exist in a message that is stated as us-ascii. Ie the // mailer used by yahoo support ('KANA') does this. We could @@ -587,7 +575,7 @@ void MimeHandlerMail::walkmime(Binc::MimePart* doc, int depth) } MHMailAttach *att = new MHMailAttach; if (att == 0) { - LOGERR(("Out of memory\n")); + LOGERR("Out of memory\n" ); return; } att->m_contentType = content_type.value; @@ -596,11 +584,7 @@ void MimeHandlerMail::walkmime(Binc::MimePart* doc, int depth) att->m_charset = charset; att->m_contentTransferEncoding = cte; att->m_part = doc; - LOGDEB(("walkmime: attachmnt: ct [%s] cte [%s] cs [%s] fn [%s]\n", - att->m_contentType.c_str(), - att->m_contentTransferEncoding.c_str(), - att->m_charset.c_str(), - filename.c_str())); + LOGDEB("walkmime: attachmnt: ct [" << (att->m_contentType) << "] cte [" << (att->m_contentTransferEncoding) << "] cs [" << (att->m_charset) << "] fn [" << (filename) << "]\n" ); m_attachments.push_back(att); return; } @@ -610,15 +594,14 @@ void MimeHandlerMail::walkmime(Binc::MimePart* doc, int depth) // filter stack work: this would create another subdocument, but // we want instead to decode a body part of this message document. - LOGDEB2(("walkmime: final: body start offset %d, length %d\n", - doc->getBodyStartOffset(), doc->getBodyLength())); + LOGDEB2("walkmime: final: body start offset " << (doc->getBodyStartOffset()) << ", length " << (doc->getBodyLength()) << "\n" ); string body; doc->getBody(body, 0, doc->bodylength); { string decoded; const string *bdp; if (!decodeBody(cte, body, decoded, &bdp)) { - LOGERR(("MimeHandlerMail::walkmime: failed decoding body\n")); + LOGERR("MimeHandlerMail::walkmime: failed decoding body\n" ); } if (bdp != &body) body.swap(decoded); @@ -639,10 +622,9 @@ void MimeHandlerMail::walkmime(Binc::MimePart* doc, int depth) } else { string utf8; // Transcode to utf-8 - LOGDEB1(("walkmime: transcoding from %s to UTF-8\n", charset.c_str())); + LOGDEB1("walkmime: transcoding from " << (charset) << " to UTF-8\n" ); if (!transcode(body, utf8, charset, cstr_utf8)) { - LOGERR(("walkmime: transcode failed from cs '%s' to UTF-8\n", - charset.c_str())); + LOGERR("walkmime: transcode failed from cs '" << (charset) << "' to UTF-8\n" ); out += body; } else { out += utf8; @@ -652,5 +634,6 @@ void MimeHandlerMail::walkmime(Binc::MimePart* doc, int depth) if (out.length() && out[out.length()-1] != '\n') out += '\n'; - LOGDEB2(("walkmime: out now: [%s]\n", out.c_str())); + LOGDEB2("walkmime: out now: [" << (out) << "]\n" ); } + diff --git a/src/internfile/mh_mbox.cpp b/src/internfile/mh_mbox.cpp index 418004d4..e5385c25 100644 --- a/src/internfile/mh_mbox.cpp +++ b/src/internfile/mh_mbox.cpp @@ -18,27 +18,34 @@ #include "autoconfig.h" #include -#include #include -#include +#include +#include "safesysstat.h" #include + +#if defined(_WIN32) +#define USING_STD_REGEX +#endif + +#ifdef USING_STD_REGEX +#include +#else #include -#include -#include +#endif #include #include +#include #include "cstr.h" #include "mimehandler.h" -#include "debuglog.h" +#include "log.h" #include "readfile.h" #include "mh_mbox.h" #include "smallut.h" #include "rclconfig.h" #include "md5ut.h" #include "conftree.h" -#include "ptmutex.h" using namespace std; @@ -58,7 +65,7 @@ public: private: FILE **m_fpp; }; -static PTMutexInit o_mcache_mutex; +static std::mutex o_mcache_mutex; /** * Handles a cache for message numbers to offset translations. Permits direct @@ -72,6 +79,14 @@ static PTMutexInit o_mcache_mutex; * offsets for all message "From_" lines follow. The format is purely * binary, values are not even byte-swapped to be proc-idependant. */ + +#ifdef _WIN32 +// vc++ does not let define an array of size o_b1size because non-const?? +#define M_o_b1size 1024 +#else +#define M_o_b1size o_b1size +#endif + class MboxCache { public: typedef MimeHandlerMbox::mbhoff_type mbhoff_type; @@ -85,47 +100,43 @@ public: ~MboxCache() {} mbhoff_type get_offset(RclConfig *config, const string& udi, int msgnum) { - LOGDEB0(("MboxCache::get_offsets: udi [%s] msgnum %d\n", udi.c_str(), - msgnum)); + LOGDEB0("MboxCache::get_offsets: udi [" << (udi) << "] msgnum " << (msgnum) << "\n" ); if (!ok(config)) { - LOGDEB0(("MboxCache::get_offsets: init failed\n")); + LOGDEB0("MboxCache::get_offsets: init failed\n" ); return -1; } - PTMutexLocker locker(o_mcache_mutex); + std::unique_lock locker(o_mcache_mutex); string fn = makefilename(udi); FILE *fp = 0; if ((fp = fopen(fn.c_str(), "r")) == 0) { - LOGDEB(("MboxCache::get_offsets: open failed, errno %d\n", errno)); + LOGDEB("MboxCache::get_offsets: open failed, errno " << (errno) << "\n" ); return -1; } FpKeeper keeper(&fp); - char blk1[o_b1size]; + char blk1[M_o_b1size]; if (fread(blk1, 1, o_b1size, fp) != o_b1size) { - LOGDEB0(("MboxCache::get_offsets: read blk1 errno %d\n", errno)); + LOGDEB0("MboxCache::get_offsets: read blk1 errno " << (errno) << "\n" ); return -1; } ConfSimple cf(string(blk1, o_b1size)); string fudi; if (!cf.get("udi", fudi) || fudi.compare(udi)) { - LOGINFO(("MboxCache::get_offset:badudi fn %s udi [%s], fudi [%s]\n", - fn.c_str(), udi.c_str(), fudi.c_str())); + LOGINFO("MboxCache::get_offset:badudi fn " << (fn) << " udi [" << (udi) << "], fudi [" << (fudi) << "]\n" ); return -1; } if (fseeko(fp, cacheoffset(msgnum), SEEK_SET) != 0) { - LOGDEB0(("MboxCache::get_offsets: seek %lld errno %d\n", - cacheoffset(msgnum), errno)); + LOGDEB0("MboxCache::get_offsets: seek " << (lltodecstr(cacheoffset(msgnum))) << " errno " << (errno) << "\n" ); return -1; } mbhoff_type offset = -1; - int ret; + size_t ret; if ((ret = fread(&offset, 1, sizeof(mbhoff_type), fp)) != sizeof(mbhoff_type)) { - LOGDEB0(("MboxCache::get_offsets: read ret %d errno %d\n", - ret, errno)); + LOGDEB0("MboxCache::get_offsets: read ret " << (ret) << " errno " << (errno) << "\n" ); return -1; } - LOGDEB0(("MboxCache::get_offsets: ret %lld\n", (long long)offset)); + LOGDEB0("MboxCache::get_offsets: ret " << (lltodecstr(offset)) << "\n" ); return offset; } @@ -133,16 +144,16 @@ public: void put_offsets(RclConfig *config, const string& udi, mbhoff_type fsize, vector& offs) { - LOGDEB0(("MboxCache::put_offsets: %u offsets\n", offs.size())); + LOGDEB0("MboxCache::put_offsets: " << (offs.size()) << " offsets\n" ); if (!ok(config) || !maybemakedir()) return; if (fsize < m_minfsize) return; - PTMutexLocker locker(o_mcache_mutex); + std::unique_lock locker(o_mcache_mutex); string fn = makefilename(udi); FILE *fp; if ((fp = fopen(fn.c_str(), "w")) == 0) { - LOGDEB(("MboxCache::put_offsets: fopen errno %d\n", errno)); + LOGDEB("MboxCache::put_offsets: fopen errno " << (errno) << "\n" ); return; } FpKeeper keeper(&fp); @@ -152,7 +163,7 @@ public: blk1.append(cstr_newline); blk1.resize(o_b1size, 0); if (fwrite(blk1.c_str(), 1, o_b1size, fp) != o_b1size) { - LOGDEB(("MboxCache::put_offsets: fwrite errno %d\n", errno)); + LOGDEB("MboxCache::put_offsets: fwrite errno " << (errno) << "\n" ); return; } @@ -168,7 +179,7 @@ public: // Check state, possibly initialize bool ok(RclConfig *config) { - PTMutexLocker locker(o_mcache_mutex); + std::unique_lock locker(o_mcache_mutex); if (m_minfsize == -1) return false; if (!m_ok) { @@ -181,13 +192,7 @@ public: } m_minfsize = minmbs * 1000 * 1000; - config->getConfParam("mboxcachedir", m_dir); - if (m_dir.empty()) - m_dir = "mboxcache"; - m_dir = path_tildexpand(m_dir); - // If not an absolute path, compute relative to config dir - if (m_dir.at(0) != '/') - m_dir = path_cat(config->getConfDir(), m_dir); + m_dir = config->getMboxcacheDir(); m_ok = true; } return m_ok; @@ -228,7 +233,6 @@ private: }; const size_t MboxCache::o_b1size = 1024; - static class MboxCache o_mcache; static const string cstr_keyquirks("mhmboxquirks"); @@ -253,7 +257,7 @@ void MimeHandlerMbox::clear() bool MimeHandlerMbox::set_document_file(const string& mt, const string &fn) { - LOGDEB(("MimeHandlerMbox::set_document_file(%s)\n", fn.c_str())); + LOGDEB("MimeHandlerMbox::set_document_file(" << (fn) << ")\n" ); RecollFilter::set_document_file(mt, fn); m_fn = fn; if (m_vfp) { @@ -263,8 +267,7 @@ bool MimeHandlerMbox::set_document_file(const string& mt, const string &fn) m_vfp = fopen(fn.c_str(), "r"); if (m_vfp == 0) { - LOGERR(("MimeHandlerMail::set_document_file: error opening %s\n", - fn.c_str())); + LOGERR("MimeHandlerMail::set_document_file: error opening " << (fn) << "\n" ); return false; } #if defined O_NOATIME && O_NOATIME != 0 @@ -272,9 +275,14 @@ bool MimeHandlerMbox::set_document_file(const string& mt, const string &fn) // perror("fcntl"); } #endif - fseek((FILE *)m_vfp, 0, SEEK_END); - m_fsize = ftell((FILE*)m_vfp); - fseek((FILE*)m_vfp, 0, SEEK_SET); + // Used to use ftell() here: no good beyond 2GB + {struct stat st; + if (fstat(fileno((FILE*)m_vfp), &st) < 0) { + LOGERR("MimeHandlerMbox:setdocfile: fstat(" << (fn) << ") failed errno " << (errno) << "\n" ); + return false; + } + m_fsize = st.st_size; + } m_havedoc = true; m_offsets.clear(); m_quirks = 0; @@ -283,16 +291,15 @@ bool MimeHandlerMbox::set_document_file(const string& mt, const string &fn) string quirks; if (m_config && m_config->getConfParam(cstr_keyquirks, quirks)) { if (quirks == "tbird") { - LOGDEB(("MimeHandlerMbox: setting quirks TBIRD\n")); + LOGDEB("MimeHandlerMbox: setting quirks TBIRD\n" ); m_quirks |= MBOXQUIRK_TBIRD; } } // And double check for thunderbird string tbirdmsf = fn + ".msf"; - if ((m_quirks&MBOXQUIRK_TBIRD) == 0 && access(tbirdmsf.c_str(), 0) == 0) { - LOGDEB(("MimeHandlerMbox: detected unconfigured tbird mbox in %s\n", - fn.c_str())); + if ((m_quirks&MBOXQUIRK_TBIRD) == 0 && path_exists(tbirdmsf)) { + LOGDEB("MimeHandlerMbox: detected unconfigured tbird mbox in " << (fn) << "\n" ); m_quirks |= MBOXQUIRK_TBIRD; } @@ -303,7 +310,7 @@ bool MimeHandlerMbox::set_document_file(const string& mt, const string &fn) typedef char line_type[LL+10]; static inline void stripendnl(line_type& line, int& ll) { - ll = strlen(line); + ll = int(strlen(line)); while (ll > 0) { if (line[ll-1] == '\n' || line[ll-1] == '\r') { line[ll-1] = 0; @@ -372,29 +379,44 @@ static const char *frompat = // exactly like: From ^M (From followed by space and eol). We only // test for this if QUIRKS_TBIRD is set static const char *miniTbirdFrom = "^From $"; - +#ifndef USING_STD_REGEX static regex_t fromregex; static regex_t minifromregex; +#define M_regexec(A,B,C,D,E) regexec(&(A),B,C,D,E) +#else +basic_regex fromregex; +basic_regex minifromregex; +#define REG_NOSUB std::regex_constants::nosubs +#define REG_EXTENDED std::regex_constants::extended +#define M_regexec(A, B, C, D, E) (!regex_match(B,A)) + +#endif + static bool regcompiled; -static PTMutexInit o_regex_mutex; +static std::mutex o_regex_mutex; static void compileregexes() { - PTMutexLocker locker(o_regex_mutex); + std::unique_lock locker(o_regex_mutex); // As the initial test of regcompiled is unprotected the value may // have changed while we were waiting for the lock. Test again now // that we are alone. if (regcompiled) return; +#ifndef USING_STD_REGEX regcomp(&fromregex, frompat, REG_NOSUB|REG_EXTENDED); regcomp(&minifromregex, miniTbirdFrom, REG_NOSUB|REG_EXTENDED); +#else + fromregex = basic_regex(frompat, REG_NOSUB | REG_EXTENDED); + minifromregex = basic_regex(miniTbirdFrom, REG_NOSUB | REG_EXTENDED); +#endif regcompiled = true; } bool MimeHandlerMbox::next_document() { if (m_vfp == 0) { - LOGERR(("MimeHandlerMbox::next_document: not open\n")); + LOGERR("MimeHandlerMbox::next_document: not open\n" ); return false; } if (!m_havedoc) { @@ -406,11 +428,10 @@ bool MimeHandlerMbox::next_document() sscanf(m_ipath.c_str(), "%d", &mtarg); } else if (m_forPreview) { // Can't preview an mbox. - LOGDEB(("MimeHandlerMbox::next_document: can't preview folders!\n")); + LOGDEB("MimeHandlerMbox::next_document: can't preview folders!\n" ); return false; } - LOGDEB0(("MimeHandlerMbox::next_document: fn %s, msgnum %d mtarg %d \n", - m_fn.c_str(), m_msgnum, mtarg)); + LOGDEB0("MimeHandlerMbox::next_document: fn " << (m_fn) << ", msgnum " << (m_msgnum) << " mtarg " << (mtarg) << " \n" ); if (mtarg == 0) mtarg = -1; @@ -430,16 +451,15 @@ bool MimeHandlerMbox::next_document() if (mtarg > 0) { mbhoff_type off; line_type line; - LOGDEB0(("MimeHandlerMbox::next_doc: mtarg %d m_udi[%s]\n", - mtarg, m_udi.c_str())); + LOGDEB0("MimeHandlerMbox::next_doc: mtarg " << (mtarg) << " m_udi[" << (m_udi) << "]\n" ); if (!m_udi.empty() && (off = o_mcache.get_offset(m_config, m_udi, mtarg)) >= 0 && fseeko(fp, (off_t)off, SEEK_SET) >= 0 && fgets(line, LL, fp) && - (!regexec(&fromregex, line, 0, 0, 0) || + (!M_regexec(fromregex, line, 0, 0, 0) || ((m_quirks & MBOXQUIRK_TBIRD) && - !regexec(&minifromregex, line, 0, 0, 0))) ) { - LOGDEB0(("MimeHandlerMbox: Cache: From_ Ok\n")); + !M_regexec(minifromregex, line, 0, 0, 0))) ) { + LOGDEB0("MimeHandlerMbox: Cache: From_ Ok\n" ); fseeko(fp, (off_t)off, SEEK_SET); m_msgnum = mtarg -1; storeoffsets = false; @@ -458,7 +478,7 @@ bool MimeHandlerMbox::next_document() for (;;) { message_end = ftello(fp); if (!fgets(line, LL, fp)) { - LOGDEB2(("MimeHandlerMbox:next: eof\n")); + LOGDEB2("MimeHandlerMbox:next: eof\n" ); iseof = true; m_msgnum++; break; @@ -466,8 +486,7 @@ bool MimeHandlerMbox::next_document() m_lineno++; int ll; stripendnl(line, ll); - LOGDEB2(("mhmbox:next: hadempty %d lineno %d ll %d Line: [%s]\n", - hademptyline, m_lineno, ll, line)); + LOGDEB2("mhmbox:next: hadempty " << (hademptyline) << " lineno " << (m_lineno) << " ll " << (ll) << " Line: [" << (line) << "]\n" ); if (hademptyline) { if (ll > 0) { // Non-empty line with empty line flag set, reset flag @@ -481,12 +500,11 @@ bool MimeHandlerMbox::next_document() /* The 'F' compare is redundant but it improves performance A LOT */ if (line[0] == 'F' && ( - !regexec(&fromregex, line, 0, 0, 0) || + !M_regexec(fromregex, line, 0, 0, 0) || ((m_quirks & MBOXQUIRK_TBIRD) && - !regexec(&minifromregex, line, 0, 0, 0))) + !M_regexec(minifromregex, line, 0, 0, 0))) ) { - LOGDEB1(("MimeHandlerMbox: msgnum %d, " - "From_ at line %d: [%s]\n", m_msgnum, m_lineno, line)); + LOGDEB0("MimeHandlerMbox: msgnum " << (m_msgnum) << ", From_ at line " << (m_lineno) << ": [" << (line) << "]\n" ); if (storeoffsets) m_offsets.push_back(message_end); m_msgnum++; @@ -509,15 +527,13 @@ bool MimeHandlerMbox::next_document() line[ll+1] = 0; msgtxt += line; if (msgtxt.size() > max_mbox_member_size) { - LOGERR(("mh_mbox: huge message (more than %u MB) inside %s," - " giving up\n", max_mbox_member_size/(1024*1024), - m_fn.c_str())); + LOGERR("mh_mbox: huge message (more than " << (max_mbox_member_size/(1024*1024)) << " MB) inside " << (m_fn) << ", giving up\n" ); return false; } } } - LOGDEB2(("Message text length %d\n", msgtxt.size())); - LOGDEB2(("Message text: [%s]\n", msgtxt.c_str())); + LOGDEB2("Message text length " << (msgtxt.size()) << "\n" ); + LOGDEB2("Message text: [" << (msgtxt) << "]\n" ); char buf[20]; // m_msgnum was incremented when hitting the next From_ or eof, so the data // is for m_msgnum - 1 @@ -525,7 +541,7 @@ bool MimeHandlerMbox::next_document() m_metaData[cstr_dj_keyipath] = buf; m_metaData[cstr_dj_keymt] = "message/rfc822"; if (iseof) { - LOGDEB2(("MimeHandlerMbox::next: eof hit\n")); + LOGDEB2("MimeHandlerMbox::next: eof hit\n" ); m_havedoc = false; if (!m_udi.empty() && storeoffsets) { o_mcache.put_offsets(m_config, m_udi, m_fsize, m_offsets); @@ -650,3 +666,4 @@ int main(int argc, char **argv) #endif // TEST_MH_MBOX + diff --git a/src/internfile/mh_null.h b/src/internfile/mh_null.h new file mode 100644 index 00000000..5554d57d --- /dev/null +++ b/src/internfile/mh_null.h @@ -0,0 +1,59 @@ +/* Copyright (C) 2004 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _MH_NULL_H_INCLUDED_ +#define _MH_NULL_H_INCLUDED_ + +// It may make sense in some cases to set this null filter (no output) +// instead of using recoll_noindex or leaving the default filter in +// case one doesn't want to install it: this will avoid endless retries +// to reindex the affected files, as recoll will think it has succeeded +// indexing them. Downside: the files won't be indexed when one +// actually installs the real filter, will need a -z +// Actually used for empty files +// Associated to application/x-zerosize, so use +// = internal application/x-zerosize +// in mimeconf +#include +#include "cstr.h" +#include "mimehandler.h" + +class MimeHandlerNull : public RecollFilter { + public: + MimeHandlerNull(RclConfig *cnf, const std::string& id) + : RecollFilter(cnf, id) + { + } + virtual ~MimeHandlerNull() + { + } + virtual bool set_document_file(const string& mt, const string& fn) + { + RecollFilter::set_document_file(mt, fn); + return m_havedoc = true; + } + virtual bool next_document() + { + if (m_havedoc == false) + return false; + m_havedoc = false; + m_metaData[cstr_dj_keycontent] = cstr_null; + m_metaData[cstr_dj_keymt] = cstr_textplain; + return true; + } +}; + +#endif /* _MH_NULL_H_INCLUDED_ */ diff --git a/src/internfile/mh_symlink.h b/src/internfile/mh_symlink.h index f144ecf6..8f200af8 100644 --- a/src/internfile/mh_symlink.h +++ b/src/internfile/mh_symlink.h @@ -18,14 +18,14 @@ #define _MH_SYMLINK_H_INCLUDED_ #include -#include +#include "safeunistd.h" #include #include "cstr.h" #include "mimehandler.h" #include "transcode.h" #include "pathut.h" -#include "debuglog.h" +#include "log.h" /** Index symlink target * @@ -61,8 +61,7 @@ class MimeHandlerSymlink : public RecollFilter { transcode(path_getsimple(slc), m_metaData[cstr_dj_keycontent], m_config->getDefCharset(true), "UTF-8"); } else { - LOGDEB(("Symlink: readlink [%s] failed, errno %d\n", m_fn.c_str(), - errno)); + LOGDEB("Symlink: readlink [" << (m_fn) << "] failed, errno " << (errno) << "\n" ); } m_metaData[cstr_dj_keymt] = cstr_textplain; return true; @@ -72,3 +71,4 @@ private: }; #endif /* _MH_SYMLINK_H_INCLUDED_ */ + diff --git a/src/internfile/mh_text.cpp b/src/internfile/mh_text.cpp index 6aab33fa..5cc38444 100644 --- a/src/internfile/mh_text.cpp +++ b/src/internfile/mh_text.cpp @@ -17,23 +17,24 @@ #include "autoconfig.h" #include -#include -#include #include +#include "safefcntl.h" +#include +#include "safeunistd.h" #include #include -#ifndef NO_NAMESPACES -using namespace std; -#endif /* NO_NAMESPACES */ #include "cstr.h" #include "mh_text.h" -#include "debuglog.h" +#include "log.h" #include "readfile.h" #include "md5ut.h" #include "rclconfig.h" #include "pxattr.h" +#include "pathut.h" + +using namespace std; const int MB = 1024*1024; const int KB = 1024; @@ -41,28 +42,35 @@ const int KB = 1024; // Process a plain text file bool MimeHandlerText::set_document_file(const string& mt, const string &fn) { - LOGDEB(("MimeHandlerText::set_document_file: [%s]\n", fn.c_str())); + LOGDEB("MimeHandlerText::set_document_file: [" << fn << "] offs " << + m_offs << "\n"); RecollFilter::set_document_file(mt, fn); + m_fn = fn; + // This should not be necessary, but it happens on msw that offset is large + // negative at this point, could not find the reason (still trying). + m_offs = 0; // file size for oversize check - struct stat st; - if (stat(m_fn.c_str(), &st) < 0) { - LOGERR(("MimeHandlerText::set_document_file: stat(%s) errno %d\n", - m_fn.c_str(), errno)); + long long fsize = path_filesize(m_fn); + if (fsize < 0) { + LOGERR("MimeHandlerText::set_document_file: stat " << m_fn << + " errno " << errno << "\n"); return false; } +#ifndef _WIN32 // Check for charset defined in extended attribute as per: // http://freedesktop.org/wiki/CommonExtendedAttributes pxattr::get(m_fn, "charset", &m_charsetfromxattr); +#endif // Max file size parameter: texts over this size are not indexed int maxmbs = 20; m_config->getConfParam("textfilemaxmbs", &maxmbs); - if (maxmbs == -1 || st.st_size / MB <= maxmbs) { + if (maxmbs == -1 || fsize / MB <= maxmbs) { // Text file page size: if set, we split text files into // multiple documents int ps = 1000; @@ -100,10 +108,11 @@ bool MimeHandlerText::set_document_string(const string& mt, const string& otext) bool MimeHandlerText::skip_to_document(const string& ipath) { - long long t; - if (sscanf(ipath.c_str(), "%lld", &t) != 1) { - LOGERR(("MimeHandlerText::skip_to_document: bad ipath offs [%s]\n", - ipath.c_str())); + char *endptr; + long long t = strtoll(ipath.c_str(), &endptr, 10); + if (endptr == ipath.c_str()) { + LOGERR("MimeHandlerText::skip_to_document: bad ipath offs [" << + ipath << "]\n"); return false; } m_offs = (off_t)t; @@ -113,7 +122,7 @@ bool MimeHandlerText::skip_to_document(const string& ipath) bool MimeHandlerText::next_document() { - LOGDEB(("MimeHandlerText::next_document: m_havedoc %d\n", int(m_havedoc))); + LOGDEB("MimeHandlerText::next_document: m_havedoc " << m_havedoc << "\n"); if (m_havedoc == false) return false; @@ -147,8 +156,7 @@ bool MimeHandlerText::next_document() // first chunk). This is a hack. The right thing to do would // be to use a different mtype for files over the page size, // and keep text/plain only for smaller files. - char buf[30]; - sprintf(buf, "%lld", (long long)(m_offs - srclen)); + string buf = lltodecstr(m_offs - srclen); if (m_offs - srclen != 0) m_metaData[cstr_dj_keyipath] = buf; readnext(); @@ -167,7 +175,7 @@ bool MimeHandlerText::readnext() string reason; m_text.clear(); if (!file_to_string(m_fn, m_text, m_offs, m_pagesz, &reason)) { - LOGERR(("MimeHandlerText: can't read file: %s\n", reason.c_str())); + LOGERR("MimeHandlerText: can't read file: " << (reason) << "\n" ); m_havedoc = false; return false; } @@ -189,3 +197,4 @@ bool MimeHandlerText::readnext() m_offs += m_text.length(); return true; } + diff --git a/src/internfile/mh_text.h b/src/internfile/mh_text.h index 1861ee76..9e4dfe12 100644 --- a/src/internfile/mh_text.h +++ b/src/internfile/mh_text.h @@ -19,7 +19,6 @@ #include #include -using std::string; #include "mimehandler.h" @@ -30,22 +29,22 @@ using std::string; */ class MimeHandlerText : public RecollFilter { public: - MimeHandlerText(RclConfig *cnf, const string& id) - : RecollFilter(cnf, id), m_paging(false), m_offs(0) + MimeHandlerText(RclConfig *cnf, const std::string& id) + : RecollFilter(cnf, id), m_paging(false), m_offs(0), m_pagesz(0) { } virtual ~MimeHandlerText() { } - virtual bool set_document_file(const string& mt, const string &file_path); - virtual bool set_document_string(const string&, const string&); + virtual bool set_document_file(const std::string& mt, const std::string &file_path); + virtual bool set_document_string(const std::string&, const std::string&); virtual bool is_data_input_ok(DataInput input) const { if (input == DOCUMENT_FILE_NAME || input == DOCUMENT_STRING) return true; return false; } virtual bool next_document(); - virtual bool skip_to_document(const string& s); + virtual bool skip_to_document(const std::string& s); virtual void clear() { m_paging = false; @@ -56,11 +55,11 @@ class MimeHandlerText : public RecollFilter { } private: bool m_paging; - string m_text; - string m_fn; + std::string m_text; + std::string m_fn; off_t m_offs; // Offset of next read in file if we're paging size_t m_pagesz; - string m_charsetfromxattr; + std::string m_charsetfromxattr; bool readnext(); }; diff --git a/src/internfile/mimehandler.cpp b/src/internfile/mimehandler.cpp index 4e72cb52..f75a9fe7 100644 --- a/src/internfile/mimehandler.cpp +++ b/src/internfile/mimehandler.cpp @@ -23,15 +23,15 @@ #include #include #include +#include using namespace std; #include "cstr.h" #include "mimehandler.h" -#include "debuglog.h" +#include "log.h" #include "rclconfig.h" #include "smallut.h" #include "md5ut.h" - #include "mh_exec.h" #include "mh_execm.h" #include "mh_html.h" @@ -40,7 +40,7 @@ using namespace std; #include "mh_text.h" #include "mh_symlink.h" #include "mh_unknown.h" -#include "ptmutex.h" +#include "mh_null.h" // Performance help: we use a pool of already known and created // handlers. There can be several instances for a given mime type @@ -50,18 +50,17 @@ static multimap o_handlers; static list::iterator> o_hlru; typedef list::iterator>::iterator hlruit_tp; -static PTMutexInit o_handlers_mutex; +static std::mutex o_handlers_mutex; static const unsigned int max_handlers_cache_size = 100; /* Look for mime handler in pool */ static RecollFilter *getMimeHandlerFromCache(const string& key) { - PTMutexLocker locker(o_handlers_mutex); + std::unique_lock locker(o_handlers_mutex); string xdigest; MD5HexPrint(key, xdigest); - LOGDEB(("getMimeHandlerFromCache: %s cache size %u\n", - xdigest.c_str(), o_handlers.size())); + LOGDEB("getMimeHandlerFromCache: " << (xdigest) << " cache size " << (o_handlers.size()) << "\n" ); multimap::iterator it = o_handlers.find(key); if (it != o_handlers.end()) { @@ -70,14 +69,13 @@ static RecollFilter *getMimeHandlerFromCache(const string& key) if (it1 != o_hlru.end()) { o_hlru.erase(it1); } else { - LOGERR(("getMimeHandlerFromCache: lru position not found\n")); + LOGERR("getMimeHandlerFromCache: lru position not found\n" ); } o_handlers.erase(it); - LOGDEB(("getMimeHandlerFromCache: %s found size %u\n", - xdigest.c_str(), o_handlers.size())); + LOGDEB("getMimeHandlerFromCache: " << (xdigest) << " found size " << (o_handlers.size()) << "\n" ); return h; } - LOGDEB(("getMimeHandlerFromCache: %s not found\n", xdigest.c_str())); + LOGDEB("getMimeHandlerFromCache: " << (xdigest) << " not found\n" ); return 0; } @@ -87,15 +85,14 @@ void returnMimeHandler(RecollFilter *handler) typedef multimap::value_type value_type; if (handler == 0) { - LOGERR(("returnMimeHandler: bad parameter\n")); + LOGERR("returnMimeHandler: bad parameter\n" ); return; } handler->clear(); - PTMutexLocker locker(o_handlers_mutex); + std::unique_lock locker(o_handlers_mutex); - LOGDEB(("returnMimeHandler: returning filter for %s cache size %d\n", - handler->get_mime_type().c_str(), o_handlers.size())); + LOGDEB("returnMimeHandler: returning filter for " << (handler->get_mime_type()) << " cache size " << (o_handlers.size()) << "\n" ); // Limit pool size. The pool can grow quite big because there are // many filter types, each of which can be used in several copies @@ -108,9 +105,9 @@ void returnMimeHandler(RecollFilter *handler) if (once) { once = 0; for (it = o_handlers.begin(); it != o_handlers.end(); it++) { - LOGDEB1(("Cache full. key: %s\n", it->first.c_str())); + LOGDEB1("Cache full. key: " << (it->first) << "\n" ); } - LOGDEB1(("Cache LRU size: %u\n", o_hlru.size())); + LOGDEB1("Cache LRU size: " << (o_hlru.size()) << "\n" ); } if (o_hlru.size() > 0) { it = o_hlru.back(); @@ -125,10 +122,9 @@ void returnMimeHandler(RecollFilter *handler) void clearMimeHandlerCache() { - LOGDEB(("clearMimeHandlerCache()\n")); - typedef multimap::value_type value_type; + LOGDEB("clearMimeHandlerCache()\n" ); multimap::iterator it; - PTMutexLocker locker(o_handlers_mutex); + std::unique_lock locker(o_handlers_mutex); for (it = o_handlers.begin(); it != o_handlers.end(); it++) { delete it->second; } @@ -140,44 +136,47 @@ void clearMimeHandlerCache() static RecollFilter *mhFactory(RclConfig *config, const string &mime, bool nobuild, string& id) { - LOGDEB2(("mhFactory(%s)\n", mime.c_str())); + LOGDEB2("mhFactory(" << (mime) << ")\n" ); string lmime(mime); stringtolower(lmime); if (cstr_textplain == lmime) { - LOGDEB2(("mhFactory(%s): returning MimeHandlerText\n", mime.c_str())); + LOGDEB2("mhFactory(" << (mime) << "): returning MimeHandlerText\n" ); MD5String("MimeHandlerText", id); return nobuild ? 0 : new MimeHandlerText(config, id); } else if ("text/html" == lmime) { - LOGDEB2(("mhFactory(%s): returning MimeHandlerHtml\n", mime.c_str())); + LOGDEB2("mhFactory(" << (mime) << "): returning MimeHandlerHtml\n" ); MD5String("MimeHandlerHtml", id); return nobuild ? 0 : new MimeHandlerHtml(config, id); } else if ("text/x-mail" == lmime) { - LOGDEB2(("mhFactory(%s): returning MimeHandlerMbox\n", mime.c_str())); + LOGDEB2("mhFactory(" << (mime) << "): returning MimeHandlerMbox\n" ); MD5String("MimeHandlerMbox", id); return nobuild ? 0 : new MimeHandlerMbox(config, id); } else if ("message/rfc822" == lmime) { - LOGDEB2(("mhFactory(%s): returning MimeHandlerMail\n", mime.c_str())); + LOGDEB2("mhFactory(" << (mime) << "): returning MimeHandlerMail\n" ); MD5String("MimeHandlerMail", id); return nobuild ? 0 : new MimeHandlerMail(config, id); } else if ("inode/symlink" == lmime) { - LOGDEB2(("mhFactory(%s): ret MimeHandlerSymlink\n", mime.c_str())); + LOGDEB2("mhFactory(" << (mime) << "): ret MimeHandlerSymlink\n" ); MD5String("MimeHandlerSymlink", id); return nobuild ? 0 : new MimeHandlerSymlink(config, id); + } else if ("application/x-zerosize" == lmime) { + LOGDEB("mhFactory(" << (mime) << "): ret MimeHandlerNull\n" ); + MD5String("MimeHandlerNull", id); + return nobuild ? 0 : new MimeHandlerNull(config, id); } else if (lmime.find("text/") == 0) { // Try to handle unknown text/xx as text/plain. This // only happen if the text/xx was defined as "internal" in // mimeconf, not at random. For programs, for example this // allows indexing and previewing as text/plain (no filter // exec) but still opening with a specific editor. - LOGDEB2(("mhFactory(%s): returning MimeHandlerText(x)\n",mime.c_str())); + LOGDEB2("mhFactory(" << (mime) << "): returning MimeHandlerText(x)\n" ); MD5String("MimeHandlerText", id); return nobuild ? 0 : new MimeHandlerText(config, id); } else { // We should not get there. It means that "internal" was set // as a handler in mimeconf for a mime type we actually can't // handle. - LOGERR(("mhFactory: mime type [%s] set as internal but unknown\n", - lmime.c_str())); + LOGERR("mhFactory: mime type [" << (lmime) << "] set as internal but unknown\n" ); MD5String("MimeHandlerUnknown", id); return nobuild ? 0 : new MimeHandlerUnknown(config, id); } @@ -200,23 +199,35 @@ MimeHandlerExec *mhExecFactory(RclConfig *cfg, const string& mtype, string& hs, string cmdstr; if (!cfg->valueSplitAttributes(hs, cmdstr, attrs)) { - LOGERR(("mhExecFactory: bad config line for [%s]: [%s]\n", - mtype.c_str(), hs.c_str())); + LOGERR("mhExecFactory: bad config line for [" << (mtype) << "]: [" << (hs) << "]\n" ); return 0; } // Split command name and args, and build exec object - list cmdtoks; + vector cmdtoks; stringToStrings(cmdstr, cmdtoks); if (cmdtoks.empty()) { - LOGERR(("mhExecFactory: bad config line for [%s]: [%s]\n", - mtype.c_str(), hs.c_str())); + LOGERR("mhExecFactory: bad config line for [" << (mtype) << "]: [" << (hs) << "]\n" ); return 0; } MimeHandlerExec *h = multiple ? new MimeHandlerExecMultiple(cfg, id) : new MimeHandlerExec(cfg, id); - list::iterator it = cmdtoks.begin(); + vector::iterator it = cmdtoks.begin(); + + // Special-case python and perl on windows: we need to also locate the + // first argument which is the script name "python somescript.py". + // On Unix, thanks to #!, we usually just run "somescript.py", but need + // the same change if we ever want to use the same cmdling as windows + if (!stringlowercmp("python", *it) || !stringlowercmp("perl", *it)) { + if (cmdtoks.size() < 2) { + LOGERR("mhExecFactory: python/perl cmd: no script?. [" << (mtype) << "]: [" << (hs) << "]\n" ); + } + vector::iterator it1(it); + it1++; + *it1 = cfg->findFilter(*it1); + } + h->params.push_back(cfg->findFilter(*it++)); h->params.insert(h->params.end(), it, cmdtoks.end()); @@ -233,9 +244,7 @@ MimeHandlerExec *mhExecFactory(RclConfig *cfg, const string& mtype, string& hs, for (it = h->params.begin(); it != h->params.end(); it++) { scmd += string("[") + *it + "] "; } - LOGDEB(("mhExecFactory:mt [%s] cfgmt [%s] cfgcs [%s] cmd: [%s]\n", - mtype.c_str(), h->cfgFilterOutputMtype.c_str(), h->cfgFilterOutputCharset.c_str(), - scmd.c_str())); + LOGDEB("mhExecFactory:mt [" << (mtype) << "] cfgmt [" << (h->cfgFilterOutputMtype) << "] cfgcs [" << (h->cfgFilterOutputCharset) << "] cmd: [" << (scmd) << "]\n" ); #endif return h; @@ -245,8 +254,7 @@ MimeHandlerExec *mhExecFactory(RclConfig *cfg, const string& mtype, string& hs, RecollFilter *getMimeHandler(const string &mtype, RclConfig *cfg, bool filtertypes) { - LOGDEB(("getMimeHandler: mtype [%s] filtertypes %d\n", - mtype.c_str(), filtertypes)); + LOGDEB("getMimeHandler: mtype [" << (mtype) << "] filtertypes " << (filtertypes) << "\n" ); RecollFilter *h = 0; // Get handler definition for mime type. We do this even if an @@ -279,18 +287,12 @@ RecollFilter *getMimeHandler(const string &mtype, RclConfig *cfg, MD5String(hs, id); } -#if 0 - { // string xdigest; LOGDEB2(("getMimeHandler: [%s] hs [%s] id [%s]\n", - //mtype.c_str(), hs.c_str(), MD5HexPrint(id, xdigest).c_str())); - } -#endif - // Do we already have a handler object in the cache ? h = getMimeHandlerFromCache(id); if (h != 0) goto out; - LOGDEB2(("getMimeHandler: %s not in cache\n", mtype.c_str())); + LOGDEB2("getMimeHandler: " << (mtype) << " not in cache\n" ); // Not in cache. if (internal) { @@ -301,14 +303,13 @@ RecollFilter *getMimeHandler(const string &mtype, RclConfig *cfg, // partly redundant with the localfields/rclaptg, but // better and the latter will probably go away at some // point in the future. - LOGDEB2(("handlertype internal, cmdstr [%s]\n", cmdstr.c_str())); + LOGDEB2("handlertype internal, cmdstr [" << (cmdstr) << "]\n" ); h = mhFactory(cfg, cmdstr.empty() ? mtype : cmdstr, false, id); goto out; } else if (!stringlowercmp("dll", handlertype)) { } else { if (cmdstr.empty()) { - LOGERR(("getMimeHandler: bad line for %s: %s\n", - mtype.c_str(), hs.c_str())); + LOGERR("getMimeHandler: bad line for " << (mtype) << ": " << (hs) << "\n" ); goto out; } if (!stringlowercmp("exec", handlertype)) { @@ -318,8 +319,7 @@ RecollFilter *getMimeHandler(const string &mtype, RclConfig *cfg, h = mhExecFactory(cfg, mtype, cmdstr, true, id); goto out; } else { - LOGERR(("getMimeHandler: bad line for %s: %s\n", - mtype.c_str(), hs.c_str())); + LOGERR("getMimeHandler: bad line for " << (mtype) << ": " << (hs) << "\n" ); goto out; } } @@ -362,3 +362,4 @@ bool canIntern(const std::string mtype, RclConfig *cfg) return false; return true; } + diff --git a/src/internfile/mimehandler.h b/src/internfile/mimehandler.h index c85ec0ba..981436f8 100644 --- a/src/internfile/mimehandler.h +++ b/src/internfile/mimehandler.h @@ -24,6 +24,7 @@ #include "Filter.h" #include "cstr.h" +#include "smallut.h" class RclConfig; @@ -86,16 +87,16 @@ public: return false; } virtual bool set_document_data(const std::string& mtype, - const char *cp, unsigned int sz) + const char *cp, size_t sz) { return set_document_string(mtype, std::string(cp, sz)); } - virtual void set_docsize(size_t size) - { - char csize[30]; - sprintf(csize, "%lld", (long long)size); - m_metaData[cstr_dj_keydocsize] = csize; + virtual void set_docsize(off_t size) { + m_docsize = size; + } + virtual off_t get_docsize() const { + return m_docsize; } virtual bool has_documents() const {return m_havedoc;} @@ -147,6 +148,7 @@ protected: // m_id is and md5 of the filter definition line (from mimeconf) and // is used when fetching/returning filters to / from the cache. std::string m_id; + off_t m_docsize; // Size of the top document }; /** @@ -158,7 +160,7 @@ protected: * indexedmimetypes (if this is set at all). */ extern RecollFilter *getMimeHandler(const std::string &mtyp, RclConfig *cfg, - bool filtertypes=false); + bool filtertypes); /// Free up filter for reuse (you can also delete it) extern void returnMimeHandler(RecollFilter *); diff --git a/src/internfile/myhtmlparse.cpp b/src/internfile/myhtmlparse.cpp index 5567fa9d..fcb3439f 100644 --- a/src/internfile/myhtmlparse.cpp +++ b/src/internfile/myhtmlparse.cpp @@ -23,6 +23,10 @@ * -----END-LICENCE----- */ #include +#ifdef _WIN32 +// Local implementation in windows directory +#include "strptime.h" +#endif #include #include #include @@ -33,7 +37,7 @@ #include "mimeparse.h" #include "smallut.h" #include "cancelcheck.h" -#include "debuglog.h" +#include "log.h" #include "transcode.h" static const string cstr_html_charset("charset"); @@ -189,7 +193,7 @@ MyHtmlParser::MyHtmlParser() void MyHtmlParser::decode_entities(string &s) { - LOGDEB2(("MyHtmlParser::decode_entities\n")); + LOGDEB2("MyHtmlParser::decode_entities\n" ); // This has no meaning whatsoever if the character encoding is unknown, // so don't do it. If charset known, caller has converted text to utf-8, // and this is also how we translate entities @@ -257,14 +261,7 @@ void MyHtmlParser::decode_entities(string &s) void MyHtmlParser::process_text(const string &text) { - LOGDEB2(("process_text: title %d script %d style %d pre %d " - "pending_space %d txt [%s]\n", - in_title_tag, - in_script_tag, - in_style_tag, - in_pre_tag, - pending_space, - text.c_str())); + LOGDEB2("process_text: title " << (in_title_tag) << " script " << (in_script_tag) << " style " << (in_style_tag) << " pre " << (in_pre_tag) << " pending_space " << (pending_space) << " txt [" << (text) << "]\n" ); CancelCheck::instance().checkCancel(); if (!in_script_tag && !in_style_tag) { @@ -303,7 +300,7 @@ MyHtmlParser::process_text(const string &text) bool MyHtmlParser::opening_tag(const string &tag) { - LOGDEB2(("opening_tag: [%s]\n", tag.c_str())); + LOGDEB2("opening_tag: [" << (tag) << "]\n" ); #if 0 cout << "TAG: " << tag << ": " << endl; map::const_iterator x; @@ -415,10 +412,7 @@ MyHtmlParser::opening_tag(const string &tag) charset = k->second; if (!charset.empty() && !samecharset(charset, fromcharset)) { - LOGDEB1(("Doc http-equiv charset '%s' " - "differs from dir deflt '%s'\n", - charset.c_str(), - fromcharset.c_str())); + LOGDEB1("Doc http-equiv charset '" << (charset) << "' differs from dir deflt '" << (fromcharset) << "'\n" ); throw false; } } @@ -432,10 +426,7 @@ MyHtmlParser::opening_tag(const string &tag) charset = newcharset; if (!charset.empty() && !samecharset(charset, fromcharset)) { - LOGDEB1(("Doc html5 charset '%s' " - "differs from dir deflt '%s'\n", - charset.c_str(), - fromcharset.c_str())); + LOGDEB1("Doc html5 charset '" << (charset) << "' differs from dir deflt '" << (fromcharset) << "'\n" ); throw false; } } @@ -490,7 +481,7 @@ MyHtmlParser::opening_tag(const string &tag) bool MyHtmlParser::closing_tag(const string &tag) { - LOGDEB2(("closing_tag: [%s]\n", tag.c_str())); + LOGDEB2("closing_tag: [" << (tag) << "]\n" ); if (tag.empty()) return true; switch (tag[0]) { case 'a': @@ -588,3 +579,4 @@ void MyHtmlParser::do_eof() { } + diff --git a/src/internfile/txtdcode.cpp b/src/internfile/txtdcode.cpp index 307b5d34..7eb78b20 100644 --- a/src/internfile/txtdcode.cpp +++ b/src/internfile/txtdcode.cpp @@ -15,51 +15,112 @@ */ #include "autoconfig.h" +#include + #include "cstr.h" #include "transcode.h" #include "mimehandler.h" -#include "debuglog.h" +#include "log.h" #include "smallut.h" - +#include "listmem.h" // Called after decoding from utf-8 failed. Handle the common case // where this is a good old 8bit-encoded text document left-over when // the locale was switched to utf-8. We try to guess a charset // according to the locale language and use it. This is a very rough -// heuristic, but may be better than discarding the data. -static bool alternate_decode(const string& in, string& out) +// heuristic, but may be better than discarding the data. +// If we still get a significant number of decode errors, the doc is +// quite probably binary, so just fail. +// Note that we could very well get a wrong transcoding (e.g. between +// iso-8859 variations), there is no way to detect it. +static bool alternate_decode(const string& in, string& out, const string& ocs) { - string lang = localelang(); - string code = langtocode(lang); - LOGDEB(("RecollFilter::txtdcode: trying alternate decode from %s\n", - code.c_str())); - return transcode(in, out, code, cstr_utf8); + int ecnt; + if (samecharset(ocs, cstr_utf8)) { + string lang = localelang(); + string code = langtocode(lang); + LOGDEB("RecollFilter::txtdcode: trying alternate decode from " << + code << "\n"); + bool ret = transcode(in, out, code, cstr_utf8, &ecnt); + return ecnt > 5 ? false : ret; + } else { + // Give a try to utf-8 anyway, as this is self-detecting. This + // handles UTF-8 docs in a non-utf-8 environment. Note that + // this will almost never be called, as most encodings are + // unable to detect errors so that the first try at + // transcoding will have succeeded and alternate_decode() will + // not be called at all. + // + // To avoid this, we would have to attempt an utf-8 decode + // first, but this is a costly proposition as we don't know + // how much data to test, so need to test all (the beginning + // of the text could be ascii even if there are 8-bit chars + // later). + bool ret = transcode(in, out, cstr_utf8, cstr_utf8, &ecnt); + return ecnt > 5 ? false : ret; + } +} + +static string bomtocode(const string& itext) +{ +#if 0 + std::ostringstream strm; + listmem(strm, itext.c_str(), MIN(itext.size(), 8)); + LOGDEB("txtdcode:bomtocode: input " << strm.str() << "\n"); +#endif + + const unsigned char *utxt = (const unsigned char *)itext.c_str(); + if (itext.size() >= 3 && utxt[0] == 0xEF && utxt[1] == 0xBB && + utxt[2] == 0xBF) { + LOGDEB("txtdcode:bomtocode: UTF-8\n"); + return "UTF-8"; + } else if (itext.size() >= 2 && utxt[0] == 0xFE && utxt[1] == 0xFF) { + return "UTF-16BE"; + } else if (itext.size() >= 2 && utxt[0] == 0xFF && utxt[1] == 0xFE) { + return "UTF-16LE"; + } else if (itext.size() >= 4 && utxt[0] == 0 && utxt[1] == 0 && + utxt[2] == 0xFE && utxt[3] == 0xFF) { + return "UTF-32BE"; + } else if (itext.size() >= 4 && utxt[3] == 0 && utxt[2] == 0 && + utxt[1] == 0xFE && utxt[0] == 0xFF) { + return "UTF-32LE"; + } else { + return string(); + } } bool RecollFilter::txtdcode(const string& who) { if (m_metaData[cstr_dj_keymt].compare(cstr_textplain)) { - LOGERR(("%s::txtdcode: called on non txt/plain: %s\n", who.c_str(), - m_metaData[cstr_dj_keymt].c_str())); + LOGERR(who << "::txtdcode: called on non txt/plain: " << + m_metaData[cstr_dj_keymt] << "\n"); return false; } string& ocs = m_metaData[cstr_dj_keyorigcharset]; string& itext = m_metaData[cstr_dj_keycontent]; - LOGDEB1(("%s::txtdcode: %d bytes from [%s] to UTF-8\n", - who.c_str(), itext.size(), ocs.c_str())); + LOGDEB(who << "::txtdcode: " << itext.size() << " bytes from [" << + ocs << "] to UTF-8\n"); int ecnt; string otext; + + string bomfromcode = bomtocode(itext); + if (!bomfromcode.empty()) { + LOGDEB(who << "::txtdcode: " << " input charset changed from " << + ocs << " to " << bomfromcode << " from BOM detection\n"); + ocs = bomfromcode; + } + bool ret = transcode(itext, otext, ocs, cstr_utf8, &ecnt); if (!ret || ecnt > int(itext.size() / 100)) { - LOGERR(("%s::txtdcode: transcode %d bytes to UTF-8 failed " - "for input charset [%s] ret %d ecnt %d\n", - who.c_str(), itext.size(), ocs.c_str(), ret, ecnt)); + LOGERR(who << "::txtdcode: transcode " << itext.size() << + " bytes to UTF-8 failed for input charset [" << ocs << + "] ret " << ret << " ecnt " << ecnt << "\n"); + + ret = alternate_decode(itext, otext, ocs); - if (samecharset(ocs, cstr_utf8)) { - ret = alternate_decode(itext, otext); - } if (!ret) { + LOGDEB("txtdcode: failed. Doc is not text?\n" ); itext.erase(); return false; } @@ -69,4 +130,3 @@ bool RecollFilter::txtdcode(const string& who) m_metaData[cstr_dj_keycharset] = cstr_utf8; return true; } - diff --git a/src/internfile/uncomp.cpp b/src/internfile/uncomp.cpp index 7ef4d694..5741c3fb 100644 --- a/src/internfile/uncomp.cpp +++ b/src/internfile/uncomp.cpp @@ -18,28 +18,28 @@ #include "autoconfig.h" #include -#include #include #include #include -using std::map; -using std::string; -using std::vector; #include "uncomp.h" -#include "debuglog.h" +#include "log.h" #include "smallut.h" #include "execmd.h" #include "pathut.h" +using std::map; +using std::string; +using std::vector; + Uncomp::UncompCache Uncomp::o_cache; bool Uncomp::uncompressfile(const string& ifn, const vector& cmdv, string& tfile) { if (m_docache) { - PTMutexLocker lock(o_cache.m_lock); + std::unique_lock lock(o_cache.m_lock); if (!o_cache.m_srcpath.compare(ifn)) { m_dir = o_cache.m_dir; m_tfile = tfile = o_cache.m_tfile; @@ -57,7 +57,7 @@ bool Uncomp::uncompressfile(const string& ifn, } // Make sure tmp dir is empty. we guarantee this to filters if (!m_dir || !m_dir->ok() || !m_dir->wipe()) { - LOGERR(("uncompressfile: can't clear temp dir %s\n", m_dir->dirname())); + LOGERR("uncompressfile: can't clear temp dir " << (m_dir->dirname()) << "\n" ); return false; } @@ -66,14 +66,12 @@ bool Uncomp::uncompressfile(const string& ifn, int pc; long long availmbs; if (!fsocc(m_dir->dirname(), &pc, &availmbs)) { - LOGERR(("uncompressfile: can't retrieve avail space for %s\n", - m_dir->dirname())); + LOGERR("uncompressfile: can't retrieve avail space for " << (m_dir->dirname()) << "\n" ); // Hope for the best } else { - struct stat stb; - if (stat(ifn.c_str(), &stb) < 0) { - LOGERR(("uncompressfile: stat input file %s errno %d\n", - ifn.c_str(), errno)); + long long fsize = path_filesize(ifn); + if (fsize < 0) { + LOGERR("uncompressfile: stat input file " << (ifn) << " errno " << (errno) << "\n" ); return false; } // We need at least twice the file size for the uncompressed @@ -82,12 +80,10 @@ bool Uncomp::uncompressfile(const string& ifn, // have enough space before trying. We take a little margin // use same Mb def as fsocc() - long long filembs = stb.st_size / (1024 * 1024); + long long filembs = fsize / (1024 * 1024); if (availmbs < 2 * filembs + 1) { - LOGERR(("uncompressfile. %lld MBs available in %s not enough " - "to uncompress %s of size %lld mbs\n", availmbs, - m_dir->dirname(), ifn.c_str(), filembs)); + LOGERR("uncompressfile. " << (lltodecstr(availmbs)) << " MBs available in " << (m_dir->dirname()) << " not enough to uncompress " << (ifn) << " of size " << (lltodecstr(filembs)) << " mbs\n" ); return false; } } @@ -111,10 +107,9 @@ bool Uncomp::uncompressfile(const string& ifn, ExecCmd ex; int status = ex.doexec(cmd, args, 0, &tfile); if (status || tfile.empty()) { - LOGERR(("uncompressfile: doexec: failed for [%s] status 0x%x\n", - ifn.c_str(), status)); + LOGERR("uncompressfile: doexec: failed for [" << (ifn) << "] status 0x" << (status) << "\n" ); if (!m_dir->wipe()) { - LOGERR(("uncompressfile: wipedir failed\n")); + LOGERR("uncompressfile: wipedir failed\n" ); } return false; } @@ -128,7 +123,7 @@ bool Uncomp::uncompressfile(const string& ifn, Uncomp::~Uncomp() { if (m_docache) { - PTMutexLocker lock(o_cache.m_lock); + std::unique_lock lock(o_cache.m_lock); delete o_cache.m_dir; o_cache.m_dir = m_dir; o_cache.m_tfile = m_tfile; @@ -138,3 +133,4 @@ Uncomp::~Uncomp() } } + diff --git a/src/internfile/uncomp.h b/src/internfile/uncomp.h index 367d3608..1d2d2754 100644 --- a/src/internfile/uncomp.h +++ b/src/internfile/uncomp.h @@ -19,9 +19,10 @@ #include #include +#include #include "pathut.h" -#include "ptmutex.h" +#include "rclutil.h" /// Uncompression script interface. class Uncomp { @@ -43,8 +44,8 @@ public: private: TempDir *m_dir; - string m_tfile; - string m_srcpath; + std::string m_tfile; + std::string m_srcpath; bool m_docache; class UncompCache { @@ -57,10 +58,10 @@ private: { delete m_dir; } - PTMutexInit m_lock; + std::mutex m_lock; TempDir *m_dir; - string m_tfile; - string m_srcpath; + std::string m_tfile; + std::string m_srcpath; }; static UncompCache o_cache; }; diff --git a/src/kde/kioslave/kio_recoll-kde4/00README.txt b/src/kde/kioslave/kio_recoll-kde4/00README.txt new file mode 100644 index 00000000..933b73b7 --- /dev/null +++ b/src/kde/kioslave/kio_recoll-kde4/00README.txt @@ -0,0 +1,94 @@ +Recoll KIO slave +================ + +An experiment with a recoll KIO slave. + +Caveat: I am only currently testing this with a production, but very +recent, version of KDE 4.1, and I don't intend to really support +older versions. The most usable aspects work under KDE 4.0 though. As +a reference, my test system is an up to date (2009-01) Kubuntu 8.10. + +Usage +===== + +Depending on the protocol name used, the search results will be +returned either as HTML pages (looking quite like a normal Recoll +result list), or as directory entries. + +The HTML mode only works with Konqueror, not Dolphin. The directory +mode is available with both browsers, and also application open dialog +(ie Kate). + +The HTML mode is much more usable than the directory mode at this point + +More detailed help/explanations can be found a document accessible +from the slave: + +To try things out, after building and installing, enter "recoll:/" in +a Konqueror URL entry. Depending on the KDE version, this will bring +you either to an HTML search form, or to a directory listing, where +you should READ THE HELP FILE. + +Building and installing: +======================= + +Only tested with KDE 4.1 and later. + +The main Recoll installation shares its prefix with the KIO slave, +which needs to use the KDE one. This means that, if KDE lives in /usr, +Recoll must be configured with --prefix=/usr, not /usr/local. Else +you'll have run-time problems, the slave will not be able to find the +Recoll configuration. + +!!*Notice: You cannot share a build directory between recoll and kio_recoll +because they use different configure options for the main lib, but build it +in the same place. The main lib "configure" is run at "cmake" time for +kio_recoll, the build is done at "make" time. + + +Recipe: + - Make sure the KDE4 core devel packages and cmake are installed. + + - Extract the Recoll source. + + - IF Recoll is not installed yet: configure recoll with + --prefix=/usr (or wherever KDE lives), build and install + Recoll. + + - In the Recoll source, go to kde/kioslave/recoll, then build and + install the kio slave: + +mkdir builddir +cd builddir +cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DQT_QMAKE_EXECUTABLE=/usr/bin/qmake-qt4 +make +sudo make install + + - You should have a look at where "make install" copies things, + because misconfigured distribution, generating wrong targets, are + frequent. Especially, you should check that kio_recoll.so is copied + to the right place, meaning among the output of "kde4-config --path + module". As an additional check, there should be many other + kio_[xxx].so in there. Same for the protocol file, check that it's + not alone in its directory (really, this sounds strange, but, to + this point, I've seen more systems with broken cmake/KDE configs + than correct ones). + +You need to build/update the index with recollindex, the KIO slave +doesn't deal with indexing for now. + + +Misc build problems: +=================== + +KUBUNTU 8.10 (updated to 2008-27-11) +------------------------------------ +cmake generates a bad dependancy on + /build/buildd/kde4libs-4.1.2/obj-i486-linux-gnu/lib/libkdecore.so +inside CMakeFiles/kio_recoll.dir/build.make + +Found no way to fix this. You need to edit the line and replace the +/build/[...]/lib with /usr/lib. This manifests itself with the +following error message: + + make[2]: *** No rule to make target `/build/buildd/kde4libs-4.1.2/obj-i486-linux-gnu/lib/libkdecore.so', needed by `lib/kio_recoll.so'. Stop. diff --git a/src/kde/kioslave/kio_recoll-kde4/CMakeLists.txt b/src/kde/kioslave/kio_recoll-kde4/CMakeLists.txt new file mode 100644 index 00000000..c8dbd240 --- /dev/null +++ b/src/kde/kioslave/kio_recoll-kde4/CMakeLists.txt @@ -0,0 +1,75 @@ +cmake_minimum_required(VERSION 2.6) + +project(kio_recoll) + +find_package(KDE4 REQUIRED) + +add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) +add_definitions(-DKDE_DEFAULT_DEBUG_AREA=7130 + -DRECOLL_DATADIR=\\"${CMAKE_INSTALL_PREFIX}/share/recoll\\" + -DLIBDIR=\\"${CMAKE_INSTALL_PREFIX}/lib\\" + -DHAVE_CONFIG_H +) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") + +set(rcltop ${CMAKE_CURRENT_SOURCE_DIR}/../../../) + +# Execute recoll configuration to create autoconfig.h and version.h and +# generate a PIC lib +execute_process(COMMAND ${rcltop}/configure --disable-static --disable-qtgui --disable-x11mon --prefix=${CMAKE_INSTALL_PREFIX} --mandir=${CMAKE_INSTALL_PREFIX}/share/man + WORKING_DIRECTORY ${rcltop} +) + +link_directories(${rcltop}/.libs ${CMAKE_INSTALL_PREFIX}/lib) + +include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES} + ${rcltop}/aspell + ${rcltop}/bincimapmime + ${rcltop}/common + ${rcltop}/index + ${rcltop}/internfile + ${rcltop}/query + ${rcltop}/rcldb + ${rcltop}/unac + ${rcltop}/utils + ${rcltop}/qtgui +) + +set(kio_recoll_SRCS kio_recoll.cpp htmlif.cpp dirif.cpp ${rcltop}/qtgui/guiutils.cpp) + +CHECK_LIBRARY_EXISTS(dl dlopen "" DLOPEN_IN_LIBDL) +IF(DLOPEN_IN_LIBDL) + LIST(APPEND EXTRA_LIBS dl) +ENDIF(DLOPEN_IN_LIBDL) +CHECK_LIBRARY_EXISTS(pthread pthread_sigmask "" PTHREAD_IN_LIBPTHREAD) +IF(PTHREAD_IN_LIBPTHREAD) + LIST(APPEND EXTRA_LIBS pthread) +ENDIF(PTHREAD_IN_LIBPTHREAD) + +# Had the idea to add e.g. /usr/lib/recoll to the rpath so that the dyn lib +# will be found at run time. But this does not seem to work with debian +# which strips RPATH by default (I think there is a way for libs in app-specific +# paths but I did not find it). Link with the .a instead. +#SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib/recoll") + +kde4_add_plugin(kio_recoll ${kio_recoll_SRCS}) + +add_custom_target(rcllib + COMMAND make PicStatic + WORKING_DIRECTORY ${rcltop} +) +add_dependencies(kio_recoll rcllib) + +target_link_libraries(kio_recoll recoll xapian z ${EXTRA_LIBS} ${KDE4_KIO_LIBS}) + +install(TARGETS kio_recoll DESTINATION ${PLUGIN_INSTALL_DIR}) + +IF ("${KDE_VERSION_MAJOR}.${KDE_VERSION_MINOR}" GREATER 4.0) + install(FILES recoll.protocol recollf.protocol DESTINATION ${SERVICES_INSTALL_DIR}) +ELSE ("${KDE_VERSION_MAJOR}.${KDE_VERSION_MINOR}" GREATER 4.0) + install(FILES recollnolist.protocol DESTINATION ${SERVICES_INSTALL_DIR} + RENAME recoll.protocol) +ENDIF ("${KDE_VERSION_MAJOR}.${KDE_VERSION_MINOR}" GREATER 4.0) + +install(FILES data/welcome.html data/help.html + DESTINATION ${DATA_INSTALL_DIR}/kio_recoll) diff --git a/src/kde/kioslave/kio_recoll-kde4/Makefile.kde3 b/src/kde/kioslave/kio_recoll-kde4/Makefile.kde3 new file mode 100644 index 00000000..634087f7 --- /dev/null +++ b/src/kde/kioslave/kio_recoll-kde4/Makefile.kde3 @@ -0,0 +1,59 @@ +depth=../../.. +include $(depth)/mk/sysconf + +# Need to set this by hand until we decide how to autoconf this without +# bringing 2mbytes of kde config files. +# May also need to adjust stuff such as the --rpath's below +# +KDE_INCLUDES=/usr/include/kde +QT_INCLUDES=/usr/include/qt3 + + +all: kio_recoll.so + +DEPS_CXXFLAGS = -MT pop3.lo -MD -MP -MF .deps/pop3.Tpo + +INC_CXXFLAGS = -I. \ + -I$(KDE_INCLUDES) -I$(QT_INCLUDES) \ + -I$(depth)/common -I$(depth)/query -I$(depth)/utils \ + -I$(depth)/qtgui -I$(depth)/rcldb + +PIC_CXXFLAGS = -fPIC -DPIC +DEBUG_CXXFLAGS = -DNDEBUG -DNO_DEBUG -O2 -O +LANG_CXXFLAGS = -fno-exceptions -fno-check-new -fno-common +QT_CXXFLAGS = -DQT_CLEAN_NAMESPACE -DQT_NO_ASCII_CAST -DQT_NO_STL \ + -DQT_NO_COMPAT -DQT_NO_TRANSLATION -DQT_THREAD_SUPPORT +SYS_CXXFLAGS = -D_GNU_SOURCE +THREAD_CXXFLAGS = -D_THREAD_SAFE -pthread -D_THREAD_SAFE -pthread + + +# -rpath=/usr/lib:/usr/local/lib \ + +LDFLAGS = \ + -Wl,--rpath -Wl,/usr/local/lib \ + -Wl,--rpath -Wl,/usr/X11R6/lib \ + -Wl,-export-dynamic -Wl,-soname -Wl,kio_recoll.so +THREAD_LDFLAGS = -pthread + +kio_recoll.so : kio_recoll.o libpic + c++ -shared $(LDFLAGS) $(THREAD_LDFLAGS) \ + -Wl,--no-undefined \ + kio_recoll.o piclib/librcl.a \ + $(LIBXAPIAN) $(LIBICONV) \ + -L/opt/kde3/lib -L/usr/local/lib -L/usr/X11R6/lib -lkio -lkdecore \ + -L/usr/lib/qt3/lib -lqt-mt \ + -L/usr/lib -lstdc++ -lm -lc \ + -o kio_recoll.so + +kio_recoll.o : kio_recoll.cpp kio_recoll.h + $(CXX) -c -pipe kio_recoll.cpp $(INC_CXXFLAGS) $(PIC_CXXFLAGS) \ + $(DEBUG_CXXFLAGS) $(LANG_CXXFLAGS) $(QT_CXXFLAGS) $(SYS_CXXFLAGS) \ + $(THREAD_CXXFLAGS) \ + -o kio_recoll.o + +libpic: + cd piclib;test -f Makefile || depth=$(depth)/.. sh $(depth)/../lib/mkMake + cd piclib;$(MAKE) CXXFLAGS="$(CXXFLAGS) $(PIC_CXXFLAGS)" \ + CFLAGS="$(CFLAGS) $(PIC_CXXFLAGS)" + +.PHONY: all libpic diff --git a/src/kde/kioslave/kio_recoll-kde4/cleancmakestuff.sh b/src/kde/kioslave/kio_recoll-kde4/cleancmakestuff.sh new file mode 100644 index 00000000..52176afe --- /dev/null +++ b/src/kde/kioslave/kio_recoll-kde4/cleancmakestuff.sh @@ -0,0 +1,6 @@ +#!/bin/sh +rm -rf CMakeCache.txt CMakeFiles CTestTestfile.cmake \ + cmake_install.cmake CMakeTmp cmake_uninstall.cmake \ + CPackConfig.cmake CPackSourceConfig.cmake DartTestfile.txt \ + install_manifest.txt kio_recoll_automoc.cpp \ + kio_recoll_automoc.cpp.files kio_recoll.so lib Makefile diff --git a/src/kde/kioslave/kio_recoll-kde4/data/help.html b/src/kde/kioslave/kio_recoll-kde4/data/help.html new file mode 100644 index 00000000..689cbdf8 --- /dev/null +++ b/src/kde/kioslave/kio_recoll-kde4/data/help.html @@ -0,0 +1,100 @@ + + + + Recoll Kio Slave + + + Recoll search +

            Recoll kio slave

            + +

            Use this module to perform Recoll searches from any program with + a KIO interface.

            + +

            The module can work in two modes:

            +
              +
            • Html interface, close to a simplified QT Recoll + interface.
            • +
            • File manager interface, Only with KDE 4.1 and + newer, which presents results as directory entries
            • +
            + +

            The module is still in its infancy. You will undoubtedly obtain + strange effects from time to time. If you have any remarks or + ideas about improving kio_recoll, or observe an interesting and + reproducible sequence, please + report it.

            +

            kio_recoll is primarily + designed and tested with konqueror, and you will + undoubtedly get even more surprising effects with other tools.

            + +

            The Html interface is currently much more usable. The directory + interface is extremely quirky.

            + +

            The module is particularly unhelpful with search hits inside + email folders, which Konqueror has no way to access.

            + + +

            HTML interface

            + +

            This works more or less like the Recoll QT GUI, much simplified. The + + Recoll manual describes the queries that can be performed.

            + +

            Most pages in the interface should quite self-explanatory.

            + +

            You normally enter this interface by entering "recoll:" or + "recoll:/" in the Konqueror URL entry, and following the "search" + link. You can also directly enter "recoll:/search.html".
            + + In most circumstances, entering a link like + recoll:/john smith will also + yield an HTML result list.

            + +

            Compared to QT Recoll, the nice point is that you can click or + drag/drop the icons to access the results in the standard desktop + way.

            + +

            File manager interface

            + +

            The path part of the URI is taken as a Recoll query + language string and executed. The results are displayed as + directory entries.

            + +

            There are several ways to enter this interface:

            +
              +
            • Using "recollf" as protocol name instead of "recoll". This is + probably the easiest option inside open dialogs.
            • + +
            • Using an URL ending with a '/', ie: +
              + + recoll:/red apples ext:html/ +
              +
            • +
            • Users who will want to use the file manager view most of the + time can set the RECOLL_KIO_ALWAYS_DIR environment + variable or the kio_always_dir recoll.conf variable + to 1. The HTML interface will then only be accessible + through the search link in the top "recoll:" view.
            • +
            + +

            No search result details (samples, relevance etc.) are available, + but this interface allows multiple selections and copies, usage + inside any KDE open dialog, etc.

            + +

            To avoid swamping the interface with thousands of results, the + result count is limited to 100 by default. You can change this value + by setting the kio_max_direntries parameter in your recoll + configuration file (typically ~/.recoll/recoll.conf)

            + +

            Because of limitations in the current KIO slave usage, the actual + entry names are not those displayed but synthetic ones like + "recollResultxxx". This has unfortunate side-effects when + dragging/dropping the entries to some other application, or when + using an open dialog (the opened file doesn't have the correct path + to the original file).

            + +

            Recoll Search

            + + + diff --git a/src/kde/kioslave/kio_recoll-kde4/data/searchable.html b/src/kde/kioslave/kio_recoll-kde4/data/searchable.html new file mode 100644 index 00000000..6e44f269 --- /dev/null +++ b/src/kde/kioslave/kio_recoll-kde4/data/searchable.html @@ -0,0 +1,28 @@ + + + Recoll searchable HTML + + + + + + + +

            A Recoll-searchable HTML page

            + +

            This is a text sample in which links have been inserted for + words, such as system installation, + which can be searched for in the whole document set by + using recoll

            + +

            Also a little bit of javascript magic can make + all words searchable (try double-clicking any word).

            + + + diff --git a/src/kde/kioslave/kio_recoll-kde4/data/welcome.html b/src/kde/kioslave/kio_recoll-kde4/data/welcome.html new file mode 100644 index 00000000..0f13d428 --- /dev/null +++ b/src/kde/kioslave/kio_recoll-kde4/data/welcome.html @@ -0,0 +1,29 @@ + + + + Recoll Search + + + +

            Recoll search

            + +

            +

            + Query type:
            + Query language
            + All terms
            + Any term
            + File name
            + + + Enter search string: + + + + +
            +

            + + + diff --git a/src/kde/kioslave/kio_recoll-kde4/dirif.cpp b/src/kde/kioslave/kio_recoll-kde4/dirif.cpp new file mode 100644 index 00000000..4dee9242 --- /dev/null +++ b/src/kde/kioslave/kio_recoll-kde4/dirif.cpp @@ -0,0 +1,318 @@ +/* Copyright (C) 2008 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * A lot of code in this file was copied from kio_beagle 0.4.0, + * which is a GPL program. The authors listed are: + * Debajyoti Bera + * + * KDE4 port: + * Stephan Binner + */ + +#include "autoconfig.h" + +#include + +#if KDE_IS_VERSION(4,1,0) +// Couldn't get listDir() to work with kde 4.0, konqueror keeps +// crashing because of kdirmodel, couldn't find a workaround (not +// saying it's impossible)... + +#include + +#include +#include +#include +#include + +#include "kio_recoll.h" +#include "pathut.h" + +using namespace KIO; + +static const QString resultBaseName("recollResult"); + +// Check if the input URL is of the form that konqueror builds by +// appending one of our result file names to the directory name (which +// is the search string). If it is, extract return the result document +// number. Possibly restart the search if the search string does not +// match the current one +bool RecollProtocol::isRecollResult(const KUrl &url, int *num, QString *q) +{ + *num = -1; + kDebug() << "url" << url; + + // Basic checks + if (!url.host().isEmpty() || url.path().isEmpty() || + (url.protocol().compare("recoll") && url.protocol().compare("recollf"))) + return false; + QString path = url.path(); + if (!path.startsWith("/")) + return false; + + // Look for the last '/' and check if it is followed by + // resultBaseName (riiiight...) + int slashpos = path.lastIndexOf("/"); + if (slashpos == -1 || slashpos == 0 || slashpos == path.length() -1) + return false; + slashpos++; + //kDebug() << "Comparing " << path.mid(slashpos, resultBaseName.length()) << + // "and " << resultBaseName; + if (path.mid(slashpos, resultBaseName.length()).compare(resultBaseName)) + return false; + + // Extract the result number + QString snum = path.mid(slashpos + resultBaseName.length()); + sscanf(snum.toAscii(), "%d", num); + if (*num == -1) + return false; + + //kDebug() << "URL analysis ok, num:" << *num; + + // We do have something that ressembles a recoll result locator. Check if + // this matches the current search, else have to run the requested one + *q = path.mid(1, slashpos-2); + return true; +} + +// Translate rcldoc result into directory entry +static const UDSEntry resultToUDSEntry(const Rcl::Doc& doc, int num) +{ + UDSEntry entry; + + KUrl url(doc.url.c_str()); +// kDebug() << doc.url.c_str(); + + entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, url.fileName()); + char cnum[30];sprintf(cnum, "%04d", num); + entry.insert(KIO::UDSEntry::UDS_NAME, resultBaseName + cnum); + + if (!doc.mimetype.compare("application/x-fsdirectory") || + !doc.mimetype.compare("inode/directory")) { + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory"); + entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + } else { + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, doc.mimetype.c_str()); + entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); + } + entry.insert(KIO::UDSEntry::UDS_LOCAL_PATH, url.path()); + // For local files, supply the usual file stat information + struct stat info; + if (lstat(url.path().toAscii(), &info) >= 0) { + entry.insert( KIO::UDSEntry::UDS_SIZE, info.st_size); + entry.insert( KIO::UDSEntry::UDS_ACCESS, info.st_mode); + entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, info.st_mtime); + entry.insert( KIO::UDSEntry::UDS_ACCESS_TIME, info.st_atime); + entry.insert( KIO::UDSEntry::UDS_CREATION_TIME, info.st_ctime); + } + entry.insert(KIO::UDSEntry::UDS_TARGET_URL, doc.url.c_str()); + + return entry; +} + + +// From kio_beagle +static void createRootEntry(KIO::UDSEntry& entry) +{ + entry.clear(); + entry.insert( KIO::UDSEntry::UDS_NAME, "."); + entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.insert( KIO::UDSEntry::UDS_ACCESS, 0700); + entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory"); +} + +// Points to html query screen +static void createGoHomeEntry(KIO::UDSEntry& entry) +{ + entry.clear(); + entry.insert(KIO::UDSEntry::UDS_NAME, "search.html"); + entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, "Recoll search (click me)"); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); + entry.insert(KIO::UDSEntry::UDS_TARGET_URL, "recoll:///search.html"); + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0500); + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, "text/html"); + entry.insert(KIO::UDSEntry::UDS_ICON_NAME, "recoll"); +} + +// Points to help file +static void createGoHelpEntry(KIO::UDSEntry& entry) +{ + QString location = + KStandardDirs::locate("data", "kio_recoll/help.html"); + entry.clear(); + entry.insert(KIO::UDSEntry::UDS_NAME, "help"); + entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, "Recoll help (click me first)"); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); + entry.insert(KIO::UDSEntry::UDS_TARGET_URL, QString("file://") + + location); + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0500); + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, "text/html"); + entry.insert(KIO::UDSEntry::UDS_ICON_NAME, "help"); +} + +void RecollProtocol::stat(const KUrl & url) +{ + kDebug() << url << endl ; + + UrlIngester ingest(this, url); + + KIO::UDSEntry entry; + entry.insert(KIO::UDSEntry::UDS_TARGET_URL, url.url()); + UrlIngester::RootEntryType rettp; + QueryDesc qd; + int num; + if (ingest.isRootEntry(&rettp)) { + switch(rettp) { + case UrlIngester::UIRET_ROOT: + createRootEntry(entry); + break; + case UrlIngester::UIRET_HELP: + createGoHelpEntry(entry); + break; + case UrlIngester::UIRET_SEARCH: + createGoHomeEntry(entry); + break; + default: + error(ERR_DOES_NOT_EXIST, ""); + break; + } + } else if (ingest.isResult(&qd, &num)) { + if (syncSearch(qd)) { + Rcl::Doc doc; + if (num >= 0 && m_source && + m_source->getDoc(num, doc)) { + entry = resultToUDSEntry(doc, num); + } else { + error(ERR_DOES_NOT_EXIST, ""); + } + } else { + // hopefully syncSearch() set the error? + } + } else if (ingest.isQuery(&qd)) { + // ie "recoll:/some string" or "recoll:/some string/" + // + // We have a problem here. We'd like to let the user enter + // either form and get an html or a dir contents result, + // depending on the ending /. Otoh this makes the name space + // inconsistent, because /toto can't be a file (the html + // result page) while /toto/ would be a directory ? or can it + // + // Another approach would be to use different protocol names + // to avoid any possibility of mixups + if (m_alwaysdir || ingest.alwaysDir() || ingest.endSlashQuery()) { + kDebug() << "Directory type"; + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0700); + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory"); + entry.insert(KIO::UDSEntry::UDS_NAME, qd.query); + entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, time(0)); + entry.insert( KIO::UDSEntry::UDS_CREATION_TIME, time(0)); + } + } + statEntry(entry); + finished(); +} + +void RecollProtocol::listDir(const KUrl& url) +{ + kDebug() << url << endl; + + UrlIngester ingest(this, url); + UrlIngester::RootEntryType rettp; + QueryDesc qd; + + if (ingest.isRootEntry(&rettp)) { + switch(rettp) { + case UrlIngester::UIRET_ROOT: + { + kDebug() << "list /" << endl; + UDSEntryList entries; + KIO::UDSEntry entry; + createRootEntry(entry); + entries.append(entry); + createGoHomeEntry(entry); + entries.append(entry); + createGoHelpEntry(entry); + entries.append(entry); + listEntries(entries); + finished(); + } + return; + default: + error(ERR_CANNOT_ENTER_DIRECTORY, ""); + return; + } + } else if (ingest.isQuery(&qd)) { + // At this point, it seems that when the request is from + // konqueror autocompletion it comes with a / at the end, + // which offers an opportunity to not perform it. + if (ingest.endSlashQuery()) { + kDebug() << "Ends With /" << endl; + error(ERR_SLAVE_DEFINED, "Autocompletion search aborted"); + return; + } + if (!syncSearch(qd)) { + // syncSearch did the error thing + return; + } + // Fallthrough to actually listing the directory + } else { + kDebug() << "Cant grok input url"; + error(ERR_CANNOT_ENTER_DIRECTORY, ""); + return; + } + + static int maxentries = -1; + if (maxentries == -1) { + if (o_rclconfig) + o_rclconfig->getConfParam("kio_max_direntries", &maxentries); + if (maxentries == -1) + maxentries = 10000; + } + static const int pagesize = 200; + int pagebase = 0; + while (pagebase < maxentries) { + vector page; + int pagelen = m_source->getSeqSlice(pagebase, pagesize, page); + UDSEntry entry; + if (pagelen < 0) { + error(ERR_SLAVE_DEFINED, "Internal error"); + listEntry(entry, true); + break; + } + for (int i = 0; i < pagelen; i++) { + listEntry(resultToUDSEntry(page[i].doc, i), false); + } + if (pagelen != pagesize) { + listEntry(entry, true); + break; + } + pagebase += pagelen; + } + finished(); +} + +#else // <--- KDE 4.1+ + +#include +#include "kio_recoll.h" +bool RecollProtocol::isRecollResult(const KUrl &, int *, QString *) +{ + return false; +} +#endif diff --git a/src/kde/kioslave/kio_recoll-kde4/htmlif.cpp b/src/kde/kioslave/kio_recoll-kde4/htmlif.cpp new file mode 100644 index 00000000..48043640 --- /dev/null +++ b/src/kde/kioslave/kio_recoll-kde4/htmlif.cpp @@ -0,0 +1,301 @@ +/* Copyright (C) 2005 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include + +#include + +using namespace std; + +#include +#include + +#include "rclconfig.h" +#include "rcldb.h" +#include "rclinit.h" +#include "pathut.h" +#include "searchdata.h" +#include "rclquery.h" +#include "wasatorcl.h" +#include "kio_recoll.h" +#include "docseqdb.h" +#include "readfile.h" +#include "smallut.h" +#include "plaintorich.h" +#include "internfile.h" +#include "wipedir.h" +#include "hldata.h" + +using namespace KIO; + +bool RecollKioPager::append(const string& data) +{ + if (!m_parent) + return false; + m_parent->data(QByteArray(data.c_str())); + return true; +} +#include +string RecollProtocol::makeQueryUrl(int page, bool isdet) +{ + ostringstream str; + str << "recoll://search/query?q=" << + url_encode((const char*)m_query.query.toUtf8()) << + "&qtp=" << (const char*)m_query.opt.toUtf8(); + if (page >= 0) + str << "&p=" << page; + if (isdet) + str << "&det=1"; + return str.str(); +} + +string RecollKioPager::detailsLink() +{ + string chunk = string("makeQueryUrl(m_parent->m_pager.pageNumber(), true) + "\">" + + "(show query)" + ""; + return chunk; +} + +static string parformat; +const string& RecollKioPager::parFormat() +{ + // Need to escape the % inside the query url + string qurl = m_parent->makeQueryUrl(-1, false), escurl; + for (string::size_type pos = 0; pos < qurl.length(); pos++) { + switch(qurl.at(pos)) { + case '%': + escurl += "%%"; + break; + default: + escurl += qurl.at(pos); + } + } + + ostringstream str; + str << + "" + "%R %S " + "Preview  " << + "Open " << + "%T
            " + "%M %D   %U  %i
            " + "%A %K"; + return parformat = str.str(); +} + +string RecollKioPager::pageTop() +{ + string pt = "

            m_query.query.toUtf8())); + pt += "\">New Search"; + return pt; +// Would be nice to have but doesnt work because the query may be executed +// by another kio instance which has no idea of the current page o +#if 0 && KDE_IS_VERSION(4,1,0) + "    m_query.query.toUtf8())) + + "/\">Directory view (you may need to reload the page)" +#endif +} + +string RecollKioPager::nextUrl() +{ + int pagenum = pageNumber(); + if (pagenum < 0) + pagenum = 0; + else + pagenum++; + return m_parent->makeQueryUrl(pagenum); +} + +string RecollKioPager::prevUrl() +{ + int pagenum = pageNumber(); + if (pagenum <= 0) + pagenum = 0; + else + pagenum--; + return m_parent->makeQueryUrl(pagenum); +} + +static string welcomedata; + +void RecollProtocol::searchPage() +{ + mimeType("text/html"); + if (welcomedata.empty()) { + QString location = + KStandardDirs::locate("data", "kio_recoll/welcome.html"); + string reason; + if (location.isEmpty() || + !file_to_string((const char *)location.toUtf8(), + welcomedata, &reason)) { + welcomedata = "Recoll Error" + "

            Could not locate Recoll welcome.html file: "; + welcomedata += reason; + welcomedata += "

            "; + } + } + + string catgq; +#if 0 + // Catg filtering. A bit complicated to do because of the + // stateless thing (one more thing to compare to check if same + // query) right now. Would be easy by adding to the query + // language, but not too useful in this case, so scrap it for now. + list cats; + if (o_rclconfig->getMimeCategories(cats) && !cats.empty()) { + catgq = "

            Filter on types: " + "All"; + for (list::iterator it = cats.begin(); it != cats.end();it++) { + catgq += "\n" + *it ; + } + } +#endif + + string tmp; + map subs; + subs['Q'] = (const char *)m_query.query.toUtf8(); + subs['C'] = catgq; + subs['S'] = ""; + pcSubst(welcomedata, tmp, subs); + data(tmp.c_str()); +} + +void RecollProtocol::queryDetails() +{ + mimeType("text/html"); + QByteArray array; + QTextStream os(&array, QIODevice::WriteOnly); + + os << "" << endl; + os << "" << endl; + os << "" << "Recoll query details" << "\n" << endl; + os << "" << endl; + os << "

            Query details:

            " << endl; + os << "

            " << m_pager.queryDescription().c_str() <<"

            "<< endl; + os << "

            Return to results" << endl; + os << "" << endl; + data(array); +} + +class PlainToRichKio : public PlainToRich { +public: + PlainToRichKio(const string& nm) + : m_name(nm) + { + } + + virtual string header() { + if (m_inputhtml) { + return cstr_null; + } else { + return string("" + ""). + append(m_name). + append("

            ");
            +	}
            +    }
            +
            +    virtual string startMatch(unsigned int)
            +    {
            +	return string("");
            +    }
            +
            +    virtual string endMatch() 
            +    {
            +	return string("");
            +    }
            +
            +    const string &m_name;
            +};
            +
            +void RecollProtocol::showPreview(const Rcl::Doc& idoc)
            +{
            +    FileInterner interner(idoc, o_rclconfig, FileInterner::FIF_forPreview);
            +    Rcl::Doc fdoc;
            +    string ipath = idoc.ipath;
            +    if (!interner.internfile(fdoc, ipath)) {
            +	error(KIO::ERR_SLAVE_DEFINED, "Cannot convert file to internal format");
            +	return;
            +    }
            +    if (!interner.get_html().empty()) {
            +	fdoc.text = interner.get_html();
            +	fdoc.mimetype = "text/html";
            +    }
            +
            +    mimeType("text/html");
            +
            +    string fname =  path_getsimple(fdoc.url).c_str();
            +    PlainToRichKio ptr(fname);
            +    ptr.set_inputhtml(!fdoc.mimetype.compare("text/html"));
            +    list otextlist;
            +    HighlightData hdata;
            +    if (m_source)
            +	m_source->getTerms(hdata);
            +    ptr.plaintorich(fdoc.text, otextlist, hdata);
            +
            +    QByteArray array;
            +    QTextStream os(&array, QIODevice::WriteOnly);
            +    for (list::iterator it = otextlist.begin(); 
            +	 it != otextlist.end(); it++) {
            +	os << (*it).c_str();
            +    }
            +    os << "" << endl;
            +    data(array);
            +}
            +
            +void RecollProtocol::htmlDoSearch(const QueryDesc& qd)
            +{
            +    kDebug() << "q" << qd.query << "option" << qd.opt << "page" << qd.page <<
            +	"isdet" << qd.isDetReq << endl;
            + 
            +    mimeType("text/html");
            +
            +    if (!syncSearch(qd))
            +	return;
            +    // syncSearch/doSearch do the setDocSource when needed
            +    if (m_pager.pageNumber() < 0) {
            +	m_pager.resultPageNext();
            +    }
            +    if (qd.isDetReq) {
            +	queryDetails();
            +	return;
            +    }
            +
            +    // Check / adjust page number
            +    if (qd.page > m_pager.pageNumber()) {
            +	int npages = qd.page - m_pager.pageNumber();
            +	for (int i = 0; i < npages; i++)
            +	    m_pager.resultPageNext();
            +    } else if (qd.page < m_pager.pageNumber()) {
            +	int npages = m_pager.pageNumber() - qd.page;
            +	for (int i = 0; i < npages; i++) 
            +	    m_pager.resultPageBack();
            +    }
            +    // Display
            +    m_pager.displayPage(o_rclconfig);
            +}
            diff --git a/src/kde/kioslave/kio_recoll-kde4/kio_recoll.cpp b/src/kde/kioslave/kio_recoll-kde4/kio_recoll.cpp
            new file mode 100644
            index 00000000..a7db6174
            --- /dev/null
            +++ b/src/kde/kioslave/kio_recoll-kde4/kio_recoll.cpp
            @@ -0,0 +1,381 @@
            +/* Copyright (C) 2005 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
            + *   (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 General Public License for more details.
            + *
            + *   You should have received a copy of the GNU General Public License
            + *   along with this program; if not, write to the
            + *   Free Software Foundation, Inc.,
            + *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
            + */
            +
            +#include 
            +#include 
            +#include 
            +#include  
            +
            +#include 
            +using namespace std;
            +
            +#include 
            +#include 
            +#include 
            +#include 
            +
            +#include 
            +#include 
            +#include 
            +
            +#include "rclconfig.h"
            +#include "rcldb.h"
            +#include "rclinit.h"
            +#include "pathut.h"
            +#include "searchdata.h"
            +#include "rclquery.h"
            +#include "wasatorcl.h"
            +#include "kio_recoll.h"
            +#include "docseqdb.h"
            +#include "readfile.h"
            +#include "smallut.h"
            +#include "textsplit.h"
            +#include "guiutils.h"
            +
            +using namespace KIO;
            +
            +RclConfig *RecollProtocol::o_rclconfig;
            +
            +RecollProtocol::RecollProtocol(const QByteArray &pool, const QByteArray &app) 
            +    : SlaveBase("recoll", pool, app), m_initok(false), m_rcldb(0),
            +      m_alwaysdir(false)
            +{
            +    kDebug() << endl;
            +    if (o_rclconfig == 0) {
            +	o_rclconfig = recollinit(0, 0, m_reason);
            +	if (!o_rclconfig || !o_rclconfig->ok()) {
            +	    m_reason = string("Configuration problem: ") + m_reason;
            +	    return;
            +	}
            +    }
            +    if (o_rclconfig->getDbDir().empty()) {
            +	// Note: this will have to be replaced by a call to a
            +	// configuration building dialog for initial configuration? Or
            +	// do we assume that the QT GUO is always used for this ?
            +	m_reason = "No db directory in configuration ??";
            +	return;
            +    }
            +    rwSettings(false);
            +
            +    m_rcldb = new Rcl::Db(o_rclconfig);
            +    if (!m_rcldb) {
            +	m_reason = "Could not build database object. (out of memory ?)";
            +	return;
            +    }
            +
            +    // Decide if we allow switching between html and file manager
            +    // presentation by using an end slash or not. Can also be done dynamically 
            +    // by switching proto names.
            +    const char *cp = getenv("RECOLL_KIO_ALWAYS_DIR");
            +    if (cp) {
            +	m_alwaysdir = stringToBool(cp);
            +    } else {
            +	o_rclconfig->getConfParam("kio_always_dir", &m_alwaysdir);
            +    }
            +
            +    cp = getenv("RECOLL_KIO_STEMLANG");
            +    if (cp) {
            +        m_stemlang = cp;
            +    } else {
            +        m_stemlang = "english";
            +    }
            +    m_pager.setParent(this);
            +    m_initok = true;
            +    return;
            +}
            +
            +// There should be an object counter somewhere to delete the config when done.
            +// Doesn't seem needed in the kio context.
            +RecollProtocol::~RecollProtocol()
            +{
            +    kDebug();
            +    delete m_rcldb;
            +}
            +
            +bool RecollProtocol::maybeOpenDb(string &reason)
            +{
            +    if (!m_rcldb) {
            +	reason = "Internal error: initialization error";
            +	return false;
            +    }
            +    if (!m_rcldb->isopen() && !m_rcldb->open(Rcl::Db::DbRO)) {
            +	reason = "Could not open database in " + o_rclconfig->getDbDir();
            +	return false;
            +    }
            +    return true;
            +}
            +
            +// This is never called afaik
            +void RecollProtocol::mimetype(const KUrl &url)
            +{
            +    kDebug() << url << endl;
            +    mimeType("text/html");
            +    finished();
            +}
            +
            +UrlIngester::UrlIngester(RecollProtocol *p, const KUrl& url)
            +    : m_parent(p), m_slashend(false), m_alwaysdir(false),
            +      m_retType(UIRET_NONE), m_resnum(0), m_type(UIMT_NONE)
            +{
            +    kDebug() << "Url" << url;
            +    m_alwaysdir = !url.protocol().compare("recollf");
            +    QString path = url.path();
            +    if (url.host().isEmpty()) {
            +	if (path.isEmpty() || !path.compare("/")) {
            +	    m_type = UIMT_ROOTENTRY;
            +	    m_retType = UIRET_ROOT;
            +	    return;
            +	} else if (!path.compare("/help.html")) {
            +	    m_type = UIMT_ROOTENTRY;
            +	    m_retType = UIRET_HELP;
            +	    return;
            +	} else if (!path.compare("/search.html")) {
            +	    m_type = UIMT_ROOTENTRY;
            +	    m_retType = UIRET_SEARCH;
            +	    // Retrieve the query value for preloading the form
            +	    m_query.query = url.queryItem("q");
            +	    return;
            +	} else if (m_parent->isRecollResult(url, &m_resnum, &m_query.query)) {
            +	    m_type = UIMT_QUERYRESULT;
            +	    m_query.opt = "l";
            +	    m_query.page = 0;
            +	} else {
            +	    // Have to think this is some search string
            +	    m_type = UIMT_QUERY;
            +	    m_query.query = url.path();
            +	    m_query.opt = "l";
            +	    m_query.page = 0;
            +	}
            +    } else {
            +	// Non empty host, url must be something like :
            +	//      //search/query?q=query¶m=value...
            +	kDebug() << "host" << url.host() << "path" << url.path();
            +	if (url.host().compare("search") || url.path().compare("/query")) {
            +	    return;
            +	}
            +	m_type = UIMT_QUERY;
            +	// Decode the forms' arguments
            +	m_query.query = url.queryItem("q");
            +
            +	m_query.opt = url.queryItem("qtp");
            +	if (m_query.opt.isEmpty()) {
            +	    m_query.opt = "l";
            +	} 
            +	QString p = url.queryItem("p");
            +	if (p.isEmpty()) {
            +	    m_query.page = 0;
            +	} else {
            +	    sscanf(p.toAscii(), "%d", &m_query.page);
            +	}
            +	p = url.queryItem("det");
            +	m_query.isDetReq = !p.isEmpty();
            +	
            +	p = url.queryItem("cmd");
            +	if (!p.isEmpty() && !p.compare("pv")) {
            +	    p = url.queryItem("dn");
            +	    if (!p.isEmpty()) {
            +		// Preview and no docnum ??
            +		m_resnum = atoi((const char *)p.toUtf8());
            +		// Result in page is 1+
            +		m_resnum--;
            +		m_type = UIMT_PREVIEW;
            +	    }
            +	}
            +    }
            +    if (m_query.query.startsWith("/"))
            +	m_query.query.remove(0,1);
            +    if (m_query.query.endsWith("/")) {
            +	kDebug() << "Ends with /";
            +	m_slashend = true;
            +	m_query.query.chop(1);
            +    } else {
            +	m_slashend = false;
            +    }
            +    return;
            +}
            +
            +bool RecollProtocol::syncSearch(const QueryDesc &qd)
            +{
            +    kDebug();
            +    if (!m_initok || !maybeOpenDb(m_reason)) {
            +	string reason = "RecollProtocol::listDir: Init error:" + m_reason;
            +	error(KIO::ERR_SLAVE_DEFINED, reason.c_str());
            +	return false;
            +    }
            +    if (qd.sameQuery(m_query)) {
            +	return true;
            +    }
            +    // doSearch() calls error() if appropriate.
            +    return doSearch(qd);
            +}
            +
            +// This is used by the html interface, but also by the directory one
            +// when doing file copies for exemple. This is the central dispatcher
            +// for requests, it has to know a little about both models.
            +void RecollProtocol::get(const KUrl& url)
            +{
            +    kDebug() << url << endl;
            +
            +    if (!m_initok || !maybeOpenDb(m_reason)) {
            +	string reason = "Recoll: init error: " + m_reason;
            +	error(KIO::ERR_SLAVE_DEFINED, reason.c_str());
            +	return;
            +    }
            +
            +    UrlIngester ingest(this, url);
            +    UrlIngester::RootEntryType rettp;
            +    QueryDesc qd;
            +    int resnum;
            +    if (ingest.isRootEntry(&rettp)) {
            +	switch(rettp) {
            +	case UrlIngester::UIRET_HELP: 
            +	    {
            +		QString location = 
            +		    KStandardDirs::locate("data", "kio_recoll/help.html");
            +		redirection(location);
            +	    }
            +	    goto out;
            +	default:
            +	    searchPage();
            +	    goto out;
            +	}
            +    } else if (ingest.isResult(&qd, &resnum)) {
            +	// Url matched one generated by konqueror/Dolphin out of a
            +	// search directory listing: ie: 
            +        // recoll:/some search string/recollResultxx
            +	//
            +	// This happens when the user drags/drop the result to another
            +	// app, or with the "open-with" right-click. Does not happen
            +	// if the entry itself is clicked (the UDS_URL is apparently
            +	// used in this case
            +	//
            +	// Redirect to the result document URL
            +	if (!syncSearch(qd)) {
            +	    return;
            +	}
            +	Rcl::Doc doc;
            +	if (resnum >= 0 && m_source && m_source->getDoc(resnum, doc)) {
            +	    mimeType(doc.mimetype.c_str());
            +	    redirection(KUrl::fromLocalFile((const char *)(doc.url.c_str()+7)));
            +	    goto out;
            +	}
            +    } else if (ingest.isPreview(&qd, &resnum)) {
            +	if (!syncSearch(qd)) {
            +	    return;
            +	}
            +	Rcl::Doc doc;
            +	if (resnum >= 0 && m_source && m_source->getDoc(resnum, doc)) {
            +	    showPreview(doc);
            +	    goto out;
            +	}
            +    } else if (ingest.isQuery(&qd)) {
            +#if 0 
            +// Do we need this ?
            +	if (host.isEmpty()) {
            +	    char cpage[20];sprintf(cpage, "%d", page);
            +	    QString nurl = QString::fromAscii("recoll://search/query?q=") +
            +		query + "&qtp=" + opt + "&p=" + cpage;
            +	    redirection(KUrl(nurl));
            +	    goto out;
            +	}
            +#endif
            +	// htmlDoSearch does the search syncing (needs to know about changes).
            +	htmlDoSearch(qd);
            +	goto out;
            +    }
            +
            +    error(KIO::ERR_SLAVE_DEFINED, "Unrecognized URL or internal error");
            + out:
            +    finished();
            +}
            +
            +// Execute Recoll search, and set the docsource
            +bool RecollProtocol::doSearch(const QueryDesc& qd)
            +{
            +    kDebug() << "query" << qd.query << "opt" << qd.opt;
            +    m_query = qd;
            +
            +    char opt = qd.opt.isEmpty() ? 'l' : qd.opt.toUtf8().at(0);
            +    string qs = (const char *)qd.query.toUtf8();
            +    Rcl::SearchData *sd = 0;
            +    if (opt != 'l') {
            +	Rcl::SearchDataClause *clp = 0;
            +	if (opt == 'f') {
            +	    clp = new Rcl::SearchDataClauseFilename(qs);
            +	} else {
            +            clp = new Rcl::SearchDataClauseSimple(opt == 'o' ? Rcl::SCLT_OR : 
            +                                                  Rcl::SCLT_AND, qs);
            +	}
            +	sd = new Rcl::SearchData(Rcl::SCLT_OR, m_stemlang);
            +	if (sd && clp)
            +	    sd->addClause(clp);
            +    } else {
            +	sd = wasaStringToRcl(o_rclconfig, m_stemlang, qs, m_reason);
            +    }
            +    if (!sd) {
            +	m_reason = "Internal Error: cant build search";
            +	error(KIO::ERR_SLAVE_DEFINED, m_reason.c_str());
            +	return false;
            +    }
            +
            +    std::shared_ptr sdata(sd);
            +    std::shared_ptrquery(new Rcl::Query(m_rcldb));
            +    query->setCollapseDuplicates(prefs.collapseDuplicates);
            +    if (!query->setQuery(sdata)) {
            +	m_reason = "Query execute failed. Invalid query or syntax error?";
            +	error(KIO::ERR_SLAVE_DEFINED, m_reason.c_str());
            +	return false;
            +    }
            +
            +    DocSequenceDb *src = 
            +	new DocSequenceDb(std::shared_ptr(query), "Query results", sdata);
            +    if (src == 0) {
            +	error(KIO::ERR_SLAVE_DEFINED, "Can't build result sequence");
            +	return false;
            +    }
            +    m_source = std::shared_ptr(src);
            +    // Reset pager in all cases. Costs nothing, stays at page -1 initially
            +    // htmldosearch will fetch the first page if needed.
            +    m_pager.setDocSource(m_source);
            +    return true;
            +}
            +
            +// Note: KDE_EXPORT is actually needed on Unix when building with
            +// cmake. Says something like __attribute__(visibility(defautl))
            +// (cmake apparently sets all symbols to not exported)
            +extern "C" {KDE_EXPORT int kdemain(int argc, char **argv);}
            +
            +int kdemain(int argc, char **argv)
            +{
            +#ifdef KDE_VERSION_3
            +    KInstance instance("kio_recoll");
            +#else
            +    KComponentData instance("kio_recoll");
            +#endif
            +    kDebug() << "*** starting kio_recoll " << endl;
            +
            +    if (argc != 4)  {
            +	kDebug() << "Usage: kio_recoll proto dom-socket1 dom-socket2\n" << endl;
            +	exit(-1);
            +    }
            +
            +    RecollProtocol slave(argv[2], argv[3]);
            +    slave.dispatchLoop();
            +
            +    kDebug() << "kio_recoll Done" << endl;
            +    return 0;
            +}
            diff --git a/src/kde/kioslave/kio_recoll-kde4/kio_recoll.h b/src/kde/kioslave/kio_recoll-kde4/kio_recoll.h
            new file mode 100644
            index 00000000..c2da5578
            --- /dev/null
            +++ b/src/kde/kioslave/kio_recoll-kde4/kio_recoll.h
            @@ -0,0 +1,191 @@
            +#ifndef _RECOLL_H
            +#define _RECOLL_H
            +/* Copyright (C) 2005 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
            + *   (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 General Public License for more details.
            + *
            + *   You should have received a copy of the GNU General Public License
            + *   along with this program; if not, write to the
            + *   Free Software Foundation, Inc.,
            + *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
            + */
            +
            +#include 
            +using std::string;
            +
            +#include 
            +#include 
            +
            +#include 
            +#include 
            +#include 
            +#include 
            +
            +#include "rclconfig.h"
            +#include "rcldb.h"
            +#include "reslistpager.h"
            +#include "docseq.h"
            +#include 
            +
            +class RecollProtocol;
            +
            +/** Specialize the recoll html pager for the kind of links we use etc. */
            +class RecollKioPager : public ResListPager {
            +public:
            +    RecollKioPager() : m_parent(0) {}
            +    void setParent(RecollProtocol *proto) {m_parent = proto;}
            +
            +    virtual bool append(const string& data);
            +    virtual bool append(const string& data, int, const Rcl::Doc&)
            +    {return append(data);}
            +    virtual string detailsLink();
            +    virtual const string &parFormat();
            +    virtual string nextUrl();
            +    virtual string prevUrl();
            +    virtual string pageTop();
            +
            +private:
            +    RecollProtocol *m_parent;
            +};
            +
            +class QueryDesc {
            +public:
            +    QueryDesc() : opt("l"), page(0), isDetReq(false) {}
            +    QString query;
            +    QString opt;
            +    int page;
            +    bool isDetReq;
            +    bool sameQuery(const QueryDesc& o) const {
            +	return !opt.compare(o.opt) && !query.compare(o.query);
            +    }
            +};
            +
            +// Our virtual tree is a bit complicated. We need a class to analyse an URL
            +// and tell what we should do with it
            +class UrlIngester {
            +public:
            +    UrlIngester(RecollProtocol *p, const KUrl& url);
            +    enum RootEntryType {UIRET_NONE, UIRET_ROOT, UIRET_HELP, UIRET_SEARCH};
            +    bool isRootEntry(RootEntryType *tp) {
            +	if (m_type != UIMT_ROOTENTRY) return false;
            +	*tp = m_retType;
            +	return true;
            +    }
            +    bool isQuery(QueryDesc *q) {
            +	if (m_type != UIMT_QUERY) return false;
            +	*q = m_query;
            +	return true;
            +    }
            +    bool isResult(QueryDesc *q, int *num) {
            +	if (m_type != UIMT_QUERYRESULT) return false;
            +	*q = m_query;
            +	*num = m_resnum;
            +	return true;
            +    }
            +    bool isPreview(QueryDesc *q, int *num) {
            +	if (m_type != UIMT_PREVIEW) return false;
            +	*q = m_query;
            +	*num = m_resnum;
            +	return true;
            +    }
            +    bool endSlashQuery() {return m_slashend;}
            +    bool alwaysDir() {return m_alwaysdir;}
            +
            +private:
            +    RecollProtocol *m_parent;
            +    QueryDesc       m_query;
            +    bool            m_slashend;
            +    bool            m_alwaysdir;
            +    RootEntryType   m_retType;
            +    int             m_resnum;
            +    enum MyType {UIMT_NONE, UIMT_ROOTENTRY, UIMT_QUERY, UIMT_QUERYRESULT,
            +		 UIMT_PREVIEW};
            +    MyType           m_type;
            +};
            +
            +
            +/**
            + * A KIO slave to execute and display Recoll searches.
            + *
            + * Things are made a little complicated because KIO slaves can't hope
            + * that their internal state will remain consistent with their user
            + * application state: slaves die, are restarted, reused, at random
            + * between requests. 
            + * In our case, this means that any request has to be processed
            + * without reference to the last operation performed. Ie, if the
            + * search parameters are not those from the last request, the search
            + * must be restarted anew. This happens for example with different
            + * searches in 2 konqueror screens: typically only one kio_slave will
            + * be used.
            + * The fact that we check if the search is the same as the last one,
            + * to avoid restarting is an optimization, not the base mechanism
            + * (contrary to what was initially assumed, and may have left a few
            + * crumbs around).
            + *
            + * We have two modes of operation, one based on html forms and result
            + * pages, which can potentially be developped to the full Recoll
            + * functionality, and one based on a directory listing model, which
            + * will always be more limited, but may be useful in some cases to
            + * allow easy copying of files etc. Which one is in use is decided by
            + * the form of the URL. 
            + */
            +class RecollProtocol : public KIO::SlaveBase {
            + public:
            +    RecollProtocol(const QByteArray &pool, const QByteArray &app );
            +    virtual ~RecollProtocol();
            +    virtual void mimetype(const KUrl& url);
            +    virtual void get(const KUrl& url);
            +    // The directory mode is not available with KDE 4.0, I could find
            +    // no way to avoid crashing kdirmodel
            +#if KDE_IS_VERSION(4,1,0)
            +    virtual void stat(const KUrl & url);
            +    virtual void listDir(const KUrl& url);
            +#endif
            +
            +    static RclConfig  *o_rclconfig;
            +
            +    friend class RecollKioPager;
            +    friend class UrlIngester;
            +
            + private:
            +    bool maybeOpenDb(string& reason);
            +    bool URLToQuery(const KUrl &url, QString& q, QString& opt, int *page=0);
            +    bool doSearch(const QueryDesc& qd);
            +
            +    void searchPage();
            +    void queryDetails();
            +    string makeQueryUrl(int page, bool isdet = false);
            +    bool syncSearch(const QueryDesc& qd);
            +    void htmlDoSearch(const QueryDesc& qd);
            +    void showPreview(const Rcl::Doc& doc);
            +    bool isRecollResult(const KUrl &url, int *num, QString* q);
            +
            +    bool        m_initok;
            +    Rcl::Db    *m_rcldb;
            +    string      m_reason;
            +    bool        m_alwaysdir;
            +    string      m_stemlang; // english by default else env[RECOLL_KIO_STEMLANG]
            +
            +    // Search state: because of how the KIO slaves are used / reused,
            +    // we can't be sure that the next request will be for the same
            +    // search, and we need to check and restart one if the data
            +    // changes. This is very wasteful but hopefully won't happen too
            +    // much in actual use. One possible workaround for some scenarios
            +    // (one slave several konqueror windows) would be to have a small
            +    // cache of recent searches kept open.
            +    RecollKioPager m_pager;
            +    std::shared_ptr m_source;
            +    // Note: page here is not used, current page always comes from m_pager.
            +    QueryDesc      m_query;
            +};
            +
            +extern "C" {int kdemain(int, char**);}
            +
            +#endif // _RECOLL_H
            diff --git a/src/kde/kioslave/kio_recoll-kde4/notes.txt b/src/kde/kioslave/kio_recoll-kde4/notes.txt
            new file mode 100644
            index 00000000..8b8127ff
            --- /dev/null
            +++ b/src/kde/kioslave/kio_recoll-kde4/notes.txt
            @@ -0,0 +1,188 @@
            +Recoll KIO Slave notes/todoes/etc.
            +
            +Goal: access Recoll search from other applications. 
            +
            +We want to allow URLs like recoll:/?? to be used inside
            +Konqueror/Dolphin and open dialogs, to start a search with results
            +displayed/used inside the application.
            +
            +
            +Todoes
            +======
            +
            +
            +Implementation notes:
            +====================
            +
            +- There are two main ways to do this: 
            +  - a-la kio_beagle, using listDir() to list entries pointing to the
            +    different operations or objects (help, status, search result
            +    entries, bookmarks, whatever...). The nice thing is that the
            +    results really look like file object in a directory (probably,
            +    didn't try it actually), no need for look and feel, it's provided by
            +    KDE 
            +
            +  - Or a la strigi: all interactions are through html pages and get()
            +    operations.  Much simpler, but does not allow file management
            +    operations (except by drag/drop from the result list), and can't
            +    use it inside other application's Open dialogs.
            +
            +Recoll is currently doing both things on KDE4.1 (only html for KDE4.0). 
            +
            +
            +Virtual tree:
            +=============
            +
            +recoll: 
            +recoll:/
            +recoll:/help.html
            +recoll:/search.html
            +    Purely synthetic top level entries. Yes, given the following, this means
            +    that you can't search for 'help.html' directly (but you can do it through 
            +    the html interface). These have to exist in the top level directory
            +
            +recoll:/some search string
            +recoll:/some search string/
            + We have a 'mode' determined by the protocol name:
            +  	 recoll -> mixed
            +	 recollf -> file manager
            +  - html mode: these redirect to recoll://search/query?q="some search string"
            +  - file manager mode: do the search and display results.
            +  - mixed mode: what mode is entered depends on ending /
            +
            +recoll://search/query?q=...
            +  html mode search  
            +
            +recoll:/some search string/recollResultxyz
            +xyz is the index in result list.
            +
            +When generating a directory, with use bogus names for the result
            +entries (the displayed ones are from UDS_DISPLAY_NAME and are the real
            +names). When doing drag/drop or "open with" Konqueror/Dolphin builds
            +an url by concatenating the current directory name and the UDS_NAME,
            +instead of using UDS_TARGET_URL as when the entry is clicked. This
            +forces us to use identifying names including the result number, which
            +has many ennoying side effects (such as the target app not knowing the
            +real file path...
            +
            +
            +KIO notes:
            +=========
            +
            +- Slaves are reused seemingly at random. Even with connection-oriented
            +  ones (ie ftp), you can have 2 konqueror windows on different hosts
            +  with only one slave which will have to close/reopen the connection at
            +  each switch.
            +   For slaves with really expensive internal state, this is very wasteful.
            +
            +- Use cases for father_url+name or target_url are ill defined.
            +- Need a way to block autocompletion queries !
            +- No way to display the target URL in konqueror
            +
            +Todoes
            +======
            +- Improve the html interface to support more functions
            + - Category filtering
            + - Sorting
            +
            +- Find a way to use the html form to enter the query and get the
            +  results as a directory ?  - Would it be possible to use a redirect
            +  to switch to the directory-oriented results for a query from the
            +  html form ? 
            +  -> No, even a redirect to a form that would initially trigger a
            +     listDir() triggers a get() when performed from a get()
            +
            +KDE misc notes
            +==================
            +Debug areas: /usr/share/kde4/config/kdebug.areas
            +kdebugdialog [--fullmode] pour configurer
            +./.kde/share/config/kdebugrc
            +Output to ~/.xession-errors by default. How to change ?
            +
            +kio_recoll misc notes:
            +===========================
            +Probleme quand l'url se termine par un / et qu'on edite le mot,
            +konqueror lance une recherche a chaque lettre.
            +
            +Apparemment c'est l'entree "listing" du .protocol qui decide si le plugin
            +est traité plutot comme un dirlister ou comme un htmlgetter. Curieusement,
            +le changement ne s'opere pas toujours immediatement quand on change le
            +fichier .proto, y compris apres avoir tue tous les process kde (changement
            +à la deuxieme execution de konqueror sur kde4.0). Sur kde4.0 il faut que le
            +.proto soit sans entree "listing"
            +
            +Problemes de gestion de l'etat
            +===============================
            +Les KIO slaves ne sont pas associes a une fenetre ! ils sont
            +reutilises au hasard, et leur etat n'a aucune raison de correspondre a
            +celui de l'affichage. On peut tres bien avoir 1 fenetre 2 kio ou 1 kio
            +deux fenetres, et le next d'un search peut arriver au kio qui a
            +l'autre search, donc n'importenaouak. Il faudrait que l'etat soit
            +partage et accede par un identifiant uniquement determine par l'url de
            +la fenetre.
            +
            +Meme pour une fenetre unique, au bout d'un moment le kio timeout et exite.
            +
            +En fait les slaves ne peuvent pas stocker d'etat du tout. Donc:
            + - soit un serveur central auquel ils parlent
            + - soit ils relancent dynamiquement les recherches si pas de match
            +C'est vrai aussi bien pour les dirlists que pour la version html. 
            +
            +J'ai essaye de mettre une boucle timeout callback callspecial() mais
            +ca ne sert a rien, c'est gere dans le process kio_slave, ca ne
            +maintient pas l'association avec un konqueror.
            +
            +KDE_FORK_SLAVES sort of solves the problem in a limited way: 
            + - It applies to an application instance, not a KIO slave type, so it
            +   affects other KIO usages.  
            + - If the application has 2 sessions with the same KIO there are no 
            +   warranties that 1 kio per session will be used ?
            +
            +
            +
            +Old KDE3 notes, 
            +===============
            +
            +kio_recoll has not been checked or worked under KDE3 for eons, no
            +reason to believe it works.
            +
            +- Not using libtool. Probably should. compilation flags in the Makefile
            +  were copy-pasted from a kdebase compilation tree on FreeBSD (kio/man).
            +- You MUST install a kio_recoll.la in lib/kde3 along with kio_recoll.so,
            +  else kdeinit won't be able to load the lib (probably uses the libltdl
            +  thingy?). The one in this directory was duplicated/adjusted from
            +  kio_man.la. The contents don't seem too critical, just needs to exist.
            +- If you want to try, compile, then install kio_recoll.la kio_recoll.so
            +  wherever kde keeps its plugins (ie: lib/kde3), and recoll.protocol in the
            +  services directory (share/services ? look for other .protocol file).
            +- I saw after doing the build/config mockup that kdevelop can generate a
            +  kio_slave project. This might be the next thing to do. otoh would need to
            +  separate the kio from the main source to avoid having to distribute 2megs
            +  of kde build config files.
            +
            +
            +
            +Connected mode
            +==============
            +Tried to add bogus connection status to see if this would prevent switching between apps/slaves, doesnt... checked that's the same for kio_ftp
            +
            +void RecollProtocol::openConnection() 
            +{
            +    kDebug();
            +    connected();
            +}
            +void RecollProtocol::closeConnection() 
            +{
            +    kDebug();
            +}
            +void RecollProtocol::setHost(const QString& host, quint16, 
            +			     const QString&, const QString&)
            +{
            +    kDebug() << host;
            +}
            +void RecollProtocol::slave_status()
            +{
            +    kDebug();
            +    slaveStatus("search", true);
            +}
            ++    connected(); call in maybeopendb()
            diff --git a/src/kde/kioslave/kio_recoll-kde4/recoll.protocol b/src/kde/kioslave/kio_recoll-kde4/recoll.protocol
            new file mode 100644
            index 00000000..c69fc77d
            --- /dev/null
            +++ b/src/kde/kioslave/kio_recoll-kde4/recoll.protocol
            @@ -0,0 +1,11 @@
            +[Protocol]
            +exec=kio_recoll
            +protocol=recoll
            +input=none
            +output=filesystem
            +listing=Name,Type, URL
            +reading=true
            +defaultMimeType=text/html
            +Icon=recoll
            +Class=:local
            +URIMode=rawuri
            diff --git a/src/kde/kioslave/kio_recoll-kde4/recollf.protocol b/src/kde/kioslave/kio_recoll-kde4/recollf.protocol
            new file mode 100644
            index 00000000..fb150ecf
            --- /dev/null
            +++ b/src/kde/kioslave/kio_recoll-kde4/recollf.protocol
            @@ -0,0 +1,11 @@
            +[Protocol]
            +exec=kio_recoll
            +protocol=recollf
            +input=none
            +output=filesystem
            +listing=Name,Type, URL
            +reading=true
            +defaultMimeType=text/html
            +Icon=recoll
            +Class=:local
            +URIMode=rawuri
            diff --git a/src/kde/kioslave/kio_recoll-kde4/recollnolist.protocol b/src/kde/kioslave/kio_recoll-kde4/recollnolist.protocol
            new file mode 100644
            index 00000000..cb5d55e4
            --- /dev/null
            +++ b/src/kde/kioslave/kio_recoll-kde4/recollnolist.protocol
            @@ -0,0 +1,11 @@
            +[Protocol]
            +exec=kio_recoll
            +protocol=recoll
            +input=none
            +output=filesystem
            +# Version for kde4.0: no "listing" entry
            +reading=true
            +defaultMimeType=text/html
            +Icon=recoll
            +Class=:local
            +URIMode=rawuri
            diff --git a/src/kde/kioslave/kio_recoll/00README.txt b/src/kde/kioslave/kio_recoll/00README.txt
            index 933b73b7..04f80651 100644
            --- a/src/kde/kioslave/kio_recoll/00README.txt
            +++ b/src/kde/kioslave/kio_recoll/00README.txt
            @@ -55,12 +55,12 @@ Recipe:
                --prefix=/usr (or wherever KDE lives), build and install 
                Recoll.
             
            - - In the Recoll source, go to kde/kioslave/recoll, then build and
            + - In the Recoll source, go to kde/kioslave/kio_recoll, then build and
                install the kio slave:
             
             mkdir builddir
             cd builddir
            -cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DQT_QMAKE_EXECUTABLE=/usr/bin/qmake-qt4
            +cmake .. -DCMAKE_INSTALL_PREFIX=/usr 
             make
             sudo make install
             
            @@ -78,17 +78,3 @@ You need to build/update the index with recollindex, the KIO slave
             doesn't deal with indexing for now.
             
             
            -Misc build problems:
            -===================
            -
            -KUBUNTU 8.10 (updated to 2008-27-11)
            -------------------------------------
            -cmake generates a bad dependancy on
            -      /build/buildd/kde4libs-4.1.2/obj-i486-linux-gnu/lib/libkdecore.so 
            -inside CMakeFiles/kio_recoll.dir/build.make 
            -
            -Found no way to fix this. You need to edit the line and replace the
            -/build/[...]/lib with /usr/lib. This manifests itself with the
            -following error message:
            -
            -   make[2]: *** No rule to make target `/build/buildd/kde4libs-4.1.2/obj-i486-linux-gnu/lib/libkdecore.so', needed by `lib/kio_recoll.so'.  Stop.
            diff --git a/src/kde/kioslave/kio_recoll/CMakeLists.txt b/src/kde/kioslave/kio_recoll/CMakeLists.txt
            index 2a2700bc..816c1916 100644
            --- a/src/kde/kioslave/kio_recoll/CMakeLists.txt
            +++ b/src/kde/kioslave/kio_recoll/CMakeLists.txt
            @@ -1,31 +1,54 @@
            -cmake_minimum_required(VERSION 2.6)
            -
             project(kio_recoll)
             
            -find_package(KDE4 REQUIRED)
            +cmake_minimum_required(VERSION 2.8.12)
             
            -add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS})
            -add_definitions(-DKDE_DEFAULT_DEBUG_AREA=7130
            -    -DRECOLL_DATADIR=\\"${CMAKE_INSTALL_PREFIX}/share/recoll\\"
            -    -DLIBDIR=\\"${CMAKE_INSTALL_PREFIX}/lib\\"
            +include(FeatureSummary)
            +
            +set(QT_MIN_VERSION 5.2.0)
            +set(KF5_MIN_VERSION 5.0.0)
            +
            +find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
            +    Network
            +    Widgets)
            +
            +find_package(ECM REQUIRED NO_MODULE)
            +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
            +
            +include(KDEInstallDirs)
            +include(KDECMakeSettings)
            +include(KDECompilerSettings NO_POLICY_SCOPE)
            +
            +# CoreAddons?
            +find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS
            +    KIO)
            +
            +add_definitions(-DQT_NO_URL_CAST_FROM_STRING)
            +
            +include_directories(
            +   ${CMAKE_SOURCE_DIR}
            +   ${CMAKE_BINARY_DIR}
            +)
            +
            +
            +## Recoll stuff
            +add_definitions(
            +    -DRECOLL_DATADIR="${CMAKE_INSTALL_PREFIX}/share/recoll"
            +    -DLIBDIR="${CMAKE_INSTALL_PREFIX}/lib"
                 -DHAVE_CONFIG_H
             )
            -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") 
            +
             
             set(rcltop ${CMAKE_CURRENT_SOURCE_DIR}/../../../)
             
             # Execute recoll configuration to create autoconfig.h and version.h and
             # generate a PIC lib
            -execute_process(COMMAND ${rcltop}/configure --disable-qtgui --disable-x11mon --enable-pic --prefix=${CMAKE_INSTALL_PREFIX} --mandir=${CMAKE_INSTALL_PREFIX}/share/man
            +execute_process(COMMAND ${rcltop}/configure --disable-static --disable-qtgui --disable-x11mon --prefix=${CMAKE_INSTALL_PREFIX} --mandir=${CMAKE_INSTALL_PREFIX}/share/man
             		WORKING_DIRECTORY ${rcltop}
             )
            -execute_process(COMMAND /bin/sh mkMake
            -		WORKING_DIRECTORY ${rcltop}/lib
            -)
             
            -link_directories(${rcltop}/lib ${CMAKE_INSTALL_PREFIX}/lib)
            +link_directories(${rcltop}/.libs ${CMAKE_INSTALL_PREFIX}/lib)
             
            -include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES}
            +include_directories (${CMAKE_SOURCE_DIR}
               ${rcltop}/aspell 
               ${rcltop}/bincimapmime
               ${rcltop}/common 
            @@ -38,46 +61,41 @@ include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES}
               ${rcltop}/qtgui
             )
             
            -set(kio_recoll_SRCS  kio_recoll.cpp htmlif.cpp dirif.cpp ${rcltop}/qtgui/guiutils.cpp)
            +set(kio_recoll_SRCS  kio_recoll.cpp htmlif.cpp dirif.cpp 
            +${rcltop}/qtgui/guiutils.cpp)
             
            -CHECK_LIBRARY_EXISTS(dl dlopen "" DLOPEN_IN_LIBDL)
            -IF(DLOPEN_IN_LIBDL)
            -	LIST(APPEND EXTRA_LIBS dl)
            -ENDIF(DLOPEN_IN_LIBDL)
            -CHECK_LIBRARY_EXISTS(pthread pthread_sigmask "" PTHREAD_IN_LIBPTHREAD)
            -IF(PTHREAD_IN_LIBPTHREAD)
            -	LIST(APPEND EXTRA_LIBS pthread)
            -ENDIF(PTHREAD_IN_LIBPTHREAD)
             
             # Had the idea to add e.g. /usr/lib/recoll to the rpath so that the dyn lib 
             # will be found at run time. But this does not seem to work with debian 
             # which strips RPATH by default (I think there is a way for libs in app-specific
            -# paths but I did not find it. Link with the .a instead.
            +# paths but I did not find it). Link with the .a instead.
             #SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib/recoll")
             
            -kde4_add_plugin(kio_recoll ${kio_recoll_SRCS})
            +add_library(kio_recoll MODULE ${kio_recoll_SRCS})
             
            -add_custom_target(parser
            -                 COMMAND make wasaparse.tab.cpp
            -                 WORKING_DIRECTORY ${rcltop}/query
            -)
             add_custom_target(rcllib
            -                 COMMAND make librecoll.a
            -                 WORKING_DIRECTORY ${rcltop}/lib
            +                 COMMAND make -j 6 PicStatic
            +                 WORKING_DIRECTORY ${rcltop}
             )
            -add_dependencies(rcllib parser)
            -add_dependencies(kio_recoll parser rcllib)
            +add_dependencies(kio_recoll rcllib)
             
            -target_link_libraries(kio_recoll recoll xapian z ${EXTRA_LIBS} ${KDE4_KIO_LIBS})
            -
            -install(TARGETS kio_recoll DESTINATION ${PLUGIN_INSTALL_DIR})
            -
            -IF ("${KDE_VERSION_MAJOR}.${KDE_VERSION_MINOR}" GREATER 4.0)
            -     install(FILES recoll.protocol recollf.protocol DESTINATION ${SERVICES_INSTALL_DIR})
            -ELSE ("${KDE_VERSION_MAJOR}.${KDE_VERSION_MINOR}" GREATER 4.0)
            -   install(FILES recollnolist.protocol DESTINATION ${SERVICES_INSTALL_DIR}
            -   		 RENAME recoll.protocol)
            -ENDIF ("${KDE_VERSION_MAJOR}.${KDE_VERSION_MINOR}" GREATER 4.0)
            +target_link_libraries(kio_recoll
            +recoll 
            +xapian 
            +KF5::KIOCore
            +z 
            +pthread
            +)
             
            +install(FILES recoll.protocol recollf.protocol 
            +              DESTINATION ${SERVICES_INSTALL_DIR})
             install(FILES data/welcome.html	data/help.html
             	      DESTINATION  ${DATA_INSTALL_DIR}/kio_recoll)
            +
            +# Tried but could not use PLUGIN_INSTALL_DIR (/usr/lib64/plugins), or
            +# /usr/lib64/qt5/plugins/kf5/kio/recoll.so: the module is not found by
            +# dolphin). Actually that's because of the protocol file. recoll has 
            +# exec=kio_recoll, file has exec=kf5/kio/file
            +
            +set_target_properties(kio_recoll PROPERTIES OUTPUT_NAME "kio_recoll")
            +install(TARGETS kio_recoll DESTINATION ${LIB_INSTALL_DIR}/qt5/plugins)
            diff --git a/src/kde/kioslave/kio_recoll/data/help.html b/src/kde/kioslave/kio_recoll/data/help.html
            index 689cbdf8..84824de5 100644
            --- a/src/kde/kioslave/kio_recoll/data/help.html
            +++ b/src/kde/kioslave/kio_recoll/data/help.html
            @@ -18,20 +18,17 @@
             	newer, which presents results as directory entries
               
          -

          The module is still in its infancy. You will undoubtedly obtain - strange effects from time to time. If you have any remarks or - ideas about improving kio_recoll, or observe an interesting and - reproducible sequence, please - report it.

          -

          kio_recoll is primarily - designed and tested with konqueror, and you will - undoubtedly get even more surprising effects with other tools.

          - -

          The Html interface is currently much more usable. The directory - interface is extremely quirky.

          +

          With recent KDE versions (now: 2016), the file manager + interface works in Dolphin, and both the file manager and the HTML + interface work in Konqueror.

          + +

          You will undoubtedly obtain strange effects from time to time. If + you have any remarks or ideas about improving kio_recoll, or + observe an interesting and reproducible sequence, + please report it.

          The module is particularly unhelpful with search hits inside - email folders, which Konqueror has no way to access.

          + email folders, which Konqueror or Dolphin have no way to access.

          HTML interface

          @@ -62,37 +59,42 @@

          There are several ways to enter this interface:

            -
          • Using "recollf" as protocol name instead of "recoll". This is - probably the easiest option inside open dialogs.
          • +
          • Dolphin will only present the file manager interface. Enter + recoll:/some query + or recoll: some query in the address bar. Note: + single-slash, not double, you will get something like "Protocol + unknown" if you enter 2 slashes.
          • -
          • Using an URL ending with a '/', ie: -
            - - recoll:/red apples ext:html/ -
            -
          • -
          • Users who will want to use the file manager view most of the - time can set the RECOLL_KIO_ALWAYS_DIR environment - variable or the kio_always_dir recoll.conf variable - to 1. The HTML interface will then only be accessible - through the search link in the top "recoll:" view.
          • +
          • Konqueror: this supports both the file manager and HTML + interfaces. You can force using the file manager interface in the + following ways: +
              +
            • Using "recollf" as protocol name instead of "recoll". This is + probably the easiest option inside open dialogs.
            • + +
            • Using an URL ending with a '/', ie: +
              + + recoll:/red apples ext:html/ +
              +
            • +
            • If you would like to use the file manager view most of the + time, you can set the RECOLL_KIO_ALWAYS_DIR environment + variable or the kio_always_dir recoll.conf variable + to 1. The HTML interface will then only be accessible + through the search link in the top "recoll:" view.
            • +

          No search result details (samples, relevance etc.) are available, but this interface allows multiple selections and copies, usage inside any KDE open dialog, etc.

          -

          To avoid swamping the interface with thousands of results, the - result count is limited to 100 by default. You can change this value - by setting the kio_max_direntries parameter in your recoll - configuration file (typically ~/.recoll/recoll.conf)

          - -

          Because of limitations in the current KIO slave usage, the actual - entry names are not those displayed but synthetic ones like - "recollResultxxx". This has unfortunate side-effects when - dragging/dropping the entries to some other application, or when - using an open dialog (the opened file doesn't have the correct path - to the original file).

          +

          To avoid swamping the interface with too many thousands of + results, the result count is limited to 10000 by default. You can + change this value by setting the kio_max_direntries + parameter in your recoll configuration file (typically + ~/.recoll/recoll.conf)

          Recoll Search

          diff --git a/src/kde/kioslave/kio_recoll/dirif.cpp b/src/kde/kioslave/kio_recoll/dirif.cpp index 07d89ed8..3a96891b 100644 --- a/src/kde/kioslave/kio_recoll/dirif.cpp +++ b/src/kde/kioslave/kio_recoll/dirif.cpp @@ -19,26 +19,22 @@ * A lot of code in this file was copied from kio_beagle 0.4.0, * which is a GPL program. The authors listed are: * Debajyoti Bera - * + * * KDE4 port: * Stephan Binner */ #include "autoconfig.h" -#include - -#if KDE_IS_VERSION(4,1,0) // Couldn't get listDir() to work with kde 4.0, konqueror keeps // crashing because of kdirmodel, couldn't find a workaround (not // saying it's impossible)... #include -#include -#include -#include -#include +#include +#include +#include #include "kio_recoll.h" #include "pathut.h" @@ -49,44 +45,53 @@ static const QString resultBaseName("recollResult"); // Check if the input URL is of the form that konqueror builds by // appending one of our result file names to the directory name (which -// is the search string). If it is, extract return the result document -// number. Possibly restart the search if the search string does not -// match the current one -bool RecollProtocol::isRecollResult(const KUrl &url, int *num, QString *q) +// is the search string). If it is, extract and return the result +// document number. Possibly restart the search if the search string +// does not match the current one +bool RecollProtocol::isRecollResult(const QUrl& url, int *num, QString *q) { *num = -1; - kDebug() << "url" << url; + qDebug() << "RecollProtocol::isRecollResult: url: " << url; // Basic checks - if (!url.host().isEmpty() || url.path().isEmpty() || - (url.protocol().compare("recoll") && url.protocol().compare("recollf"))) + if (!url.host().isEmpty() || url.path().isEmpty() || + (url.scheme().compare("recoll") && url.scheme().compare("recollf"))) { + qDebug() << "RecollProtocol::isRecollResult: no: url.host " << + url.host() << " path " << url.path() << " scheme " << url.scheme(); return false; + } + QString path = url.path(); - if (!path.startsWith("/")) + qDebug() << "RecollProtocol::isRecollResult: path: " << path; + if (!path.startsWith("/")) { return false; + } // Look for the last '/' and check if it is followed by // resultBaseName (riiiight...) int slashpos = path.lastIndexOf("/"); - if (slashpos == -1 || slashpos == 0 || slashpos == path.length() -1) + if (slashpos == -1 || slashpos == 0 || slashpos == path.length() - 1) { return false; + } slashpos++; - //kDebug() << "Comparing " << path.mid(slashpos, resultBaseName.length()) << + //qDebug() << "Comparing " << path.mid(slashpos, resultBaseName.length()) << // "and " << resultBaseName; - if (path.mid(slashpos, resultBaseName.length()).compare(resultBaseName)) + if (path.mid(slashpos, resultBaseName.length()).compare(resultBaseName)) { return false; + } // Extract the result number QString snum = path.mid(slashpos + resultBaseName.length()); - sscanf(snum.toAscii(), "%d", num); - if (*num == -1) + sscanf(snum.toUtf8(), "%d", num); + if (*num == -1) { return false; + } - //kDebug() << "URL analysis ok, num:" << *num; + //qDebug() << "URL analysis ok, num:" << *num; // We do have something that ressembles a recoll result locator. Check if // this matches the current search, else have to run the requested one - *q = path.mid(1, slashpos-2); + *q = path.mid(1, slashpos - 2); return true; } @@ -94,33 +99,59 @@ bool RecollProtocol::isRecollResult(const KUrl &url, int *num, QString *q) static const UDSEntry resultToUDSEntry(const Rcl::Doc& doc, int num) { UDSEntry entry; - - KUrl url(doc.url.c_str()); -// kDebug() << doc.url.c_str(); - entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, url.fileName()); - char cnum[30];sprintf(cnum, "%04d", num); + QUrl url(doc.url.c_str()); + //qDebug() << doc.url.c_str(); + + /// Filename - as displayed in directory listings etc. + /// "." has the usual special meaning of "current directory" + /// UDS_NAME must always be set and never be empty, neither contain '/'. + /// + /// Note that KIO will append the UDS_NAME to the url of their + /// parent directory, so all kioslaves must use that naming scheme + /// ("url_of_parent/filename" will be the full url of that file). + /// To customize the appearance of files without changing the url + /// of the items, use UDS_DISPLAY_NAME. + // + // Use the result number to designate the file in case we are + // asked to access it + char cnum[30]; + sprintf(cnum, "%04d", num); entry.insert(KIO::UDSEntry::UDS_NAME, resultBaseName + cnum); - if (!doc.mimetype.compare("application/x-fsdirectory") || - !doc.mimetype.compare("inode/directory")) { + // Display the real file name + entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, url.fileName()); + + /// A local file path if the ioslave display files sitting on the + /// local filesystem (but in another hierarchy, e.g. settings:/ or + /// remote:/) + entry.insert(KIO::UDSEntry::UDS_LOCAL_PATH, url.path()); + + /// This file is a shortcut or mount, pointing to an + /// URL in a different hierarchy + /// @since 4.1 + // We should probably set this only if the scheme is not 'file' (e.g. + // from the web cache). + entry.insert(KIO::UDSEntry::UDS_TARGET_URL, doc.url.c_str()); + + if (!doc.mimetype.compare("application/x-fsdirectory") || + !doc.mimetype.compare("inode/directory")) { entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory"); - entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); } else { entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, doc.mimetype.c_str()); - entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); } - entry.insert(KIO::UDSEntry::UDS_LOCAL_PATH, url.path()); + // For local files, supply the usual file stat information struct stat info; - if (lstat(url.path().toAscii(), &info) >= 0) { - entry.insert( KIO::UDSEntry::UDS_SIZE, info.st_size); - entry.insert( KIO::UDSEntry::UDS_ACCESS, info.st_mode); - entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, info.st_mtime); - entry.insert( KIO::UDSEntry::UDS_ACCESS_TIME, info.st_atime); - entry.insert( KIO::UDSEntry::UDS_CREATION_TIME, info.st_ctime); + if (lstat(url.path().toUtf8(), &info) >= 0) { + entry.insert(KIO::UDSEntry::UDS_SIZE, info.st_size); + entry.insert(KIO::UDSEntry::UDS_ACCESS, info.st_mode); + entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, info.st_mtime); + entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, info.st_atime); + entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, info.st_ctime); } - entry.insert(KIO::UDSEntry::UDS_TARGET_URL, doc.url.c_str()); return entry; } @@ -130,10 +161,10 @@ static const UDSEntry resultToUDSEntry(const Rcl::Doc& doc, int num) static void createRootEntry(KIO::UDSEntry& entry) { entry.clear(); - entry.insert( KIO::UDSEntry::UDS_NAME, "."); - entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); - entry.insert( KIO::UDSEntry::UDS_ACCESS, 0700); - entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory"); + entry.insert(KIO::UDSEntry::UDS_NAME, "."); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0700); + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory"); } // Points to html query screen @@ -152,8 +183,9 @@ static void createGoHomeEntry(KIO::UDSEntry& entry) // Points to help file static void createGoHelpEntry(KIO::UDSEntry& entry) { - QString location = - KStandardDirs::locate("data", "kio_recoll/help.html"); + QString location = + QStandardPaths::locate(QStandardPaths::GenericDataLocation, + "kio_recoll/help.html"); entry.clear(); entry.insert(KIO::UDSEntry::UDS_NAME, "help"); entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, "Recoll help (click me first)"); @@ -165,46 +197,54 @@ static void createGoHelpEntry(KIO::UDSEntry& entry) entry.insert(KIO::UDSEntry::UDS_ICON_NAME, "help"); } -void RecollProtocol::stat(const KUrl & url) +// As far as I can see we only ever get this on '/' so why all the code? +void RecollProtocol::stat(const QUrl& url) { - kDebug() << url << endl ; + qDebug() << "RecollProtocol::stat:" << url; UrlIngester ingest(this, url); KIO::UDSEntry entry; - entry.insert(KIO::UDSEntry::UDS_TARGET_URL, url.url()); +// entry.insert(KIO::UDSEntry::UDS_TARGET_URL, url.url()); +// entry.insert(KIO::UDSEntry::UDS_URL, url.url()); UrlIngester::RootEntryType rettp; QueryDesc qd; int num; if (ingest.isRootEntry(&rettp)) { - switch(rettp) { + qDebug() << "RecollProtocol::stat: root entry"; + switch (rettp) { case UrlIngester::UIRET_ROOT: + qDebug() << "RecollProtocol::stat: root"; createRootEntry(entry); break; - case UrlIngester::UIRET_HELP: + case UrlIngester::UIRET_HELP: + qDebug() << "RecollProtocol::stat: root help"; createGoHelpEntry(entry); break; case UrlIngester::UIRET_SEARCH: + qDebug() << "RecollProtocol::stat: root search"; createGoHomeEntry(entry); break; - default: - error(ERR_DOES_NOT_EXIST, ""); + default: + qDebug() << "RecollProtocol::stat: ??"; + error(ERR_DOES_NOT_EXIST, QString()); break; } } else if (ingest.isResult(&qd, &num)) { + qDebug() << "RecollProtocol::stat: isresult"; if (syncSearch(qd)) { Rcl::Doc doc; - if (num >= 0 && !m_source.isNull() && - m_source->getDoc(num, doc)) { + if (num >= 0 && m_source && m_source->getDoc(num, doc)) { entry = resultToUDSEntry(doc, num); } else { - error(ERR_DOES_NOT_EXIST, ""); + error(ERR_DOES_NOT_EXIST, QString()); } } else { // hopefully syncSearch() set the error? } } else if (ingest.isQuery(&qd)) { - // ie "recoll:/some string" or "recoll:/some string/" + qDebug() << "RecollProtocol::stat: isquery"; + // ie "recoll:/some string" or "recoll:/some string/" // // We have a problem here. We'd like to let the user enter // either form and get an html or a dir contents result, @@ -215,46 +255,48 @@ void RecollProtocol::stat(const KUrl & url) // Another approach would be to use different protocol names // to avoid any possibility of mixups if (m_alwaysdir || ingest.alwaysDir() || ingest.endSlashQuery()) { - kDebug() << "Directory type"; - entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); - entry.insert(KIO::UDSEntry::UDS_ACCESS, 0700); - entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory"); + qDebug() << "RecollProtocol::stat: Directory type:"; + // Need to check no / in there entry.insert(KIO::UDSEntry::UDS_NAME, qd.query); - entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, time(0)); - entry.insert( KIO::UDSEntry::UDS_CREATION_TIME, time(0)); + entry.insert(KIO::UDSEntry::UDS_ACCESS, 0700); + entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, time(0)); + entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, time(0)); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory"); } + } else { + qDebug() << "RecollProtocol::stat: none of the above ??"; } statEntry(entry); finished(); } -void RecollProtocol::listDir(const KUrl& url) +void RecollProtocol::listDir(const QUrl& url) { - kDebug() << url << endl; + qDebug() << "RecollProtocol::listDir: url: " << url; UrlIngester ingest(this, url); UrlIngester::RootEntryType rettp; QueryDesc qd; if (ingest.isRootEntry(&rettp)) { - switch(rettp) { - case UrlIngester::UIRET_ROOT: - { - kDebug() << "list /" << endl; - UDSEntryList entries; - KIO::UDSEntry entry; - createRootEntry(entry); - entries.append(entry); - createGoHomeEntry(entry); - entries.append(entry); - createGoHelpEntry(entry); - entries.append(entry); - listEntries(entries); - finished(); - } - return; + switch (rettp) { + case UrlIngester::UIRET_ROOT: { + qDebug() << "RecollProtocol::listDir:list /"; + UDSEntryList entries; + KIO::UDSEntry entry; + createRootEntry(entry); + entries.append(entry); + createGoHomeEntry(entry); + entries.append(entry); + createGoHelpEntry(entry); + entries.append(entry); + listEntries(entries); + finished(); + } + return; default: - error(ERR_CANNOT_ENTER_DIRECTORY, ""); + error(ERR_CANNOT_ENTER_DIRECTORY, QString()); return; } } else if (ingest.isQuery(&qd)) { @@ -262,8 +304,8 @@ void RecollProtocol::listDir(const KUrl& url) // konqueror autocompletion it comes with a / at the end, // which offers an opportunity to not perform it. if (ingest.endSlashQuery()) { - kDebug() << "Ends With /" << endl; - error(ERR_SLAVE_DEFINED, "Autocompletion search aborted"); + qDebug() << "RecollProtocol::listDir: Ends With /"; + error(ERR_SLAVE_DEFINED, u8s2qs("Autocompletion search aborted")); return; } if (!syncSearch(qd)) { @@ -272,17 +314,19 @@ void RecollProtocol::listDir(const KUrl& url) } // Fallthrough to actually listing the directory } else { - kDebug() << "Cant grok input url"; - error(ERR_CANNOT_ENTER_DIRECTORY, ""); + qDebug() << "RecollProtocol::listDir: Cant grok input url"; + error(ERR_CANNOT_ENTER_DIRECTORY, QString()); return; } static int maxentries = -1; if (maxentries == -1) { - if (o_rclconfig) + if (o_rclconfig) { o_rclconfig->getConfParam("kio_max_direntries", &maxentries); - if (maxentries == -1) + } + if (maxentries == -1) { maxentries = 10000; + } } static const int pagesize = 200; int pagebase = 0; @@ -291,28 +335,18 @@ void RecollProtocol::listDir(const KUrl& url) int pagelen = m_source->getSeqSlice(pagebase, pagesize, page); UDSEntry entry; if (pagelen < 0) { - error(ERR_SLAVE_DEFINED, "Internal error"); - listEntry(entry, true); + error(ERR_SLAVE_DEFINED, u8s2qs("Internal error")); break; } + UDSEntryList entries; for (int i = 0; i < pagelen; i++) { - listEntry(resultToUDSEntry(page[i].doc, i), false); + entries.push_back(resultToUDSEntry(page[i].doc, i)); } + listEntries(entries); if (pagelen != pagesize) { - listEntry(entry, true); break; } pagebase += pagelen; } finished(); } - -#else // <--- KDE 4.1+ - -#include -#include "kio_recoll.h" -bool RecollProtocol::isRecollResult(const KUrl &, int *, QString *) -{ - return false; -} -#endif diff --git a/src/kde/kioslave/kio_recoll/htmlif.cpp b/src/kde/kioslave/kio_recoll/htmlif.cpp index d31dd72c..3e790382 100644 --- a/src/kde/kioslave/kio_recoll/htmlif.cpp +++ b/src/kde/kioslave/kio_recoll/htmlif.cpp @@ -18,15 +18,11 @@ #include #include #include -#include +#include #include #include - -using namespace std; - -#include -#include +#include #include "rclconfig.h" #include "rcldb.h" @@ -44,12 +40,14 @@ using namespace std; #include "wipedir.h" #include "hldata.h" +using namespace std; using namespace KIO; bool RecollKioPager::append(const string& data) { - if (!m_parent) - return false; + if (!m_parent) { + return false; + } m_parent->data(QByteArray(data.c_str())); return true; } @@ -57,21 +55,23 @@ bool RecollKioPager::append(const string& data) string RecollProtocol::makeQueryUrl(int page, bool isdet) { ostringstream str; - str << "recoll://search/query?q=" << - url_encode((const char*)m_query.query.toUtf8()) << - "&qtp=" << (const char*)m_query.opt.toUtf8(); - if (page >= 0) - str << "&p=" << page; - if (isdet) - str << "&det=1"; + str << "recoll://search/query?q=" << + url_encode((const char*)m_query.query.toUtf8()) << + "&qtp=" << (const char*)m_query.opt.toUtf8(); + if (page >= 0) { + str << "&p=" << page; + } + if (isdet) { + str << "&det=1"; + } return str.str(); } string RecollKioPager::detailsLink() { - string chunk = string("makeQueryUrl(m_parent->m_pager.pageNumber(), true) + "\">" - + "(show query)" + ""; + string chunk = string("makeQueryUrl(m_parent->m_pager.pageNumber(), true) + "\">" + + "(show query)" + ""; return chunk; } @@ -81,24 +81,24 @@ const string& RecollKioPager::parFormat() // Need to escape the % inside the query url string qurl = m_parent->makeQueryUrl(-1, false), escurl; for (string::size_type pos = 0; pos < qurl.length(); pos++) { - switch(qurl.at(pos)) { - case '%': - escurl += "%%"; - break; - default: - escurl += qurl.at(pos); - } + switch (qurl.at(pos)) { + case '%': + escurl += "%%"; + break; + default: + escurl += qurl.at(pos); + } } ostringstream str; - str << - "" - "%R %S " - "Preview  " << - "Open " << - "%T
          " - "%M %D   %U  %i
          " - "%A %K"; + str << + "" + "%R %S " + "Preview  " << + "Open " << + "%T
          " + "%M %D   %U  %i
          " + "%A %K"; return parformat = str.str(); } @@ -110,30 +110,32 @@ string RecollKioPager::pageTop() return pt; // Would be nice to have but doesnt work because the query may be executed // by another kio instance which has no idea of the current page o -#if 0 && KDE_IS_VERSION(4,1,0) - "    m_query.query.toUtf8())) + - "/\">Directory view (you may need to reload the page)" +#if 0 + "    m_query.query.toUtf8())) + + "/\">Directory view (you may need to reload the page)" #endif } string RecollKioPager::nextUrl() { int pagenum = pageNumber(); - if (pagenum < 0) - pagenum = 0; - else - pagenum++; + if (pagenum < 0) { + pagenum = 0; + } else { + pagenum++; + } return m_parent->makeQueryUrl(pagenum); } string RecollKioPager::prevUrl() { int pagenum = pageNumber(); - if (pagenum <= 0) - pagenum = 0; - else - pagenum--; + if (pagenum <= 0) { + pagenum = 0; + } else { + pagenum--; + } return m_parent->makeQueryUrl(pagenum); } @@ -143,18 +145,19 @@ void RecollProtocol::searchPage() { mimeType("text/html"); if (welcomedata.empty()) { - QString location = - KStandardDirs::locate("data", "kio_recoll/welcome.html"); - string reason; - if (location.isEmpty() || - !file_to_string((const char *)location.toUtf8(), - welcomedata, &reason)) { - welcomedata = "Recoll Error" - "

          Could not locate Recoll welcome.html file: "; - welcomedata += reason; - welcomedata += "

          "; - } - } + QString location = + QStandardPaths::locate(QStandardPaths::GenericDataLocation, + "kio_recoll/welcome.html"); + string reason; + if (location.isEmpty() || + !file_to_string((const char *)location.toUtf8(), + welcomedata, &reason)) { + welcomedata = "Recoll Error" + "

          Could not locate Recoll welcome.html file: "; + welcomedata += reason; + welcomedata += "

          "; + } + } string catgq; #if 0 @@ -164,14 +167,14 @@ void RecollProtocol::searchPage() // language, but not too useful in this case, so scrap it for now. list cats; if (o_rclconfig->getMimeCategories(cats) && !cats.empty()) { - catgq = "

          Filter on types: " - "All"; - for (list::iterator it = cats.begin(); it != cats.end();it++) { - catgq += "\n" + *it ; - } + catgq = "

          Filter on types: " + "All"; + for (list::iterator it = cats.begin(); it != cats.end(); it++) { + catgq += "\n" + *it ; + } } -#endif +#endif string tmp; map subs; @@ -190,47 +193,44 @@ void RecollProtocol::queryDetails() os << "" << endl; os << "" << endl; + "charset=utf-8\">" << endl; os << "" << "Recoll query details" << "\n" << endl; os << "" << endl; os << "

          Query details:

          " << endl; - os << "

          " << m_pager.queryDescription().c_str() <<"

          "<< endl; - os << "

          Return to results" << endl; + os << "

          " << m_pager.queryDescription().c_str() << "

          " << endl; + os << "

          Return to results" << endl; os << "" << endl; data(array); } class PlainToRichKio : public PlainToRich { public: - PlainToRichKio(const string& nm) - : m_name(nm) - { - } + PlainToRichKio(const string& nm) + : m_name(nm) { + } virtual string header() { - if (m_inputhtml) { - return cstr_null; - } else { - return string("" - ""). - append(m_name). - append("

          ");
          -	}
          +        if (m_inputhtml) {
          +            return cstr_null;
          +        } else {
          +            return string(""
          +                          "").
          +                   append(m_name).
          +                   append("
          ");
          +        }
               }
           
          -    virtual string startMatch(unsigned int)
          -    {
          -	return string("");
          +    virtual string startMatch(unsigned int) {
          +        return string("");
               }
           
          -    virtual string endMatch() 
          -    {
          -	return string("");
          +    virtual string endMatch() {
          +        return string("");
               }
           
          -    const string &m_name;
          +    const string& m_name;
           };
           
           void RecollProtocol::showPreview(const Rcl::Doc& idoc)
          @@ -239,12 +239,13 @@ void RecollProtocol::showPreview(const Rcl::Doc& idoc)
               Rcl::Doc fdoc;
               string ipath = idoc.ipath;
               if (!interner.internfile(fdoc, ipath)) {
          -	error(KIO::ERR_SLAVE_DEFINED, "Cannot convert file to internal format");
          -	return;
          +        error(KIO::ERR_SLAVE_DEFINED,
          +              u8s2qs("Cannot convert file to internal format"));
          +        return;
               }
               if (!interner.get_html().empty()) {
          -	fdoc.text = interner.get_html();
          -	fdoc.mimetype = "text/html";
          +        fdoc.text = interner.get_html();
          +        fdoc.mimetype = "text/html";
               }
           
               mimeType("text/html");
          @@ -254,15 +255,16 @@ void RecollProtocol::showPreview(const Rcl::Doc& idoc)
               ptr.set_inputhtml(!fdoc.mimetype.compare("text/html"));
               list otextlist;
               HighlightData hdata;
          -    if (!m_source.isNull())
          -	m_source->getTerms(hdata);
          +    if (m_source) {
          +        m_source->getTerms(hdata);
          +    }
               ptr.plaintorich(fdoc.text, otextlist, hdata);
           
               QByteArray array;
               QTextStream os(&array, QIODevice::WriteOnly);
          -    for (list::iterator it = otextlist.begin(); 
          -	 it != otextlist.end(); it++) {
          -	os << (*it).c_str();
          +    for (list::iterator it = otextlist.begin();
          +            it != otextlist.end(); it++) {
          +        os << (*it).c_str();
               }
               os << "" << endl;
               data(array);
          @@ -270,31 +272,34 @@ void RecollProtocol::showPreview(const Rcl::Doc& idoc)
           
           void RecollProtocol::htmlDoSearch(const QueryDesc& qd)
           {
          -    kDebug() << "q" << qd.query << "option" << qd.opt << "page" << qd.page <<
          -	"isdet" << qd.isDetReq << endl;
          - 
          +    qDebug() << "q" << qd.query << "option" << qd.opt << "page" << qd.page <<
          +             "isdet" << qd.isDetReq << endl;
          +
               mimeType("text/html");
           
          -    if (!syncSearch(qd))
          -	return;
          +    if (!syncSearch(qd)) {
          +        return;
          +    }
               // syncSearch/doSearch do the setDocSource when needed
               if (m_pager.pageNumber() < 0) {
          -	m_pager.resultPageNext();
          +        m_pager.resultPageNext();
               }
               if (qd.isDetReq) {
          -	queryDetails();
          -	return;
          +        queryDetails();
          +        return;
               }
           
               // Check / adjust page number
               if (qd.page > m_pager.pageNumber()) {
          -	int npages = qd.page - m_pager.pageNumber();
          -	for (int i = 0; i < npages; i++)
          -	    m_pager.resultPageNext();
          +        int npages = qd.page - m_pager.pageNumber();
          +        for (int i = 0; i < npages; i++) {
          +            m_pager.resultPageNext();
          +        }
               } else if (qd.page < m_pager.pageNumber()) {
          -	int npages = m_pager.pageNumber() - qd.page;
          -	for (int i = 0; i < npages; i++) 
          -	    m_pager.resultPageBack();
          +        int npages = m_pager.pageNumber() - qd.page;
          +        for (int i = 0; i < npages; i++) {
          +            m_pager.resultPageBack();
          +        }
               }
               // Display
               m_pager.displayPage(o_rclconfig);
          diff --git a/src/kde/kioslave/kio_recoll/kio_recoll.cpp b/src/kde/kioslave/kio_recoll/kio_recoll.cpp
          index 4ea76825..ca4df5e9 100644
          --- a/src/kde/kioslave/kio_recoll/kio_recoll.cpp
          +++ b/src/kde/kioslave/kio_recoll/kio_recoll.cpp
          @@ -18,19 +18,15 @@
           #include 
           #include 
           #include 
          -#include  
          +#include 
           
           #include 
          -using namespace std;
           
          -#include 
          -#include 
          -#include 
          -#include 
          -
          -#include 
          -#include 
          -#include 
          +#include 
          +#include 
          +#include 
          +#include 
          +#include 
           
           #include "rclconfig.h"
           #include "rcldb.h"
          @@ -47,44 +43,45 @@ using namespace std;
           #include "guiutils.h"
           
           using namespace KIO;
          +using namespace std;
           
           RclConfig *RecollProtocol::o_rclconfig;
           
          -RecollProtocol::RecollProtocol(const QByteArray &pool, const QByteArray &app) 
          +RecollProtocol::RecollProtocol(const QByteArray& pool, const QByteArray& app)
               : SlaveBase("recoll", pool, app), m_initok(false), m_rcldb(0),
                 m_alwaysdir(false)
           {
          -    kDebug() << endl;
          +    qDebug() << "RecollProtocol::RecollProtocol()";
               if (o_rclconfig == 0) {
          -	o_rclconfig = recollinit(0, 0, m_reason);
          -	if (!o_rclconfig || !o_rclconfig->ok()) {
          -	    m_reason = string("Configuration problem: ") + m_reason;
          -	    return;
          -	}
          +        o_rclconfig = recollinit(0, 0, m_reason);
          +        if (!o_rclconfig || !o_rclconfig->ok()) {
          +            m_reason = string("Configuration problem: ") + m_reason;
          +            return;
          +        }
               }
               if (o_rclconfig->getDbDir().empty()) {
          -	// Note: this will have to be replaced by a call to a
          -	// configuration building dialog for initial configuration? Or
          -	// do we assume that the QT GUO is always used for this ?
          -	m_reason = "No db directory in configuration ??";
          -	return;
          +        // Note: this will have to be replaced by a call to a
          +        // configuration building dialog for initial configuration? Or
          +        // do we assume that the QT GUO is always used for this ?
          +        m_reason = "No db directory in configuration ??";
          +        return;
               }
               rwSettings(false);
           
               m_rcldb = new Rcl::Db(o_rclconfig);
               if (!m_rcldb) {
          -	m_reason = "Could not build database object. (out of memory ?)";
          -	return;
          +        m_reason = "Could not build database object. (out of memory ?)";
          +        return;
               }
           
               // Decide if we allow switching between html and file manager
          -    // presentation by using an end slash or not. Can also be done dynamically 
          +    // presentation by using an end slash or not. Can also be done dynamically
               // by switching proto names.
               const char *cp = getenv("RECOLL_KIO_ALWAYS_DIR");
               if (cp) {
          -	m_alwaysdir = stringToBool(cp);
          +        m_alwaysdir = stringToBool(cp);
               } else {
          -	o_rclconfig->getConfParam("kio_always_dir", &m_alwaysdir);
          +        o_rclconfig->getConfParam("kio_always_dir", &m_alwaysdir);
               }
           
               cp = getenv("RECOLL_KIO_STEMLANG");
          @@ -102,122 +99,127 @@ RecollProtocol::RecollProtocol(const QByteArray &pool, const QByteArray &app)
           // Doesn't seem needed in the kio context.
           RecollProtocol::~RecollProtocol()
           {
          -    kDebug();
          +    qDebug() << "RecollProtocol::~RecollProtocol()";
               delete m_rcldb;
           }
           
          -bool RecollProtocol::maybeOpenDb(string &reason)
          +bool RecollProtocol::maybeOpenDb(string& reason)
           {
               if (!m_rcldb) {
          -	reason = "Internal error: initialization error";
          -	return false;
          +        reason = "Internal error: initialization error";
          +        return false;
               }
               if (!m_rcldb->isopen() && !m_rcldb->open(Rcl::Db::DbRO)) {
          -	reason = "Could not open database in " + o_rclconfig->getDbDir();
          -	return false;
          +        reason = "Could not open database in " + o_rclconfig->getDbDir();
          +        return false;
               }
               return true;
           }
           
           // This is never called afaik
          -void RecollProtocol::mimetype(const KUrl &url)
          +void RecollProtocol::mimetype(const QUrl& url)
           {
          -    kDebug() << url << endl;
          +    qDebug() << "RecollProtocol::mimetype: url: " << url;
               mimeType("text/html");
               finished();
           }
           
          -UrlIngester::UrlIngester(RecollProtocol *p, const KUrl& url)
          +UrlIngester::UrlIngester(RecollProtocol *p, const QUrl& url)
               : m_parent(p), m_slashend(false), m_alwaysdir(false),
                 m_retType(UIRET_NONE), m_resnum(0), m_type(UIMT_NONE)
           {
          -    kDebug() << "Url" << url;
          -    m_alwaysdir = !url.protocol().compare("recollf");
          +    qDebug() << "UrlIngester::UrlIngester: Url: " << url;
          +    m_alwaysdir = !url.scheme().compare("recollf");
               QString path = url.path();
               if (url.host().isEmpty()) {
          -	if (path.isEmpty() || !path.compare("/")) {
          -	    m_type = UIMT_ROOTENTRY;
          -	    m_retType = UIRET_ROOT;
          -	    return;
          -	} else if (!path.compare("/help.html")) {
          -	    m_type = UIMT_ROOTENTRY;
          -	    m_retType = UIRET_HELP;
          -	    return;
          -	} else if (!path.compare("/search.html")) {
          -	    m_type = UIMT_ROOTENTRY;
          -	    m_retType = UIRET_SEARCH;
          -	    // Retrieve the query value for preloading the form
          -	    m_query.query = url.queryItem("q");
          -	    return;
          -	} else if (m_parent->isRecollResult(url, &m_resnum, &m_query.query)) {
          -	    m_type = UIMT_QUERYRESULT;
          -	    m_query.opt = "l";
          -	    m_query.page = 0;
          -	} else {
          -	    // Have to think this is some search string
          -	    m_type = UIMT_QUERY;
          -	    m_query.query = url.path();
          -	    m_query.opt = "l";
          -	    m_query.page = 0;
          -	}
          +        if (path.isEmpty() || !path.compare("/")) {
          +            m_type = UIMT_ROOTENTRY;
          +            m_retType = UIRET_ROOT;
          +            return;
          +        } else if (!path.compare("/help.html")) {
          +            m_type = UIMT_ROOTENTRY;
          +            m_retType = UIRET_HELP;
          +            return;
          +        } else if (!path.compare("/search.html")) {
          +            m_type = UIMT_ROOTENTRY;
          +            m_retType = UIRET_SEARCH;
          +            QUrlQuery q(url);
          +            // Retrieve the query value for preloading the form
          +            m_query.query = q.queryItemValue("q");
          +            return;
          +        } else if (m_parent->isRecollResult(url, &m_resnum, &m_query.query)) {
          +            m_type = UIMT_QUERYRESULT;
          +            m_query.opt = "l";
          +            m_query.page = 0;
          +        } else {
          +            // Have to think this is some search string
          +            m_type = UIMT_QUERY;
          +            m_query.query = url.path();
          +            m_query.opt = "l";
          +            m_query.page = 0;
          +        }
               } else {
          -	// Non empty host, url must be something like :
          -	//      //search/query?q=query¶m=value...
          -	kDebug() << "host" << url.host() << "path" << url.path();
          -	if (url.host().compare("search") || url.path().compare("/query")) {
          -	    return;
          -	}
          -	m_type = UIMT_QUERY;
          -	// Decode the forms' arguments
          -	m_query.query = url.queryItem("q");
          +        // Non empty host, url must be something like :
          +        //      //search/query?q=query¶m=value...
          +        qDebug() << "UrlIngester::UrlIngester: host " <<
          +                 url.host() << " path " << url.path();
          +        if (url.host().compare("search") || url.path().compare("/query")) {
          +            return;
          +        }
          +        m_type = UIMT_QUERY;
          +        // Decode the forms' arguments
          +        // Retrieve the query value for preloading the form
          +        QUrlQuery q(url);
          +        m_query.query = q.queryItemValue("q");
           
          -	m_query.opt = url.queryItem("qtp");
          -	if (m_query.opt.isEmpty()) {
          -	    m_query.opt = "l";
          -	} 
          -	QString p = url.queryItem("p");
          -	if (p.isEmpty()) {
          -	    m_query.page = 0;
          -	} else {
          -	    sscanf(p.toAscii(), "%d", &m_query.page);
          -	}
          -	p = url.queryItem("det");
          -	m_query.isDetReq = !p.isEmpty();
          -	
          -	p = url.queryItem("cmd");
          -	if (!p.isEmpty() && !p.compare("pv")) {
          -	    p = url.queryItem("dn");
          -	    if (!p.isEmpty()) {
          -		// Preview and no docnum ??
          -		m_resnum = atoi((const char *)p.toUtf8());
          -		// Result in page is 1+
          -		m_resnum--;
          -		m_type = UIMT_PREVIEW;
          -	    }
          -	}
          +        m_query.opt = q.queryItemValue("qtp");
          +        if (m_query.opt.isEmpty()) {
          +            m_query.opt = "l";
          +        }
          +        QString p = q.queryItemValue("p");
          +        if (p.isEmpty()) {
          +            m_query.page = 0;
          +        } else {
          +            sscanf(p.toUtf8(), "%d", &m_query.page);
          +        }
          +        p = q.queryItemValue("det");
          +        m_query.isDetReq = !p.isEmpty();
          +
          +        p = q.queryItemValue("cmd");
          +        if (!p.isEmpty() && !p.compare("pv")) {
          +            p = q.queryItemValue("dn");
          +            if (!p.isEmpty()) {
          +                // Preview and no docnum ??
          +                m_resnum = atoi((const char *)p.toUtf8());
          +                // Result in page is 1+
          +                m_resnum--;
          +                m_type = UIMT_PREVIEW;
          +            }
          +        }
          +    }
          +    if (m_query.query.startsWith("/")) {
          +        m_query.query.remove(0, 1);
               }
          -    if (m_query.query.startsWith("/"))
          -	m_query.query.remove(0,1);
               if (m_query.query.endsWith("/")) {
          -	kDebug() << "Ends with /";
          -	m_slashend = true;
          -	m_query.query.chop(1);
          +        qDebug() << "UrlIngester::UrlIngester: query Ends with /";
          +        m_slashend = true;
          +        m_query.query.chop(1);
               } else {
          -	m_slashend = false;
          +        m_slashend = false;
               }
               return;
           }
           
          -bool RecollProtocol::syncSearch(const QueryDesc &qd)
          +bool RecollProtocol::syncSearch(const QueryDesc& qd)
           {
          -    kDebug();
          +    qDebug() << "RecollProtocol::syncSearch";
               if (!m_initok || !maybeOpenDb(m_reason)) {
          -	string reason = "RecollProtocol::listDir: Init error:" + m_reason;
          -	error(KIO::ERR_SLAVE_DEFINED, reason.c_str());
          -	return false;
          +        string reason = "RecollProtocol::listDir: Init error:" + m_reason;
          +        error(KIO::ERR_SLAVE_DEFINED, u8s2qs(reason));
          +        return false;
               }
               if (qd.sameQuery(m_query)) {
          -	return true;
          +        return true;
               }
               // doSearch() calls error() if appropriate.
               return doSearch(qd);
          @@ -226,14 +228,14 @@ bool RecollProtocol::syncSearch(const QueryDesc &qd)
           // This is used by the html interface, but also by the directory one
           // when doing file copies for exemple. This is the central dispatcher
           // for requests, it has to know a little about both models.
          -void RecollProtocol::get(const KUrl& url)
          +void RecollProtocol::get(const QUrl& url)
           {
          -    kDebug() << url << endl;
          +    qDebug() << "RecollProtocol::get: " << url;
           
               if (!m_initok || !maybeOpenDb(m_reason)) {
          -	string reason = "Recoll: init error: " + m_reason;
          -	error(KIO::ERR_SLAVE_DEFINED, reason.c_str());
          -	return;
          +        string reason = "Recoll: init error: " + m_reason;
          +        error(KIO::ERR_SLAVE_DEFINED, u8s2qs(reason));
          +        return;
               }
           
               UrlIngester ingest(this, url);
          @@ -241,141 +243,134 @@ void RecollProtocol::get(const KUrl& url)
               QueryDesc qd;
               int resnum;
               if (ingest.isRootEntry(&rettp)) {
          -	switch(rettp) {
          -	case UrlIngester::UIRET_HELP: 
          -	    {
          -		QString location = 
          -		    KStandardDirs::locate("data", "kio_recoll/help.html");
          -		redirection(location);
          -	    }
          -	    goto out;
          -	default:
          -	    searchPage();
          -	    goto out;
          -	}
          +        switch (rettp) {
          +        case UrlIngester::UIRET_HELP: {
          +            QString location =
          +                QStandardPaths::locate(QStandardPaths::GenericDataLocation,
          +                                       "kio_recoll/help.html");
          +            redirection(QUrl::fromLocalFile(location));
          +        }
          +        goto out;
          +        default:
          +            searchPage();
          +            goto out;
          +        }
               } else if (ingest.isResult(&qd, &resnum)) {
          -	// Url matched one generated by konqueror/Dolphin out of a
          -	// search directory listing: ie: 
          +        // Url matched one generated by konqueror/Dolphin out of a
          +        // search directory listing: ie:
                   // recoll:/some search string/recollResultxx
          -	//
          -	// This happens when the user drags/drop the result to another
          -	// app, or with the "open-with" right-click. Does not happen
          -	// if the entry itself is clicked (the UDS_URL is apparently
          -	// used in this case
          -	//
          -	// Redirect to the result document URL
          -	if (!syncSearch(qd)) {
          -	    return;
          -	}
          -	Rcl::Doc doc;
          -	if (resnum >= 0 && !m_source.isNull() && m_source->getDoc(resnum, doc)) {
          -	    mimeType(doc.mimetype.c_str());
          -	    redirection(KUrl::fromLocalFile((const char *)(doc.url.c_str()+7)));
          -	    goto out;
          -	}
          +        //
          +        // This happens when the user drags/drop the result to another
          +        // app, or with the "open-with" right-click. Does not happen
          +        // if the entry itself is clicked (the UDS_URL is apparently
          +        // used in this case
          +        //
          +        // Redirect to the result document URL
          +        if (!syncSearch(qd)) {
          +            return;
          +        }
          +        Rcl::Doc doc;
          +        if (resnum >= 0 && m_source && m_source->getDoc(resnum, doc)) {
          +            mimeType(doc.mimetype.c_str());
          +            redirection(QUrl::fromLocalFile((const char *)(doc.url.c_str() + 7)));
          +            goto out;
          +        }
               } else if (ingest.isPreview(&qd, &resnum)) {
          -	if (!syncSearch(qd)) {
          -	    return;
          -	}
          -	Rcl::Doc doc;
          -	if (resnum >= 0 && !m_source.isNull() && m_source->getDoc(resnum, doc)) {
          -	    showPreview(doc);
          -	    goto out;
          -	}
          +        if (!syncSearch(qd)) {
          +            return;
          +        }
          +        Rcl::Doc doc;
          +        if (resnum >= 0 && m_source && m_source->getDoc(resnum, doc)) {
          +            showPreview(doc);
          +            goto out;
          +        }
               } else if (ingest.isQuery(&qd)) {
          -#if 0 
          +#if 0
           // Do we need this ?
          -	if (host.isEmpty()) {
          -	    char cpage[20];sprintf(cpage, "%d", page);
          -	    QString nurl = QString::fromAscii("recoll://search/query?q=") +
          -		query + "&qtp=" + opt + "&p=" + cpage;
          -	    redirection(KUrl(nurl));
          -	    goto out;
          -	}
          +        if (host.isEmpty()) {
          +            char cpage[20];
          +            sprintf(cpage, "%d", page);
          +            QString nurl = QString::fromAscii("recoll://search/query?q=") +
          +                           query + "&qtp=" + opt + "&p=" + cpage;
          +            redirection(QUrl(nurl));
          +            goto out;
          +        }
           #endif
          -	// htmlDoSearch does the search syncing (needs to know about changes).
          -	htmlDoSearch(qd);
          -	goto out;
          +        // htmlDoSearch does the search syncing (needs to know about changes).
          +        htmlDoSearch(qd);
          +        goto out;
               }
           
          -    error(KIO::ERR_SLAVE_DEFINED, "Unrecognized URL or internal error");
          - out:
          +    error(KIO::ERR_SLAVE_DEFINED, u8s2qs("Unrecognized URL or internal error"));
          +out:
               finished();
           }
           
           // Execute Recoll search, and set the docsource
           bool RecollProtocol::doSearch(const QueryDesc& qd)
           {
          -    kDebug() << "query" << qd.query << "opt" << qd.opt;
          +    qDebug() << "RecollProtocol::doSearch:query" << qd.query << "opt" << qd.opt;
               m_query = qd;
           
               char opt = qd.opt.isEmpty() ? 'l' : qd.opt.toUtf8().at(0);
               string qs = (const char *)qd.query.toUtf8();
               Rcl::SearchData *sd = 0;
               if (opt != 'l') {
          -	Rcl::SearchDataClause *clp = 0;
          -	if (opt == 'f') {
          -	    clp = new Rcl::SearchDataClauseFilename(qs);
          -	} else {
          -            clp = new Rcl::SearchDataClauseSimple(opt == 'o' ? Rcl::SCLT_OR : 
          +        Rcl::SearchDataClause *clp = 0;
          +        if (opt == 'f') {
          +            clp = new Rcl::SearchDataClauseFilename(qs);
          +        } else {
          +            clp = new Rcl::SearchDataClauseSimple(opt == 'o' ? Rcl::SCLT_OR :
                                                             Rcl::SCLT_AND, qs);
          -	}
          -	sd = new Rcl::SearchData(Rcl::SCLT_OR, m_stemlang);
          -	if (sd && clp)
          -	    sd->addClause(clp);
          +        }
          +        sd = new Rcl::SearchData(Rcl::SCLT_OR, m_stemlang);
          +        if (sd && clp) {
          +            sd->addClause(clp);
          +        }
               } else {
          -	sd = wasaStringToRcl(o_rclconfig, m_stemlang, qs, m_reason);
          +        sd = wasaStringToRcl(o_rclconfig, m_stemlang, qs, m_reason);
               }
               if (!sd) {
          -	m_reason = "Internal Error: cant build search";
          -	error(KIO::ERR_SLAVE_DEFINED, m_reason.c_str());
          -	return false;
          +        m_reason = "Internal Error: cant build search";
          +        error(KIO::ERR_SLAVE_DEFINED, u8s2qs(m_reason));
          +        return false;
               }
           
          -    RefCntr sdata(sd);
          -    RefCntrquery(new Rcl::Query(m_rcldb));
          +    std::shared_ptr sdata(sd);
          +    std::shared_ptrquery(new Rcl::Query(m_rcldb));
               query->setCollapseDuplicates(prefs.collapseDuplicates);
               if (!query->setQuery(sdata)) {
          -	m_reason = "Query execute failed. Invalid query or syntax error?";
          -	error(KIO::ERR_SLAVE_DEFINED, m_reason.c_str());
          -	return false;
          +        m_reason = "Query execute failed. Invalid query or syntax error?";
          +        error(KIO::ERR_SLAVE_DEFINED, u8s2qs(m_reason));
          +        return false;
               }
           
          -    DocSequenceDb *src = 
          -	new DocSequenceDb(RefCntr(query), "Query results", sdata);
          +    DocSequenceDb *src =
          +        new DocSequenceDb(std::shared_ptr(query), "Query results", sdata);
               if (src == 0) {
          -	error(KIO::ERR_SLAVE_DEFINED, "Can't build result sequence");
          -	return false;
          +        error(KIO::ERR_SLAVE_DEFINED, u8s2qs("Can't build result sequence"));
          +        return false;
               }
          -    m_source = RefCntr(src);
          +    m_source = std::shared_ptr(src);
               // Reset pager in all cases. Costs nothing, stays at page -1 initially
               // htmldosearch will fetch the first page if needed.
               m_pager.setDocSource(m_source);
               return true;
           }
           
          -// Note: KDE_EXPORT is actually needed on Unix when building with
          -// cmake. Says something like __attribute__(visibility(defautl))
          -// (cmake apparently sets all symbols to not exported)
          -extern "C" {KDE_EXPORT int kdemain(int argc, char **argv);}
          -
           int kdemain(int argc, char **argv)
           {
          -#ifdef KDE_VERSION_3
          -    KInstance instance("kio_recoll");
          -#else
          -    KComponentData instance("kio_recoll");
          -#endif
          -    kDebug() << "*** starting kio_recoll " << endl;
          +    QCoreApplication::setApplicationName("kio_recoll");
          +    qDebug() << "*** starting kio_recoll ";
           
               if (argc != 4)  {
          -	kDebug() << "Usage: kio_recoll proto dom-socket1 dom-socket2\n" << endl;
          -	exit(-1);
          +        qDebug() << "Usage: kio_recoll proto dom-socket1 dom-socket2\n";
          +        exit(-1);
               }
           
               RecollProtocol slave(argv[2], argv[3]);
               slave.dispatchLoop();
           
          -    kDebug() << "kio_recoll Done" << endl;
          +    qDebug() << "kio_recoll Done";
               return 0;
           }
          diff --git a/src/kde/kioslave/kio_recoll/kio_recoll.h b/src/kde/kioslave/kio_recoll/kio_recoll.h
          index 96be7c87..6529f91e 100644
          --- a/src/kde/kioslave/kio_recoll/kio_recoll.h
          +++ b/src/kde/kioslave/kio_recoll/kio_recoll.h
          @@ -18,21 +18,17 @@
            */
           
           #include 
          -using std::string;
           
          -#include 
          -#include 
          +#include 
          +#include 
           
          -#include 
          -#include 
           #include 
          -#include 
           
           #include "rclconfig.h"
           #include "rcldb.h"
          -#include "reslistpager.h"
           #include "docseq.h"
          -#include "refcntr.h"
          +#include "reslistpager.h"
          +#include 
           
           class RecollProtocol;
           
          @@ -40,16 +36,19 @@ class RecollProtocol;
           class RecollKioPager : public ResListPager {
           public:
               RecollKioPager() : m_parent(0) {}
          -    void setParent(RecollProtocol *proto) {m_parent = proto;}
          +    void setParent(RecollProtocol *proto) {
          +        m_parent = proto;
          +    }
           
          -    virtual bool append(const string& data);
          -    virtual bool append(const string& data, int, const Rcl::Doc&)
          -    {return append(data);}
          -    virtual string detailsLink();
          -    virtual const string &parFormat();
          -    virtual string nextUrl();
          -    virtual string prevUrl();
          -    virtual string pageTop();
          +    virtual bool append(const std::string& data);
          +    virtual bool append(const std::string& data, int, const Rcl::Doc&) {
          +        return append(data);
          +    }
          +    virtual std::string detailsLink();
          +    virtual const std::string& parFormat();
          +    virtual std::string nextUrl();
          +    virtual std::string prevUrl();
          +    virtual std::string pageTop();
           
           private:
               RecollProtocol *m_parent;
          @@ -63,7 +62,7 @@ public:
               int page;
               bool isDetReq;
               bool sameQuery(const QueryDesc& o) const {
          -	return !opt.compare(o.opt) && !query.compare(o.query);
          +        return !opt.compare(o.opt) && !query.compare(o.query);
               }
           };
           
          @@ -71,32 +70,44 @@ public:
           // and tell what we should do with it
           class UrlIngester {
           public:
          -    UrlIngester(RecollProtocol *p, const KUrl& url);
          +    UrlIngester(RecollProtocol *p, const QUrl& url);
               enum RootEntryType {UIRET_NONE, UIRET_ROOT, UIRET_HELP, UIRET_SEARCH};
               bool isRootEntry(RootEntryType *tp) {
          -	if (m_type != UIMT_ROOTENTRY) return false;
          -	*tp = m_retType;
          -	return true;
          +        if (m_type != UIMT_ROOTENTRY) {
          +            return false;
          +        }
          +        *tp = m_retType;
          +        return true;
               }
               bool isQuery(QueryDesc *q) {
          -	if (m_type != UIMT_QUERY) return false;
          -	*q = m_query;
          -	return true;
          +        if (m_type != UIMT_QUERY) {
          +            return false;
          +        }
          +        *q = m_query;
          +        return true;
               }
               bool isResult(QueryDesc *q, int *num) {
          -	if (m_type != UIMT_QUERYRESULT) return false;
          -	*q = m_query;
          -	*num = m_resnum;
          -	return true;
          +        if (m_type != UIMT_QUERYRESULT) {
          +            return false;
          +        }
          +        *q = m_query;
          +        *num = m_resnum;
          +        return true;
               }
               bool isPreview(QueryDesc *q, int *num) {
          -	if (m_type != UIMT_PREVIEW) return false;
          -	*q = m_query;
          -	*num = m_resnum;
          -	return true;
          +        if (m_type != UIMT_PREVIEW) {
          +            return false;
          +        }
          +        *q = m_query;
          +        *num = m_resnum;
          +        return true;
          +    }
          +    bool endSlashQuery() {
          +        return m_slashend;
          +    }
          +    bool alwaysDir() {
          +        return m_alwaysdir;
               }
          -    bool endSlashQuery() {return m_slashend;}
          -    bool alwaysDir() {return m_alwaysdir;}
           
           private:
               RecollProtocol *m_parent;
          @@ -106,18 +117,18 @@ private:
               RootEntryType   m_retType;
               int             m_resnum;
               enum MyType {UIMT_NONE, UIMT_ROOTENTRY, UIMT_QUERY, UIMT_QUERYRESULT,
          -		 UIMT_PREVIEW};
          +                 UIMT_PREVIEW
          +                };
               MyType           m_type;
           };
           
          -
           /**
            * A KIO slave to execute and display Recoll searches.
            *
            * Things are made a little complicated because KIO slaves can't hope
            * that their internal state will remain consistent with their user
            * application state: slaves die, are restarted, reused, at random
          - * between requests. 
          + * between requests.
            * In our case, this means that any request has to be processed
            * without reference to the last operation performed. Ie, if the
            * search parameters are not those from the last request, the search
          @@ -134,44 +145,43 @@ private:
            * functionality, and one based on a directory listing model, which
            * will always be more limited, but may be useful in some cases to
            * allow easy copying of files etc. Which one is in use is decided by
          - * the form of the URL. 
          + * the form of the URL.
            */
           class RecollProtocol : public KIO::SlaveBase {
          - public:
          -    RecollProtocol(const QByteArray &pool, const QByteArray &app );
          +public:
          +    RecollProtocol(const QByteArray& pool, const QByteArray& app);
               virtual ~RecollProtocol();
          -    virtual void mimetype(const KUrl& url);
          -    virtual void get(const KUrl& url);
          +    virtual void mimetype(const QUrl& url);
          +    virtual void get(const QUrl& url);
               // The directory mode is not available with KDE 4.0, I could find
               // no way to avoid crashing kdirmodel
          -#if KDE_IS_VERSION(4,1,0)
          -    virtual void stat(const KUrl & url);
          -    virtual void listDir(const KUrl& url);
          -#endif
          +    virtual void stat(const QUrl& url);
          +    virtual void listDir(const QUrl& url);
           
               static RclConfig  *o_rclconfig;
           
               friend class RecollKioPager;
               friend class UrlIngester;
           
          - private:
          -    bool maybeOpenDb(string& reason);
          -    bool URLToQuery(const KUrl &url, QString& q, QString& opt, int *page=0);
          +private:
          +    bool maybeOpenDb(std::string& reason);
          +    bool URLToQuery(const QUrl& url, QString& q, QString& opt, int *page = 0);
               bool doSearch(const QueryDesc& qd);
           
               void searchPage();
               void queryDetails();
          -    string makeQueryUrl(int page, bool isdet = false);
          +    std::string makeQueryUrl(int page, bool isdet = false);
               bool syncSearch(const QueryDesc& qd);
               void htmlDoSearch(const QueryDesc& qd);
               void showPreview(const Rcl::Doc& doc);
          -    bool isRecollResult(const KUrl &url, int *num, QString* q);
          +    bool isRecollResult(const QUrl& url, int *num, QString* q);
           
               bool        m_initok;
               Rcl::Db    *m_rcldb;
          -    string      m_reason;
          +    std::string      m_reason;
               bool        m_alwaysdir;
          -    string      m_stemlang; // english by default else env[RECOLL_KIO_STEMLANG]
          +    // english by default else env[RECOLL_KIO_STEMLANG]
          +    std::string      m_stemlang;
           
               // Search state: because of how the KIO slaves are used / reused,
               // we can't be sure that the next request will be for the same
          @@ -181,11 +191,20 @@ class RecollProtocol : public KIO::SlaveBase {
               // (one slave several konqueror windows) would be to have a small
               // cache of recent searches kept open.
               RecollKioPager m_pager;
          -    RefCntr m_source;
          +    std::shared_ptr m_source;
               // Note: page here is not used, current page always comes from m_pager.
               QueryDesc      m_query;
           };
           
          -extern "C" {int kdemain(int, char**);}
          +extern "C" {
          +    __attribute__((visibility("default"))) int
          +    kdemain(int argc, char **argv);
          +}
          +
          +inline QString u8s2qs(const string& s)
          +{
          +    return QString::fromUtf8(s.c_str());
          +}
          +
           
           #endif // _RECOLL_H
          diff --git a/src/kde/kioslave/kio_recoll/recoll.protocol b/src/kde/kioslave/kio_recoll/recoll.protocol
          index c69fc77d..75f444fa 100644
          --- a/src/kde/kioslave/kio_recoll/recoll.protocol
          +++ b/src/kde/kioslave/kio_recoll/recoll.protocol
          @@ -1,11 +1,11 @@
           [Protocol]
           exec=kio_recoll
           protocol=recoll
          +Icon=recoll
           input=none
           output=filesystem
           listing=Name,Type, URL
           reading=true
          -defaultMimeType=text/html
          -Icon=recoll
           Class=:local
           URIMode=rawuri
          +defaultMimeType=text/html
          diff --git a/src/lib/mkMake.in b/src/lib/mkMake.in
          deleted file mode 100755
          index 25479a36..00000000
          --- a/src/lib/mkMake.in
          +++ /dev/null
          @@ -1,198 +0,0 @@
          -#!/bin/sh
          -
          -mk=Makefile
          -depth=${depth:-..}
          -sys=`uname`
          -
          -SRC_CPP="\
          -${depth}/aspell/rclaspell.cpp \
          -${depth}/common/beaglequeuecache.cpp \
          -${depth}/common/cstr.cpp \
          -${depth}/common/rclconfig.cpp \
          -${depth}/common/rclinit.cpp \
          -${depth}/common/textsplit.cpp \
          -${depth}/common/unacpp.cpp \
          -${depth}/index/beaglequeue.cpp \
          -${depth}/index/bglfetcher.cpp \
          -${depth}/index/checkretryfailed.cpp \
          -${depth}/index/fetcher.cpp \
          -${depth}/index/fsfetcher.cpp \
          -${depth}/index/fsindexer.cpp \
          -${depth}/index/indexer.cpp \
          -${depth}/index/mimetype.cpp \
          -${depth}/index/subtreelist.cpp \
          -${depth}/internfile/htmlparse.cpp \
          -${depth}/internfile/internfile.cpp \
          -${depth}/internfile/mh_exec.cpp \
          -${depth}/internfile/mh_execm.cpp \
          -${depth}/internfile/mh_html.cpp \
          -${depth}/internfile/mh_mail.cpp \
          -${depth}/internfile/mh_mbox.cpp \
          -${depth}/internfile/mh_text.cpp \
          -${depth}/internfile/mimehandler.cpp \
          -${depth}/internfile/myhtmlparse.cpp \
          -${depth}/internfile/extrameta.cpp \
          -${depth}/internfile/txtdcode.cpp \
          -${depth}/internfile/uncomp.cpp \
          -${depth}/query/docseq.cpp \
          -${depth}/query/docseqdb.cpp \
          -${depth}/query/docseqhist.cpp \
          -${depth}/query/filtseq.cpp \
          -${depth}/query/dynconf.cpp \
          -${depth}/query/plaintorich.cpp \
          -${depth}/query/recollq.cpp \
          -${depth}/query/reslistpager.cpp \
          -${depth}/query/sortseq.cpp \
          -${depth}/query/wasaparse.cpp \
          -${depth}/query/wasaparse.tab.cpp \
          -${depth}/rcldb/daterange.cpp \
          -${depth}/rcldb/expansiondbs.cpp \
          -${depth}/rcldb/rclabstract.cpp \
          -${depth}/rcldb/rcldb.cpp \
          -${depth}/rcldb/rcldoc.cpp \
          -${depth}/rcldb/rcldups.cpp \
          -${depth}/rcldb/rclquery.cpp \
          -${depth}/rcldb/rclterms.cpp \
          -${depth}/rcldb/searchdata.cpp \
          -${depth}/rcldb/searchdatatox.cpp \
          -${depth}/rcldb/searchdataxml.cpp \
          -${depth}/rcldb/stemdb.cpp \
          -${depth}/rcldb/stoplist.cpp \
          -${depth}/rcldb/synfamily.cpp \
          -${depth}/unac/unac.cpp \
          -${depth}/utils/appformime.cpp \
          -${depth}/utils/base64.cpp \
          -${depth}/utils/circache.cpp \
          -${depth}/utils/closefrom.cpp \
          -${depth}/utils/conftree.cpp \
          -${depth}/utils/copyfile.cpp \
          -${depth}/utils/cpuconf.cpp \
          -${depth}/utils/debuglog.cpp \
          -${depth}/utils/ecrontab.cpp \
          -${depth}/utils/execmd.cpp \
          -${depth}/utils/fstreewalk.cpp \
          -${depth}/utils/idfile.cpp \
          -${depth}/utils/fileudi.cpp \
          -${depth}/utils/md5.cpp \
          -${depth}/utils/md5ut.cpp \
          -${depth}/utils/mimeparse.cpp \
          -${depth}/utils/netcon.cpp \
          -${depth}/utils/pathut.cpp \
          -${depth}/utils/pxattr.cpp \
          -${depth}/utils/rclionice.cpp \
          -${depth}/utils/readfile.cpp \
          -${depth}/utils/smallut.cpp \
          -${depth}/utils/strmatcher.cpp \
          -${depth}/utils/transcode.cpp \
          -${depth}/utils/wipedir.cpp \
          -${depth}/utils/x11mon.cpp \
          -"
          -
          -SRC_CC="\
          -${depth}/bincimapmime/mime-parsefull.cc \
          -${depth}/bincimapmime/mime-parseonlyheader.cc \
          -${depth}/bincimapmime/mime-printbody.cc \
          -${depth}/bincimapmime/mime.cc \
          -${depth}/bincimapmime/convert.cc \
          -${depth}/bincimapmime/iodevice.cc \
          -${depth}/bincimapmime/iofactory.cc \
          -"
          -
          -for c in $SRC_CPP;do
          -    o=`basename $c .cpp`.o
          -    OBJS="$OBJS $o"
          -    d=`basename $c .cpp`.dep
          -    cp /dev/null $d
          -    s=`basename $c .cpp`.dep.stamp
          -    DEPS="$DEPS $s"
          -done
          -for c in $SRC_CC;do
          -    o=`basename $c .cc`.o
          -    OBJS="$OBJS $o"
          -    d=`basename $c .cc`.dep
          -    cp /dev/null $d
          -    s=`basename $c .cc`.dep.stamp
          -    DEPS="$DEPS $s"
          -done
          -
          -# The objects need to depend on the localdefs file in case it is
          -# changed by a re-configure (it has the local compile flags)
          -defs=\$\(depth\)/mk/localdefs
          -
          -test -f $mk && chmod +w $mk
          -
          -NODYNLIB=@NODYNLIB@
          -RCLLIBVERSION=@RCLLIBVERSION@
          -  
          -cat > $mk <> $mk
          -    echo "	\$(CXX) \$(ALL_CXXFLAGS) -c $c" >> $mk
          -done
          -for c in $SRC_CC;do
          -    o=`basename $c .cc`.o
          -    echo "$o : $c $defs" >> $mk
          -    echo "	\$(CXX) \$(ALL_CXXFLAGS) -c $c" >> $mk
          -done
          -
          -cat >> $mk <> $mk
          -    echo "	\$(CXX) -M \$(ALL_CXXFLAGS) $c > $d" >> $mk
          -    echo "	touch $s" >> $mk
          -done
          -for c in $SRC_CPP;do
          -    d=`basename $c .cpp`.dep
          -    echo "include $d" >> $mk
          -done
          -for c in $SRC_CC;do
          -    d=`basename $c .cc`.dep
          -    s=`basename $c .cc`.dep.stamp
          -    echo "$s : $c $defs" >> $mk
          -    echo "	\$(CXX) -M \$(ALL_CXXFLAGS) $c > $d" >> $mk
          -    echo "	touch $s" >> $mk
          -done
          -for c in $SRC_CC;do
          -    d=`basename $c .cc`.dep
          -    echo "include $d" >> $mk
          -done
          diff --git a/src/makesrcdist.sh b/src/makesrcdist.sh
          deleted file mode 100644
          index 8a2c7e66..00000000
          --- a/src/makesrcdist.sh
          +++ /dev/null
          @@ -1,171 +0,0 @@
          -#!/bin/sh
          -# Copyright (C) 2005 J.F.Dockes
          -# A shell-script to make a recoll source distribution
          -
          -fatal() {
          -    echo $*
          -    exit 1
          -}
          -usage() {
          -    echo 'Usage: makescrdist.sh -t -s do_it'
          -    echo ' -t : no tagging'
          -    echo ' -s : snapshot release: use date instead of VERSION'
          -    echo ' -s implies -t'
          -    exit 1
          -}
          -tagtopsvn() {
          -    (cd ..; svn copy -m "Release $version tagged" . $SVNREPOS/tags/$1) \
          -    	|| fatal tag failed
          -}
          -tagtophg() {
          -    hg tag -m "Release $version tagged" $1 || fatal tag failed
          -}
          -tagexists() {
          -    hg tags | cut -d' ' -f 1 | egrep '^'$1'$'
          -}
          -tagtop() {
          -    tagtophg $*
          -}
          -
          -#set -x
          -
          -TAR=${TAR-/bin/tar}
          -
          -#VCCMD=svn
          -#SVNREPOS=svn+ssh://y/home/subversion/recoll/
          -
          -VCCMD=hg
          -
          -if test ! -d qtgui;then
          -    echo "Should be executed in the master recoll directory"
          -    exit 1
          -fi
          -targetdir=${targetdir-/tmp}
          -dotag=${dotag-yes}
          -snap=${snap-no}
          -
          -if ! test configure -nt configure.ac -a configure -nt VERSION ;then
          -    set -x
          -    rm -f configure
          -    autoconf || exit 1
          -    set +x
          -fi
          -
          -while getopts ts o
          -do	case "$o" in
          -	t)	dotag=no;;
          -	s)	snap=yes;dotag=no;;
          -	[?])	usage;;
          -	esac
          -done
          -shift `expr $OPTIND - 1`
          -
          -test $dotag = "yes" -a $snap = "yes" && usage
          -
          -test $# -eq 1 || usage
          -
          -echo dotag $dotag snap $snap
          -
          -if test $snap = yes ; then
          -# The id would be nicer but it's not increasing which is a problem for
          -# the exp package repo
          -#  version=`hg id | awk '{print $1}'`
          -   version=`hg summary | head -1 | awk -F: '{print $2}' | sed -e 's/ //g'`
          -  versionforcvs=$version
          -  TAG=""
          -else
          -  version=`cat VERSION`
          -  versionforcvs=`echo $version | sed -e 's/\./_/g'`
          -  TAG="RECOLL_$versionforcvs"
          -fi
          -
          -if test "$dotag" = "yes" ; then
          -  echo Creating AND TAGGING version $versionforcvs
          -  tagexists $TAG  && fatal "Tag $TAG already exists"
          -else
          -  echo Creating version $versionforcvs, no tagging
          -fi
          -sleep 2
          -
          -editedfiles=`$VCCMD status . | egrep -v '^\?'`
          -if test "$dotag" = "yes" -a ! -z "$editedfiles"; then
          -  fatal  "Edited files exist: " $editedfiles
          -fi
          -
          -
          -case $version in
          -*.*.*) releasename=recoll-$version;;
          -*) releasename=betarecoll-$version;;
          -esac
          -
          -topdir=$targetdir/$releasename
          -if test ! -d $topdir ; then
          -    mkdir $topdir || exit 1
          -else 
          -    echo "Removing everything under $topdir Ok ? (y/n)"
          -    read rep 
          -    if test $rep = 'y';then
          -    	rm -rf $topdir/*
          -    fi
          -fi
          -
          -################################### Documentation
          -###### Html doc
          -RECOLLDOC=${RECOLLDOC:=doc/user}
          -(cd $RECOLLDOC; make) || exit 1
          -
          -###### Text Doc
          -chmod +w README INSTALL
          -cat < README
          -
          -More documentation can be found in the doc/ directory or at http://www.recoll.org
          -
          -
          -EOF
          -cat < INSTALL
          -
          -More documentation can be found in the doc/ directory or at http://www.recoll.org
          -
          -
          -EOF
          -
          -echo "Dumping html documentation to text files"
          -links -dump ${RECOLLDOC}/usermanual.html >> README
          -
          -links -dump ${RECOLLDOC}/RCL.INSTALL.html >> INSTALL
          -links -dump ${RECOLLDOC}/RCL.INSTALL.EXTERNAL.html >> INSTALL
          -links -dump ${RECOLLDOC}/RCL.INSTALL.BUILDING.html >> INSTALL
          -links -dump ${RECOLLDOC}/RCL.INSTALL.CONFIG.html >> INSTALL
          -
          -$VCCMD commit -m "release $version" README INSTALL
          -
          -# Clean up this dir and copy the dist-specific files 
          -make distclean
          -yes | clean.O
          -rm -f lib/*.dep
          -# Possibly clean up the cmake stuff
          -(cd kde/kioslave/kio_recoll/ || exit 1
          -rm -rf CMakeCache.txt CMakeFiles/ CMakeTmp/ CPack* CTestTestfile.cmake cmake_* *automoc* lib builddir)
          -
          -$TAR chfX - excludefile .  | (cd $topdir;$TAR xf -)
          -if test $snap = "yes" ; then
          -    echo $version > $topdir/VERSION
          -fi
          -
          -# Can't now put ./Makefile in excludefile, gets ignored everywhere. So delete
          -# the top Makefile here (its' output by configure on the target system):
          -rm -f $topdir/Makefile
          -
          -out=$releasename.tar.gz
          -(cd $targetdir ; \
          -    $TAR chf - $releasename | \
          -    	gzip > $out)
          -echo "$targetdir/$out created"
          -
          -# Check manifest against current reference
          -export LANG=C
          -tar tzf $targetdir/$out | sort | cut -d / -f 2- | \
          -    diff mk/manifest.txt - || fatal "Please fix file list mk/manifest.txt"
          -
          -# We tag .. as there is the 'packaging/' directory in there
          -[ $dotag = "yes" ] && tagtop $TAG
          diff --git a/src/makestaticdist.sh b/src/makestaticdist.sh
          deleted file mode 100644
          index d268b8da..00000000
          --- a/src/makestaticdist.sh
          +++ /dev/null
          @@ -1,98 +0,0 @@
          -#!/bin/sh
          -#set -x
          -# A shell-script to make a recoll static binary distribution:
          -
          -fatal()
          -{
          -    echo $*;exit 1
          -}
          -
          -TAR=tar
          - 
          -targetdir=${targetdir-/tmp}
          -
          -if test ! -d qtgui;then
          -    echo "Should be executed in the master recoll directory"
          -    exit 1
          -fi
          -
          -version=`cat VERSION`
          -sys=`uname -s`
          -sysrel=`uname -r`
          -
          -qtguiassign=`egrep '^QTGUI=' recollinstall`
          -stripassign=`egrep '^STRIP=' recollinstall`
          -test ! -z "$qtguiassign" || fatal "Can't find qt version"
          -test ! -z "$stripassign" || fatal "Can't find strip string"
          -eval $qtguiassign
          -eval $stripassign
          -echo "QTGUI: " $QTGUI "STRIP: " $STRIP
          -
          -topdirsimple=recoll-${version}-${sys}-${sysrel}
          -topdir=$targetdir/$topdirsimple
          -
          -tarfile=$targetdir/recoll-${version}-${sys}-${sysrel}.tgz
          -
          -if test ! -d $topdir ; then
          -    mkdir $topdir || exit 1
          -else 
          -    echo "Removing everything under $topdir Ok ? (y/n)"
          -    read rep 
          -    if test $rep = 'y';then
          -    	rm -rf $topdir/*
          -    else
          -	exit 1
          -    fi
          -fi
          -
          -rm -f index/recollindex ${QTGUI}/recoll
          -
          -make static || exit 1
          -
          -${STRIP} index/recollindex ${QTGUI}/recoll
          -
          -files="
          -COPYING 
          -INSTALL 
          -Makefile 
          -README 
          -VERSION 
          -desktop 
          -desktop/recoll-searchgui.desktop
          -desktop/recoll.png 
          -doc/man
          -doc/user 
          -filters 
          -index/rclmon.sh 
          -index/recollindex 
          -qtgui/i18n/*.qm 
          -qtgui/images
          -qtgui/mtpics/*.png 
          -qtgui/recoll 
          -recollinstall
          -sampleconf 
          -"
          -
          -$TAR chf - $files  | (cd $topdir; $TAR xf -)
          -
          -# Remove any install dependancy
          -chmod +w $topdir/Makefile || exit 1
          -sed -e '/^install:/c\
          -install: ' < $topdir/Makefile > $topdir/toto && \
          -	 mv $topdir/toto $topdir/Makefile
          -
          -# Clean up .svn directories from target. This would be easier with a
          -# --exclude tar option, but we want this to work with non-gnu tars
          -cd $topdir || exit 1
          -svndirs=`find . -name .svn -print`
          -echo "In: `pwd`. Removing $svndirs ok ?"
          -read rep 
          -test "$rep" = 'y' -o "$rep" = 'Y' && rm -rf $svndirs
          -
          -cd $targetdir
          -
          -(cd $targetdir ; \
          -    $TAR chf - $topdirsimple | \
          -    	gzip > $tarfile)
          -
          -echo $tarfile created
          diff --git a/src/mk/AIX b/src/mk/AIX
          deleted file mode 100644
          index 2302a01b..00000000
          --- a/src/mk/AIX
          +++ /dev/null
          @@ -1,9 +0,0 @@
          -include $(depth)/mk/commondefs
          -include $(depth)/mk/localdefs
          -
          -ALL_CXXFLAGS = $(CXXFLAGS) $(COMMONCXXFLAGS) $(LOCALCXXFLAGS) 
          -
          -CC = gcc
          -CXX = g++
          -
          -LIBSYS = -lpthread
          diff --git a/src/mk/CYGWIN b/src/mk/CYGWIN
          deleted file mode 100644
          index 0112ae61..00000000
          --- a/src/mk/CYGWIN
          +++ /dev/null
          @@ -1,10 +0,0 @@
          -
          -include $(depth)/mk/commondefs
          -include $(depth)/mk/localdefs
          -
          -ALL_CXXFLAGS = $(CXXFLAGS) $(COMMONCXXFLAGS) $(LOCALCXXFLAGS) \
          -	 -D_GNU_SOURCE 
          -
          -LIBSYS = -lpthread -ldl
          -LIBSYSTHREADS = 
          -
          diff --git a/src/mk/Darwin b/src/mk/Darwin
          deleted file mode 100644
          index a1fc31dd..00000000
          --- a/src/mk/Darwin
          +++ /dev/null
          @@ -1,9 +0,0 @@
          -include $(depth)/mk/commondefs
          -include $(depth)/mk/localdefs
          -
          -ALL_CXXFLAGS = $(CXXFLAGS) $(COMMONCXXFLAGS) $(LOCALCXXFLAGS)
          -
          -LIBRECOLL = -L$(depth)/lib -lrecoll
          -
          -RANLIB = ranlib
          -LIBSYS = 
          diff --git a/src/mk/Default b/src/mk/Default
          deleted file mode 100644
          index aa9a9774..00000000
          --- a/src/mk/Default
          +++ /dev/null
          @@ -1,8 +0,0 @@
          -
          -include $(depth)/mk/commondefs
          -include $(depth)/mk/localdefs
          -
          -ALL_CXXFLAGS = $(CXXFLAGS) $(COMMONCXXFLAGS) $(LOCALCXXFLAGS) \
          -	 -D_GNU_SOURCE 
          -
          -LIBSYS = -lpthread -ldl
          diff --git a/src/mk/FreeBSD b/src/mk/FreeBSD
          deleted file mode 100644
          index e5df10bd..00000000
          --- a/src/mk/FreeBSD
          +++ /dev/null
          @@ -1,5 +0,0 @@
          -include $(depth)/mk/commondefs
          -include $(depth)/mk/localdefs
          -
          -ALL_CXXFLAGS = $(CXXFLAGS) $(COMMONCXXFLAGS) $(LOCALCXXFLAGS) -pthread 
          -LIBSYS = -pthread -lz
          diff --git a/src/mk/Linux b/src/mk/Linux
          deleted file mode 100644
          index 0112ae61..00000000
          --- a/src/mk/Linux
          +++ /dev/null
          @@ -1,10 +0,0 @@
          -
          -include $(depth)/mk/commondefs
          -include $(depth)/mk/localdefs
          -
          -ALL_CXXFLAGS = $(CXXFLAGS) $(COMMONCXXFLAGS) $(LOCALCXXFLAGS) \
          -	 -D_GNU_SOURCE 
          -
          -LIBSYS = -lpthread -ldl
          -LIBSYSTHREADS = 
          -
          diff --git a/src/mk/OpenBSD b/src/mk/OpenBSD
          deleted file mode 100644
          index bcbce000..00000000
          --- a/src/mk/OpenBSD
          +++ /dev/null
          @@ -1,6 +0,0 @@
          -include $(depth)/mk/commondefs
          -include $(depth)/mk/localdefs
          -
          -ALL_CXXFLAGS = $(CXXFLAGS) $(COMMONCXXFLAGS) $(LOCALCXXFLAGS) \
          -	 -pthread  -I/usr/include
          -LIBSYS = 
          diff --git a/src/mk/SunOS b/src/mk/SunOS
          deleted file mode 100644
          index 784c0960..00000000
          --- a/src/mk/SunOS
          +++ /dev/null
          @@ -1,9 +0,0 @@
          -include $(depth)/mk/commondefs
          -include $(depth)/mk/localdefs
          -
          -ALL_CXXFLAGS = $(CXXFLAGS) $(COMMONCXXFLAGS) $(LOCALCXXFLAGS) 
          -
          -CC = gcc
          -CXX = g++
          -
          -LIBSYS = -lpthread -lnsl -lsocket -ldl
          diff --git a/src/mk/commondefs b/src/mk/commondefs
          deleted file mode 100644
          index 4d9ab24d..00000000
          --- a/src/mk/commondefs
          +++ /dev/null
          @@ -1,21 +0,0 @@
          -
          -# Common/default for all systems, can be overridden by sys-specific include
          -
          -COMMONCXXFLAGS = -I. \
          -	       -I$(depth)/aspell \
          -	       -I$(depth)/bincimapmime \
          -	       -I$(depth)/common \
          -	       -I$(depth)/index \
          -	       -I$(depth)/internfile \
          -	       -I$(depth)/rcldb \
          -	       -I$(depth)/unac \
          -	       -I$(depth)/utils 
          -
          -# We happen to be using gcc on all platforms for now. Can be overridden in
          -# the sys file or localdefs
          -SYSPICFLAGS = -fPIC -DPIC
          -
          -LIBRECOLL = -L$(depth)/lib -lrecoll -Wl,-rpath=$(libdir)/recoll
          -
          -RANLIB = test -f
          -AR=ar
          diff --git a/src/mk/commontargets b/src/mk/commontargets
          deleted file mode 100644
          index a68c369e..00000000
          --- a/src/mk/commontargets
          +++ /dev/null
          @@ -1,14 +0,0 @@
          -
          -librecoll:
          -	make -C $(depth)/lib
          -
          -depend: alldeps.stamp
          -alldeps.stamp : $(SRCS)
          -	$(CXX) -M $(ALL_CXXFLAGS) $(SRCS) > alldeps
          -	touch alldeps.stamp
          -
          -clean:
          -	cp /dev/null alldeps
          -	rm -f alldeps.stamp
          -	rm -f *.o ${LIBS} ${PROGS} ${OBJS} 
          -
          diff --git a/src/mk/localdefs.in b/src/mk/localdefs.in
          deleted file mode 100644
          index e8a5cd56..00000000
          --- a/src/mk/localdefs.in
          +++ /dev/null
          @@ -1,39 +0,0 @@
          -# @(#$Id: localdefs.in,v 1.14 2008-09-07 06:43:30 dockes Exp $  (C) 2006 J.F.Dockes
          -
          -# 'Make' definitions which depend on local configuration.
          -
          -LIBXAPIAN=@LIBXAPIAN@
          -XAPIANCXXFLAGS=@XAPIANCXXFLAGS@
          -
          -LIBICONV=@LIBICONV@
          -INCICONV=@INCICONV@
          -
          -LIBFAM = @LIBFAM@
          -
          -RCLLIBVERSION=@RCLLIBVERSION@
          -
          -X_CFLAGS=@X_CFLAGS@
          -X_PRE_LIBS=@X_PRE_LIBS@
          -X_LIBS=@X_LIBS@
          -X_EXTRA_LIBS=@X_EXTRA_LIBS@
          -X_LIBX11=@X_LIBX11@
          -
          -prefix = @prefix@
          -exec_prefix = @exec_prefix@
          -datarootdir = @datarootdir@
          -datadir = @datadir@
          -libdir = @libdir@
          -
          -RECOLL_DATADIR = ${datadir}/recoll
          -
          -@NOPIC@PICFLAGS = $(SYSPICFLAGS)
          -@NOTHREADS@LIBTHREADS = $(LIBSYSTHREADS)
          -
          -LOCALCXXFLAGS = -Wall -Wno-unused \
          -	      $(INCICONV) $(XAPIANCXXFLAGS) $(X_CFLAGS) \
          -	      -DRECOLL_DATADIR=\"$(RECOLL_DATADIR)\" \
          -	      $(PICFLAGS) \
          -	      @DEFS@
          -
          -CXXFLAGS ?= @CXXFLAGS@
          -
          diff --git a/src/mk/manifest.txt b/src/mk/manifest.txt
          deleted file mode 100644
          index 10c3532b..00000000
          --- a/src/mk/manifest.txt
          +++ /dev/null
          @@ -1,592 +0,0 @@
          -
          -COPYING
          -ChangeLog
          -INSTALL
          -Makefile.in
          -README
          -VERSION
          -aspell/
          -aspell/Makefile
          -aspell/aspell-local.h
          -aspell/rclaspell.cpp
          -aspell/rclaspell.h
          -bincimapmime/
          -bincimapmime/00README.recoll
          -bincimapmime/AUTHORS
          -bincimapmime/COPYING
          -bincimapmime/Makefile
          -bincimapmime/config.h
          -bincimapmime/convert.cc
          -bincimapmime/convert.h
          -bincimapmime/iodevice.cc
          -bincimapmime/iodevice.h
          -bincimapmime/iofactory.cc
          -bincimapmime/iofactory.h
          -bincimapmime/mime-inputsource.h
          -bincimapmime/mime-parsefull.cc
          -bincimapmime/mime-parseonlyheader.cc
          -bincimapmime/mime-printbody.cc
          -bincimapmime/mime-utils.h
          -bincimapmime/mime.cc
          -bincimapmime/mime.h
          -bincimapmime/trbinc.cc
          -common/
          -common/Makefile
          -common/autoconfig.h.in
          -common/beaglequeuecache.cpp
          -common/beaglequeuecache.h
          -common/cstr.cpp
          -common/cstr.h
          -common/rclconfig.cpp
          -common/rclconfig.h
          -common/rclinit.cpp
          -common/rclinit.h
          -common/rclversion.h.in
          -common/textsplit.cpp
          -common/textsplit.h
          -common/unacpp.cpp
          -common/unacpp.h
          -common/unordered_defs.h
          -common/uproplist.h
          -configure
          -configure.ac
          -desktop/
          -desktop/hotrecoll.py
          -desktop/recoll-searchgui.desktop
          -desktop/recoll.appdata.xml
          -desktop/recoll.png
          -desktop/recoll.svg
          -desktop/recoll.xcf
          -desktop/recoll_index_on_ac.sh
          -desktop/recollindex.desktop
          -desktop/xdg-utils-1.0.1/
          -desktop/xdg-utils-1.0.1/LICENSE
          -desktop/xdg-utils-1.0.1/scripts/
          -desktop/xdg-utils-1.0.1/scripts/xdg-desktop-menu
          -desktop/xdg-utils-1.0.1/scripts/xdg-icon-resource
          -desktop/xdg-utils-1.0.1/scripts/xdg-open
          -doc/
          -doc/man/
          -doc/man/recoll.1
          -doc/man/recoll.conf.5
          -doc/man/recollindex.1
          -doc/man/recollq.1
          -doc/prog/
          -doc/prog/Doxyfile
          -doc/prog/Makefile
          -doc/prog/filters.txt
          -doc/prog/top.txt
          -doc/user/
          -doc/user/00README.txt
          -doc/user/Makefile
          -doc/user/RCL.INDEXING.CONFIG.html
          -doc/user/RCL.INDEXING.EXTATTR.html
          -doc/user/RCL.INDEXING.EXTTAGS.html
          -doc/user/RCL.INDEXING.MONITOR.html
          -doc/user/RCL.INDEXING.PERIODIC.html
          -doc/user/RCL.INDEXING.STORAGE.html
          -doc/user/RCL.INDEXING.WEBQUEUE.html
          -doc/user/RCL.INDEXING.html
          -doc/user/RCL.INSTALL.BUILDING.html
          -doc/user/RCL.INSTALL.CONFIG.html
          -doc/user/RCL.INSTALL.EXTERNAL.html
          -doc/user/RCL.INSTALL.html
          -doc/user/RCL.INTRODUCTION.RECOLL.html
          -doc/user/RCL.INTRODUCTION.SEARCH.html
          -doc/user/RCL.INTRODUCTION.html
          -doc/user/RCL.PROGRAM.API.html
          -doc/user/RCL.PROGRAM.FIELDS.html
          -doc/user/RCL.PROGRAM.html
          -doc/user/RCL.SEARCH.ANCHORWILD.html
          -doc/user/RCL.SEARCH.CASEDIAC.html
          -doc/user/RCL.SEARCH.COMMANDLINE.html
          -doc/user/RCL.SEARCH.DESKTOP.html
          -doc/user/RCL.SEARCH.KIO.html
          -doc/user/RCL.SEARCH.LANG.html
          -doc/user/RCL.SEARCH.PTRANS.html
          -doc/user/RCL.SEARCH.html
          -doc/user/docbook-xsl.css
          -doc/user/docbook.css
          -doc/user/index.html
          -doc/user/usermanual-italian.html
          -doc/user/usermanual.html
          -doc/user/usermanual.xml
          -filters/
          -filters/injectcommon.sh
          -filters/msodump.zip
          -filters/ppt-dump.py
          -filters/rcl7z
          -filters/rclabw
          -filters/rclaptosidman
          -filters/rclaudio
          -filters/rclcheckneedretry.sh
          -filters/rclchm
          -filters/rcldia
          -filters/rcldjvu
          -filters/rcldoc
          -filters/rcldvi
          -filters/rclepub
          -filters/rclexecm.py
          -filters/rclfb2
          -filters/rclgaim
          -filters/rclgnm
          -filters/rclics
          -filters/rclimg
          -filters/rclinfo
          -filters/rclkar
          -filters/rclkwd
          -filters/rcllatinclass.py
          -filters/rcllatinstops.zip
          -filters/rcllyx
          -filters/rclman
          -filters/rclmpdf
          -filters/rclnull
          -filters/rclokulnote
          -filters/rclopxml
          -filters/rclpdf
          -filters/rclppt
          -filters/rclps
          -filters/rclpurple
          -filters/rclpython
          -filters/rclrar
          -filters/rclrtf
          -filters/rclscribus
          -filters/rclshowinfo
          -filters/rclsiduxman
          -filters/rclsoff
          -filters/rclsvg
          -filters/rcltar
          -filters/rcltex
          -filters/rcltext
          -filters/rcluncomp
          -filters/rclwar
          -filters/rclwpd
          -filters/rclxls
          -filters/rclxml
          -filters/rclzip
          -filters/recfiltcommon
          -filters/xls-dump.py
          -filters/xlsxmltocsv.py
          -index/
          -index/Makefile
          -index/beaglequeue.cpp
          -index/beaglequeue.h
          -index/bglfetcher.cpp
          -index/bglfetcher.h
          -index/checkretryfailed.cpp
          -index/checkretryfailed.h
          -index/fetcher.cpp
          -index/fetcher.h
          -index/fsfetcher.cpp
          -index/fsfetcher.h
          -index/fsindexer.cpp
          -index/fsindexer.h
          -index/indexer.cpp
          -index/indexer.h
          -index/mimetype.cpp
          -index/mimetype.h
          -index/rclmon.h
          -index/rclmon.sh
          -index/rclmonprc.cpp
          -index/rclmonrcv.cpp
          -index/recollindex.cpp
          -index/recollindex.h
          -index/subtreelist.cpp
          -index/subtreelist.h
          -internfile/
          -internfile/Filter.h
          -internfile/Makefile
          -internfile/extrameta.cpp
          -internfile/extrameta.h
          -internfile/htmlparse.cpp
          -internfile/htmlparse.h
          -internfile/indextext.h
          -internfile/internfile.cpp
          -internfile/internfile.h
          -internfile/mh_exec.cpp
          -internfile/mh_exec.h
          -internfile/mh_execm.cpp
          -internfile/mh_execm.h
          -internfile/mh_html.cpp
          -internfile/mh_html.h
          -internfile/mh_mail.cpp
          -internfile/mh_mail.h
          -internfile/mh_mbox.cpp
          -internfile/mh_mbox.h
          -internfile/mh_symlink.h
          -internfile/mh_text.cpp
          -internfile/mh_text.h
          -internfile/mh_unknown.h
          -internfile/mimehandler.cpp
          -internfile/mimehandler.h
          -internfile/myhtmlparse.cpp
          -internfile/myhtmlparse.h
          -internfile/txtdcode.cpp
          -internfile/uncomp.cpp
          -internfile/uncomp.h
          -kde/
          -kde/kioslave/
          -kde/kioslave/kio_recoll/
          -kde/kioslave/kio_recoll/00README.txt
          -kde/kioslave/kio_recoll/CMakeLists.txt
          -kde/kioslave/kio_recoll/Makefile.kde3
          -kde/kioslave/kio_recoll/cleancmakestuff.sh
          -kde/kioslave/kio_recoll/data/
          -kde/kioslave/kio_recoll/data/help.html
          -kde/kioslave/kio_recoll/data/searchable.html
          -kde/kioslave/kio_recoll/data/welcome.html
          -kde/kioslave/kio_recoll/dirif.cpp
          -kde/kioslave/kio_recoll/htmlif.cpp
          -kde/kioslave/kio_recoll/kio_recoll.cpp
          -kde/kioslave/kio_recoll/kio_recoll.h
          -kde/kioslave/kio_recoll/kio_recoll.la
          -kde/kioslave/kio_recoll/notes.txt
          -kde/kioslave/kio_recoll/recoll.protocol
          -kde/kioslave/kio_recoll/recollf.protocol
          -kde/kioslave/kio_recoll/recollnolist.protocol
          -lib/
          -lib/mkMake.in
          -makestaticdist.sh
          -mk/
          -mk/AIX
          -mk/CYGWIN
          -mk/Darwin
          -mk/Default
          -mk/FreeBSD
          -mk/Linux
          -mk/OpenBSD
          -mk/SunOS
          -mk/commondefs
          -mk/commontargets
          -mk/localdefs.in
          -php/
          -php/00README.txt
          -php/recoll/
          -php/recoll/config.m4
          -php/recoll/make.sh
          -php/recoll/php_recoll.h
          -php/recoll/recoll.cpp
          -php/sample/
          -php/sample/shell.php
          -python/
          -python/README.txt
          -python/recoll/
          -python/recoll/Makefile
          -python/recoll/pyrclextract.cpp
          -python/recoll/pyrecoll.cpp
          -python/recoll/pyrecoll.h
          -python/recoll/recoll/
          -python/recoll/recoll/__init__.py
          -python/recoll/recoll/rclconfig.py
          -python/recoll/setup.py.in
          -python/samples/
          -python/samples/docdups.py
          -python/samples/mutt-recoll.py
          -python/samples/rcldlkp.py
          -python/samples/rclmbox.py
          -python/samples/recollgui/
          -python/samples/recollgui/Makefile
          -python/samples/recollgui/qrecoll.py
          -python/samples/recollgui/rclmain.ui
          -python/samples/recollq.py
          -python/samples/recollqsd.py
          -python/samples/trconfig.py
          -qtgui/
          -qtgui/advsearch.ui
          -qtgui/advsearch_w.cpp
          -qtgui/advsearch_w.h
          -qtgui/advshist.cpp
          -qtgui/advshist.h
          -qtgui/confgui/
          -qtgui/confgui/confgui.cpp
          -qtgui/confgui/confgui.h
          -qtgui/confgui/confguiindex.cpp
          -qtgui/confgui/confguiindex.h
          -qtgui/confgui/conflinkrcl.h
          -qtgui/confgui/main.cpp
          -qtgui/confgui/trconf.pro
          -qtgui/crontool.cpp
          -qtgui/crontool.h
          -qtgui/crontool.ui
          -qtgui/editdialog.h
          -qtgui/editdialog.ui
          -qtgui/firstidx.h
          -qtgui/firstidx.ui
          -qtgui/fragbuts.cpp
          -qtgui/fragbuts.h
          -qtgui/guiutils.cpp
          -qtgui/guiutils.h
          -qtgui/i18n/
          -qtgui/i18n/recoll_cs.qm
          -qtgui/i18n/recoll_cs.ts
          -qtgui/i18n/recoll_da.qm
          -qtgui/i18n/recoll_da.ts
          -qtgui/i18n/recoll_de.qm
          -qtgui/i18n/recoll_de.ts
          -qtgui/i18n/recoll_el.qm
          -qtgui/i18n/recoll_el.ts
          -qtgui/i18n/recoll_es.qm
          -qtgui/i18n/recoll_es.ts
          -qtgui/i18n/recoll_fr.qm
          -qtgui/i18n/recoll_fr.ts
          -qtgui/i18n/recoll_it.qm
          -qtgui/i18n/recoll_it.ts
          -qtgui/i18n/recoll_lt.qm
          -qtgui/i18n/recoll_lt.ts
          -qtgui/i18n/recoll_pl.qm
          -qtgui/i18n/recoll_pl.ts
          -qtgui/i18n/recoll_ru.qm
          -qtgui/i18n/recoll_ru.ts
          -qtgui/i18n/recoll_tr.qm
          -qtgui/i18n/recoll_tr.ts
          -qtgui/i18n/recoll_uk.qm
          -qtgui/i18n/recoll_uk.ts
          -qtgui/i18n/recoll_xx.qm
          -qtgui/i18n/recoll_xx.ts
          -qtgui/i18n/recoll_zh.qm
          -qtgui/i18n/recoll_zh.ts
          -qtgui/i18n/recoll_zh_CN.qm
          -qtgui/i18n/recoll_zh_CN.ts
          -qtgui/idxsched.h
          -qtgui/idxsched.ui
          -qtgui/images/
          -qtgui/images/asearch.png
          -qtgui/images/cancel.png
          -qtgui/images/close.png
          -qtgui/images/code-block.png
          -qtgui/images/down.png
          -qtgui/images/firstpage.png
          -qtgui/images/history.png
          -qtgui/images/nextpage.png
          -qtgui/images/prevpage.png
          -qtgui/images/recoll.icns
          -qtgui/images/recoll.png
          -qtgui/images/sortparms.png
          -qtgui/images/spell.png
          -qtgui/images/table.png
          -qtgui/images/up.png
          -qtgui/listdialog.h
          -qtgui/listdialog.ui
          -qtgui/main.cpp
          -qtgui/mtpics/
          -qtgui/mtpics/License_sidux.txt
          -qtgui/mtpics/README
          -qtgui/mtpics/aptosid-book.png
          -qtgui/mtpics/aptosid-manual-copyright.txt
          -qtgui/mtpics/aptosid-manual.png
          -qtgui/mtpics/archive.png
          -qtgui/mtpics/book.png
          -qtgui/mtpics/bookchap.png
          -qtgui/mtpics/document.png
          -qtgui/mtpics/drawing.png
          -qtgui/mtpics/emblem-symbolic-link.png
          -qtgui/mtpics/folder.png
          -qtgui/mtpics/html.png
          -qtgui/mtpics/image.png
          -qtgui/mtpics/message.png
          -qtgui/mtpics/mozilla_doc.png
          -qtgui/mtpics/pdf.png
          -qtgui/mtpics/pidgin.png
          -qtgui/mtpics/postscript.png
          -qtgui/mtpics/presentation.png
          -qtgui/mtpics/sidux-book.png
          -qtgui/mtpics/soffice.png
          -qtgui/mtpics/source.png
          -qtgui/mtpics/sownd.png
          -qtgui/mtpics/spreadsheet.png
          -qtgui/mtpics/text-x-python.png
          -qtgui/mtpics/txt.png
          -qtgui/mtpics/video.png
          -qtgui/mtpics/wordprocessing.png
          -qtgui/multisave.cpp
          -qtgui/multisave.h
          -qtgui/preview_w.cpp
          -qtgui/preview_w.h
          -qtgui/ptrans.ui
          -qtgui/ptrans_w.cpp
          -qtgui/ptrans_w.h
          -qtgui/rclhelp.cpp
          -qtgui/rclhelp.h
          -qtgui/rclm_idx.cpp
          -qtgui/rclm_preview.cpp
          -qtgui/rclm_saveload.cpp
          -qtgui/rclm_view.cpp
          -qtgui/rclm_wins.cpp
          -qtgui/rclmain.ui
          -qtgui/rclmain_w.cpp
          -qtgui/rclmain_w.h
          -qtgui/rclzg.cpp
          -qtgui/rclzg.h
          -qtgui/recoll.h
          -qtgui/recoll.pro.in
          -qtgui/recoll.qrc
          -qtgui/reslist.cpp
          -qtgui/reslist.h
          -qtgui/respopup.cpp
          -qtgui/respopup.h
          -qtgui/restable.cpp
          -qtgui/restable.h
          -qtgui/restable.ui
          -qtgui/rtitool.cpp
          -qtgui/rtitool.h
          -qtgui/rtitool.ui
          -qtgui/searchclause_w.cpp
          -qtgui/searchclause_w.h
          -qtgui/snippets.ui
          -qtgui/snippets_w.cpp
          -qtgui/snippets_w.h
          -qtgui/spell.ui
          -qtgui/spell_w.cpp
          -qtgui/spell_w.h
          -qtgui/ssearch_w.cpp
          -qtgui/ssearch_w.h
          -qtgui/ssearchb.ui
          -qtgui/systray.cpp
          -qtgui/systray.h
          -qtgui/ui_rclmain.h-4.5
          -qtgui/uiprefs.ui
          -qtgui/uiprefs_w.cpp
          -qtgui/uiprefs_w.h
          -qtgui/viewaction.ui
          -qtgui/viewaction_w.cpp
          -qtgui/viewaction_w.h
          -qtgui/xmltosd.cpp
          -qtgui/xmltosd.h
          -query/
          -query/Makefile
          -query/docseq.cpp
          -query/docseq.h
          -query/docseqdb.cpp
          -query/docseqdb.h
          -query/docseqdocs.h
          -query/docseqhist.cpp
          -query/docseqhist.h
          -query/dynconf.cpp
          -query/dynconf.h
          -query/filtseq.cpp
          -query/filtseq.h
          -query/plaintorich.cpp
          -query/plaintorich.h
          -query/recollq.cpp
          -query/recollq.h
          -query/reslistpager.cpp
          -query/reslistpager.h
          -query/sortseq.cpp
          -query/sortseq.h
          -query/wasaparse.cpp
          -query/wasaparse.y
          -query/wasaparserdriver.h
          -query/wasatorcl.h
          -query/xadump.cpp
          -rcldb/
          -rcldb/Makefile
          -rcldb/daterange.cpp
          -rcldb/daterange.h
          -rcldb/expansiondbs.cpp
          -rcldb/expansiondbs.h
          -rcldb/rclabstract.cpp
          -rcldb/rcldb.cpp
          -rcldb/rcldb.h
          -rcldb/rcldb_p.h
          -rcldb/rcldoc.cpp
          -rcldb/rcldoc.h
          -rcldb/rcldups.cpp
          -rcldb/rclquery.cpp
          -rcldb/rclquery.h
          -rcldb/rclquery_p.h
          -rcldb/rclterms.cpp
          -rcldb/searchdata.cpp
          -rcldb/searchdata.h
          -rcldb/searchdatatox.cpp
          -rcldb/searchdataxml.cpp
          -rcldb/stemdb.cpp
          -rcldb/stemdb.h
          -rcldb/stoplist.cpp
          -rcldb/stoplist.h
          -rcldb/synfamily.cpp
          -rcldb/synfamily.h
          -rcldb/termproc.h
          -rcldb/xmacros.h
          -recollinstall.in
          -sampleconf/
          -sampleconf/fields
          -sampleconf/fragbuts.xml
          -sampleconf/mimeconf
          -sampleconf/mimemap
          -sampleconf/mimeview
          -sampleconf/mimeview.mac
          -sampleconf/recoll.conf.in
          -sampleconf/recoll.qss
          -unac/
          -unac/AUTHORS
          -unac/COPYING
          -unac/README
          -unac/README.recoll
          -unac/unac.c
          -unac/unac.cpp
          -unac/unac.h
          -unac/unac_version.h
          -utils/
          -utils/Makefile
          -utils/appformime.cpp
          -utils/appformime.h
          -utils/base64.cpp
          -utils/base64.h
          -utils/cancelcheck.h
          -utils/circache.cpp
          -utils/circache.h
          -utils/closefrom.cpp
          -utils/closefrom.h
          -utils/conftree.cpp
          -utils/conftree.h
          -utils/copyfile.cpp
          -utils/copyfile.h
          -utils/cpuconf.cpp
          -utils/cpuconf.h
          -utils/debuglog.cpp
          -utils/debuglog.h
          -utils/ecrontab.cpp
          -utils/ecrontab.h
          -utils/execmd.cpp
          -utils/execmd.h
          -utils/fileudi.cpp
          -utils/fileudi.h
          -utils/fstreewalk.cpp
          -utils/fstreewalk.h
          -utils/hldata.h
          -utils/idfile.cpp
          -utils/idfile.h
          -utils/md5.cpp
          -utils/md5.h
          -utils/md5ut.cpp
          -utils/md5ut.h
          -utils/mimeparse.cpp
          -utils/mimeparse.h
          -utils/netcon.cpp
          -utils/netcon.h
          -utils/pathut.cpp
          -utils/pathut.h
          -utils/ptmutex.cpp
          -utils/ptmutex.h
          -utils/pxattr.cpp
          -utils/pxattr.h
          -utils/rclionice.cpp
          -utils/rclionice.h
          -utils/readfile.cpp
          -utils/readfile.h
          -utils/refcntr.h
          -utils/smallut.cpp
          -utils/smallut.h
          -utils/strmatcher.cpp
          -utils/strmatcher.h
          -utils/transcode.cpp
          -utils/transcode.h
          -utils/utf8iter.cpp
          -utils/utf8iter.h
          -utils/utf8testin.txt
          -utils/wipedir.cpp
          -utils/wipedir.h
          -utils/workqueue.cpp
          -utils/workqueue.h
          -utils/x11mon.cpp
          -utils/x11mon.h
          diff --git a/src/php/recoll/recoll.cpp b/src/php/recoll/recoll.cpp
          index 8a096feb..9b2addc0 100644
          --- a/src/php/recoll/recoll.cpp
          +++ b/src/php/recoll/recoll.cpp
          @@ -36,7 +36,7 @@
           #include "rclconfig.h"
           #include "pathut.h"
           #include "rclinit.h"
          -#include "debuglog.h"
          +#include "log.h"
           #include "wasatorcl.h"
           #include "internfile.h"
           #include "wipedir.h"
          @@ -131,7 +131,7 @@ PHP_METHOD(Query, query)
           	RETURN_BOOL(false);
               }
           
          -    RefCntr rq(sd);
          +    std::shared_ptr rq(sd);
               Rcl::Query *pRclQuery = new Rcl::Query(pRclDb);
               pRclQuery->setQuery(rq);
           
          @@ -238,3 +238,4 @@ extern "C" {
           ZEND_GET_MODULE(recoll)
           }
           #endif
          +
          diff --git a/src/python/recoll/Makefile b/src/python/recoll/Makefile.in
          similarity index 81%
          rename from src/python/recoll/Makefile
          rename to src/python/recoll/Makefile.in
          index 2e1ccced..ac67f56a 100644
          --- a/src/python/recoll/Makefile
          +++ b/src/python/recoll/Makefile.in
          @@ -1,5 +1,6 @@
           all:
           	echo libdir: $(libdir)
          +	test '@srcdir@' = '.' || cp -rp @srcdir@/recoll .
           	libdir=$(libdir) python setup.py build
           install:
           	sudo python setup.py install
          diff --git a/src/python/recoll/pyrclextract.cpp b/src/python/recoll/pyrclextract.cpp
          index 7cd913c7..8f632e81 100644
          --- a/src/python/recoll/pyrclextract.cpp
          +++ b/src/python/recoll/pyrclextract.cpp
          @@ -25,7 +25,7 @@
           #include 
           using namespace std;
           
          -#include "debuglog.h"
          +#include "log.h"
           #include "rcldoc.h"
           #include "internfile.h"
           #include "rclconfig.h"
          @@ -49,7 +49,7 @@ typedef struct {
           static void 
           Extractor_dealloc(rclx_ExtractorObject *self)
           {
          -    LOGDEB(("Extractor_dealloc\n"));
          +    LOGDEB("Extractor_dealloc\n" );
               delete self->xtr;
               Py_TYPE(self)->tp_free((PyObject*)self);
           }
          @@ -57,7 +57,7 @@ Extractor_dealloc(rclx_ExtractorObject *self)
           static PyObject *
           Extractor_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
           {
          -    LOGDEB(("Extractor_new\n"));
          +    LOGDEB("Extractor_new\n" );
               rclx_ExtractorObject *self = 
           	(rclx_ExtractorObject *)type->tp_alloc(type, 0);
               if (self == 0) 
          @@ -70,7 +70,7 @@ Extractor_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
           static int
           Extractor_init(rclx_ExtractorObject *self, PyObject *args, PyObject *kwargs)
           {
          -    LOGDEB(("Extractor_init\n"));
          +    LOGDEB("Extractor_init\n" );
               static const char* kwlist[] = {"doc", NULL};
               PyObject *pdobj;
           
          @@ -99,7 +99,7 @@ static PyObject *
           Extractor_textextract(rclx_ExtractorObject* self, PyObject *args, 
           		      PyObject *kwargs)
           {
          -    LOGDEB(("Extractor_textextract\n"));
          +    LOGDEB("Extractor_textextract\n" );
               static const char* kwlist[] = {"ipath", NULL};
               char *sipath = 0;
           
          @@ -153,7 +153,7 @@ static PyObject *
           Extractor_idoctofile(rclx_ExtractorObject* self, PyObject *args, 
           		     PyObject *kwargs)
           {
          -    LOGDEB(("Extractor_idoctofile\n"));
          +    LOGDEB("Extractor_idoctofile\n" );
               static const char* kwlist[] = {"ipath", "mimetype", "ofilename", NULL};
               char *sipath = 0;
               char *smt = 0;
          @@ -359,3 +359,4 @@ initrclextract(void)
               return module;
           #endif
           }
          +
          diff --git a/src/python/recoll/pyrecoll.cpp b/src/python/recoll/pyrecoll.cpp
          index 55a5e2c9..0b8a1716 100644
          --- a/src/python/recoll/pyrecoll.cpp
          +++ b/src/python/recoll/pyrecoll.cpp
          @@ -32,11 +32,13 @@ using namespace std;
           #include "searchdata.h"
           #include "rclquery.h"
           #include "pathut.h"
          +#include "rclutil.h"
           #include "wasatorcl.h"
          -#include "debuglog.h"
          +#include "log.h"
           #include "pathut.h"
           #include "plaintorich.h"
           #include "hldata.h"
          +#include "smallut.h"
           
           #include "pyrecoll.h"
           
          @@ -55,22 +57,21 @@ static RclConfig *rclconfig;
           typedef struct {
               PyObject_HEAD
               /* Type-specific fields go here. */
          -    RefCntr sd;
          +    std::shared_ptr sd;
           } recoll_SearchDataObject;
           
           static void 
           SearchData_dealloc(recoll_SearchDataObject *self)
           {
          -    LOGDEB(("SearchData_dealloc. Releasing. Count before: %d\n",
          -            self->sd.getcnt()));
          -    self->sd.release();
          +    LOGDEB("SearchData_dealloc. Releasing. Count before: "  << (self->sd.use_count()) << "\n" );
          +    self->sd.reset();
               Py_TYPE(self)->tp_free((PyObject*)self);
           }
           
           static PyObject *
           SearchData_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
           {
          -    LOGDEB(("SearchData_new\n"));
          +    LOGDEB("SearchData_new\n" );
               recoll_SearchDataObject *self;
           
               self = (recoll_SearchDataObject *)type->tp_alloc(type, 0);
          @@ -89,7 +90,7 @@ PyDoc_STRVAR(doc_SearchDataObject,
           static int
           SearchData_init(recoll_SearchDataObject *self, PyObject *args, PyObject *kwargs)
           {
          -    LOGDEB(("SearchData_init\n"));
          +    LOGDEB("SearchData_init\n" );
               static const char* kwlist[] = {"type", "stemlang", NULL};
               char *stp = 0;
               char *steml = 0;
          @@ -108,7 +109,7 @@ SearchData_init(recoll_SearchDataObject *self, PyObject *args, PyObject *kwargs)
               } else {
           	stemlang = "english";
               }
          -    self->sd = RefCntr(new Rcl::SearchData(tp, stemlang));
          +    self->sd = std::shared_ptr(new Rcl::SearchData(tp, stemlang));
               return 0;
           }
           
          @@ -179,9 +180,9 @@ static PyObject *
           SearchData_addclause(recoll_SearchDataObject* self, PyObject *args, 
           		     PyObject *kwargs)
           {
          -    LOGDEB0(("SearchData_addclause\n"));
          -    if (self->sd.isNull()) {
          -	LOGERR(("SearchData_addclause: not init??\n"));
          +    LOGDEB0("SearchData_addclause\n" );
          +    if (!self->sd) {
          +	LOGERR("SearchData_addclause: not init??\n" );
                   PyErr_SetString(PyExc_AttributeError, "sd");
                   return 0;
               }
          @@ -293,7 +294,7 @@ SearchData_addclause(recoll_SearchDataObject* self, PyObject *args,
           static void 
           Doc_dealloc(recoll_DocObject *self)
           {
          -    LOGDEB(("Doc_dealloc\n"));
          +    LOGDEB("Doc_dealloc\n" );
               if (self->doc)
           	the_docs.erase(self->doc);
               deleteZ(self->doc);
          @@ -303,7 +304,7 @@ Doc_dealloc(recoll_DocObject *self)
           static PyObject *
           Doc_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
           {
          -    LOGDEB(("Doc_new\n"));
          +    LOGDEB("Doc_new\n" );
               recoll_DocObject *self;
           
               self = (recoll_DocObject *)type->tp_alloc(type, 0);
          @@ -317,7 +318,7 @@ Doc_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
           static int
           Doc_init(recoll_DocObject *self, PyObject *, PyObject *)
           {
          -    LOGDEB(("Doc_init\n"));
          +    LOGDEB("Doc_init\n" );
               if (self->doc)
           	the_docs.erase(self->doc);
               delete self->doc;
          @@ -339,7 +340,7 @@ PyDoc_STRVAR(doc_Doc_getbinurl,
           static PyObject *
           Doc_getbinurl(recoll_DocObject *self)
           {
          -    LOGDEB0(("Doc_getbinurl\n"));
          +    LOGDEB0("Doc_getbinurl\n" );
               if (self->doc == 0 || 
           	the_docs.find(self->doc) == the_docs.end()) {
                   PyErr_SetString(PyExc_AttributeError, "doc");
          @@ -358,7 +359,7 @@ PyDoc_STRVAR(doc_Doc_setbinurl,
           static PyObject *
           Doc_setbinurl(recoll_DocObject *self, PyObject *value)
           {
          -    LOGDEB0(("Doc_setbinurl\n"));
          +    LOGDEB0("Doc_setbinurl\n" );
               if (self->doc == 0 || 
           	the_docs.find(self->doc) == the_docs.end()) {
                   PyErr_SetString(PyExc_AttributeError, "doc??");
          @@ -380,7 +381,7 @@ PyDoc_STRVAR(doc_Doc_keys,
           static PyObject *
           Doc_keys(recoll_DocObject *self)
           {
          -    LOGDEB0(("Doc_keys\n"));
          +    LOGDEB0("Doc_keys\n" );
               if (self->doc == 0 || 
           	the_docs.find(self->doc) == the_docs.end()) {
                   PyErr_SetString(PyExc_AttributeError, "doc");
          @@ -405,7 +406,7 @@ PyDoc_STRVAR(doc_Doc_items,
           static PyObject *
           Doc_items(recoll_DocObject *self)
           {
          -    LOGDEB0(("Doc_items\n"));
          +    LOGDEB0("Doc_items\n" );
               if (self->doc == 0 || 
           	the_docs.find(self->doc) == the_docs.end()) {
                   PyErr_SetString(PyExc_AttributeError, "doc");
          @@ -428,6 +429,86 @@ Doc_items(recoll_DocObject *self)
               return pdict;
           }
           
          +static bool idocget(recoll_DocObject *self, const string& key, string& value)
          +{
          +    switch (key.at(0)) {
          +    case 'u':
          +	if (!key.compare(Rcl::Doc::keyurl)) {
          +	    value = self->doc->url;
          +            return true;
          +	}
          +	break;
          +    case 'f':
          +	if (!key.compare(Rcl::Doc::keyfs)) {
          +	    value = self->doc->fbytes;
          +            return true;
          +	} else if (!key.compare(Rcl::Doc::keyfmt)) {
          +	    value = self->doc->fmtime;
          +            return true;
          +	}
          +	break;
          +    case 'd':
          +	if (!key.compare(Rcl::Doc::keyds)) {
          +	    value = self->doc->dbytes;
          +            return true;
          +	} else if (!key.compare(Rcl::Doc::keydmt)) {
          +	    value = self->doc->dmtime;
          +            return true;
          +	}
          +	break;
          +    case 'i':
          +	if (!key.compare(Rcl::Doc::keyipt)) {
          +	    value = self->doc->ipath;
          +            return true;
          +	}
          +	break;
          +    case 'm':
          +	if (!key.compare(Rcl::Doc::keytp)) {
          +	    value = self->doc->mimetype;
          +            return true;
          +	} else if (!key.compare(Rcl::Doc::keymt)) {
          +	    value = self->doc->dmtime.empty() ? self->doc->fmtime : 
          +		self->doc->dmtime;
          +            return true;
          +	}
          +	break;
          +    case 'o':
          +	if (!key.compare(Rcl::Doc::keyoc)) {
          +	    value = self->doc->origcharset;
          +            return true;
          +	}
          +	break;
          +    case 's':
          +	if (!key.compare(Rcl::Doc::keysig)) {
          +	    value = self->doc->sig;
          +            return true;
          +	} else 	if (!key.compare(Rcl::Doc::keysz)) {
          +	    value = self->doc->dbytes.empty() ? self->doc->fbytes : 
          +		self->doc->dbytes;
          +            return true;
          +	}
          +	break;
          +    case 't':
          +	if (!key.compare("text")) {
          +	    value = self->doc->text;
          +            return true;
          +	}
          +	break;
          +    case 'x':
          +        if (!key.compare("xdocid")) {
          +            ulltodecstr(self->doc->xdocid, value);
          +            return true;
          +        }
          +        break;
          +    }
          +
          +    if (self->doc->getmeta(key, 0)) {
          +        value = self->doc->meta[key];
          +        return true;
          +    }
          +    return false;
          +}
          +
           PyDoc_STRVAR(doc_Doc_get,
           "get(key) -> value\n"
           "Retrieve the named doc attribute\n"
          @@ -435,40 +516,21 @@ PyDoc_STRVAR(doc_Doc_get,
           static PyObject *
           Doc_get(recoll_DocObject *self, PyObject *args)
           {
          -    LOGDEB0(("Doc_get\n"));
          +    LOGDEB1("Doc_get\n" );
          +    if (self->doc == 0 || the_docs.find(self->doc) == the_docs.end()) {
          +        PyErr_SetString(PyExc_AttributeError, "doc??");
          +	return 0;
          +    }
               char *sutf8 = 0; // needs freeing
          -    if (!PyArg_ParseTuple(args, "es:Doc_get",
          -			  "utf-8", &sutf8)) {
          +    if (!PyArg_ParseTuple(args, "es:Doc_get", "utf-8", &sutf8)) {
           	return 0;
               }
               string key(sutf8);
               PyMem_Free(sutf8);
           
          -    if (self->doc == 0 || 
          -	the_docs.find(self->doc) == the_docs.end()) {
          -        PyErr_SetString(PyExc_AttributeError, "doc??");
          -	return 0;
          -    }
               string value;
          -    bool found = false;
          -
          -    // 
          -    if (!key.compare("xdocid")) {
          -        char cid[30];
          -        sprintf(cid, "%lu", (unsigned long)self->doc->xdocid);
          -        value = cid;
          -        found = true;
          -    } else {
          -        if (self->doc->getmeta(key, 0)) {
          -            value = self->doc->meta[key];
          -            found = true;
          -        }
          -    }
          -
          -    if (found) {
          -	return PyUnicode_Decode(value.c_str(), 
          -				value.size(), 
          -				"UTF-8", "replace");
          +    if (idocget(self, key, value)) {
          +	return PyUnicode_Decode(value.c_str(), value.size(), "UTF-8","replace");
               }
           
               Py_RETURN_NONE;
          @@ -480,6 +542,7 @@ static PyMethodDef Doc_methods[] = {
               {"keys", (PyCFunction)Doc_keys, METH_NOARGS, doc_Doc_keys},
               {"items", (PyCFunction)Doc_items, METH_NOARGS, doc_Doc_items},
               {"get", (PyCFunction)Doc_get, METH_VARARGS, doc_Doc_get},
          +        
               {NULL}  /* Sentinel */
           };
           
          @@ -489,22 +552,22 @@ static PyMethodDef Doc_methods[] = {
           static PyObject *
           Doc_getattro(recoll_DocObject *self, PyObject *nameobj)
           {
          -    LOGDEB0(("Doc_getattro\n"));
               if (self->doc == 0 || the_docs.find(self->doc) == the_docs.end()) {
                   PyErr_SetString(PyExc_AttributeError, "doc");
           	return 0;
               }
           
          -    bool found = false;
          -    string value;
          -    string key;
          +    PyObject *meth = PyObject_GenericGetAttr((PyObject*)self, nameobj);
          +    if (meth) {
          +        return meth;
          +    }
          +    PyErr_Clear();
          +    
               char *name = 0;
          -    PyObject* utf8o = 0;
          -
               if (PyUnicode_Check(nameobj)) {
          -	utf8o = PyUnicode_AsUTF8String(nameobj);
          +	PyObject* utf8o = PyUnicode_AsUTF8String(nameobj);
           	if (utf8o == 0) {
          -	    LOGERR(("Doc_getattro: encoding name to utf8 failed\n"));
          +	    LOGERR("Doc_getattro: encoding name to utf8 failed\n" );
           	    PyErr_SetString(PyExc_AttributeError, "name??");
           	    Py_RETURN_NONE;
           	}
          @@ -517,89 +580,12 @@ Doc_getattro(recoll_DocObject *self, PyObject *nameobj)
           	Py_RETURN_NONE;
               }
           
          -    key = rclconfig->fieldQCanon(string(name));
          -
          -    switch (key.at(0)) {
          -    case 'u':
          -	if (!key.compare(Rcl::Doc::keyurl)) {
          -	    value = self->doc->url; found = true;
          -	}
          -	break;
          -    case 'f':
          -	if (!key.compare(Rcl::Doc::keyfs)) {
          -	    value = self->doc->fbytes; found = true;
          -	} else if (!key.compare(Rcl::Doc::keyfmt)) {
          -	    value = self->doc->fmtime; found = true;
          -	}
          -	break;
          -    case 'd':
          -	if (!key.compare(Rcl::Doc::keyds)) {
          -	    value = self->doc->dbytes; found = true;
          -	} else if (!key.compare(Rcl::Doc::keydmt)) {
          -	    value = self->doc->dmtime; found = true;
          -	}
          -	break;
          -    case 'i':
          -	if (!key.compare(Rcl::Doc::keyipt)) {
          -	    value = self->doc->ipath; found = true;
          -	}
          -	break;
          -    case 'm':
          -	if (!key.compare(Rcl::Doc::keytp)) {
          -	    value = self->doc->mimetype; found = true;
          -	} else if (!key.compare(Rcl::Doc::keymt)) {
          -	    value = self->doc->dmtime.empty() ? self->doc->fmtime : 
          -		self->doc->dmtime; found = true;
          -	}
          -	break;
          -    case 'o':
          -	if (!key.compare(Rcl::Doc::keyoc)) {
          -	    value = self->doc->origcharset; found = true;
          -	}
          -	break;
          -    case 's':
          -	if (!key.compare(Rcl::Doc::keysig)) {
          -	    value = self->doc->sig; found = true;
          -	} else 	if (!key.compare(Rcl::Doc::keysz)) {
          -	    value = self->doc->dbytes.empty() ? self->doc->fbytes : 
          -		self->doc->dbytes; found = true;
          -	}
          -	break;
          -    case 't':
          -	if (!key.compare("text")) {
          -	    value = self->doc->text; found = true;
          -	}
          -	break;
          -    case 'x':
          -        if (!key.compare("xdocid")) {
          -            char cid[30];
          -            sprintf(cid, "%lu", (unsigned long)self->doc->xdocid);
          -            value = cid;
          -            found = true;
          -        }
          -        break;
          -    }
          -
          -    if (!found) {
          -	// This will look up a method name (we have no other standard
          -	// attributes)
          -	PyObject *meth = PyObject_GenericGetAttr((PyObject*)self, nameobj);
          -	if (meth) {
          -	    return meth;
          -	}
          -	PyErr_Clear();
          -	// Else look for another attribute
          -	if (self->doc->getmeta(key, 0)) {
          -	    value = self->doc->meta[key];
          -	    found = true;
          -	}
          -    }
          -
          -    if (found) {
          -	LOGDEB1(("Doc_getattro: [%s] -> [%s]\n", key.c_str(), value.c_str()));
          +    string key = rclconfig->fieldQCanon(string(name));
          +    string value;
          +    if (idocget(self, key, value)) {
          +	LOGDEB1("Doc_getattro: ["  << key << "] -> ["  << value << "]\n");
           	// Return a python unicode object
          -	return PyUnicode_Decode(value.c_str(), value.size(), "utf-8",
          -				"replace");
          +	return PyUnicode_Decode(value.c_str(), value.size(), "utf-8","replace");
               }
               
               Py_RETURN_NONE;
          @@ -608,22 +594,13 @@ Doc_getattro(recoll_DocObject *self, PyObject *nameobj)
           static int
           Doc_setattr(recoll_DocObject *self, char *name, PyObject *value)
           {
          -    LOGDEB0(("Doc_setattr: doc %p\n", self->doc));
               if (self->doc == 0 || the_docs.find(self->doc) == the_docs.end()) {
                   PyErr_SetString(PyExc_AttributeError, "doc??");
           	return -1;
               }
          -
          -#if PY_MAJOR_VERSION < 3
          -    if (PyString_Check(value)) {
          -	value = PyUnicode_FromObject(value);
          -	if (value == 0) 
          -	    return -1;
          -    } 
          -#endif
          -
          -    if (!PyUnicode_Check(value)) {
          -	PyErr_SetString(PyExc_AttributeError, "value not str/unicode??");
          +    if (!rclconfig || !rclconfig->ok()) {
          +	PyErr_SetString(PyExc_EnvironmentError,
          +                        "Configuration not initialized");
           	return -1;
               }
               if (name == 0) {
          @@ -631,17 +608,29 @@ Doc_setattr(recoll_DocObject *self, char *name, PyObject *value)
           	return -1;
               }
           
          +    if (PyBytes_Check(value)) {
          +	value = PyUnicode_FromEncodedObject(value, "UTF-8", "strict");
          +	if (value == 0) 
          +	    return -1;
          +    }
          +
          +    if (!PyUnicode_Check(value)) {
          +	PyErr_SetString(PyExc_AttributeError, "value not unicode??");
          +	return -1;
          +    }
          +
               PyObject* putf8 = PyUnicode_AsUTF8String(value);
               if (putf8 == 0) {
          -	LOGERR(("Doc_setmeta: encoding to utf8 failed\n"));
          +	LOGERR("Doc_setmeta: encoding to utf8 failed\n" );
           	PyErr_SetString(PyExc_AttributeError, "value??");
           	return -1;
               }
               char* uvalue = PyBytes_AsString(putf8);
          -    Py_DECREF(putf8);
               string key = rclconfig->fieldQCanon(string(name));
           
          -    LOGDEB0(("Doc_setattr: [%s] (%s) -> [%s]\n", key.c_str(), name, uvalue));
          +    LOGDEB0("Doc_setattr: doc " << self->doc << " [" << key << "] (" << name <<
          +            ") -> [" << uvalue << "]\n");
          +
               // We set the value in the meta array in all cases. Good idea ? or do it
               // only for fields without a dedicated Doc:: entry?
               self->doc->meta[key] = uvalue;
          @@ -695,9 +684,59 @@ Doc_setattr(recoll_DocObject *self, char *name, PyObject *value)
           	}
           	break;
               }
          +    Py_DECREF(putf8);
               return 0;
           }
           
          +static Py_ssize_t
          +Doc_length(recoll_DocObject *self)
          +{
          +    if (self->doc == 0 || the_docs.find(self->doc) == the_docs.end()) {
          +        PyErr_SetString(PyExc_AttributeError, "doc??");
          +	return -1;
          +    }
          +    return self->doc->meta.size();
          +}
          +
          +static PyObject *
          +Doc_subscript(recoll_DocObject *self, PyObject *key)
          +{
          +    if (self->doc == 0 || the_docs.find(self->doc) == the_docs.end()) {
          +        PyErr_SetString(PyExc_AttributeError, "doc??");
          +	return NULL;
          +    }
          +    char *name = 0;
          +    if (PyUnicode_Check(key)) {
          +        PyObject* utf8o = PyUnicode_AsUTF8String(key);
          +	if (utf8o == 0) {
          +	    LOGERR("Doc_getitemo: encoding name to utf8 failed\n" );
          +	    PyErr_SetString(PyExc_AttributeError, "name??");
          +	    Py_RETURN_NONE;
          +	}
          +	name = PyBytes_AsString(utf8o);
          +	Py_DECREF(utf8o);
          +    }  else if (PyBytes_Check(key)) {
          +	name = PyBytes_AsString(key);
          +    } else {
          +	PyErr_SetString(PyExc_AttributeError, "key not unicode nor string??");
          +	Py_RETURN_NONE;
          +    }
          +
          +    string skey = rclconfig->fieldQCanon(string(name));
          +    string value;
          +    if (idocget(self, skey, value)) {
          +	return PyUnicode_Decode(value.c_str(), value.size(), "UTF-8","replace");
          +    }
          +
          +    Py_RETURN_NONE;
          +}
          +
          +static PyMappingMethods doc_as_mapping = {
          +    (lenfunc)Doc_length, /*mp_length*/
          +    (binaryfunc)Doc_subscript, /*mp_subscript*/
          +    (objobjargproc)0, /*mp_ass_subscript*/
          +};
          +
           
           PyDoc_STRVAR(doc_DocObject,
           "Doc()\n"
          @@ -742,20 +781,20 @@ static PyTypeObject recoll_DocType = {
               0,                         /*tp_itemsize*/
               (destructor)Doc_dealloc,    /*tp_dealloc*/
               0,                         /*tp_print*/
          -    0,  /*tp_getattr*/
          -    (setattrfunc)Doc_setattr, /*tp_setattr*/
          +    0,                         /*tp_getattr*/
          +    (setattrfunc)Doc_setattr,  /*tp_setattr*/
               0,                         /*tp_compare*/
               0,                         /*tp_repr*/
               0,                         /*tp_as_number*/
               0,                         /*tp_as_sequence*/
          -    0,                         /*tp_as_mapping*/
          +    &doc_as_mapping,          /*tp_as_mapping */
               0,                         /*tp_hash */
               0,                         /*tp_call*/
               0,                         /*tp_str*/
               (getattrofunc)Doc_getattro,/*tp_getattro*/
               0,                         /*tp_setattro*/
               0,                         /*tp_as_buffer*/
          -    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,        /*tp_flags*/
          +    Py_TPFLAGS_DEFAULT,        /*tp_flags*/
               doc_DocObject,             /* tp_doc */
               0,		               /* tp_traverse */
               0,		               /* tp_clear */
          @@ -799,7 +838,7 @@ PyDoc_STRVAR(doc_Query_close,
           static PyObject *
           Query_close(recoll_QueryObject *self)
           {
          -    LOGDEB(("Query_close\n"));
          +    LOGDEB("Query_close\n" );
               if (self->query) {
           	the_queries.erase(self->query);
                   deleteZ(self->query);
          @@ -815,7 +854,7 @@ Query_close(recoll_QueryObject *self)
           static void 
           Query_dealloc(recoll_QueryObject *self)
           {
          -    LOGDEB(("Query_dealloc\n"));
          +    LOGDEB("Query_dealloc\n" );
               PyObject *ret = Query_close(self);
               Py_DECREF(ret);
               Py_TYPE(self)->tp_free((PyObject*)self);
          @@ -824,7 +863,7 @@ Query_dealloc(recoll_QueryObject *self)
           static PyObject *
           Query_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
           {
          -    LOGDEB(("Query_new\n"));
          +    LOGDEB("Query_new\n" );
               recoll_QueryObject *self;
           
               self = (recoll_QueryObject *)type->tp_alloc(type, 0);
          @@ -846,7 +885,7 @@ Query_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
           static int
           Query_init(recoll_QueryObject *self, PyObject *, PyObject *)
           {
          -    LOGDEB(("Query_init\n"));
          +    LOGDEB("Query_init\n" );
           
               if (self->query)
           	the_queries.erase(self->query);
          @@ -874,7 +913,7 @@ PyDoc_STRVAR(doc_Query_sortby,
           static PyObject *
           Query_sortby(recoll_QueryObject* self, PyObject *args, PyObject *kwargs)
           {
          -    LOGDEB0(("Query_sortby\n"));
          +    LOGDEB0("Query_sortby\n" );
               static const char *kwlist[] = {"field", "ascending", NULL};
               char *sfield = 0;
               PyObject *ascobj = 0;
          @@ -908,7 +947,7 @@ PyDoc_STRVAR(doc_Query_execute,
           static PyObject *
           Query_execute(recoll_QueryObject* self, PyObject *args, PyObject *kwargs)
           {
          -    LOGDEB0(("Query_execute\n"));
          +    LOGDEB0("Query_execute\n" );
               static const char *kwlist[] = {"query_string", "stemming", "stemlang", NULL};
               char *sutf8 = 0; // needs freeing
               char *sstemlang = 0;
          @@ -931,8 +970,7 @@ Query_execute(recoll_QueryObject* self, PyObject *args, PyObject *kwargs)
           	PyMem_Free(sstemlang);
               }
           
          -    LOGDEB0(("Query_execute: [%s] dostem %d stemlang [%s]\n", utf8.c_str(), 
          -	    dostem, stemlang.c_str()));
          +    LOGDEB0("Query_execute: ["  << (utf8) << "] dostem "  << (dostem) << " stemlang ["  << (stemlang) << "]\n" );
           
               if (self->query == 0 || 
           	the_queries.find(self->query) == the_queries.end()) {
          @@ -951,7 +989,7 @@ Query_execute(recoll_QueryObject* self, PyObject *args, PyObject *kwargs)
           	return 0;
               }
           
          -    RefCntr rq(sd);
          +    std::shared_ptr rq(sd);
               self->query->setSortBy(*self->sortfield, self->ascending);
               self->query->setQuery(rq);
               int cnt = self->query->getResCnt();
          @@ -969,7 +1007,7 @@ PyDoc_STRVAR(doc_Query_executesd,
           static PyObject *
           Query_executesd(recoll_QueryObject* self, PyObject *args, PyObject *kwargs)
           {
          -    LOGDEB0(("Query_executeSD\n"));
          +    LOGDEB0("Query_executeSD\n" );
               static const char *kwlist[] = {"searchdata", NULL};
               recoll_SearchDataObject *pysd = 0;
               if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!:Query_execute", 
          @@ -1011,7 +1049,7 @@ PyDoc_STRVAR(doc_Query_fetchone,
           static PyObject *
           Query_fetchone(PyObject *_self)
           {
          -    LOGDEB0(("Query_fetchone/next\n"));
          +    LOGDEB0("Query_fetchone/next\n" );
               recoll_QueryObject* self = (recoll_QueryObject*)_self;
           
               if (self->query == 0 || 
          @@ -1030,13 +1068,12 @@ Query_fetchone(PyObject *_self)
                   PyErr_SetString(PyExc_EnvironmentError, "doc create failed");
           	return 0;
               }
          -    if (self->next >= self->rowcount) {
          -        PyErr_SetNone(PyExc_StopIteration);
          -	return 0;
          -    }
          +
          +    // We used to check against rowcount here, but this was wrong:
          +    // xapian result count estimate are sometimes wrong, we must go on
          +    // fetching until we fail
               if (!self->query->getDoc(self->next, *result->doc)) {
          -        PyErr_SetString(PyExc_EnvironmentError, "query: cant fetch result");
          -	self->next = -1;
          +        PyErr_SetNone(PyExc_StopIteration);
           	return 0;
               }
               self->next++;
          @@ -1053,7 +1090,7 @@ PyDoc_STRVAR(doc_Query_fetchmany,
           static PyObject *
           Query_fetchmany(recoll_QueryObject* self, PyObject *args, PyObject *kwargs)
           {
          -    LOGDEB0(("Query_fetchmany\n"));
          +    LOGDEB0("Query_fetchmany\n" );
               static const char *kwlist[] = {"size", NULL};
               int size = 0;
           
          @@ -1076,8 +1113,7 @@ Query_fetchmany(recoll_QueryObject* self, PyObject *args, PyObject *kwargs)
               }
           
               PyObject *reslist = PyList_New(0);
          -    int howmany = MIN(self->rowcount - self->next, size);
          -    for (int i = 0; i < howmany; i++) {
          +    for (int i = 0; i < size; i++) {
                   recoll_DocObject *docobj = (recoll_DocObject *)
           	    PyObject_CallObject((PyObject *)&recoll_DocType, 0);
                   if (!docobj) {
          @@ -1085,9 +1121,8 @@ Query_fetchmany(recoll_QueryObject* self, PyObject *args, PyObject *kwargs)
                       return 0;
                   }
                   if (!self->query->getDoc(self->next, *docobj->doc)) {
          -            PyErr_SetString(PyExc_EnvironmentError, "can't fetch");
          -            self->next = -1;
          -            return 0;
          +            PyErr_SetNone(PyExc_StopIteration);
          +            break;
                   }
                   self->next++;
                   movedocfields(docobj->doc);
          @@ -1105,7 +1140,7 @@ PyDoc_STRVAR(doc_Query_scroll,
           static PyObject *
           Query_scroll(recoll_QueryObject* self, PyObject *args, PyObject *kwargs)
           {
          -    LOGDEB0(("Query_scroll\n"));
          +    LOGDEB0("Query_scroll\n" );
               static const char *kwlist[] = {"position", "mode", NULL};
               int pos = 0;
               char *smode = 0;
          @@ -1192,7 +1227,7 @@ public:
           static PyObject *
           Query_highlight(recoll_QueryObject* self, PyObject *args, PyObject *kwargs)
           {
          -    LOGDEB0(("Query_highlight\n"));
          +    LOGDEB0("Query_highlight\n" );
               static const char *kwlist[] = {"text", "ishtml", "eolbr", "methods", NULL};
               char *sutf8 = 0; // needs freeing
               int ishtml = 0;
          @@ -1214,7 +1249,7 @@ Query_highlight(recoll_QueryObject* self, PyObject *args, PyObject *kwargs)
           	ishtml = 1;
               if (eolbrobj && !PyObject_IsTrue(eolbrobj))
           	eolbr = 0;
          -    LOGDEB0(("Query_highlight: ishtml %d\n", ishtml));
          +    LOGDEB0("Query_highlight: ishtml "  << (ishtml) << "\n" );
           
               if (self->query == 0 || 
           	the_queries.find(self->query) == the_queries.end()) {
          @@ -1222,8 +1257,8 @@ Query_highlight(recoll_QueryObject* self, PyObject *args, PyObject *kwargs)
           	return 0;
               }
           
          -    RefCntr sd = self->query->getSD();
          -    if (sd.isNull()) {
          +    std::shared_ptr sd = self->query->getSD();
          +    if (!sd) {
           	PyErr_SetString(PyExc_ValueError, "Query not initialized");
           	return 0;
               }
          @@ -1252,7 +1287,7 @@ PyDoc_STRVAR(doc_Query_makedocabstract,
           static PyObject *
           Query_makedocabstract(recoll_QueryObject* self, PyObject *args,PyObject *kwargs)
           {
          -    LOGDEB0(("Query_makeDocAbstract\n"));
          +    LOGDEB0("Query_makeDocAbstract\n" );
               static const char *kwlist[] = {"doc", "methods", NULL};
               recoll_DocObject *pydoc = 0;
               PyObject *hlmethods = 0;
          @@ -1264,17 +1299,17 @@ Query_makedocabstract(recoll_QueryObject* self, PyObject *args,PyObject *kwargs)
               }
           
               if (pydoc->doc == 0 || the_docs.find(pydoc->doc) == the_docs.end()) {
          -	LOGERR(("Query_makeDocAbstract: doc not found %p\n", pydoc->doc));
          +	LOGERR("Query_makeDocAbstract: doc not found "  << (pydoc->doc) << "\n" );
                   PyErr_SetString(PyExc_AttributeError, "doc");
                   return 0;
               }
               if (the_queries.find(self->query) == the_queries.end()) {
          -	LOGERR(("Query_makeDocAbstract: query not found %p\n", self->query));
          +	LOGERR("Query_makeDocAbstract: query not found "  << (self->query) << "\n" );
                   PyErr_SetString(PyExc_AttributeError, "query");
                   return 0;
               }
          -    RefCntr sd = self->query->getSD();
          -    if (sd.isNull()) {
          +    std::shared_ptr sd = self->query->getSD();
          +    if (!sd) {
           	PyErr_SetString(PyExc_ValueError, "Query not initialized");
           	return 0;
               }
          @@ -1322,15 +1357,15 @@ PyDoc_STRVAR(doc_Query_getxquery,
           static PyObject *
           Query_getxquery(recoll_QueryObject* self, PyObject *, PyObject *)
           {
          -    LOGDEB0(("Query_getxquery self->query %p\n", self->query));
          +    LOGDEB0("Query_getxquery self->query "  << (self->query) << "\n" );
           
               if (self->query == 0 || 
           	the_queries.find(self->query) == the_queries.end()) {
                   PyErr_SetString(PyExc_AttributeError, "query");
           	return 0;
               }
          -    RefCntr sd = self->query->getSD();
          -    if (sd.isNull()) {
          +    std::shared_ptr sd = self->query->getSD();
          +    if (!sd) {
           	PyErr_SetString(PyExc_ValueError, "Query not initialized");
           	return 0;
               }
          @@ -1350,15 +1385,15 @@ PyDoc_STRVAR(doc_Query_getgroups,
           static PyObject *
           Query_getgroups(recoll_QueryObject* self, PyObject *, PyObject *)
           {
          -    LOGDEB0(("Query_getgroups\n"));
          +    LOGDEB0("Query_getgroups\n" );
           
               if (self->query == 0 || 
           	the_queries.find(self->query) == the_queries.end()) {
                   PyErr_SetString(PyExc_AttributeError, "query");
           	return 0;
               }
          -    RefCntr sd = self->query->getSD();
          -    if (sd.isNull()) {
          +    std::shared_ptr sd = self->query->getSD();
          +    if (!sd) {
           	PyErr_SetString(PyExc_ValueError, "Query not initialized");
           	return 0;
               }
          @@ -1495,7 +1530,7 @@ typedef struct recoll_DbObject {
           static PyObject *
           Db_close(recoll_DbObject *self)
           {
          -    LOGDEB(("Db_close. self %p\n", self));
          +    LOGDEB("Db_close. self "  << (self) << "\n" );
               if (self->db) {
           	the_dbs.erase(self->db);
                   delete self->db;
          @@ -1507,7 +1542,7 @@ Db_close(recoll_DbObject *self)
           static void 
           Db_dealloc(recoll_DbObject *self)
           {
          -    LOGDEB(("Db_dealloc\n"));
          +    LOGDEB("Db_dealloc\n" );
               PyObject *ret = Db_close(self);
               Py_DECREF(ret);
               Py_TYPE(self)->tp_free((PyObject*)self);
          @@ -1516,7 +1551,7 @@ Db_dealloc(recoll_DbObject *self)
           static PyObject *
           Db_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
           {
          -    LOGDEB2(("Db_new\n"));
          +    LOGDEB2("Db_new\n" );
               recoll_DbObject *self;
           
               self = (recoll_DbObject *)type->tp_alloc(type, 0);
          @@ -1548,7 +1583,7 @@ Db_init(recoll_DbObject *self, PyObject *args, PyObject *kwargs)
               } else {
           	rclconfig = recollinit(0, 0, reason, 0);
               }
          -    LOGDEB(("Db_init\n"));
          +    LOGDEB("Db_init\n" );
           
               if (rclconfig == 0) {
           	PyErr_SetString(PyExc_EnvironmentError, reason.c_str());
          @@ -1564,7 +1599,7 @@ Db_init(recoll_DbObject *self, PyObject *args, PyObject *kwargs)
               delete self->db;
               self->db = new Rcl::Db(rclconfig);
               if (!self->db->open(writable ? Rcl::Db::DbUpd : Rcl::Db::DbRO)) {
          -	LOGERR(("Db_init: db open error\n"));
          +	LOGERR("Db_init: db open error\n" );
           	PyErr_SetString(PyExc_EnvironmentError, "Can't open index");
                   return -1;
               }
          @@ -1607,9 +1642,9 @@ Db_init(recoll_DbObject *self, PyObject *args, PyObject *kwargs)
           static PyObject *
           Db_query(recoll_DbObject* self)
           {
          -    LOGDEB(("Db_query\n"));
          +    LOGDEB("Db_query\n" );
               if (self->db == 0 || the_dbs.find(self->db) == the_dbs.end()) {
          -	LOGERR(("Db_query: db not found %p\n", self->db));
          +	LOGERR("Db_query: db not found "  << (self->db) << "\n" );
                   PyErr_SetString(PyExc_AttributeError, "db");
                   return 0;
               }
          @@ -1628,18 +1663,18 @@ Db_query(recoll_DbObject* self)
           static PyObject *
           Db_setAbstractParams(recoll_DbObject *self, PyObject *args, PyObject *kwargs)
           {
          -    LOGDEB0(("Db_setAbstractParams\n"));
          +    LOGDEB0("Db_setAbstractParams\n" );
               static const char *kwlist[] = {"maxchars", "contextwords", NULL};
               int ctxwords = -1, maxchars = -1;
               if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", (char**)kwlist,
           				     &maxchars, &ctxwords))
           	return 0;
               if (self->db == 0 || the_dbs.find(self->db) == the_dbs.end()) {
          -	LOGERR(("Db_query: db not found %p\n", self->db));
          +	LOGERR("Db_query: db not found "  << (self->db) << "\n" );
                   PyErr_SetString(PyExc_AttributeError, "db id not found");
                   return 0;
               }
          -    LOGDEB0(("Db_setAbstractParams: mxchrs %d, ctxwrds %d\n", maxchars, ctxwords));
          +    LOGDEB0("Db_setAbstractParams: mxchrs "  << (maxchars) << ", ctxwrds "  << (ctxwords) << "\n" );
               self->db->setAbstractParams(-1, maxchars, ctxwords);
               Py_RETURN_NONE;
           }
          @@ -1647,7 +1682,7 @@ Db_setAbstractParams(recoll_DbObject *self, PyObject *args, PyObject *kwargs)
           static PyObject *
           Db_makeDocAbstract(recoll_DbObject* self, PyObject *args)
           {
          -    LOGDEB0(("Db_makeDocAbstract\n"));
          +    LOGDEB0("Db_makeDocAbstract\n" );
               recoll_DocObject *pydoc = 0;
               recoll_QueryObject *pyquery = 0;
               if (!PyArg_ParseTuple(args, "O!O!:Db_makeDocAbstract",
          @@ -1656,18 +1691,18 @@ Db_makeDocAbstract(recoll_DbObject* self, PyObject *args)
           	return 0;
               }
               if (self->db == 0 || the_dbs.find(self->db) == the_dbs.end()) {
          -	LOGERR(("Db_makeDocAbstract: db not found %p\n", self->db));
          +	LOGERR("Db_makeDocAbstract: db not found "  << (self->db) << "\n" );
                   PyErr_SetString(PyExc_AttributeError, "db");
                   return 0;
               }
               if (pydoc->doc == 0 || the_docs.find(pydoc->doc) == the_docs.end()) {
          -	LOGERR(("Db_makeDocAbstract: doc not found %p\n", pydoc->doc));
          +	LOGERR("Db_makeDocAbstract: doc not found "  << (pydoc->doc) << "\n" );
                   PyErr_SetString(PyExc_AttributeError, "doc");
                   return 0;
               }
               if (pyquery->query == 0 || 
           	the_queries.find(pyquery->query) == the_queries.end()) {
          -	LOGERR(("Db_makeDocAbstract: query not found %p\n", pyquery->query));
          +	LOGERR("Db_makeDocAbstract: query not found "  << (pyquery->query) << "\n" );
                   PyErr_SetString(PyExc_AttributeError, "query");
                   return 0;
               }
          @@ -1692,7 +1727,7 @@ PyDoc_STRVAR(doc_Db_termMatch,
           static PyObject *
           Db_termMatch(recoll_DbObject* self, PyObject *args, PyObject *kwargs)
           {
          -    LOGDEB0(("Db_termMatch\n"));
          +    LOGDEB0("Db_termMatch\n" );
               static const char *kwlist[] = {"type", "expr", "field", "maxlen", 
           				   "casesens", "diacsens", "lang", NULL};
               char *tp = 0;
          @@ -1715,7 +1750,7 @@ Db_termMatch(recoll_DbObject* self, PyObject *args, PyObject *kwargs)
           	return 0;
           
               if (self->db == 0 || the_dbs.find(self->db) == the_dbs.end()) {
          -	LOGERR(("Db_termMatch: db not found %p\n", self->db));
          +	LOGERR("Db_termMatch: db not found "  << (self->db) << "\n" );
                   PyErr_SetString(PyExc_AttributeError, "db");
           	goto out;
               }
          @@ -1740,7 +1775,7 @@ Db_termMatch(recoll_DbObject* self, PyObject *args, PyObject *kwargs)
           
               if (!self->db->termMatch(typ_sens, lang ? lang : "english", 
           			     expr, result, maxlen, field ? field : "")) {
          -	LOGERR(("Db_termMatch: db termMatch error\n"));
          +	LOGERR("Db_termMatch: db termMatch error\n" );
                   PyErr_SetString(PyExc_AttributeError, "rcldb termMatch error");
           	goto out;
               }
          @@ -1761,7 +1796,7 @@ out:
           static PyObject *
           Db_needUpdate(recoll_DbObject* self, PyObject *args, PyObject *kwds)
           {
          -    LOGDEB0(("Db_needUpdate\n"));
          +    LOGDEB0("Db_needUpdate\n" );
               char *udi = 0; // needs freeing
               char *sig = 0; // needs freeing
               if (!PyArg_ParseTuple(args, "eses:Db_needUpdate", 
          @@ -1769,7 +1804,7 @@ Db_needUpdate(recoll_DbObject* self, PyObject *args, PyObject *kwds)
           	return 0;
               }
               if (self->db == 0 || the_dbs.find(self->db) == the_dbs.end()) {
          -	LOGERR(("Db_needUpdate: db not found %p\n", self->db));
          +	LOGERR("Db_needUpdate: db not found "  << (self->db) << "\n" );
                   PyErr_SetString(PyExc_AttributeError, "db");
           	PyMem_Free(udi);
           	PyMem_Free(sig);
          @@ -1784,13 +1819,13 @@ Db_needUpdate(recoll_DbObject* self, PyObject *args, PyObject *kwds)
           static PyObject *
           Db_delete(recoll_DbObject* self, PyObject *args, PyObject *kwds)
           {
          -    LOGDEB0(("Db_delete\n"));
          +    LOGDEB0("Db_delete\n" );
               char *udi = 0; // needs freeing
               if (!PyArg_ParseTuple(args, "es:Db_delete", "utf-8", &udi)) {
           	return 0;
               }
               if (self->db == 0 || the_dbs.find(self->db) == the_dbs.end()) {
          -	LOGERR(("Db_delete: db not found %p\n", self->db));
          +	LOGERR("Db_delete: db not found "  << (self->db) << "\n" );
                   PyErr_SetString(PyExc_AttributeError, "db");
           	PyMem_Free(udi);
                   return 0;
          @@ -1803,9 +1838,9 @@ Db_delete(recoll_DbObject* self, PyObject *args, PyObject *kwds)
           static PyObject *
           Db_purge(recoll_DbObject* self)
           {
          -    LOGDEB0(("Db_purge\n"));
          +    LOGDEB0("Db_purge\n" );
               if (self->db == 0 || the_dbs.find(self->db) == the_dbs.end()) {
          -	LOGERR(("Db_purge: db not found %p\n", self->db));
          +	LOGERR("Db_purge: db not found "  << (self->db) << "\n" );
                   PyErr_SetString(PyExc_AttributeError, "db");
                   return 0;
               }
          @@ -1816,7 +1851,7 @@ Db_purge(recoll_DbObject* self)
           static PyObject *
           Db_addOrUpdate(recoll_DbObject* self, PyObject *args, PyObject *)
           {
          -    LOGDEB0(("Db_addOrUpdate\n"));
          +    LOGDEB0("Db_addOrUpdate\n" );
               char *sudi = 0; // needs freeing
               char *sparent_udi = 0; // needs freeing
               recoll_DocObject *pydoc;
          @@ -1832,17 +1867,17 @@ Db_addOrUpdate(recoll_DbObject* self, PyObject *args, PyObject *)
               PyMem_Free(sparent_udi);
           
               if (self->db == 0 || the_dbs.find(self->db) == the_dbs.end()) {
          -	LOGERR(("Db_addOrUpdate: db not found %p\n", self->db));
          +	LOGERR("Db_addOrUpdate: db not found "  << (self->db) << "\n" );
                   PyErr_SetString(PyExc_AttributeError, "db");
                   return 0;
               }
               if (pydoc->doc == 0 || the_docs.find(pydoc->doc) == the_docs.end()) {
          -	LOGERR(("Db_addOrUpdate: doc not found %p\n", pydoc->doc));
          +	LOGERR("Db_addOrUpdate: doc not found "  << (pydoc->doc) << "\n" );
                   PyErr_SetString(PyExc_AttributeError, "doc");
                   return 0;
               }
               if (!self->db->addOrUpdate(udi, parent_udi, *pydoc->doc)) {
          -	LOGERR(("Db_addOrUpdate: rcldb error\n"));
          +	LOGERR("Db_addOrUpdate: rcldb error\n" );
                   PyErr_SetString(PyExc_AttributeError, "rcldb error");
                   return 0;
               }
          @@ -1957,7 +1992,7 @@ static PyTypeObject recoll_DbType = {
           static PyObject *
           recoll_connect(PyObject *self, PyObject *args, PyObject *kwargs)
           {
          -    LOGDEB2(("recoll_connect\n"));
          +    LOGDEB2("recoll_connect\n" );
               recoll_DbObject *db = (recoll_DbObject *)
           	PyObject_Call((PyObject *)&recoll_DbType, args, kwargs);
               return (PyObject *)db;
          @@ -2092,3 +2127,4 @@ initrecoll(void)
               return module;
           #endif
           }
          +
          diff --git a/src/python/recoll/recoll/rclconfig.py b/src/python/recoll/recoll/rclconfig.py
          index 12485fa9..7bbca274 100755
          --- a/src/python/recoll/recoll/rclconfig.py
          +++ b/src/python/recoll/recoll/rclconfig.py
          @@ -1,10 +1,12 @@
           #!/usr/bin/env python
          +from __future__ import print_function
           
           import locale
           import re
           import os
           import sys
           import base64
          +import platform
           
           class ConfSimple:
               """A ConfSimple class reads a recoll configuration file, which is a typical
          @@ -73,7 +75,7 @@ class ConfSimple:
               def getNames(self, sk = ''):
                   if not sk in self.submaps:
                       return None
          -        return self.submaps[sk].keys()
          +        return list(self.submaps[sk].keys())
               
           class ConfTree(ConfSimple):
               """A ConfTree adds path-hierarchical interpretation of the section keys,
          @@ -143,14 +145,24 @@ class RclDynConf:
               
           class RclConfig:
               def __init__(self, argcnf = None):
          +        self.config = None
          +        platsys = platform.system()
                   # Find configuration directory
                   if argcnf is not None:
                       self.confdir = os.path.abspath(argcnf)
                   elif "RECOLL_CONFDIR" in os.environ:
                       self.confdir = os.environ["RECOLL_CONFDIR"]
                   else:
          -            self.confdir = os.path.expanduser("~/.recoll")
          -        #print("Confdir: [%s]" % self.confdir)
          +            if platsys == "Windows":
          +                if "LOCALAPPDATA" in os.environ:
          +                    dir = os.environ["LOCALAPPDATA"]
          +                else:
          +                    dir = os.path.expanduser("~")
          +                self.confdir = os.path.join(dir, "Recoll")
          +            else:
          +                self.confdir = os.path.expanduser("~/.recoll")
          +        #print("Confdir: [%s]" % self.confdir, file=sys.stderr)
          +        
                   # Also find datadir. This is trickier because this is set by
                   # "configure" in the C code. We can only do our best. Have to
                   # choose a preference order. Use RECOLL_DATADIR if the order is wrong
          @@ -158,14 +170,17 @@ class RclConfig:
                   if "RECOLL_DATADIR" in os.environ:
                       self.datadir = os.environ["RECOLL_DATADIR"]
                   else:
          -            dirs = ("/opt/local", "/usr", "/usr/local")
          -            for dir in dirs:
          -                dd = os.path.join(dir, "share/recoll")
          -                if os.path.exists(dd):
          -                    self.datadir = dd
          +            if platsys == "Windows":
          +                self.datadir = os.path.join(os.path.dirname(sys.argv[0]), "..")
          +            else:
          +                dirs = ("/opt/local", "/usr", "/usr/local")
          +                for dir in dirs:
          +                    dd = os.path.join(dir, "share/recoll")
          +                    if os.path.exists(dd):
          +                        self.datadir = dd
                   if self.datadir is None:
                       self.datadir = "/usr/share/recoll"
          -        #print("Datadir: [%s]" % self.datadir)
          +        #print("Datadir: [%s]" % self.datadir, file=sys.stderr)
                   self.cdirs = []
                   
                   # Additional config directory, values override user ones
          @@ -176,8 +191,7 @@ class RclConfig:
                   if "RECOLL_CONFMID" in os.environ:
                       self.cdirs.append(os.environ["RECOLL_CONFMID"])
                   self.cdirs.append(os.path.join(self.datadir, "examples"))
          -
          -        self.config = ConfStack("recoll.conf", self.cdirs, "tree")
          +        #print("Config dirs: %s" % self.cdirs, file=sys.stderr)
                   self.keydir = ''
           
               def getConfDir(self):
          @@ -187,6 +201,8 @@ class RclConfig:
                   self.keydir = dir
           
               def getConfParam(self, nm):
          +        if not self.config:
          +            self.config = ConfStack("recoll.conf", self.cdirs, "tree")
                   return self.config.get(nm, self.keydir)
                   
           class RclExtraDbs:
          diff --git a/src/python/recoll/setup.py.in b/src/python/recoll/setup.py.in
          index bc9054aa..eb49217e 100644
          --- a/src/python/recoll/setup.py.in
          +++ b/src/python/recoll/setup.py.in
          @@ -3,72 +3,67 @@ import os
           import sys
           
           sysname = os.uname()[0]
          -top = os.path.join('..', '..')
           
          +# For shadow builds: references to the source tree
          +top = os.path.join('@srcdir@', '..', '..')
          +pytop = '@srcdir@'
           
          -library_dirs = [os.path.join(top, 'lib')]
          +# For shadow builds: reference to the top of the local tree (for finding
          +# generated .h files, e.g. autoconfig.h)
          +localtop = os.path.join(os.path.dirname(__file__), '..', '..')
          +
          +library_dirs = [os.path.join(localtop, '.libs')]
           if "CYGWIN" in os.environ:
           	libraries =  ['recoll', 'xapian', 'iconv', 'z']
           else:
           	libraries = ['recoll']
           	
          +extra_compile_args = ['-std=c++11']
           
           if 'libdir' in os.environ and os.environ['libdir'] != "":
               runtime_library_dirs = [os.path.join(os.environ['libdir'], 'recoll')]
           else:
               runtime_library_dirs = [os.path.join('@prefix@', 'lib', 'recoll')]
           
          -# Verify that the Recoll library was compiled with the PIC options
          -localdefs = os.path.join(top, 'mk', 'localdefs')
          -try:
          -    lines = open(localdefs, 'r').readlines()
          -except:
          -    print('You need to build recoll first. Use configure --enable-pic')
          -    sys.exit(1)
          -picok = False
          -for line in lines:
          -    if line.find('PICFLAGS') == 0:
          -        picok = True
          -        break
          -if not picok:
          -    print('You need to rebuild recoll with PIC enabled. Use configure --enable-pic and make clean')
          -    sys.exit(1)
          -
          -                               
           module1 = Extension('recoll',
                               define_macros = [('MAJOR_VERSION', '1'),
                                                ('MINOR_VERSION', '0'),
                                                ('UNAC_VERSION', '"1.0.7"'),
          -                                     ('RECOLL_DATADIR', '"@QTRECOLL_DATADIR@"')
          +                                     ('RECOLL_DATADIR', '"@RECOLL_DATADIR@"')
                                                ],
                               include_dirs = ['/usr/local/include',
                                               os.path.join(top, 'utils'), 
          +                                    os.path.join(top, 'common'),
          +                                    os.path.join(localtop, 'common'),
                                               os.path.join(top, 'common'), 
                                               os.path.join(top, 'rcldb'), 
                                               os.path.join(top, 'query'), 
                                               os.path.join(top, 'unac')
                                               ],
          +                    extra_compile_args = extra_compile_args,
                               libraries = libraries,
                               library_dirs = library_dirs,
                               runtime_library_dirs = runtime_library_dirs,
          -                    sources = ['pyrecoll.cpp'])
          +                    sources = [os.path.join(pytop, 'pyrecoll.cpp')])
           
           module2 = Extension('rclextract',
                               define_macros = [('MAJOR_VERSION', '1'),
                                                ('MINOR_VERSION', '0'),
                                                ('UNAC_VERSION', '"1.0.7"'),
          -                                     ('RECOLL_DATADIR', '"@QTRECOLL_DATADIR@"')
          +                                     ('RECOLL_DATADIR', '"@RECOLL_DATADIR@"')
                                                ],
                               include_dirs = ['/usr/local/include',
                                               os.path.join(top, 'utils'), 
                                               os.path.join(top, 'common'), 
          +                                    os.path.join(localtop, 'common'),
                                               os.path.join(top, 'internfile'), 
                                               os.path.join(top, 'rcldb'), 
                                               ],
          +                    extra_compile_args = extra_compile_args,
                               libraries = libraries,
                               library_dirs = library_dirs,
                               runtime_library_dirs = runtime_library_dirs,
          -                    sources = ['pyrclextract.cpp'])
          +                    sources = [os.path.join(pytop, 'pyrclextract.cpp')])
           
           setup (name = 'Recoll',
                  version = '1.0',
          diff --git a/src/python/samples/README.txt b/src/python/samples/README.txt
          new file mode 100644
          index 00000000..33fac234
          --- /dev/null
          +++ b/src/python/samples/README.txt
          @@ -0,0 +1,27 @@
          +Python samples:
          +
          +rclmbox.py
          +backends
          +A sample external indexer and its backends link file (see the user manual
          +programming section). The sample indexes a directory containing mbox files.
          +
          +rcldlkp.py
          +Another sample indexer for a simple %-separated record format.
          +
          +recollq.py
          +recollqsd.py
          +Sample query programs based on the Python query interface.
          +
          +recollgui/
          +A sample GUI based on the python query interface.
          +
          +docdups.py
          +A script based on the Xapian Python interface which explores a Recoll index
          +and prints out sets of duplicate documents (based on the md5 hashes).
          +
          +mutt-recoll.py
          +Interface between recoll and mutt (based on mutt-notmuch). Not related to
          +the Recoll Python API, this executes recollq.
          +
          +trconfig.py
          +Not useful at all: internal exercises for the python rclconfig interface.
          diff --git a/src/python/samples/backends b/src/python/samples/backends
          new file mode 100644
          index 00000000..52ef10f2
          --- /dev/null
          +++ b/src/python/samples/backends
          @@ -0,0 +1,5 @@
          +[MBOX]
          +fetch = python \
          +/home/dockes/projets/fulltext/recoll/src/python/samples/rclmbox.py fetch
          +makesig = python \
          +/home/dockes/projets/fulltext/recoll/src/python/samples/rclmbox.py makesig
          diff --git a/src/python/samples/rclmbox.py b/src/python/samples/rclmbox.py
          index 13c2a5b3..af127de0 100644
          --- a/src/python/samples/rclmbox.py
          +++ b/src/python/samples/rclmbox.py
          @@ -1,44 +1,101 @@
           #!/usr/bin/env python
          -"""An example that uses python tools to parse mbox/rfcxxx format and index
          -messages. Not supposed to run as-is or be really useful"""
          +"""This sample uses the Recoll Python API to index a directory
          +containing mbox files. This is not particularly useful as Recoll
          +itself can do this better (e.g. this script does not process
          +attachments), but it shows the use of most of the Recoll interface
          +features, except 'parent_udi' (we do not create a 'self' document to
          +act as the parent)."""
          +from __future__ import print_function
           
          +import sys
          +import glob
          +import os
          +import stat
           import mailbox
           import email.header
           import email.utils
          -#import sys
          +
           try:
               from recoll import recoll
           except:
               import recoll
           
          -import os
          -import stat
          +# EDIT
          +# Change this for some directory with mbox files, such as a
          +# Thunderbird/Icedove mail storage directory.
          +mbdir = os.path.expanduser("~/mail")
          +#mbdir = os.path.expanduser("~/.icedove/n8n19644.default/Mail/Local Folders/")
           
          -mbfile = os.path.expanduser("~/mbox")
          -rclconf = os.path.expanduser("~/.recoll")
          +# EDIT
          +# Change this to wherever you want your recoll data to live. Create
          +# the directory with a (possibly empty) recoll.conf in it before first
          +# running the script
          +rclconf = os.path.expanduser("~/.recoll-extern")
           
          +# Utility: extract text for named header
           def header_value(msg, nm, to_utf = False):
               value = msg.get(nm)
               if value == None:
                   return ""
          -    value = value.replace("\n", "")
          -    value = value.replace("\r", "")
          -    #print value
          +    #value = value.replace("\n", "")
          +    #value = value.replace("\r", "")
               parts = email.header.decode_header(value)
          -    #print parts
               univalue = u""
               for part in parts:
          -        if part[1] != None:
          -            univalue += unicode(part[0], part[1]) + " "
          -        else:
          -            univalue += part[0] + " "
          +        try:
          +            if part[1] != None:
          +                univalue += part[0].decode(part[1]) + u" "
          +            else:
          +                if isinstance(part[0], bytes):
          +                    univalue += part[0].decode("cp1252") + u" "
          +                else:
          +                    univalue += part[0] + u" "
          +        except Exception as err:
          +            print("Failed decoding header: %s" % err, file=sys.stderr)
          +            pass
               if to_utf:
                   return univalue.encode('utf-8')
               else:
                   return univalue
           
          +# Utility: extract text parts from body
          +def extract_text(msg):
          +    """Extract and decode all text/plain parts from the message"""
          +    text = u""
          +    # We only output the headers for previewing, else they're already
          +    # output/indexed as fields.
          +    if "RECOLL_FILTER_FORPREVIEW" in os.environ and \
          +           os.environ["RECOLL_FILTER_FORPREVIEW"] == "yes":
          +        text += u"From: " + header_value(msg, "From") + u"\n"
          +        text += u"To: " + header_value(msg, "To") + u"\n"
          +        text += u"Subject: " + header_value(msg, "Subject") + u"\n"
          +        # text += u"Content-Type: text/plain; charset=UTF-8\n"
          +        #text += u"Message-ID: " + header_value(msg, "Message-ID") + u"\n"
          +        text += u"\n"
          +    for part in msg.walk():
          +        if part.is_multipart():
          +            pass 
          +        else:
          +            ct = part.get_content_type()
          +            if ct.lower() == "text/plain":
          +                charset = part.get_content_charset("cp1252")
          +                try:
          +                    ntxt = part.get_payload(None, True).decode(charset)
          +                    text += ntxt
          +                except Exception as err:
          +                    print("Failed decoding payload: %s" % err,
          +                          file=sys.stderr)
          +                    pass
          +    return text
          +
          +
          +
           class mbox_indexer:
          -    def __init__(self, mbfile):
          +    """The indexer classs. An object is created for indexing one mbox folder"""
          +    def __init__(self, db, mbfile):
          +        """Initialize for writable db recoll.Db object and mbfile mbox
          +        file. We retrieve the the file size and mtime."""
          +        self.db = db
                   self.mbfile = mbfile
                   stdata = os.stat(mbfile)
                   self.fmtime = stdata[stat.ST_MTIME]
          @@ -46,73 +103,126 @@ class mbox_indexer:
                   self.msgnum = 1
           
               def sig(self):
          +        """Create update verification value for mbox file:
          +        modification time concatenated with size should cover most
          +        cases"""
                   return str(self.fmtime) + ":" + str(self.fbytes)
          +
               def udi(self, msgnum):
          +        """Create unique document identifier for message. This should
          +        be shorter than 150 bytes, which we optimistically don't check
          +        here, as we just concatenate the mbox file name and message
          +        number"""
                   return self.mbfile + ":" + str(msgnum)
           
          -    def index(self, db):
          -        if not db.needUpdate(self.udi(1), self.sig()):
          -            print("Index is up to date");
          +    def index(self):
          +        if not self.db.needUpdate(self.udi(1), self.sig()):
          +            print("Index is up to date for %s"%self.mbfile, file=sys.stderr);
                       return None
                   mb = mailbox.mbox(self.mbfile)
                   for msg in mb.values():
          -            print("Indexing message %d" % self.msgnum);
          -            self.index_message(db, msg)
          +            print("Indexing message %d" % self.msgnum, file=sys.stderr);
          +            self.index_message(msg)
                       self.msgnum += 1
          -
          -    def index_message(self, db, msg):
          +        
          +    def getdata(self, ipath):
          +        """Implements the 'fetch' data access interface (called at
          +        query time from the command line)."""
          +        #print("mbox::getdata: ipath: %s" % ipath, file=sys.stderr)
          +        imsgnum = int(ipath)
          +        mb = mailbox.mbox(self.mbfile)
          +        msgnum = 0;
          +        for msg in mb.values():
          +            msgnum += 1
          +            if msgnum == imsgnum:
          +                return extract_text(msg)
          +        return ""
          +        
          +    def index_message(self, msg):
                   doc = recoll.Doc()
          +
          +        # Misc standard recoll fields
                   doc.author = header_value(msg, "From")
                   doc.recipient = header_value(msg, "To") + " " + header_value(msg, "Cc")
          -        # url
          -        doc.url = "file://" + self.mbfile
          -        # utf8fn
          -        # ipath
          -        doc.ipath = str(self.msgnum)
          -        # mimetype
          -        doc.mimetype = "message/rfc822"
          -        # mtime
                   dte = header_value(msg, "Date")
                   tm = email.utils.parsedate_tz(dte)
                   if tm == None:
                       doc.mtime = str(self.fmtime)
                   else:
                       doc.mtime = str(email.utils.mktime_tz(tm))
          -        # origcharset
          -        # title
                   doc.title = header_value(msg, "Subject")
          -        # keywords
          -        # abstract
          -        # author
          -        # fbytes
                   doc.fbytes = str(self.fbytes)
          -        # text
          -        text = u""
          -        text += u"From: " + header_value(msg, "From") + u"\n"
          -        text += u"To: " + header_value(msg, "To") + u"\n"
          -        text += u"Subject: " + header_value(msg, "Subject") + u"\n"
          -        #text += u"Message-ID: " + header_value(msg, "Message-ID") + u"\n"
          -        text += u"\n"
          -        for part in msg.walk():
          -            if part.is_multipart():
          -                pass 
          -            else:
          -                ct = part.get_content_type()
          -                if ct.lower() == "text/plain":
          -                    charset = part.get_content_charset("iso-8859-1")
          -                    #print "charset: ", charset
          -                    #print "text: ", part.get_payload(None, True)
          -                    text += unicode(part.get_payload(None, True), charset)
          -        doc.text = text
          -        # dbytes
          -        doc.dbytes = str(len(text))
          -        # sig
          +
          +        # Custom field
          +        doc.myfield = "some value"
          +
          +        # Main document text and MIME type
          +        doc.text = extract_text(msg)
          +        doc.dbytes = str(len(doc.text.encode('UTF-8')))
          +        doc.mimetype = "text/plain"
          +        
          +        # Store data for later "up to date" checks
                   doc.sig = self.sig()
          +        
          +        # The rclbes field is the link between the index data and this
          +        # script when used at query time
          +        doc.rclbes = "MBOX"
          +
          +        # These get stored inside the index, and returned at query
          +        # time, but the main identifier is the condensed 'udi'
          +        doc.url = "file://" + self.mbfile
          +        doc.ipath = str(self.msgnum)
          +        # The udi is the unique document identifier, later used if we
          +        # want to e.g. delete the document index data (and other ops).
                   udi = self.udi(self.msgnum)
          -        db.addOrUpdate(udi, doc)
           
          +        self.db.addOrUpdate(udi, doc)
           
          -db = recoll.connect(confdir=rclconf, writable=1)
          +# Index a directory containing mbox files
          +def index_mboxdir(dir):
          +    db = recoll.connect(confdir=rclconf, writable=1)
          +    entries = glob.glob(dir + "/*")
          +    for ent in entries:
          +        if '.' in os.path.basename(ent):
          +            # skip .log etc. our mboxes have no exts
          +            continue
          +        if not os.path.isfile(ent):
          +            continue
          +        print("Processing %s"%ent)
          +        mbidx = mbox_indexer(db, ent)
          +        mbidx.index()
          +    db.purge()
           
          -mbidx = mbox_indexer(mbfile)
          -mbidx.index(db)
          +usage_string='''Usage:
          +rclmbox.py
          +    Index the directory (the path is hard-coded inside the script)
          +rclmbox.py [fetch|makesig] udi url ipath
          +    fetch subdoc data or make signature (query time)
          +'''
          +def usage():
          +    print("%s" % usage_string, file=sys.stderr)
          +    sys.exit(1)
          +
          +if len(sys.argv) == 1:
          +    index_mboxdir(mbdir)
          +else:
          +    # cmd [fetch|makesig] udi url ipath
          +    if len(sys.argv) != 5:
          +        usage()
          +    cmd = sys.argv[1]
          +    udi = sys.argv[2]
          +    url = sys.argv[3]
          +    ipath = sys.argv[4]
          +    
          +    mbfile = url.replace('file://', '')
          +    # no need for a db for getdata or makesig.
          +    mbidx = mbox_indexer(None, mbfile)
          +
          +    if cmd == 'fetch':
          +        print("%s"%mbidx.getdata(ipath).encode('UTF-8'), end="")
          +    elif cmd == 'makesig':
          +        print(mbidx.sig(), end="")
          +    else:
          +        usage()
          +
          +sys.exit(0)
          diff --git a/src/qtgui/advsearch_w.cpp b/src/qtgui/advsearch_w.cpp
          index 557a9483..0cabbebe 100644
          --- a/src/qtgui/advsearch_w.cpp
          +++ b/src/qtgui/advsearch_w.cpp
          @@ -14,6 +14,8 @@
            *   Free Software Foundation, Inc.,
            *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
            */
          +#include "autoconfig.h"
          +
           #include "advsearch_w.h"
           
           #include 
          @@ -36,7 +38,7 @@ using namespace std;
           
           #include "recoll.h"
           #include "rclconfig.h"
          -#include "debuglog.h"
          +#include "log.h"
           #include "searchdata.h"
           #include "guiutils.h"
           #include "rclhelp.h"
          @@ -50,7 +52,7 @@ void AdvSearch::init()
           {
               (void)new HelpClient(this);
               HelpClient::installMap((const char *)objectName().toUtf8(), 
          -			   "RCL.SEARCH.COMPLEX");
          +			   "RCL.SEARCH.GUI.COMPLEX");
           
               // signals and slots connections
               connect(delFiltypPB, SIGNAL(clicked()), this, SLOT(delFiltypPB_clicked()));
          @@ -89,13 +91,13 @@ void AdvSearch::init()
               // Tune initial state according to last saved
               {
           	vector::iterator cit = m_clauseWins.begin();
          -	for (vector::iterator it = prefs.advSearchClauses.begin(); 
          -	     it != prefs.advSearchClauses.end(); it++) {
          -	    if (cit != m_clauseWins.end()) {
          -		(*cit)->tpChange(*it);
          +        unsigned int existing = m_clauseWins.size();
          +	for (unsigned int i = 0; i < prefs.advSearchClauses.size(); i++) {
          +	    if (i < existing) {
          +		(*cit)->tpChange(prefs.advSearchClauses[i]);
           		cit++;
           	    } else {
          -		addClause(*it);
          +		addClause(prefs.advSearchClauses[i]);
           	    }
           	}
               }
          @@ -162,12 +164,6 @@ void AdvSearch::saveCnf()
               }
           }
           
          -bool AdvSearch::close()
          -{
          -    saveCnf();
          -    return QWidget::close();
          -}
          -
           void AdvSearch::addClause()
           {
               addClause(0);
          @@ -364,7 +360,7 @@ using namespace Rcl;
           void AdvSearch::runSearch()
           {
               string stemLang = prefs.stemlang();
          -    RefCntr sdata(new SearchData(conjunctCMB->currentIndex() == 0 ?
          +    std::shared_ptr sdata(new SearchData(conjunctCMB->currentIndex() == 0 ?
           					     SCLT_AND : SCLT_OR, stemLang));
               bool hasclause = false;
           
          @@ -424,9 +420,20 @@ void AdvSearch::runSearch()
           
               if (!subtreeCMB->currentText().isEmpty()) {
           	QString current = subtreeCMB->currentText();
          -	sdata->addClause(new Rcl::SearchDataClausePath(
          -			     (const char*)current.toLocal8Bit(),
          -			     direxclCB->isChecked()));
          +
          +        Rcl::SearchDataClausePath *pathclause = 
          +            new Rcl::SearchDataClausePath((const char*)current.toLocal8Bit(), 
          +                                          direxclCB->isChecked());
          +        if (sdata->getTp() == SCLT_AND) {
          +            sdata->addClause(pathclause);
          +        } else {
          +            std::shared_ptr 
          +                nsdata(new SearchData(SCLT_AND, stemLang));
          +            nsdata->addClause(new Rcl::SearchDataClauseSub(sdata));
          +            nsdata->addClause(pathclause);
          +            sdata = nsdata;
          +        }
          +
           	// Keep history clean and sorted. Maybe there would be a
           	// simpler way to do this
           	list entries;
          @@ -436,7 +443,7 @@ void AdvSearch::runSearch()
           	entries.push_back(subtreeCMB->currentText());
           	entries.sort();
           	entries.unique();
          -	LOGDEB(("Subtree list now has %d entries\n", entries.size()));
          +	LOGDEB("Subtree list now has "  << (entries.size()) << " entries\n" );
           	subtreeCMB->clear();
           	for (list::iterator it = entries.begin(); 
           	     it != entries.end(); it++) {
          @@ -455,7 +462,7 @@ void AdvSearch::runSearch()
           
           // Set up fields from existing search data, which must be compatible
           // with what we can do...
          -void AdvSearch::fromSearch(RefCntr sdata)
          +void AdvSearch::fromSearch(std::shared_ptr sdata)
           {
               if (sdata->m_tp == SCLT_OR)
           	conjunctCMB->setCurrentIndex(1);
          @@ -472,7 +479,7 @@ void AdvSearch::fromSearch(RefCntr sdata)
               for (unsigned int i = 0; i < sdata->m_query.size(); i++) {
           	// Set fields from clause
           	if (sdata->m_query[i]->getTp() == SCLT_SUB) {
          -	    LOGERR(("AdvSearch::fromSearch: SUB clause found !\n"));
          +	    LOGERR("AdvSearch::fromSearch: SUB clause found !\n" );
           	    continue;
           	}
           	if (sdata->m_query[i]->getTp() == SCLT_PATH) {
          @@ -553,8 +560,8 @@ void AdvSearch::slotHistoryNext()
           {
               if (g_advshistory == 0)
           	return;
          -    RefCntr sd = g_advshistory->getnewer();
          -    if (sd.isNull())
          +    std::shared_ptr sd = g_advshistory->getnewer();
          +    if (!sd)
           	return;
               fromSearch(sd);
           }
          @@ -563,9 +570,10 @@ void AdvSearch::slotHistoryPrev()
           {
               if (g_advshistory == 0)
           	return;
          -    RefCntr sd = g_advshistory->getolder();
          -    if (sd.isNull())
          +    std::shared_ptr sd = g_advshistory->getolder();
          +    if (!sd)
           	return;
               fromSearch(sd);
           }
           
          +
          diff --git a/src/qtgui/advsearch_w.h b/src/qtgui/advsearch_w.h
          index 1e7920e6..581bf442 100644
          --- a/src/qtgui/advsearch_w.h
          +++ b/src/qtgui/advsearch_w.h
          @@ -16,6 +16,7 @@
            */
           #ifndef _ADVSEARCH_W_H_INCLUDED_
           #define _ADVSEARCH_W_H_INCLUDED_
          +#include "autoconfig.h"
           
           #include 
           
          @@ -24,7 +25,7 @@
           
           #include "searchclause_w.h"
           #include "recoll.h"
          -#include "refcntr.h"
          +#include 
           #include "searchdata.h"
           #include "advshist.h"
           
          @@ -43,7 +44,6 @@ public:
           	setupUi(this);
           	init();
               }
          -    ~AdvSearch(){}
           
           public slots:
               virtual void delFiltypPB_clicked();
          @@ -56,18 +56,17 @@ public slots:
               virtual void restrictFtCB_toggled(bool);
               virtual void restrictCtCB_toggled(bool);
               virtual void runSearch();
          -    virtual void fromSearch(RefCntr sdata);
          +    virtual void fromSearch(std::shared_ptr sdata);
               virtual void browsePB_clicked();
               virtual void saveFileTypes();
               virtual void delClause();
               virtual void addClause();
               virtual void addClause(int);
          -    virtual bool close();
               virtual void slotHistoryNext();
               virtual void slotHistoryPrev();
           
           signals:
          -    void startSearch(RefCntr, bool);
          +    void startSearch(std::shared_ptr, bool);
           
           private:
               virtual void init();
          diff --git a/src/qtgui/advshist.cpp b/src/qtgui/advshist.cpp
          index 41d2be8b..4666913d 100644
          --- a/src/qtgui/advshist.cpp
          +++ b/src/qtgui/advshist.cpp
          @@ -19,7 +19,7 @@
           
           #include "advshist.h"
           #include "guiutils.h"
          -#include "debuglog.h"
          +#include "log.h"
           #include "xmltosd.h"
           
           using namespace std;
          @@ -33,38 +33,38 @@ AdvSearchHist::AdvSearchHist()
           
           AdvSearchHist::~AdvSearchHist()
           {
          -    for (vector >::iterator it = m_entries.begin();
          +    for (vector >::iterator it = m_entries.begin();
           	 it != m_entries.end(); it++) {
          -	it->release();
          +	it->reset();
               }
           }
           
          -RefCntr AdvSearchHist::getnewest() 
          +std::shared_ptr AdvSearchHist::getnewest() 
           {
               if (m_entries.empty())
          -        return RefCntr();
          +        return std::shared_ptr();
           
               return m_entries[0];
           }
           
          -RefCntr AdvSearchHist::getolder()
          +std::shared_ptr AdvSearchHist::getolder()
           {
               m_current++;
               if (m_current >= int(m_entries.size())) {
           	m_current--;
          -	return RefCntr();
          +	return std::shared_ptr();
               }
               return m_entries[m_current];
           }
           
          -RefCntr AdvSearchHist::getnewer()
          +std::shared_ptr AdvSearchHist::getnewer()
           {
               if (m_current == -1 || m_current == 0 || m_entries.empty())
          -	return RefCntr();
          +	return std::shared_ptr();
               return m_entries[--m_current];
           }
           
          -bool AdvSearchHist::push(RefCntr sd)
          +bool AdvSearchHist::push(std::shared_ptr sd)
           {
               m_entries.insert(m_entries.begin(), sd);
               if (m_current != -1)
          @@ -83,7 +83,7 @@ bool AdvSearchHist::read()
               
               for (list::const_iterator it = lxml.begin(); it != lxml.end();
           	 it++) {
          -        RefCntr sd = xmlToSearchData(*it);
          +        std::shared_ptr sd = xmlToSearchData(*it);
                   if (sd)
                       m_entries.push_back(sd);
               }
          @@ -94,3 +94,4 @@ void AdvSearchHist::clear()
           {
               g_dynconf->eraseAll(advSearchHistSk);
           }
          +
          diff --git a/src/qtgui/advshist.h b/src/qtgui/advshist.h
          index ec10f3ab..a894b214 100644
          --- a/src/qtgui/advshist.h
          +++ b/src/qtgui/advshist.h
          @@ -16,11 +16,12 @@
            */
           #ifndef _ADVSHIST_H_INCLUDED_
           #define _ADVSHIST_H_INCLUDED_
          +#include "autoconfig.h"
           
           #include 
           
           #include "recoll.h"
          -#include "refcntr.h"
          +#include 
           #include "searchdata.h"
           
           /** Advanced search history. 
          @@ -43,14 +44,14 @@ public:
               ~AdvSearchHist();
           
               // Add entry
          -    bool push(RefCntr);
          +    bool push(std::shared_ptr);
           
               // Get latest. does not change state
          -    RefCntr getnewest();
          +    std::shared_ptr getnewest();
           
               // Cursor
          -    RefCntr getolder();
          -    RefCntr getnewer();
          +    std::shared_ptr getolder();
          +    std::shared_ptr getnewer();
           
               void clear();
           
          @@ -58,7 +59,7 @@ private:
               bool read();
           
               int m_current;
          -    std::vector > m_entries;
          +    std::vector > m_entries;
           };
           
           #endif // _ADVSHIST_H_INCLUDED_
          diff --git a/src/qtgui/confgui/confgui.cpp b/src/qtgui/confgui/confgui.cpp
          index 131adf5c..94afdc86 100644
          --- a/src/qtgui/confgui/confgui.cpp
          +++ b/src/qtgui/confgui/confgui.cpp
          @@ -40,7 +40,7 @@
           
           #include "confgui.h"
           #include "smallut.h"
          -#include "debuglog.h"
          +#include "log.h"
           #include "rcldb.h"
           #include "guiutils.h"
           
          @@ -376,15 +376,27 @@ void ConfParamSLW::showInputDialog()
           void ConfParamSLW::listToConf()
           {
               list ls;
          +    LOGDEB2("ConfParamSLW::listToConf. m_fsencoding "  << (int(m_fsencoding)) << "\n" );
               for (int i = 0; i < m_lb->count(); i++) {
                   // General parameters are encoded as utf-8. File names as
                   // local8bit There is no hope for 8bit file names anyway
                   // except for luck: the original encoding is unknown.
          +        // As a special Windows hack, if fsencoding is set, we convert
          +        // backslashes to slashes. This is an awful hack because
          +        // fsencoding does not necessarily imply that the values are
          +        // paths, and it will come back to haunt us one day.
           	QString text = m_lb->item(i)->text();
          -        if (m_fsencoding)
          +        if (m_fsencoding) {
          +#ifdef _WIN32
          +            string pth((const char *)(text.toLocal8Bit()));
          +            path_slashize(pth);
          +            ls.push_back(pth);
          +#else
                       ls.push_back((const char *)(text.toLocal8Bit()));
          -        else
          +#endif
          +        } else {
                       ls.push_back((const char *)(text.toUtf8()));
          +        }
               }
               string s;
               stringsToString(ls, s);
          @@ -411,7 +423,7 @@ void ConfParamSLW::deleteSelected()
               }
               for (vector::reverse_iterator it = idxes.rbegin(); 
           	 it != idxes.rend(); it++) {
          -	LOGDEB0(("deleteSelected: %d was selected\n", *it));
          +	LOGDEB0("deleteSelected: "  << (*it) << " was selected\n" );
           	QListWidgetItem *item = m_lb->takeItem(*it);
           	emit entryDeleted(item->text());
           	delete item;
          @@ -464,3 +476,4 @@ void ConfParamCSLW::showInputDialog()
           }
           
           } // Namespace confgui
          +
          diff --git a/src/qtgui/confgui/confgui.h b/src/qtgui/confgui/confgui.h
          index 7758e3be..7c0c01e6 100644
          --- a/src/qtgui/confgui/confgui.h
          +++ b/src/qtgui/confgui/confgui.h
          @@ -16,6 +16,8 @@
            */
           #ifndef _confgui_h_included_
           #define _confgui_h_included_
          +#include "autoconfig.h"
          +
           /**
            * This file defines a number of simple classes (virtual base: ConfParamW) 
            * which let the user input configuration parameters. 
          @@ -36,6 +38,7 @@
            * destroyed and recreated as a copy if Cancel is pressed (you have to 
            * delete/recreate the widgets in this case as the links are no longer valid).
            */
          +
           #include 
           #include 
           
          @@ -43,9 +46,7 @@
           #include 
           #include 
           
          -#include "refcntr.h"
          -
          -using std::string;
          +#include 
           
           class QHBoxLayout;
           class QLineEdit;
          @@ -61,21 +62,21 @@ namespace confgui {
               class ConfLinkRep {
               public:
           	virtual ~ConfLinkRep() {}
          -	virtual bool set(const string& val) = 0;
          -	virtual bool get(string& val) = 0;
          +	virtual bool set(const std::string& val) = 0;
          +	virtual bool get(std::string& val) = 0;
               };
          -    typedef RefCntr ConfLink;
          +    typedef std::shared_ptr ConfLink;
           
               // Useful to store/manage data which has no direct representation in
               // the config, ie list of subkey directories
               class ConfLinkNullRep : public ConfLinkRep {
               public:
           	virtual ~ConfLinkNullRep() {}
          -	virtual bool set(const string&)
          +	virtual bool set(const std::string&)
           	{
           	    return true;
           	}
          -	virtual bool get(string& val) {val = ""; return true;}
          +	virtual bool get(std::string& val) {val = ""; return true;}
               };
           
               // A widget to let the user change one configuration
          diff --git a/src/qtgui/confgui/confguiindex.cpp b/src/qtgui/confgui/confguiindex.cpp
          index 87210161..860a8e1b 100644
          --- a/src/qtgui/confgui/confguiindex.cpp
          +++ b/src/qtgui/confgui/confguiindex.cpp
          @@ -35,7 +35,7 @@ using std::list;
           #include "recoll.h"
           #include "confguiindex.h"
           #include "smallut.h"
          -#include "debuglog.h"
          +#include "log.h"
           #include "rcldb.h"
           #include "conflinkrcl.h"
           #include "execmd.h"
          @@ -68,9 +68,9 @@ ConfIndexW::ConfIndexW(QWidget *parent, RclConfig *config)
           
           void ConfIndexW::acceptChanges()
           {
          -    LOGDEB(("ConfIndexW::acceptChanges()\n"));
          +    LOGDEB("ConfIndexW::acceptChanges()\n" );
               if (!m_conf) {
          -	LOGERR(("ConfIndexW::acceptChanges: no config\n"));
          +	LOGERR("ConfIndexW::acceptChanges: no config\n" );
           	return;
               }
               // Let the changes to disk
          @@ -88,7 +88,7 @@ void ConfIndexW::acceptChanges()
           
           void ConfIndexW::rejectChanges()
           {
          -    LOGDEB(("ConfIndexW::rejectChanges()\n"));
          +    LOGDEB("ConfIndexW::rejectChanges()\n" );
               // Discard local changes.
               delete m_conf;
               m_conf = 0;
          @@ -149,8 +149,13 @@ ConfBeaglePanelW::ConfBeaglePanelW(QWidget *parent, ConfNull *config)
               ConfLink lnk3(new ConfLinkRclRep(config, "webcachemaxmbs"));
               ConfParamIntW *cp3 =
                   new ConfParamIntW(this, lnk3, tr("Max. size for the web store (MB)"),
          -		      tr("Entries will be recycled once the size is reached"),
          -                          -1, 1000);
          +		      tr("Entries will be recycled once the size is reached."
          +                         "
          " + "Only increasing the size really makes sense because " + "reducing the value will not truncate an existing " + "file (only waste space at the end)." + ), + -1, 1000*1000); // Max 1TB... cp3->setEnabled(cp1->m_cb->isChecked()); connect(cp1->m_cb, SIGNAL(toggled(bool)), cp3, SLOT(setEnabled(bool))); vboxLayout->addWidget(cp3); @@ -472,7 +477,7 @@ ConfSubPanelW::ConfSubPanelW(QWidget *parent, ConfNull *config, string cmd = "iconv"; int status = ex.doexec(cmd, args, 0, &icout); if (status) { - LOGERR(("Can't get list of charsets from 'iconv -l'")); + LOGERR("Can't get list of charsets from 'iconv -l'" ); } icout = neutchars(icout, ","); list ccsets; @@ -574,7 +579,7 @@ void ConfSubPanelW::reloadAll() void ConfSubPanelW::subDirChanged(QListWidgetItem *current, QListWidgetItem *) { - LOGDEB(("ConfSubPanelW::subDirChanged\n")); + LOGDEB("ConfSubPanelW::subDirChanged\n" ); if (current == 0 || current->text() == "") { m_sk = ""; @@ -583,13 +588,13 @@ void ConfSubPanelW::subDirChanged(QListWidgetItem *current, QListWidgetItem *) m_sk = (const char *) current->text().toUtf8(); m_groupbox->setTitle(current->text()); } - LOGDEB(("ConfSubPanelW::subDirChanged: now [%s]\n", m_sk.c_str())); + LOGDEB("ConfSubPanelW::subDirChanged: now [" << (m_sk) << "]\n" ); reloadAll(); } void ConfSubPanelW::subDirDeleted(QString sbd) { - LOGDEB(("ConfSubPanelW::subDirDeleted(%s)\n", (const char *)sbd.toUtf8())); + LOGDEB("ConfSubPanelW::subDirDeleted(" << ((const char *)sbd.toUtf8()) << ")\n" ); if (sbd == "") { // Can't do this, have to reinsert it QTimer::singleShot(0, this, SLOT(restoreEmpty())); @@ -601,8 +606,9 @@ void ConfSubPanelW::subDirDeleted(QString sbd) void ConfSubPanelW::restoreEmpty() { - LOGDEB(("ConfSubPanelW::restoreEmpty()\n")); + LOGDEB("ConfSubPanelW::restoreEmpty()\n" ); m_subdirs->getListBox()->insertItem(0, ""); } } // Namespace confgui + diff --git a/src/qtgui/confgui/conflinkrcl.h b/src/qtgui/confgui/conflinkrcl.h index 34f8155d..c5554887 100644 --- a/src/qtgui/confgui/conflinkrcl.h +++ b/src/qtgui/confgui/conflinkrcl.h @@ -25,7 +25,7 @@ */ #include "confgui.h" #include "conftree.h" -#include "debuglog.h" +#include "log.h" namespace confgui { @@ -42,10 +42,10 @@ public: { if (!m_conf) return false; - LOGDEB1(("Setting [%s] value to [%s]\n", m_nm.c_str(), val.c_str())); + LOGDEB1("Setting [" << (m_nm) << "] value to [" << (val) << "]\n" ); bool ret = m_conf->set(m_nm, val, m_sk?*m_sk:""); if (!ret) - LOGERR(("Value set failed\n")); + LOGERR("Value set failed\n" ); return ret; } virtual bool get(string& val) @@ -53,9 +53,7 @@ public: if (!m_conf) return false; bool ret = m_conf->get(m_nm, val, m_sk?*m_sk:""); - LOGDEB1(("ConfLinkRcl::get: [%s] sk [%s] -> [%s]\n", - m_nm.c_str(), m_sk?m_sk->c_str():"", - ret ? val.c_str() : "no value")); + LOGDEB1("ConfLinkRcl::get: [" << (m_nm) << "] sk [" << (m_sk?m_sk:"") << "] -> [" << (ret ? val : "no value") << "]\n" ); return ret; } private: @@ -67,3 +65,4 @@ private: } // Namespace confgui #endif /* _CONFLINKRCL_H_INCLUDED_ */ + diff --git a/src/qtgui/confgui/main.cpp b/src/qtgui/confgui/main.cpp index e62efbc9..8731bcde 100644 --- a/src/qtgui/confgui/main.cpp +++ b/src/qtgui/confgui/main.cpp @@ -39,7 +39,7 @@ using namespace std; #include "pathut.h" #include "confguiindex.h" -#include "debuglog.h" +#include "log.h" #include "rclconfig.h" #include "execmd.h" #include "conflinkrcl.h" @@ -89,9 +89,6 @@ int main(int argc, char **argv) argc--; argv++; } - DebugLog::getdbl()->setloglevel(DEBDEB1); - DebugLog::setfilename("stderr"); - string a_config = "tstconfdir"; config = new RclConfig(&a_config); if (config == 0 || !config->ok()) { @@ -130,3 +127,4 @@ int main(int argc, char **argv) // Let's go return app.exec(); } + diff --git a/src/qtgui/fragbuts.cpp b/src/qtgui/fragbuts.cpp index ef6ff1ee..a07e643b 100644 --- a/src/qtgui/fragbuts.cpp +++ b/src/qtgui/fragbuts.cpp @@ -17,7 +17,7 @@ #include "autoconfig.h" -#include +#include "safesysstat.h" #include #include @@ -34,7 +34,7 @@ #include "pathut.h" #include "smallut.h" #include "recoll.h" -#include "debuglog.h" +#include "log.h" #include "readfile.h" #include "copyfile.h" @@ -148,7 +148,7 @@ FragButs::FragButs(QWidget* parent) m_fn = path_cat(theconfig->getConfDir(), "fragbuts.xml"); string data, reason; - if (access(m_fn.c_str(), 0) != 0) { + if (!path_exists(m_fn)) { // config does not exist: try to create it from sample string src = path_cat(theconfig->getDatadir(), "examples"); src = path_cat(src, "fragbuts.xml"); @@ -158,7 +158,7 @@ FragButs::FragButs(QWidget* parent) QMessageBox::warning(0, "Recoll", tr("%1 not found.").arg( QString::fromLocal8Bit(m_fn.c_str()))); - LOGERR(("Fragbuts:: can't read [%s]\n", m_fn.c_str())); + LOGERR("Fragbuts:: can't read [" << (m_fn) << "]\n" ); return; } FragButsParser parser(this, m_buttons); @@ -199,7 +199,7 @@ bool FragButs::isStale(time_t *reftime) void FragButs::onButtonClicked(bool on) { - LOGDEB(("FragButs::onButtonClicked: [%d]\n", int(on))); + LOGDEB("FragButs::onButtonClicked: [" << (int(on)) << "]\n" ); emit fragmentsChanged(); } @@ -208,8 +208,9 @@ void FragButs::getfrags(std::vector& frags) for (vector::iterator it = m_buttons.begin(); it != m_buttons.end(); it++) { if (it->button->isChecked() && !it->fragment.empty()) { - LOGDEB(("FragButs: fragment [%s]\n", it->fragment.c_str())); + LOGDEB("FragButs: fragment [" << (it->fragment) << "]\n" ); frags.push_back(it->fragment); } } } + diff --git a/src/qtgui/guiutils.cpp b/src/qtgui/guiutils.cpp index b8d65f22..0a07a256 100644 --- a/src/qtgui/guiutils.cpp +++ b/src/qtgui/guiutils.cpp @@ -14,23 +14,21 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include +#include "autoconfig.h" #include #include #include "recoll.h" -#include "debuglog.h" +#include "log.h" #include "smallut.h" #include "guiutils.h" #include "pathut.h" #include "base64.h" #include "advshist.h" -#include #include #include -#include RclDynConf *g_dynconf; AdvSearchHist *g_advshistory; @@ -75,7 +73,7 @@ static bool havereadsettings; void rwSettings(bool writing) { - LOGDEB1(("rwSettings: write %d\n", int(writing))); + LOGDEB1("rwSettings: write " << (int(writing)) << "\n" ); if (writing && !havereadsettings) return; QSettings settings("Recoll.org", "recoll"); @@ -132,9 +130,24 @@ void rwSettings(bool writing) SETTING_RW(prefs.previewPlainPre, "/Recoll/prefs/preview/plainPre", Int, PrefsPack::PP_PREWRAP); - SETTING_RW(prefs.qtermcolor, "/Recoll/prefs/qtermcolor", String, "blue"); - if (!writing && prefs.qtermcolor == "") - prefs.qtermcolor = "blue"; + + // History: used to be able to only set a bare color name. Can now + // set any CSS style. Hack on ':' presence to keep compat with old + // values + SETTING_RW(prefs.qtermstyle, "/Recoll/prefs/qtermcolor", String, + "color: blue"); + if (!writing && prefs.qtermstyle == "") + prefs.qtermstyle = "color: blue"; + { // histo compatibility hack + int colon = prefs.qtermstyle.indexOf(":"); + int semi = prefs.qtermstyle.indexOf(";"); + // The 2nd part of the test is to keep compat with the + // injection hack of the 1st user who suggested this (had + // #ff5000;font-size:110%;... in 'qtermcolor') + if (colon == -1 || (colon != -1 && semi != -1 && semi < colon)) { + prefs.qtermstyle = QString::fromUtf8("color: ") + prefs.qtermstyle; + } + } // Abstract snippet separator SETTING_RW(prefs.abssep, "/Recoll/prefs/reslist/abssep", String,"…"); @@ -151,8 +164,6 @@ void rwSettings(bool writing) SETTING_RW(prefs.reslistfontsize, "/Recoll/prefs/reslist/fontSize", Int, 10); - prefs.fontcolor = QApplication::palette().text().color().name(); - QString rlfDflt = QString::fromUtf8(prefs.dfltResListFormat); if (writing) { if (prefs.reslistformat.compare(rlfDflt)) { @@ -202,6 +213,10 @@ void rwSettings(bool writing) SETTING_RW(prefs.autoSuffsEnable, "/Recoll/prefs/query/autoSuffsEnable", Bool, false); + SETTING_RW(prefs.synFileEnable, + "/Recoll/prefs/query/synFileEnable", Bool, false); + SETTING_RW(prefs.synFile, "/Recoll/prefs/query/synfile", String, ""); + SETTING_RW(prefs.termMatchType, "/Recoll/prefs/query/termMatchType", Int, 0); // This is not really the current program version, just a value to @@ -268,6 +283,8 @@ void rwSettings(bool writing) Bool, false); SETTING_RW(prefs.showTrayIcon, "/Recoll/prefs/showTrayIcon", Bool, false); SETTING_RW(prefs.closeToTray, "/Recoll/prefs/closeToTray", Bool, false); + SETTING_RW(prefs.showTempFileWarning, "Recoll/prefs/showTempFileWarning", + Int, -1); if (g_dynconf == 0) { // Happens @@ -310,12 +327,11 @@ void rwSettings(bool writing) continue; bool stripped; if (!Rcl::Db::testDbDir(dbdir, &stripped)) { - LOGERR(("Not a xapian index: [%s]\n", dbdir.c_str())); + LOGERR("Not a xapian index: [" << (dbdir) << "]\n" ); continue; } if (stripped != o_index_stripchars) { - LOGERR(("Incompatible character stripping: [%s]\n", - dbdir.c_str())); + LOGERR("Incompatible character stripping: [" << (dbdir) << "]\n" ); continue; } prefs.allExtraDbs.push_back(dbdir); @@ -332,8 +348,7 @@ void rwSettings(bool writing) bool stripped; if (!Rcl::Db::testDbDir(*it, &stripped) || stripped != o_index_stripchars) { - LOGINFO(("Not a Xapian index or char stripping differs: [%s]\n", - it->c_str())); + LOGINFO("Not a Xapian index or char stripping differs: [" << *it << "]\n" ); it = prefs.activeExtraDbs.erase(it); } else { it++; @@ -357,8 +372,7 @@ void rwSettings(bool writing) bool strpd; if (!Rcl::Db::testDbDir(dbdir, &strpd) || strpd != o_index_stripchars) { - LOGERR(("Not a Xapian dir or diff. char stripping: [%s]\n", - dbdir.c_str())); + LOGERR("Not a Xapian dir or diff. char stripping: [" << (dbdir) << "]\n" ); continue; } prefs.activeExtraDbs.push_back(dbdir); @@ -413,31 +427,3 @@ string PrefsPack::stemlang() return stemLang; } -QString myGetFileName(bool isdir, QString caption, bool filenosave) -{ - LOGDEB1(("myFileDialog: isdir %d\n", isdir)); - QFileDialog dialog(0, caption); - - if (isdir) { - dialog.setFileMode(QFileDialog::Directory); - dialog.setOptions(QFileDialog::ShowDirsOnly); - } else { - dialog.setFileMode(QFileDialog::AnyFile); - if (filenosave) - dialog.setAcceptMode(QFileDialog::AcceptOpen); - else - dialog.setAcceptMode(QFileDialog::AcceptSave); - } - dialog.setViewMode(QFileDialog::List); - QFlags flags = QDir::NoDotAndDotDot | QDir::Hidden; - if (isdir) - flags |= QDir::Dirs; - else - flags |= QDir::Dirs | QDir::Files; - dialog.setFilter(flags); - - if (dialog.exec() == QDialog::Accepted) { - return dialog.selectedFiles().value(0); - } - return QString(); -} diff --git a/src/qtgui/guiutils.h b/src/qtgui/guiutils.h index a2ebd3d8..005fcf4b 100644 --- a/src/qtgui/guiutils.h +++ b/src/qtgui/guiutils.h @@ -54,7 +54,7 @@ class PrefsPack { // set main character color for webkit/textbrowser reslist and // snippets window. QString fontcolor; - QString qtermcolor; // Color for query terms in reslist and preview + QString qtermstyle; // CSS style for query terms in reslist and other places int reslistfontsize; // Result list format string QString reslistformat; @@ -115,6 +115,9 @@ class PrefsPack { // language entry. QString autoSuffs; bool autoSuffsEnable; + // Synonyms file + QString synFile; + bool synFileEnable; QStringList restableFields; vector restableColWidths; @@ -133,6 +136,8 @@ class PrefsPack { bool showTrayIcon; bool closeToTray; + int showTempFileWarning; + // Advanced search window clause list state vector advSearchClauses; @@ -163,9 +168,4 @@ extern void rwSettings(bool dowrite); extern QString g_stringAllStem, g_stringNoStem; -/** Specialized version of the qt file dialog. Can't use getOpenFile() - etc. cause they hide dot files... */ -extern QString myGetFileName(bool isdir, QString caption = QString(), - bool filenosave = false); - #endif /* _GUIUTILS_H_INCLUDED_ */ diff --git a/src/qtgui/i18n/recoll_cs.qm b/src/qtgui/i18n/recoll_cs.qm index 7afede60..cec3f058 100644 Binary files a/src/qtgui/i18n/recoll_cs.qm and b/src/qtgui/i18n/recoll_cs.qm differ diff --git a/src/qtgui/i18n/recoll_cs.ts b/src/qtgui/i18n/recoll_cs.ts index 515db552..cc8a10ab 100644 --- a/src/qtgui/i18n/recoll_cs.ts +++ b/src/qtgui/i18n/recoll_cs.ts @@ -376,16 +376,17 @@ p, li { white-space: pre-wrap; } FragButs %1 not found. - + %1 nenalezen. %1: %2 - + %1: + %2 Query Fragments - + Kousky hledání @@ -635,95 +636,103 @@ KlepnÄ›te na tlaÄítko ZruÅ¡it pro úpravu souboru s nastavením, pÅ™edtím ne Default<br>character set - + Výchozí<br>znaková sada Character set used for reading files which do not identify the character set internally, for example pure text files.<br>The default value is empty, and the value from the NLS environnement is used. - + Toto je znaková sada, která se používá pro Ätení souborů, které svou znakovou sadu vnitÅ™nÄ› neurÄují, napÅ™.. soubory s textem.<br>Výchozí hodnota je prázdná a používá se hodnota prostÅ™edí NLS. Ignored endings - + PÅ™ehlížená zakonÄení These are file name endings for files which will be indexed by name only (no MIME type identification attempt, no decompression, no content indexing). - + Toto jsou zakonÄení souborů pro soubory, které se budou rejstříkovat výhradnÄ› podle svého názvu +(žádné urÄování typu MIME, žádné rozbalování, žádné rejstříkování obsahu). QWidget Create or choose save directory - + VytvoÅ™it nebo vybrat ukládací adresář Choose exactly one directory - + Vybrat pÅ™esnÄ› jeden adresář Could not read directory: - + NepodaÅ™ilo se Äíst z adresáře: Unexpected file name collision, cancelling. - + NeoÄekávaný stÅ™et v souborovém názvu. Ruší se. Cannot extract document: - + Nelze vytáhnout dokument: &Preview - &Náhled + &Náhled &Open - &Otevřít + &Otevřít Open With - + Otevřít s Run Script - + Spustit skript Copy &File Name - Kopírovat název &souboru + Kopírovat název &souboru Copy &URL - Kopírovat adresu (&URL) + Kopírovat adresu (&URL) &Write to File - &Zapsat do souboru + &Zapsat do souboru Save selection to files - Uložit výbÄ›r do souborů + Uložit výbÄ›r do souborů Preview P&arent document/folder - Náhled na &rodiÄovský dokument/složku + Náhled na &rodiÄovský dokument/složku &Open Parent document/folder - &Otevřít rodiÄovský dokument/složku + &Otevřít rodiÄovský dokument/složku Find &similar documents - Najít &podobné dokumenty + Najít &podobné dokumenty Open &Snippets window - Otevřít okno s úr&yvky + Otevřít okno s úr&yvky Show subdocuments / attachments - Ukázat podřízené dokumenty/přílohy + Ukázat podřízené dokumenty/přílohy + + + + QxtConfirmationMessage + + Do not show again. + @@ -885,7 +894,7 @@ Prověřte soubor mimeconf Indexing interrupted - Rejstříkování pÅ™eruÅ¡eno + Rejstříkování pÅ™eruÅ¡eno Stop &Indexing @@ -1093,39 +1102,149 @@ Prověřte soubor mimeconf Document filter - + Filtr dokumentu Index not up to date for this file. Refusing to risk showing the wrong entry. - + Rejstřík není pro tento soubor nejnovÄ›jší. Ukázání nesprávného záznamu bylo zamítnuto. Click Ok to update the index for this file, then you will need to re-run the query when indexing is done. - + KlepnÄ›te na tlaÄítko pro aktualizaci rejstříku pro tento soubor, potom dotaz, až bude rejstříkování hotovo, spusÅ¥te znovu. The indexer is running so things should improve when it's done. - + RejstříkovaÄ běží, takže vÄ›ci by se po dokonÄení rejstříkování mÄ›ly zlepÅ¡it. The document belongs to an external indexwhich I can't update. - + Dokument je souÄástí vnÄ›jšího rejstříku, který nelze aktualizovat. Click Cancel to return to the list. Click Ignore to show the preview anyway. - + KlepnÄ›te na tlaÄítko ZruÅ¡it pro návrat do seznamu. KlepnÄ›te na tlaÄítko PÅ™ehlížet, aby byl pÅ™esto ukázán náhled. Duplicate documents - Zdvojené dokumenty + Zdvojené dokumenty These Urls ( | ipath) share the same content: - Tyto adresy ( | ipath) sdílejí totožný obsah: + Tyto adresy ( | ipath) sdílejí totožný obsah: Bad desktop app spec for %1: [%2] Please check the desktop file + Chybná specifikace aplikace pro %1: [%2] +Prověřte soubor pracovní plochy + + + Index locked + + + + The current indexing process was not started from this interface, can't kill it + + + + Bad paths + + + + Bad paths in configuration file: + + + + + Selection patterns need topdir + + + + Selection patterns can only be used with a start directory + + + + The document belongs to an external index which I can't update. + + + + Click Cancel to return to the list. <br>Click Ignore to show the preview anyway (and remember for this session). + + + + No search + + + + No preserved previous search + + + + Choose file to save + + + + Saved Queries (*.rclq) + + + + Write failed + + + + Could not write to file + + + + Read failed + + + + Could not open file: + + + + Load error + + + + Could not load saved query + + + + Opening a temporary copy. Edits will be lost if you don't save<br/>them to a permanent location. + + + + Do not show this warning next time (use GUI preferences to restore). + + + + Unknown indexer state. Can't access webcache file. + + + + Indexer is running. Can't access webcache file. + + + + Index scheduling + + + + Sorry, not available under Windows for now, use the File menu entries to update the index + + + + Disabled because the real time indexer was not compiled in. + + + + This configuration tool only works for the main index. + + + + Can't set synonyms file (parse error?) @@ -1257,7 +1376,7 @@ Please check the desktop file &Show missing helpers - &Ukázat chybÄ›jící pomocné programy + &Ukázat chybÄ›jící pomocné programy PgDown @@ -1317,7 +1436,7 @@ Please check the desktop file &Show indexed types - &Ukázat rejstříkované typy + &Ukázat rejstříkované typy Shift+PgUp @@ -1325,7 +1444,7 @@ Please check the desktop file &Indexing schedule - Rozvrh &rejstříkování + Rozvrh &rejstříkování E&xternal index dialog @@ -1381,14 +1500,58 @@ Please check the desktop file Query Fragments - + Kousky hledání With failed files retrying - + S novým pokusem o zpracování selhavších souborů Next update will retry previously failed files + Nová aktualizace rejstříku se pokusí znovu zpracovat nyní nezpracované soubory + + + &View + + + + Missing &helpers + + + + Indexed &MIME types + + + + Indexing &schedule + + + + Enable synonyms + + + + Save last query + + + + Load saved query + + + + Special Indexing + + + + Indexing with special options + + + + Index &statistics + + + + Webcache Editor @@ -1396,11 +1559,11 @@ Please check the desktop file RclTrayIcon Restore - + Obnovit Quit - + UkonÄit @@ -1594,7 +1757,7 @@ Please check the desktop file Snippets - Úryvky + Úryvky @@ -1834,6 +1997,42 @@ Použijte odkaz <b>Ukázat hledání</b>, když máte o výsledku po <i>"term1 term2"p</i> : unordered proximity search with default distance.<br> Use <b>Show Query</b> link when in doubt about result and see manual (&lt;F1>) for more detail. + Zadejte výraz jazyka hledání. Seznam:<br> +<i>term1 term2</i> : 'term1' a 'term2' do kteréhokoli pole.<br> +<i>field:term1</i> : 'term1' do pole 'field'.<br> + Obvyklé názvy polí/synonyma:<br> + title/subject/caption, author/from, recipient/to, filename, ext.<br> + Pseudopole: dir, mime/format, type/rclcat, date.<br> + Příklady intervalů dvou dat: 2009-03-01/2009-05-20 2009-03-01/P2M.<br> +<i>term1 term2 OR term3</i> : term1 AND (term2 OR term3).<br> + Můžete použít kulaté závorky, aby byly vÄ›ci zÅ™etelnÄ›jší.<br> +<i>"term1 term2"</i> : vÄ›tný úsek (musí se objevit pÅ™esnÄ›). Možné modifikátory:<br> +<i>"term1 term2"p</i> : neuspořádané hledání podle blízkosti s výchozí vzdáleností.<br> +Použijte odkaz <b>Ukázat hledání</b>, když máte o výsledku pochybnost, a podívejte se do příruÄky (&lt;F1>) na další podrobnosti. + + + + Stemming languages for stored query: + + + + differ from current preferences (kept) + + + + Auto suffixes for stored query: + + + + External indexes for stored query: + + + + Autophrase is set but it was unset for stored query + + + + Autophrase is unset but it was set for stored query @@ -1974,7 +2173,7 @@ Use <b>Show Query</b> link when in doubt about result and see manual <p>Sorry, no exact match was found within limits. Probably the document is very big and the snippets generator got lost in a maze...</p> - + <p>V rámci omezení hledání nebyla bohužel nalezena žádná shoda. PravdÄ›podobnÄ› je dokument velice velký a vyvíjeÄ Ãºryvků se v nÄ›m ztratil (nebo skonÄil ve Å¡karpÄ›)...</p> @@ -2015,6 +2214,49 @@ Use <b>Show Query</b> link when in doubt about result and see manual Übernehmen + + SpecIdxW + + Special Indexing + + + + Do not retry previously failed files. + + + + Else only modified or failed files will be processed. + + + + Erase selected files data before indexing. + + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + + + + Browse + + + + Start directory (else use regular topdirs): + + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + + + + Selection patterns: + + + + Top indexed entity + + + SpellBase @@ -2134,11 +2376,11 @@ Use <b>Show Query</b> link when in doubt about result and see manual Smallest document length - Délka nejmenšího dokumentu + Délka nejmenšího dokumentu Longest document length - Délka nejdelšího dokumentu + Délka nejdelšího dokumentu Database directory size @@ -2156,6 +2398,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual Value Hodnota + + Smallest document length (terms) + + + + Longest document length (terms) + + + + Results from last indexing: + + + + Documents created/updated + + + + Files tested + + + + Unindexed files + + UIPrefsDialog @@ -2209,6 +2475,26 @@ Use <b>Show Query</b> link when in doubt about result and see manual Default QtWebkit font + Výchozí písmo QtWebkit + + + Any term + Jakýkoli výraz + + + All terms + VÅ¡echny výrazy + + + File name + Název souboru + + + Query language + Jazyk hledání + + + Value from previous program exit @@ -2469,6 +2755,51 @@ Dadurch sollten Ergebnisse, die exakte Übereinstimmungen der Suchworte enthalte <b>Nové hodnoty:</b> + + Webcache + + Webcache editor + + + + Search regexp + + + + + WebcacheEdit + + Copy URL + + + + Unknown indexer state. Can't edit webcache file. + + + + Indexer is running. Can't edit webcache file. + + + + Delete selection + + + + Webcache was modified, you will need to run the indexer after closing this window. + + + + + WebcacheModel + + MIME + + + + Url + + + confgui::ConfBeaglePanelW @@ -2481,7 +2812,7 @@ Dadurch sollten Ergebnisse, die exakte Übereinstimmungen der Suchworte enthalte Entries will be recycled once the size is reached - Záznamy budou opÄ›tnÄ› použity, jakmile bude velikost dosažena + Záznamy budou opÄ›tnÄ› použity, jakmile bude velikost dosažena Web page store directory name @@ -2503,6 +2834,10 @@ Dadurch sollten Ergebnisse, die exakte Übereinstimmungen der Suchworte enthalte Enables indexing Firefox visited pages.<br>(you need also install the Firefox Recoll plugin) Povolí rejstříkování Firefoxem navÅ¡tívených stránek.<br>(také je potÅ™eba, abyste nainstalovali přídavný modul Recollu pro Firefox) + + Entries will be recycled once the size is reached.<br>Only increasing the size really makes sense because reducing the value will not truncate an existing file (only waste space at the end). + + confgui::ConfIndexW @@ -2616,19 +2951,19 @@ To pomůže pÅ™i prohledávání velmi velkých textových souborů (napÅ™. soub Only mime types - + Pouze typy MIME An exclusive list of indexed mime types.<br>Nothing else will be indexed. Normally empty and inactive - + Vybraný seznam rejstříkovaných typů MIME.<br>Nic jiného se nebude rejstříkovat. ObyÄejnÄ› je seznam prázdný a neÄinný Exclude mime types - + VylouÄené typy MIME Mime types not to be indexed - + Typy MIME, které se nemají rejstříkovat @@ -2758,7 +3093,7 @@ To pomůže pÅ™i prohledávání velmi velkých textových souborů (napÅ™. soub Hide duplicate results. - Skrýt zdvojené výsledky + Skrýt zdvojené výsledky. Highlight color for query terms @@ -3060,30 +3395,46 @@ Výchozí hodnota je 2 (procenta). Decide if document filters are shown as radio buttons, toolbar combobox, or menu. - + Rozhodnout, zda se dokumentové filtry ukazují jako kulatá tlaÄítka, rozbalovací seznamy v nástrojovém pruhu, nebo jako nabídka. Document filter choice style: - + Styl výbÄ›ru filtrů dokumentů: Buttons Panel - + Panel s tlaÄítky Toolbar Combobox - + Rozbalovací seznam v nástrojovém panelu Menu - + Nabídka Show system tray icon. - + Ukázat ikonu v oznamovací oblasti panelu. Close to tray instead of exiting. + Zavřít do oznamovací oblasti panelu namísto ukonÄení. + + + Start with simple search mode + + + + Show warning when opening temporary file. + + + + User style to apply to the snippets window.<br> Note: the result page header insert is also included in the snippets window header. + + + + Synonyms file diff --git a/src/qtgui/i18n/recoll_da.qm b/src/qtgui/i18n/recoll_da.qm index b34730f3..541bb8b1 100644 Binary files a/src/qtgui/i18n/recoll_da.qm and b/src/qtgui/i18n/recoll_da.qm differ diff --git a/src/qtgui/i18n/recoll_da.ts b/src/qtgui/i18n/recoll_da.ts index 65a91a8c..8951de5e 100644 --- a/src/qtgui/i18n/recoll_da.ts +++ b/src/qtgui/i18n/recoll_da.ts @@ -37,7 +37,7 @@ message - meddelelse + besked texts @@ -249,7 +249,7 @@ p, li { white-space: pre-wrap; } It seems that manually edited entries exist for recollindex, cannot edit crontab - Det ser ud til, at manuelt redigerede indgange findes for recollindex, kan ikke redigere crontab + Det ser ud til, at manuelt redigerede indgange findes for recollindeks, kan ikke redigere crontab Error installing cron entry. Bad syntax in fields ? @@ -317,7 +317,7 @@ p, li { white-space: pre-wrap; } FirstIdxDialog First indexing setup - Første opsætning indeksering + Opsætning af første indeksering <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -385,7 +385,7 @@ p, li { white-space: pre-wrap; } IdxSchedW Index scheduling setup - Opsætning af tidsplanlægning for index + Opsætning af indeks skedulering <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -407,7 +407,7 @@ p, li { white-space: pre-wrap; } Cron scheduling - Tidsplanlægning med cron + Cron skedulering The tool will let you decide at what time indexing should run and will install a crontab entry. @@ -468,7 +468,7 @@ p, li { white-space: pre-wrap; } Loading preview text into editor - Henter forhÃ¥ndsvisningstekst ind i editoren + Henter forhÃ¥ndsvisningstekst for redigering &Search for: @@ -531,7 +531,7 @@ p, li { white-space: pre-wrap; } Fold lines - Ombryd linier + Ombryd linjer Preserve indentation @@ -598,7 +598,7 @@ p, li { white-space: pre-wrap; } Character set used for reading files which do not identify the character set internally, for example pure text files.<br>The default value is empty, and the value from the NLS environnement is used. - Tegnsæt, der bruges til at læse filer, hvor tegnsættet ikke kan identificeres ud fra indholdet, f.eks rene tekstfiler.<br>Standardværdien er tom, og værdien fra NLS-omgivelserne anvendes. + Tegnsæt, der bruges til at læse filer, hvor tegnsættet ikke kan identificeres ud fra indholdet, f.eks. rene tekstfiler.<br>Standardværdien er tom, og værdien fra NLS-omgivelserne anvendes. Ignored endings @@ -608,7 +608,7 @@ p, li { white-space: pre-wrap; } These are file name endings for files which will be indexed by name only (no MIME type identification attempt, no decompression, no content indexing). Dette er endelser pÃ¥ filnavne for filer, hvor kun navnet vil blive indekseret -(ingen forsøg pÃ¥ identification af MIME-type, ingen dekomprimering, ingen indeksering af indhold). +(ingen forsøg pÃ¥ identifikation af MIME-type, ingen dekomprimering, ingen indeksering af indhold). @@ -686,6 +686,13 @@ p, li { white-space: pre-wrap; } Vis underdokumenter / vedhæftede filer + + QxtConfirmationMessage + + Do not show again. + Vis ikke igen. + + RTIToolW @@ -843,7 +850,7 @@ p, li { white-space: pre-wrap; } message - meddelelse + besked other @@ -941,7 +948,7 @@ Do you want to start the preferences dialog ? Reset the index and start from scratch ? - Nulstil indekset og starte fra bunden? + Nulstil indekset og start forfra? Query in progress.<br>Due to limitations of the indexing library,<br>cancelling will exit the program @@ -965,7 +972,7 @@ Do you want to start the preferences dialog ? Can't update index: indexer running - Kan ikke opdatere indeks: indekseringsprogram kører + Kan ikke opdatere indeks: indeksering kører Indexed MIME Types @@ -1011,15 +1018,15 @@ Kontroller venligst mimeview-filen The indexer is running so things should improve when it's done. - Indekseringsprogram kører sÃ¥ ting burde være bedre nÃ¥r det er færdig. + Indeksering kører, sÃ¥ ting burde være bedre, nÃ¥r den er færdig. The document belongs to an external indexwhich I can't update. - Dokumentet tilhører et ekstern indeks, som jeg ikke kan opdatere. + Dokumentet tilhører et ekstern indeks, som jeg ikke kan opdatere. Click Cancel to return to the list. Click Ignore to show the preview anyway. - Klik pÃ¥ Annuller for at vende tilbage til listen. Klik pÃ¥ Ignorer for at vise forhÃ¥ndsvisningen alligevel. + Klik pÃ¥ Annuller for at vende tilbage til listen. Klik pÃ¥ Ignorer for at vise forhÃ¥ndsvisningen alligevel. Duplicate documents @@ -1047,6 +1054,107 @@ Tjek venligst desktopfilen This configuration tool only works for the main index. Dette konfigurationsværktøj virker kun for hovedindekset. + + The current indexing process was not started from this interface, can't kill it + Den nuværende indekseringsproces blev ikke startet fra denne grænseflade, kan ikke stoppe den + + + Bad paths + Ugyldige stier + + + Bad paths in configuration file: + + Ugyldige stier i konfigurationsfil: + + + Selection patterns need topdir + Mønstre for udvælgelse skal have en øverste mappe + + + Selection patterns can only be used with a start directory + Mønstre for udvælgelse kan kun bruges med en startmappe + + + No search + Ingen søgning + + + No preserved previous search + Ingen tidligere søgning er bevaret + + + Choose file to save + Vælg fil, der skal gemmes + + + Saved Queries (*.rclq) + Gemte forespørgsler (*.rclq) + + + Write failed + Skrivning mislykkedes + + + Could not write to file + Kunne ikke skrive til fil + + + Read failed + Læsning mislykkedes + + + Could not open file: + Kunne ikke Ã¥bne fil: + + + Load error + Indlæsningsfejl + + + Could not load saved query + Kunne ikke indlæse gemte forespørgsel + + + Index scheduling + Indeks skedulering + + + Sorry, not available under Windows for now, use the File menu entries to update the index + Beklager, er endnu ikke tilgængelig for Windows, bruge Fil menuindgange for at opdatere indekset + + + Can't set synonyms file (parse error?) + Kan ikke aktivere synonymer-fil (analysefejl?) + + + The document belongs to an external index which I can't update. + Dokumentet tilhører et eksternt indeks, som jeg ikke kan opdatere. + + + Click Cancel to return to the list. <br>Click Ignore to show the preview anyway (and remember for this session). + Klik pÃ¥ Annuller for at vende tilbage til listen. <br>Klik pÃ¥ Ignorer for at vise forhÃ¥ndsvisningen alligevel. (og husk for denne session). + + + Opening a temporary copy. Edits will be lost if you don't save<br/>them to a permanent location. + Ã…bner en midlertidig kopi. Ændringer vil gÃ¥ tabt, hvis du ikke gemmer<br/>dem til et permanent sted. + + + Do not show this warning next time (use GUI preferences to restore). + Vis ikke denne advarsel næste gang (brug GUI præferencer for at gendanne). + + + Index locked + Indeks lÃ¥st + + + Unknown indexer state. Can't access webcache file. + Indeksering i ukendt tilstand. Kan ikke tilgÃ¥ webcachefil. + + + Indexer is running. Can't access webcache file. + Indeksering kører. Kan ikke tilgÃ¥ webcachefil. + RclMainBase @@ -1156,7 +1264,7 @@ Tjek venligst desktopfilen &Show missing helpers - &Vis manglende hjælpere + &Vis manglende hjælpere PgDown @@ -1168,7 +1276,7 @@ Tjek venligst desktopfilen &Full Screen - &Fuldskærm + &Fuld skærm F11 @@ -1176,7 +1284,7 @@ Tjek venligst desktopfilen Full Screen - Fuldskærm + Fuld skærm &Erase search history @@ -1200,7 +1308,7 @@ Tjek venligst desktopfilen &Show indexed types - &Vis indekserede typer + &Vis indekserede typer Shift+PgUp @@ -1208,7 +1316,7 @@ Tjek venligst desktopfilen &Indexing schedule - &Tidsplan for Indeksering + &Tidsplan for Indeksering E&xternal index dialog @@ -1240,7 +1348,7 @@ Tjek venligst desktopfilen Show results in a spreadsheet-like table - Vis resultater i en regnearklignende tabel + Vis resultater i en regneark-lignende tabel Save as CSV (spreadsheet) file @@ -1274,6 +1382,50 @@ Tjek venligst desktopfilen Next update will retry previously failed files Næste opdatering vil igen forsøge med filer, der tidligere mislykkedes + + Indexing &schedule + Tid&splan for Indeksering + + + Enable synonyms + Aktiver synonymer + + + Save last query + Gem sidste forespørgsel + + + Load saved query + Indlæs gemte forespørgsel + + + Special Indexing + Særlig indeksering + + + Indexing with special options + Indeksering med særlige indstillinger + + + &View + &Vis + + + Missing &helpers + Manglende &hjælpere + + + Indexed &MIME types + Indekserede &MIME-typer + + + Index &statistics + Indeks&statistik + + + Webcache Editor + Rediger webcache + RclTrayIcon @@ -1598,6 +1750,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual <i>"ord1 ord2"p </i> : uordnet nærheds-søgning med standard afstand.<br> Brug <b>Vis Forespørgsel</b> link nÃ¥r i tvivl om resultatet og se manual (&lt;F1>) for flere detaljer. + + Stemming languages for stored query: + Ordstammer til sprogene for gemte forespørgsel: + + + differ from current preferences (kept) + adskiller sig fra de nuværende præferencer (beholdt) + + + Auto suffixes for stored query: + Automatiske suffikser for gemte forespørgsel: + + + External indexes for stored query: + Eksterne Indekser for gemte forespørgsel: + + + Autophrase is set but it was unset for stored query + Autofrase er aktiveret, men var deaktiveret for gemte forespørgsel + + + Autophrase is unset but it was set for stored query + Autofrase er deaktiveret, men var aktiveret for gemte forespørgsel + SSearchBase @@ -1703,6 +1879,49 @@ Brug <b>Vis Forespørgsel</b> link nÃ¥r i tvivl om resultatet og se <p>Desværre blev der ikke, inden for rimelige grænser, fundet en nøjagtig match. Sandsynligvis fordi dokumentet er meget stort, sÃ¥ tekststump-generatoren for vild i mængden...</ p> + + SpecIdxW + + Special Indexing + Særlig indeksering + + + Do not retry previously failed files. + Forsøg ikke igen med filer, der tidligere mislykkedes. + + + Else only modified or failed files will be processed. + Ellers vil kun ændrede eller mislykkede filer blive behandlet. + + + Erase selected files data before indexing. + Slet udvalgte filers data, før indeksering. + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + Mappe for rekursiv indeksering. Dette skal være indenfor det regulære indekserede omrÃ¥de<br> som defineret i konfigurationsfilen (øverste mapper). + + + Browse + Gennemse + + + Start directory (else use regular topdirs): + Startmappe (ellers brug de regulære øverste mapper): + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + Lad stÃ¥ tomt for at vælge alle filer. Du kan bruge adskillige mellemrums-adskilte shell-type mønstre.<br>Mønstre med indlejrede mellemrum skal citeres med dobbelte anførselstegn.<br>Kan kun bruges, hvis startmÃ¥let er angivet. + + + Selection patterns: + Mønstre for udvælgelse: + + + Top indexed entity + Top indekserede enhed + + SpellBase @@ -1814,11 +2033,11 @@ Brug <b>Vis Forespørgsel</b> link nÃ¥r i tvivl om resultatet og se Smallest document length - Mindste dokumentlængde + Mindste dokumentlængde Longest document length - Længste dokumentlængde + Længste dokumentlængde Database directory size @@ -1836,6 +2055,30 @@ Brug <b>Vis Forespørgsel</b> link nÃ¥r i tvivl om resultatet og se Value Værdi + + Smallest document length (terms) + Mindste dokumentlængde (ord) + + + Longest document length (terms) + Længste dokumentlængde (ord) + + + Results from last indexing: + Resultater fra sidste indeksering: + + + Documents created/updated + Dokumenter oprettet/opdateret + + + Files tested + Filer testet + + + Unindexed files + ikke-indekserede filer + UIPrefsDialog @@ -1887,6 +2130,26 @@ Brug <b>Vis Forespørgsel</b> link nÃ¥r i tvivl om resultatet og se Default QtWebkit font Standard skrifttype for QtWebkit + + Any term + VilkÃ¥rlig ord + + + All terms + Alle ord + + + File name + Filnavn + + + Query language + Forespørgselssprog + + + Value from previous program exit + Værdi fra tidligere programafslutning + ViewAction @@ -1958,11 +2221,56 @@ Brug <b>Vis Forespørgsel</b> link nÃ¥r i tvivl om resultatet og se <b>Nye værdier:</b> + + Webcache + + Webcache editor + Rediger webcache + + + Search regexp + Regex søgning + + + + WebcacheEdit + + Copy URL + Kopier URL + + + Unknown indexer state. Can't edit webcache file. + Indeksering i ukendt tilstand. Kan ikke redigere webcachefil. + + + Indexer is running. Can't edit webcache file. + Indeksering kører. Kan ikke redigere webcachefil. + + + Delete selection + Slet det valgte + + + Webcache was modified, you will need to run the indexer after closing this window. + WebCache blev ændret, du er nød til at køre indeksering efter lukning af dette vindue. + + + + WebcacheModel + + MIME + MIME + + + Url + Url + + confgui::ConfBeaglePanelW Entries will be recycled once the size is reached - Indgangene vil blive genbrugt, nÃ¥r størrelsen er nÃ¥et + Indgangene vil blive genbrugt, nÃ¥r størrelsen er nÃ¥et Web page store directory name @@ -1984,6 +2292,10 @@ Brug <b>Vis Forespørgsel</b> link nÃ¥r i tvivl om resultatet og se Enables indexing Firefox visited pages.<br>(you need also install the Firefox Recoll plugin) Aktiverer indeksering af sider besøgt af Firefox.<br>(Du skal ogsÃ¥ installere Firefox Recoll plugin) + + Entries will be recycled once the size is reached.<br>Only increasing the size really makes sense because reducing the value will not truncate an existing file (only waste space at the end). + Indgangene vil blive genbrugt, nÃ¥r størrelsen er nÃ¥et.<br>Kun en øgning af størrelsen giver god mening, da en reducering af værdien ikke vil afkorte en eksisterende fil (kun spildplads i slutningen). + confgui::ConfIndexW @@ -2076,7 +2388,7 @@ Dette er for at udelukke monster logfiler fra indekset. If this value is set (not equal to -1), text files will be split in chunks of this size for indexing. This will help searching very big text files (ie: log files). - Hvis denne værdi er indstillet (ikke lig med -1), vil tekstfiler opdeles i bidder af denne størrelse for indeksering. + Hvis denne værdi er angivet (ikke lig med -1), vil tekstfiler opdeles i bidder af denne størrelse for indeksering. Dette vil hjælpe søgning i meget store tekstfiler (dvs.: log-filer). @@ -2121,7 +2433,7 @@ Dette vil hjælpe søgning i meget store tekstfiler (dvs.: log-filer). These are names of directories which indexing will not enter.<br> May contain wildcards. Must match the paths seen by the indexer (ie: if topdirs includes '/home/me' and '/home' is actually a link to '/usr/home', a correct skippedPath entry would be '/home/me/tmp*', not '/usr/home/me/tmp*') - Dette er navne pÃ¥ mapper, som indeksering ikke gÃ¥r ind i.<br>Kan indeholde jokertegn. Skal stemme overens med stierne, som de er set af indekseringen (dvs. hvis de øverste mapper omfatter '/home/mig' og '/home' er et link til '/usr/home', en korrekt udeladtSti indgang ville være '/home/mig/tmp * ', ikke '/usr/home/mig/tmp * ') + Dette er navne pÃ¥ mapper, som indeksering ikke gÃ¥r ind i.<br>Kan indeholde jokertegn. Skal stemme overens med stierne, som de ses af indekseringsprogrammet (dvs. hvis de øverste mapper omfatter '/home/mig' og '/home' er et link til '/usr/home', en korrekt udeladtSti indgang ville være '/home/mig/tmp * ', ikke '/usr/home/mig/tmp * ') Stemming languages @@ -2193,7 +2505,7 @@ Dette vil hjælpe søgning i meget store tekstfiler (dvs.: log-filer). <p>These are exceptions to the unac mechanism which, by default, removes all diacritics, and performs canonic decomposition. You can override unaccenting for some characters, depending on your language, and specify additional decompositions, e.g. for ligatures. In each space-separated entry, the first character is the source one, and the rest is the translation. - <p>Disse er undtagelser fra unac mekanismen, der, som standard, fjerner alle diakritiske tegn, og udfører kanoniske nedbrydning. Du kan tilsidesætte fjernelse af accent for nogle tegn, afhængigt af dit sprog, og angive yderligere nedbrydninger, f.eks for ligaturer. I hver indgang adskilt af mellemrum, er det første tegn kildedelen, og resten er oversættelsen. + <p>Disse er undtagelser fra unac mekanismen, der, som standard, fjerner alle diakritiske tegn, og udfører kanonisk nedbrydning. Du kan tilsidesætte fjernelse af accent for nogle tegn, afhængigt af dit sprog, og angive yderligere nedbrydninger, f.eks. for ligaturer. I hver indgang adskilt af mellemrum, er det første tegn kildedelen, og resten er oversættelsen. @@ -2320,7 +2632,7 @@ Kan være langsomt for store dokumenter. Query language magic file name suffixes. - Forespørgselssprogets magiske filnavnsendelser. + Forespørgselssprogets magiske filnavnendelser. Enable @@ -2376,11 +2688,11 @@ Kan være langsomt for store dokumenter. Style sheet - Stylesheet + Stilark Opens a dialog to select the style sheet file - Ã…bn et vindue for at vælge stylesheet-filen + Ã…bn et vindue for at vælge stilark-filen Choose @@ -2388,7 +2700,7 @@ Kan være langsomt for store dokumenter. Resets the style sheet to default - Nulstil stylesheet til standard + Nulstil stilark til standard Result List @@ -2400,7 +2712,7 @@ Kan være langsomt for store dokumenter. Edit result page html header insert - Rediger kode for indsætnig i html-header for resultatside + Rediger kode for indsætnig i html-hoved for resultatside Date format (strftime(3)) @@ -2462,7 +2774,7 @@ Standardværdien er 2 (procent). Opens a dialog to select the Snippets window CSS style sheet file - Ã…bner et vindue til at vælge CSS stylesheet-fil for vinduet til tekststumper + Ã…bner et vindue til at vælge CSS stilark-fil for vinduet til tekststumper Resets the Snippets window style @@ -2496,5 +2808,21 @@ Standardværdien er 2 (procent). Close to tray instead of exiting. Luk til systembakke i stedet for at afslutte. + + Start with simple search mode + Start med enkel søgetilstand + + + User style to apply to the snippets window.<br> Note: the result page header insert is also included in the snippets window header. + Brugerstil der skal anvendes pÃ¥ vinduet til tekststumper.<br>Bemærk: Det færdige sidehoved-indstik er ogsÃ¥ inkluderet i tekststumper-vinduets hoved. + + + Synonyms file + Synonymer-fil + + + Show warning when opening temporary file. + Vis advarsel, nÃ¥r der Ã¥bnes en midlertidig fil. + diff --git a/src/qtgui/i18n/recoll_de.qm b/src/qtgui/i18n/recoll_de.qm index e934cf95..a51e5a29 100644 Binary files a/src/qtgui/i18n/recoll_de.qm and b/src/qtgui/i18n/recoll_de.qm differ diff --git a/src/qtgui/i18n/recoll_de.ts b/src/qtgui/i18n/recoll_de.ts index b01b24da..ebdeee9f 100644 --- a/src/qtgui/i18n/recoll_de.ts +++ b/src/qtgui/i18n/recoll_de.ts @@ -729,6 +729,13 @@ Der Standardwert ist "Nein", um doppelte Indizierung zu vermeiden.Untergeordnete Dokumente / Anhänge anzeigen + + QxtConfirmationMessage + + Do not show again. + + + RTIToolW @@ -1103,14 +1110,6 @@ Please check the mimeview file The indexer is running so things should improve when it's done. - - The document belongs to an external indexwhich I can't update. - - - - Click Cancel to return to the list. Click Ignore to show the preview anyway. - - Duplicate documents Doppelte Dokumente @@ -1128,6 +1127,115 @@ Please check the desktop file Indexing interrupted + + The current indexing process was not started from this interface, can't kill it + + + + Bad paths + + + + Bad paths in configuration file: + + + + + Selection patterns need topdir + + + + Selection patterns can only be used with a start directory + + + + No search + + + + No preserved previous search + + + + Choose file to save + + + + Saved Queries (*.rclq) + + + + Write failed + + + + Could not write to file + + + + Read failed + + + + Could not open file: + + + + Load error + + + + Could not load saved query + + + + Index scheduling + + + + Sorry, not available under Windows for now, use the File menu entries to update the index + + + + Disabled because the real time indexer was not compiled in. + + + + This configuration tool only works for the main index. + + + + Can't set synonyms file (parse error?) + + + + The document belongs to an external index which I can't update. + + + + Click Cancel to return to the list. <br>Click Ignore to show the preview anyway (and remember for this session). + + + + Opening a temporary copy. Edits will be lost if you don't save<br/>them to a permanent location. + + + + Do not show this warning next time (use GUI preferences to restore). + + + + Index locked + + + + Unknown indexer state. Can't access webcache file. + + + + Indexer is running. Can't access webcache file. + + RclMainBase @@ -1257,7 +1365,7 @@ Please check the desktop file &Show missing helpers - Zeige fehlende &Hilfsprogramme + Zeige fehlende &Hilfsprogramme PgDown @@ -1313,7 +1421,7 @@ Please check the desktop file &Show indexed types - Zeige indizierte &Typen + Zeige indizierte &Typen Shift+PgUp @@ -1321,7 +1429,7 @@ Please check the desktop file &Indexing schedule - &Zeitplan für Indizierung + &Zeitplan für Indizierung E&xternal index dialog @@ -1387,6 +1495,50 @@ Please check the desktop file Next update will retry previously failed files + + Indexing &schedule + + + + Enable synonyms + + + + Save last query + + + + Load saved query + + + + Special Indexing + + + + Indexing with special options + + + + &View + + + + Missing &helpers + + + + Indexed &MIME types + + + + Index &statistics + + + + Webcache Editor + + RclTrayIcon @@ -1822,6 +1974,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual + + Stemming languages for stored query: + + + + differ from current preferences (kept) + + + + Auto suffixes for stored query: + + + + External indexes for stored query: + + + + Autophrase is set but it was unset for stored query + + + + Autophrase is unset but it was set for stored query + + SSearchBase @@ -2002,6 +2178,49 @@ Drücken Sie ESC+Leerzeichen für Vervollständigungen des aktuellen Begriffs.Übernehmen + + SpecIdxW + + Special Indexing + + + + Do not retry previously failed files. + + + + Else only modified or failed files will be processed. + + + + Erase selected files data before indexing. + + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + + + + Browse + + + + Start directory (else use regular topdirs): + + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + + + + Selection patterns: + + + + Top indexed entity + + + SpellBase @@ -2121,11 +2340,11 @@ Drücken Sie ESC+Leerzeichen für Vervollständigungen des aktuellen Begriffs. Smallest document length - Minimale Zahl von Ausdrücken + Minimale Zahl von Ausdrücken Longest document length - Maximale Zahl von Ausdrücken + Maximale Zahl von Ausdrücken Database directory size @@ -2143,6 +2362,30 @@ Drücken Sie ESC+Leerzeichen für Vervollständigungen des aktuellen Begriffs.Value Wert + + Smallest document length (terms) + + + + Longest document length (terms) + + + + Results from last indexing: + + + + Documents created/updated + + + + Files tested + + + + Unindexed files + + UIPrefsDialog @@ -2198,6 +2441,26 @@ Drücken Sie ESC+Leerzeichen für Vervollständigungen des aktuellen Begriffs.Default QtWebkit font + + Any term + Irgendein Ausdruck + + + All terms + Alle Ausdrücke + + + File name + Dateiname + + + Query language + Suchsprache + + + Value from previous program exit + + UIPrefsDialogBase @@ -2457,6 +2720,51 @@ Dadurch sollten Ergebnisse, die exakte Übereinstimmungen der Suchworte enthalte <b>Neuer Wert</b> + + Webcache + + Webcache editor + + + + Search regexp + + + + + WebcacheEdit + + Copy URL + + + + Unknown indexer state. Can't edit webcache file. + + + + Indexer is running. Can't edit webcache file. + + + + Delete selection + + + + Webcache was modified, you will need to run the indexer after closing this window. + + + + + WebcacheModel + + MIME + + + + Url + + + confgui::ConfBeaglePanelW @@ -2469,7 +2777,7 @@ Dadurch sollten Ergebnisse, die exakte Übereinstimmungen der Suchworte enthalte Entries will be recycled once the size is reached - Einträge werden wiederverwendet sobald die Größe erreicht ist. + Einträge werden wiederverwendet sobald die Größe erreicht ist. Web page store directory name @@ -2491,6 +2799,10 @@ Dadurch sollten Ergebnisse, die exakte Übereinstimmungen der Suchworte enthalte Enables indexing Firefox visited pages.<br>(you need also install the Firefox Recoll plugin) + + Entries will be recycled once the size is reached.<br>Only increasing the size really makes sense because reducing the value will not truncate an existing file (only waste space at the end). + + confgui::ConfIndexW @@ -3067,5 +3379,21 @@ und vermindern den Nutzender automatischen Phrasen. Der Standardwert ist 2.Close to tray instead of exiting. + + Start with simple search mode + + + + User style to apply to the snippets window.<br> Note: the result page header insert is also included in the snippets window header. + + + + Synonyms file + + + + Show warning when opening temporary file. + + diff --git a/src/qtgui/i18n/recoll_el.qm b/src/qtgui/i18n/recoll_el.qm index e1607ad7..39d777d6 100644 Binary files a/src/qtgui/i18n/recoll_el.qm and b/src/qtgui/i18n/recoll_el.qm differ diff --git a/src/qtgui/i18n/recoll_el.ts b/src/qtgui/i18n/recoll_el.ts index ba861538..486b3cec 100644 --- a/src/qtgui/i18n/recoll_el.ts +++ b/src/qtgui/i18n/recoll_el.ts @@ -1,6 +1,6 @@ - + AdvSearch @@ -29,7 +29,7 @@ messages - μηνÏματα + ΜηνÏματα other @@ -372,16 +372,17 @@ p, li { white-space: pre-wrap; } FragButs %1 not found. - + Δεν βÏέθηκε το %1. %1: %2 - + %1: + %2 Query Fragments - + ΘÏαÏσματα εÏωτήματος @@ -440,7 +441,7 @@ p, li { white-space: pre-wrap; } Main "history" file is damaged or un(read)writeable, please check or remove it: - Το αÏχείο ιστοÏÎ¹ÎºÎ¿Ï Î´ÎµÎ½ είναι αναγνώσιμο, ελέγξτε το ή διαγÏάψτε το: + Το αÏχείο ιστοÏÎ¹ÎºÎ¿Ï ÎµÎ¯Ï„Îµ είναι κατεστÏαμμένο είτε δεν είναι αναγνώσιμο/εγγÏάψιμο, παÏακαλώ ελέγξτε το ή διαγÏάψτε το: No db directory in configuration @@ -581,7 +582,7 @@ p, li { white-space: pre-wrap; } Default character set - ΠÏοκαθοÏισμένο σÏνολο χαÏακτήÏων + ΣÏνολο χαÏακτήÏων<br>εξ οÏÎ¹ÏƒÎ¼Î¿Ï This is the character set used for reading files which do not identify the character set internally, for example pure text files.<br>The default value is empty, and the value from the NLS environnement is used. @@ -613,95 +614,95 @@ p, li { white-space: pre-wrap; } Default<br>character set - + ΣÏνολο χαÏακτήÏων<br>εξ οÏÎ¹ÏƒÎ¼Î¿Ï Character set used for reading files which do not identify the character set internally, for example pure text files.<br>The default value is empty, and the value from the NLS environnement is used. - + Το σÏνολο των χαÏακτήÏων που χÏησιμοποιείται για την ανάγνωση των αÏχείων που δεν έχουν εσωτεÏικό αναγνωÏιστικό των χαÏακτήÏων, για παÏάδειγμα αÏχεία Î±Ï€Î»Î¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï…: <br>Η τιμή εξ οÏÎ¹ÏƒÎ¼Î¿Ï ÎµÎ¯Î½Î±Î¹ κενή, και χÏησιμοποιείται η τιμή του πεÏιβάλλοντος NLS. Ignored endings - + Αγνοημένες καταλήξεις These are file name endings for files which will be indexed by name only (no MIME type identification attempt, no decompression, no content indexing). - + Αυτές είναι καταλήξεις αÏχείων στα οποία η ευÏετηÏίαση θα γίνει μόνο βάσει του ονόματος (χωÏίς Ï€Ïοσπάθεια αναγνώÏισης του Ï„Ïπου MIME, χωÏίς αποσυμπίεση, χωÏίς δεικτοδότηση του πεÏιεχομένου). QWidget Create or choose save directory - + ΔημιουÏγία ή επιλογή του καταλόγου αποθήκευσης Choose exactly one directory - + Επιλέξτε μόνο έναν κατάλογο Could not read directory: - + ΑδÏνατη η ανάγνωση του καταλόγου: Unexpected file name collision, cancelling. - + ΑπÏοσδόκητη σÏγκÏουση ονομάτων αÏχείων, ακÏÏωση. Cannot extract document: - + ΑδÏνατη η εξαγωγή του εγγÏάφου: &Preview - + &ΠÏοεπισκόπηση &Open - + Ά&νοιγμα Open With - + Άνοιγμα με Run Script - + Εκτέλεση μακÏοεντολής Copy &File Name - + ΑντιγÏαφή του ονόματος του α&Ïχείου Copy &URL - ΑντιγÏαφή του &URL + ΑντιγÏαφή του &URL &Write to File - Απο&θήκευση σε + &ΕγγÏαφή σε αÏχείο Save selection to files - Αποθήκευση της επιλογής σε αÏχεία + Αποθήκευση της επιλογής σε αÏχεία Preview P&arent document/folder - + ΠÏοεπισκόπηση του &Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/καταλόγου &Open Parent document/folder - + &Άνοιγμα του Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/καταλόγου Find &similar documents - Αναζήτηση παÏÏŒ&μοιων εγγÏάφων + Αναζήτηση παÏÏŒ&μοιων εγγÏάφων Open &Snippets window - Άνοιγμα του παÏαθÏÏου απο&σπασμάτων + Άνοιγμα του παÏαθÏÏου απο&σπασμάτων Show subdocuments / attachments - Εμφάνιση των υπο-εγγÏάφων / συνημμένων + Εμφάνιση των υπο-εγγÏάφων / συνημμένων @@ -1055,44 +1056,45 @@ Please check the mimeview file Document filter - + ΦίλτÏο εγγÏάφου Index not up to date for this file. Refusing to risk showing the wrong entry. - + Το ευÏετήÏιο δεν είναι ενημεÏωμένο για αυτό το αÏχείο. ΆÏνηση της διακινδυνευμένης εμφάνισης μιας λανθασμένης καταχώÏησης. Click Ok to update the index for this file, then you will need to re-run the query when indexing is done. - + Κάντε κλικ στο Εντάξει για να ενημεÏώσετε το ευÏετήÏιο για αυτό το αÏχείο, στη συνέχεια θα Ï€Ïέπει να εκτελέσετε εκ νέου το εÏώτημα μετ το πέÏας της δεικτοδότησης. The indexer is running so things should improve when it's done. - + Τα Ï€Ïάγματα θα βελτιωθοÏν μετά το πέÏας της δεικτοδότησης. The document belongs to an external indexwhich I can't update. - + Το έγγÏαφο ανήκει σε ένα εξωτεÏικό ευÏετήÏιο το οποίο δεν μποÏÏŽ να ενημεÏώσω. Click Cancel to return to the list. Click Ignore to show the preview anyway. - + Κάντε κλικ στο ΑκÏÏωση για να επιστÏέψετε στον κατάλογο. Κάντε κλικ στο Αγνόηση για την εμφάνιση της Ï€Ïοεπισκόπησης οÏτως ή άλλως. Duplicate documents - Διπλότυπα έγγÏαφα + Διπλότυπα έγγÏαφα These Urls ( | ipath) share the same content: - Αυτά τα URL (| ipath) μοιÏάζονται το ίδιο πεÏιεχόμενο: + Αυτά τα Url (| ipath) μοιÏάζονται το ίδιο πεÏιεχόμενο: Bad desktop app spec for %1: [%2] Please check the desktop file - + Κακοδιατυπωμένος Ï€ÏοσδιοÏισμός εφαÏμογής επιφάνειας εÏγασίας για το %1: [%2] +ΠαÏακαλώ ελέγξτε το αÏχείο της επιφάνειας εÏγασίας Indexing interrupted - + Η ευÏετηÏίαση διεκόπη @@ -1239,7 +1241,7 @@ Please check the desktop file &Indexing configuration - &ΔιαμόÏφωση της ευÏετηÏίασης + ΔιαμόÏφωση ευÏετηÏίασης &Indexing schedule @@ -1343,26 +1345,26 @@ Please check the desktop file Query Fragments - + ΘÏαÏσματα εÏωτήματος With failed files retrying - + ΠÏοσπάθεια εκ νέου με αποτυχημένα αÏχεία Next update will retry previously failed files - + Η επόμενη ενημέÏωση θα επιχειÏήσει ξανά με τα αποτυχημένα αÏχεία RclTrayIcon Restore - + ΕπαναφοÏά Quit - + Έξοδος @@ -1496,15 +1498,15 @@ Please check the desktop file &Open - &Άνοιγμα + Ά&νοιγμα Copy &File Name - ΑντιγÏαφή του ονόματος του &αÏχείου + ΑντιγÏαφή του ονόματος του α&Ïχείου Copy &URL - ΑντιγÏαφή του &URL + ΑντιγÏαφή URL &Write to File @@ -1516,11 +1518,11 @@ Please check the desktop file Preview P&arent document/folder - ΠÏοεπισκόπηση του &Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/φακέλου + ΠÏοεπισκόπηση του &Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/καταλόγου &Open Parent document/folder - Άνοιγμα του &Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/φακέλου + &Άνοιγμα του Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/καταλόγου <p><i>Alternate spellings: </i> @@ -1544,7 +1546,7 @@ Please check the desktop file Snippets - Αποσπάσματα + Αποσπάσματα @@ -1559,7 +1561,7 @@ Please check the desktop file &Preview - Π&Ïοεπισκόπηση + &ΠÏοεπισκόπηση &Open @@ -1567,11 +1569,11 @@ Please check the desktop file Copy &File Name - ΑντιγÏαφή του &ονόματος του αÏχείου + ΑντιγÏαφή του ονόματος του α&Ïχείου Copy &URL - ΑντιγÏαφή του &URL + ΑντιγÏαφή URL &Write to File @@ -1583,11 +1585,11 @@ Please check the desktop file Preview P&arent document/folder - ΠÏοεπισκόπηση του &Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου + ΠÏοεπισκόπηση του &Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/καταλόγου &Open Parent document/folder - Άνοιγμα του &Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/φακέλου + &Άνοιγμα του Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/καταλόγου &Reset sort @@ -1614,15 +1616,15 @@ Please check the desktop file &Open - &Άνοιγμα + Ά&νοιγμα Copy &File Name - ΑντιγÏαφή του ονόματος του &αÏχείου + ΑντιγÏαφή του ονόματος του α&Ïχείου Copy &URL - ΑντιγÏαφή του &URL + ΑντιγÏαφή URL &Write to File @@ -1634,11 +1636,11 @@ Please check the desktop file Preview P&arent document/folder - ΠÏοεπισκόπηση του &Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/φακέλου + ΠÏοεπισκόπηση του &Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/καταλόγου &Open Parent document/folder - Άνοιγμα του &Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/φακέλου + &Άνοιγμα του Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/καταλόγου @@ -1653,11 +1655,11 @@ Please check the desktop file Copy &File Name - ΑντιγÏαφή ο&νόματος αÏχείου + ΑντιγÏαφή του ονόματος του α&Ïχείου Copy &URL - ΑντιγÏαφή του &URL + ΑντιγÏαφή URL &Write to File @@ -1669,11 +1671,11 @@ Please check the desktop file Preview P&arent document/folder - ΠÏοεπισκόπηση του &Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/αÏχείου + ΠÏοεπισκόπηση του &Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/καταλόγου &Open Parent document/folder - Άνοιγμα του γο&Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/φακέλου + &Άνοιγμα του Î³Î¿Î½Î¹ÎºÎ¿Ï ÎµÎ³Î³Ïάφου/καταλόγου Find &similar documents @@ -1776,7 +1778,19 @@ Use <b>Show Query</b> link when in doubt about result and see manual <i>"term1 term2"p</i> : unordered proximity search with default distance.<br> Use <b>Show Query</b> link when in doubt about result and see manual (&lt;F1>) for more detail. - + Εισαγωγή έκφÏασης γλώσσας εÏωτήματος. «Σκονάκι»:<br> +<i>ÏŒÏος1 ÏŒÏος2</i> : 'ÏŒÏος1' και 'ÏŒÏος2' σε οποιοδήποτε πεδίο.<br> +<i>πεδίο:ÏŒÏος1</i> : 'ÏŒÏος1' στο πεδίο 'πεδίο'.<br> + Τυπικό πεδίο ονόματα/συνώνυμα:<br> + τίτλος/θέμα/υπόμνημα, συγγÏαφέας/από, παÏαλήπτης/Ï€Ïος, όνομα αÏχείου, επέκταση.<br> + Ψευδο-πεδία: κατάλογος, mime/μοÏφή, Ï„Ïπος/rclcat, ημεÏομηνία, μέγεθος.<br> + ΠαÏαδείγματα δυο διαστημάτων ημεÏομηνιών: 2009-03-01/2009-05-20 2009-03-01/Π2Μ.<br> +<i>ÏŒÏος1 ÏŒÏος2 OR ÏŒÏος3</i> : ÏŒÏος1 AND (ÏŒÏος2 OR ÏŒÏος3).<br> + ΜποÏείτε να χÏησιμοποιείτε παÏενθέσεις για πιο ευανάγνωστες εκφÏάσεις.<br> +<i>"ÏŒÏος1 ÏŒÏος2"</i> : φÏάση (Ï€Ïέπει να αντιστοιχεί ακÏιβώς). Πιθανοί Ï„Ïοποποιητές:<br> +<i>"ÏŒÏος1 ÏŒÏος2"p</i> : αταξινόμητη και κατά Ï€Ïοσέγγιση αναζήτηση με Ï€ÏοκαθοÏισμένη απόσταση.<br> +ΧÏησιμοποιήστε τον δεσμό <b>Εμφάνιση εÏωτήματος</b> σε πεÏίπτωση αμφιβολίας σχετικά με το αποτέλεσμα και ανατÏέξτε στο εγχειÏίδιο χÏήσης (&lt;F1>) για πεÏισσότεÏες λεπτομέÏειες. + @@ -1880,7 +1894,7 @@ Use <b>Show Query</b> link when in doubt about result and see manual <p>Sorry, no exact match was found within limits. Probably the document is very big and the snippets generator got lost in a maze...</p> - + <p>Λυπάμαι, δεν βÏέθηκε μια ακÏιβής αντιστοιχία εντός οÏίων. Πιθανώς το έγγÏαφο να είναι ογκώδες και ο δημιουÏγός αποσπασμάτων χάθηκε σε έναν λαβÏÏινθο...</p> @@ -2073,7 +2087,7 @@ Use <b>Show Query</b> link when in doubt about result and see manual Default QtWebkit font - + ΓÏαμματοσειÏά εξ οÏÎ¹ÏƒÎ¼Î¿Ï QtWebkit @@ -2307,19 +2321,19 @@ This will help searching very big text files (ie: log files). Only mime types - + Μόνο οι Ï„Ïποι MIME An exclusive list of indexed mime types.<br>Nothing else will be indexed. Normally empty and inactive - + Μια αποκλειστική λίστα δεικτοδοτημένων Ï„Ïπων mime.<br>Δεν θα δεικτοδοτηθεί τίποτα άλλο. Φυσιολογικά κενό και αδÏανές Exclude mime types - + Αποκλεισμός Ï„Ïπων αÏχείων Mime types not to be indexed - + Οι Ï„Ïποι Mime που δεν θα δεικτοδοτηθοÏν @@ -2402,7 +2416,7 @@ This will help searching very big text files (ie: log files). The language for the aspell dictionary. This should look like 'en' or 'fr' ...<br>If this value is not set, the NLS environment will be used to compute it, which usually works.To get an idea of what is installed on your system, type 'aspell config' and look for .dat files inside the 'data-dir' directory. - Η γλώσσα του Î»ÎµÎ¾Î¹ÎºÎ¿Ï Ï„Î¿Ï… aspell. Μια σωστή τιμή μοιάζει με 'en' ή 'el'...<br>Αν αυτή η τιμή δεν έχει οÏιστεί, θα χÏησιμοποιηθεί το πεÏιβάλλον για τον υπολογισμό της, κάτι που συνήθως δουλεÏει καλά. Για να πάÏετε μια ιδέα για το τι είναι εγκατεστημένο στο σÏστημά σας, πληκτÏολογήστε 'aspell config' και αναζητήστε τα αÏχεία .dat μέσα στον κατάλογο 'data-dir'. + Η γλώσσα για το λεξικό aspell. Αυτό θα Ï€Ïέπει να είναι του Ï„Ïπου «en» ή «el» ...<br> Αν αυτή η τιμή δεν οÏιστεί, χÏησιμοποιείται το εθνικό πεÏιβάλλον NLS για να την υπολογίσει, που συνήθως δουλεÏει. Για να πάÏετε μια ιδέα του τι είναι εγκατεστημένο στο σÏστημά σας, πληκτÏολογήστε «aspell config» και παÏατηÏήστε τα αÏχεία .dat στον κατάλογο «data-dir». Database directory name @@ -2410,7 +2424,7 @@ This will help searching very big text files (ie: log files). The name for a directory where to store the index<br>A non-absolute path is taken relative to the configuration directory. The default is 'xapiandb'. - Το όνομα του καταλόγου αποθήκευσης του ευÏετηÏίου.<br>Μια σχετική διαδÏομή αναφεÏόμενη στη διαδÏομή διαμόÏφωσης. Η Ï€Ïοεπιλογή είναι 'xapiandb'. + Το όνομα του καταλόγου αποθήκευσης του ευÏετηÏίου<br>Μια σχετική διαδÏομή αναφεÏόμενη στη διαδÏομή διαμόÏφωσης. Η εξ' οÏÎ¹ÏƒÎ¼Î¿Ï ÎµÎ¯Î½Î±Î¹ «xapiandb». The language for the aspell dictionary. This should look like 'en' or 'fr' ...<br>If this value is not set, the NLS environment will be used to compute it, which usually works. To get an idea of what is installed on your system, type 'aspell config' and look for .dat files inside the 'data-dir' directory. @@ -2723,31 +2737,31 @@ May be slow for big documents. Decide if document filters are shown as radio buttons, toolbar combobox, or menu. - + ΚαθοÏίζει αν τα φίλτÏα των εγγÏάφων θα εμφανίζονται ως κουμπιά επιλογών, γÏαμμή εÏγαλείων πλαισίων συνδυασμών, ή μενοÏ. Document filter choice style: - + ΤεχνοτÏοπία επιλογής φίλτÏου εγγÏάφων: Buttons Panel - + Πίνακας κουμπιών Toolbar Combobox - + ΓÏαμμή εÏγαλείων πλαισίων συνδυασμών Menu - + ÎœÎµÎ½Î¿Ï Show system tray icon. - + Εμφάνιση του εικονιδίου πλαισίου συστήματος. Close to tray instead of exiting. - + Αντί για έξοδο, καταχώνιασμα στο πλαίσιο συστήματος. diff --git a/src/qtgui/i18n/recoll_es.qm b/src/qtgui/i18n/recoll_es.qm index 7cd623e7..e8f524e1 100644 Binary files a/src/qtgui/i18n/recoll_es.qm and b/src/qtgui/i18n/recoll_es.qm differ diff --git a/src/qtgui/i18n/recoll_es.ts b/src/qtgui/i18n/recoll_es.ts index b59de84c..eefda7c7 100644 --- a/src/qtgui/i18n/recoll_es.ts +++ b/src/qtgui/i18n/recoll_es.ts @@ -717,6 +717,13 @@ Click Cancel if you want to edit the configuration file before indexing starts, Mostrar subdocumentos / adjuntos + + QxtConfirmationMessage + + Do not show again. + + + RTIToolW @@ -1097,14 +1104,6 @@ Por favor revise el archivo mimeconf The indexer is running so things should improve when it's done. - - The document belongs to an external indexwhich I can't update. - - - - Click Cancel to return to the list. Click Ignore to show the preview anyway. - - Duplicate documents Documentos duplicados @@ -1118,6 +1117,115 @@ Por favor revise el archivo mimeconf Please check the desktop file + + The current indexing process was not started from this interface, can't kill it + + + + Bad paths + + + + Bad paths in configuration file: + + + + + Selection patterns need topdir + + + + Selection patterns can only be used with a start directory + + + + No search + + + + No preserved previous search + + + + Choose file to save + + + + Saved Queries (*.rclq) + + + + Write failed + + + + Could not write to file + + + + Read failed + + + + Could not open file: + + + + Load error + + + + Could not load saved query + + + + Index scheduling + + + + Sorry, not available under Windows for now, use the File menu entries to update the index + + + + Disabled because the real time indexer was not compiled in. + + + + This configuration tool only works for the main index. + + + + Can't set synonyms file (parse error?) + + + + The document belongs to an external index which I can't update. + + + + Click Cancel to return to the list. <br>Click Ignore to show the preview anyway (and remember for this session). + + + + Opening a temporary copy. Edits will be lost if you don't save<br/>them to a permanent location. + + + + Do not show this warning next time (use GUI preferences to restore). + + + + Index locked + + + + Unknown indexer state. Can't access webcache file. + + + + Indexer is running. Can't access webcache file. + + RclMainBase @@ -1247,7 +1355,7 @@ Please check the desktop file &Show missing helpers - &Mostrar ayudantes faltantes + &Mostrar ayudantes faltantes PgDown @@ -1307,7 +1415,7 @@ Please check the desktop file &Show indexed types - &Mostrar tipos indexados + &Mostrar tipos indexados Shift+PgUp @@ -1315,7 +1423,7 @@ Please check the desktop file &Indexing schedule - &Horario de indexación + &Horario de indexación E&xternal index dialog @@ -1381,6 +1489,50 @@ Please check the desktop file Next update will retry previously failed files + + Indexing &schedule + + + + Enable synonyms + + + + Save last query + + + + Load saved query + + + + Special Indexing + + + + Indexing with special options + + + + &View + + + + Missing &helpers + + + + Indexed &MIME types + + + + Index &statistics + + + + Webcache Editor + + RclTrayIcon @@ -1823,6 +1975,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual + + Stemming languages for stored query: + + + + differ from current preferences (kept) + + + + Auto suffixes for stored query: + + + + External indexes for stored query: + + + + Autophrase is set but it was unset for stored query + + + + Autophrase is unset but it was set for stored query + + SSearchBase @@ -1956,6 +2132,49 @@ Use <b>Show Query</b> link when in doubt about result and see manual + + SpecIdxW + + Special Indexing + + + + Do not retry previously failed files. + + + + Else only modified or failed files will be processed. + + + + Erase selected files data before indexing. + + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + + + + Browse + Buscar + + + Start directory (else use regular topdirs): + + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + + + + Selection patterns: + + + + Top indexed entity + + + SpellBase @@ -2071,11 +2290,11 @@ Use <b>Show Query</b> link when in doubt about result and see manual Smallest document length - Tamaño del documento más pequeño + Tamaño del documento más pequeño Longest document length - Tamaño del documento más grande + Tamaño del documento más grande Database directory size @@ -2093,6 +2312,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual Value Valor + + Smallest document length (terms) + + + + Longest document length (terms) + + + + Results from last indexing: + + + + Documents created/updated + + + + Files tested + + + + Unindexed files + + UIPrefsDialog @@ -2148,6 +2391,26 @@ Use <b>Show Query</b> link when in doubt about result and see manual Default QtWebkit font + + Any term + Cualquier término + + + All terms + Todos los términos + + + File name + + + + Query language + Lenguaje de consulta + + + Value from previous program exit + + ViewAction @@ -2239,6 +2502,51 @@ Use <b>Show Query</b> link when in doubt about result and see manual <b>Nuevos valores</b> + + Webcache + + Webcache editor + + + + Search regexp + + + + + WebcacheEdit + + Copy URL + + + + Unknown indexer state. Can't edit webcache file. + + + + Indexer is running. Can't edit webcache file. + + + + Delete selection + + + + Webcache was modified, you will need to run the indexer after closing this window. + + + + + WebcacheModel + + MIME + + + + Url + + + confgui::ConfBeaglePanelW @@ -2251,7 +2559,7 @@ Use <b>Show Query</b> link when in doubt about result and see manual Entries will be recycled once the size is reached - Las entradas serán recicladas una vez que el tamaño es alcanzado + Las entradas serán recicladas una vez que el tamaño es alcanzado Web page store directory name @@ -2273,6 +2581,10 @@ Use <b>Show Query</b> link when in doubt about result and see manual Enables indexing Firefox visited pages.<br>(you need also install the Firefox Recoll plugin) Habilita la indexación de páginas visitadas en Firefox.<br>(necesita también el plugin Recoll para Firefox) + + Entries will be recycled once the size is reached.<br>Only increasing the size really makes sense because reducing the value will not truncate an existing file (only waste space at the end). + + confgui::ConfIndexW @@ -2841,5 +3153,21 @@ El valor por defecto es 2 (por ciento). Close to tray instead of exiting. + + Start with simple search mode + + + + User style to apply to the snippets window.<br> Note: the result page header insert is also included in the snippets window header. + + + + Synonyms file + + + + Show warning when opening temporary file. + + diff --git a/src/qtgui/i18n/recoll_fr.qm b/src/qtgui/i18n/recoll_fr.qm index fa6ae1e8..81da9eeb 100644 Binary files a/src/qtgui/i18n/recoll_fr.qm and b/src/qtgui/i18n/recoll_fr.qm differ diff --git a/src/qtgui/i18n/recoll_fr.ts b/src/qtgui/i18n/recoll_fr.ts index 282295b7..33300a52 100644 --- a/src/qtgui/i18n/recoll_fr.ts +++ b/src/qtgui/i18n/recoll_fr.ts @@ -737,6 +737,13 @@ Click Cancel if you want to edit the configuration file before indexing starts, Afficher les sous-documents et attachements + + QxtConfirmationMessage + + Do not show again. + Ne plus afficher. + + RTIToolW @@ -1127,11 +1134,11 @@ Please check the mimeview file The document belongs to an external indexwhich I can't update. - Le document appartient à un index externe que je ne peux pas mettre à jour. + Le document appartient à un index externe que je ne peux pas mettre à jour. Click Cancel to return to the list. Click Ignore to show the preview anyway. - Cliquer Annuler pour retourner à la liste. Cliquer Ignorer pour afficher tout de même. + Cliquer Annuler pour retourner à la liste. Cliquer Ignorer pour afficher tout de même. Duplicate documents @@ -1147,6 +1154,115 @@ Please check the desktop file Mauvaise spécification d'application pour %1: [%2] Merci de vérifier le fichier desktop + + Bad paths + Chemins inexistants + + + Bad paths in configuration file: + + Chemins inexistants définis dans le fichier de configuration: + + + Selection patterns need topdir + Les schémas de sélection nécessitent un répertoire de départ + + + Selection patterns can only be used with a start directory + Les schémas de sélection ne peuvent être utilisés qu'avec un répertoire de départ + + + No search + Pas de recherche + + + No preserved previous search + Pas de recherche sauvegardée + + + Choose file to save + Choisir un fichier pour sauvegarder + + + Saved Queries (*.rclq) + Recherches Sauvegardées (*.rclq) + + + Write failed + Échec d'écriture + + + Could not write to file + Impossible d'écrire dans le fichier + + + Read failed + Erreur de lecture + + + Could not open file: + Impossible d'ouvrir le fichier: + + + Load error + Erreur de chargement + + + Could not load saved query + Le chargement de la recherche sauvegardée a échoué + + + Opening a temporary copy. Edits will be lost if you don't save<br/>them to a permanent location. + Ouverture d'un fichier temporaire. Les modification seront perdues<br/>si vous ne les sauvez pas dans un emplacement permanent. + + + Do not show this warning next time (use GUI preferences to restore). + Ne plus afficher ce message (utiliser le dialogue de préférences pour rétablir). + + + Disabled because the real time indexer was not compiled in. + Désactivé parce que l'indexeur au fil de l'eau n'est pas disponible dans cet exécutable. + + + This configuration tool only works for the main index. + Cet outil de configuration ne travaille que sur l'index principal. + + + The current indexing process was not started from this interface, can't kill it + Le processus d'indexation en cours n'a pas été démarré depuis cette interface, impossible de l'arrêter + + + The document belongs to an external index which I can't update. + Le document appartient à un index externe que je ne peux pas mettre à jour. + + + Click Cancel to return to the list. <br>Click Ignore to show the preview anyway (and remember for this session). + Cliquer Annulation pour retourner à la liste.<br>Cliquer Ignorer pour afficher la prévisualisation de toutes facons (mémoriser l'option pour la session). + + + Index scheduling + Programmation de l'indexation + + + Sorry, not available under Windows for now, use the File menu entries to update the index + Désolé, pas disponible pour Windows pour le moment, utiliser les entrées du menu fichier pour mettre à jour l'index + + + Can't set synonyms file (parse error?) + Impossible d'ouvrir le fichier des synonymes (erreur dans le fichier?) + + + Index locked + L'index est verrouillé + + + Unknown indexer state. Can't access webcache file. + État de l'indexeur inconnu. Impossible d'accéder au fichier webcache. + + + Indexer is running. Can't access webcache file. + L'indexeur est actif. Impossible d'accéder au fichier webcache. + RclMainBase @@ -1276,7 +1392,7 @@ Merci de vérifier le fichier desktop &Show missing helpers - Afficher les application&s manquantes + Afficher les application&s manquantes PgDown @@ -1336,7 +1452,7 @@ Merci de vérifier le fichier desktop &Show indexed types - &Afficher les types indexés + &Afficher les types indexés Shift+PgUp @@ -1344,7 +1460,7 @@ Merci de vérifier le fichier desktop &Indexing schedule - &Planning d'indexation + &Planning d'indexation E&xternal index dialog @@ -1410,6 +1526,50 @@ Merci de vérifier le fichier desktop Next update will retry previously failed files La prochaine mise à jour de l'index essaiera de traiter les fichiers actuellement en échec + + Save last query + Sauvegarder la dernière recherche + + + Load saved query + Charger une recherche sauvegardée + + + Special Indexing + Indexation spéciale + + + Indexing with special options + Indexation avec des options spéciales + + + Indexing &schedule + Programme d'indexation + + + Enable synonyms + Activer les synonymes + + + &View + &Voir + + + Missing &helpers + &Traducteurs manquants + + + Indexed &MIME types + Types &MIME indexés + + + Index &statistics + &Statistiques de l'index + + + Webcache Editor + Editeur &Webcache + RclTrayIcon @@ -1883,6 +2043,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual Use <b>Show Query</b> link when in doubt about result and see manual (&lt;F1>) for more detail. + + Stemming languages for stored query: + Les langages d'expansion pour la recherche sauvegardée: + + + differ from current preferences (kept) + diffèrent des préférences en cours (conservées) + + + Auto suffixes for stored query: + L'option de suffixe automatique pour la recherche sauvegardée: + + + External indexes for stored query: + Les index externes pour la recherche sauvegardée: + + + Autophrase is set but it was unset for stored query + L'option autophrase est positionnée, mais ne l'était pas pour la recherche sauvegardée + + + Autophrase is unset but it was set for stored query + L'option autophrase est désactivée mais était active pour la recherche sauvegardée + SSearchBase @@ -2062,6 +2246,53 @@ Use <b>Show Query</b> link when in doubt about result and see manual Appliquer + + SpecIdxW + + Special Indexing + Indexation spéciale + + + Do not retry previously failed files. + Ne pas réessayer les fichiers en erreur. + + + Else only modified or failed files will be processed. + Sinon, seulement les fichiers modifiés ou en erreur seront traités. + + + Erase selected files data before indexing. + Effacer les données pour les fichiers sélectionnés avant de réindexer. + + + Directory to recursively index + Répertoire à indexer récursivement + + + Browse + Parcourir + + + Start directory (else use regular topdirs): + Répertoire de départ (sinon utiliser la variable normale topdirs): + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + Laisser vide pour sélectionner tous les fichiers. Vous pouvez utiliser plusieurs schémas séparés par des espaces.<br>Les schémas contenant des espaces doivent ere enclos dans des apostrophes doubles.<br>Ne peut être utilisé que si le répertoire de départ est positionné. + + + Selection patterns: + Schémas de sélection: + + + Top indexed entity + Objet indexé de démarrage + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + Répertoire à indexer récursivement. Il doit être à l'intérieur de la zone normale<br>définie par la variable topdirs. + + SpellBase @@ -2185,11 +2416,11 @@ Use <b>Show Query</b> link when in doubt about result and see manual Smallest document length - Longueur du plus petit document + Longueur du plus petit document Longest document length - Longueur du plus grand document + Longueur du plus grand document Database directory size @@ -2207,6 +2438,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual Value Valeur + + Smallest document length (terms) + Taille minimale document (termes) + + + Longest document length (terms) + Taille maximale document (termes) + + + Results from last indexing: + Résultats de la dernière indexation: + + + Documents created/updated + Documents créés ou mis à jour + + + Files tested + Fichiers testés + + + Unindexed files + Fichiers non indexés + UIPrefsDialog @@ -2262,6 +2517,26 @@ Use <b>Show Query</b> link when in doubt about result and see manual Default QtWebkit font Fonte par défaut de QtWebkit + + Any term + Certains termes + + + All terms + Tous les termes + + + File name + Nom de fichier + + + Query language + Language d'interrogation + + + Value from previous program exit + Valeur obtenue de la dernière exécution + UIPrefsDialogBase @@ -2568,6 +2843,51 @@ Ceci devrait donner une meilleure pertinence aux résultats où les termes reche <b>Nouveaux param&egrave;tres</b> + + Webcache + + Webcache editor + Editeur Webcache + + + Search regexp + Recherche (regexp) + + + + WebcacheEdit + + Copy URL + Copier l'URL + + + Unknown indexer state. Can't edit webcache file. + État indexeur inconnu. Impossible d'éditer le fichier webcache. + + + Indexer is running. Can't edit webcache file. + L'indexeur est actif. Impossible d'accéder au fichier webcache. + + + Delete selection + Détruire les entrées sélectionnées + + + Webcache was modified, you will need to run the indexer after closing this window. + Le fichier webcache a été modifié, il faudra redémarrer l'indexation après avoir fermé cette fenêtre. + + + + WebcacheModel + + MIME + MIME + + + Url + Url + + confgui::ConfBeaglePanelW @@ -2592,7 +2912,7 @@ Ceci devrait donner une meilleure pertinence aux résultats où les termes reche Entries will be recycled once the size is reached - Les pages seront écrasées quand la taille spécifiée est atteinte + Les pages seront écrasées quand la taille spécifiée est atteinte Web page store directory name @@ -2614,6 +2934,10 @@ Ceci devrait donner une meilleure pertinence aux résultats où les termes reche Enables indexing Firefox visited pages.<br>(you need also install the Firefox Recoll plugin) Permet d'indexer les pages Web visitées avec Firefox <br>(il vous faut également installer l'extension Recoll pour Firefox) + + Entries will be recycled once the size is reached.<br>Only increasing the size really makes sense because reducing the value will not truncate an existing file (only waste space at the end). + Les entrées seront recyclées quand la taille sera atteinte.<br>Seule l'augmentation de la taille a un sens parce que réduire la valeur ne tronquera pas un fichier existant (mais gachera de l'espace à la fin). + confgui::ConfIndexW @@ -3199,5 +3523,21 @@ La valeur par défaut est 2% Close to tray instead of exiting. Réduire dans la barre d'état au lieu de quitter. + + Start with simple search mode + Démarrer en mode recherche simple + + + Show warning when opening temporary file. + Afficher un avertissement quand on édite une copie temporaire. + + + User style to apply to the snippets window.<br> Note: the result page header insert is also included in the snippets window header. + Style utilisateur à appliquer à la fenêtre "snippets".<br>Note: l'en tête de page de résultat est aussi inclus dans la fenêtre "snippets". + + + Synonyms file + Fichier de synonymes + diff --git a/src/qtgui/i18n/recoll_hu.qm b/src/qtgui/i18n/recoll_hu.qm new file mode 100644 index 00000000..62b410db Binary files /dev/null and b/src/qtgui/i18n/recoll_hu.qm differ diff --git a/src/qtgui/i18n/recoll_hu.ts b/src/qtgui/i18n/recoll_hu.ts new file mode 100644 index 00000000..0fd941a1 --- /dev/null +++ b/src/qtgui/i18n/recoll_hu.ts @@ -0,0 +1,2716 @@ + + + + + AdvSearch + + All clauses + Minden feltétel + + + Any clause + Bármely feltétel + + + media + Média + + + other + Egyéb + + + Bad multiplier suffix in size filter + Hibás sokszorozó utótag a méretszűrÅ‘ben! + + + text + Szöveg + + + spreadsheet + Munkafüzet + + + presentation + Prezentáció + + + message + Üzenet + + + + AdvSearchBase + + Advanced search + Összetett keresés + + + Search for <br>documents<br>satisfying: + A keresés módja: + + + Delete clause + Feltétel törlése + + + Add clause + Új feltétel + + + Restrict file types + Fájltípus + + + Check this to enable filtering on file types + A találatok szűrése a megadott fájltípusokra + + + By categories + Kategória + + + Check this to use file categories instead of raw mime types + A találatok szűrése MIME típus helyett fájlkategóriára + + + Save as default + Mentés alapértelmezettként + + + Searched file types + Keresett fájltípusok + + + All ----> + Mind -----> + + + Sel -----> + Kijelölt -----> + + + <----- Sel + <----- Kijelölt + + + <----- All + <----- Mind + + + Ignored file types + Kizárt fájltípusok + + + Enter top directory for search + A keresés kezdÅ‘ könyvtárának megadása + + + Browse + Tallózás + + + Restrict results to files in subtree: + Keresés az alábbi könyvtárból indulva: + + + Start Search + A keresés indítása + + + Close + Bezárás + + + All non empty fields on the right will be combined with AND ("All clauses" choice) or OR ("Any clause" choice) conjunctions. <br>"Any" "All" and "None" field types can accept a mix of simple words, and phrases enclosed in double quotes.<br>Fields with no data are ignored. + A jobb oldali nem üres mezÅ‘k „Minden feltétel†választásakor ÉS, „Bármely feltétel†választásakor VAGY kapcsolatban lesznek.<br>A „Bármely szóâ€, „Minden szó†és az „Egyik sem†típusú mezÅ‘kben szavak és idézÅ‘jelbe tett részmondatok kombinációja adható meg.<br>Az üres mezÅ‘k figyelmen kívül lesznek hagyva. + + + Invert + Megfordítás + + + Minimum size. You can use k/K,m/M,g/G as multipliers + Minimális méret, sokszorozó utótag lehet a k/K, m/M, g/G + + + Min. Size + legalább + + + Maximum size. You can use k/K,m/M,g/G as multipliers + Maximális méret, sokszorozó utótag lehet a k/K, m/M, g/G + + + Max. Size + legfeljebb + + + Filter + SzűrÅ‘k + + + From + ettÅ‘l + + + To + eddig + + + Check this to enable filtering on dates + A találatok szűrése a fájlok dátuma alapján + + + Filter dates + Dátum + + + Find + Keresés + + + Check this to enable filtering on sizes + A találatok szűrése a fájlok mérete alapján + + + Filter sizes + Méret + + + + CronToolW + + Cron Dialog + Cron idÅ‘zítÅ‘ + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Recoll</span> batch indexing schedule (cron) </p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Each field can contain a wildcard (*), a single numeric value, comma-separated lists (1,3,5) and ranges (1-7). More generally, the fields will be used <span style=" font-style:italic;">as is</span> inside the crontab file, and the full crontab syntax can be used, see crontab(5).</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br />For example, entering <span style=" font-family:'Courier New,courier';">*</span> in <span style=" font-style:italic;">Days, </span><span style=" font-family:'Courier New,courier';">12,19</span> in <span style=" font-style:italic;">Hours</span> and <span style=" font-family:'Courier New,courier';">15</span> in <span style=" font-style:italic;">Minutes</span> would start recollindex every day at 12:15 AM and 7:15 PM</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A schedule with very frequent activations is probably less efficient than real time indexing.</p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A <span style=" font-weight:600;">Recoll</span> indexelÅ‘ idÅ‘zítése (cron) </p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Minden mezÅ‘ben megadható csillag (*), szám, számok listája (1,3,5) vagy számtartomány (1-7). Ãltalánosabban, a mezÅ‘k jelentése ugyanaz, <span style=" font-style:italic;">mint</span> a crontab fájlban, és a teljes crontab szintaxis használható, lásd a crontab(5) kézikönyvlapot.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br />Például <span style=" font-family:'Courier New,courier';">*</span>-ot írva a <span style=" font-style:italic;">naphoz, </span><span style=" font-family:'Courier New,courier';">12,19</span>-et az <span style=" font-style:italic;">órához</span> és <span style=" font-family:'Courier New,courier';">15</span>-öt a <span style=" font-style:italic;">perchez</span>, a recollindex minden nap 12:15-kor és du. 7:15-kor fog elindulni.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Túl gyakori ütemezés helyett célszerűbb lehet a valós idejű indexelés engedélyezése.</p></body></html> + + + Days of week (* or 0-7, 0 or 7 is Sunday) + A hét napja (* vagy 0-7, 0 vagy 7 a vasárnap) + + + Hours (* or 0-23) + Óra (* vagy 0-23) + + + Minutes (0-59) + Perc (0-59) + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Click <span style=" font-style:italic;">Disable</span> to stop automatic batch indexing, <span style=" font-style:italic;">Enable</span> to activate it, <span style=" font-style:italic;">Cancel</span> to change nothing.</p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A <span style=" font-style:italic;">Kikapcsolás</span> megszünteti, a <span style=" font-style:italic;">Bekapcsolás</span> aktiválja az idÅ‘zített indexelést, a <span style=" font-style:italic;">Mégsem</span> nem változtat a beállításon.</p></body></html> + + + Enable + Bekapcsolás + + + Disable + Kikapcsolás + + + It seems that manually edited entries exist for recollindex, cannot edit crontab + Úgy tűnik, egy kézi bejegyzése van a recollindexnek, nem sikerült a crontab szerkesztése! + + + Error installing cron entry. Bad syntax in fields ? + Hiba a cron bejegyzés hozzáadásakor! Rossz szintaxis a mezÅ‘kben? + + + + EditDialog + + Dialog + Párbeszédablak + + + + EditTrans + + Source path + Eredeti elérési út + + + Local path + Helyi elérési út + + + Config error + Beállítási hiba + + + Original path + Eredeti elérési út + + + + EditTransBase + + Path Translations + Elérési út átalakítása + + + Setting path translations for + Elérési út-átalakítás ehhez: + + + Select one or several file types, then use the controls in the frame below to change how they are processed + KijelölhetÅ‘ egy vagy több elérési út is + + + Add + Hozzáadás + + + Delete + Törlés + + + Cancel + Mégsem + + + Save + Mentés + + + + FirstIdxDialog + + First indexing setup + Az indexelés beállítása elsÅ‘ induláskor + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">It appears that the index for this configuration does not exist.</span><br /><br />If you just want to index your home directory with a set of reasonable defaults, press the <span style=" font-style:italic;">Start indexing now</span> button. You will be able to adjust the details later. </p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If you want more control, use the following links to adjust the indexing configuration and schedule.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">These tools can be accessed later from the <span style=" font-style:italic;">Preferences</span> menu.</p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">A jelenlegi beállításokhoz még nem tartozik index.</span><br /><br />A saját mappa indexelése javasolt alapbeállításokkal az <span style=" font-style:italic;">Indexelés indítása most</span> gombbal indítható. A beállítások késÅ‘bb módosíthatók.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Az alábbi hivatkozások az indexelés finomhangolására és idÅ‘zítésére szolgálnak.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Ezek a lehetÅ‘ségek késÅ‘bb a <span style=" font-style:italic;">Beállítások</span> menübÅ‘l is elérhetÅ‘k.</p></body></html> + + + Indexing configuration + Az indexelés beállításai + + + This will let you adjust the directories you want to index, and other parameters like excluded file paths or names, default character sets, etc. + Megadható az indexelendÅ‘ könyvtárak köre és egyéb paraméterek, például kizárt elérési utak vagy fájlnevek, alapértelmezett betűkészlet stb. + + + Indexing schedule + Az idÅ‘zítés beállításai + + + This will let you chose between batch and real-time indexing, and set up an automatic schedule for batch indexing (using cron). + LehetÅ‘ség van ütemezett indításra és valós idejű indexelésre, az elÅ‘bbi idÅ‘zítése is beállítható (a cron segítségével). + + + Start indexing now + Indexelés indítása most + + + + FragButs + + %1 not found. + A fájl nem található: %1. + + + %1: + %2 + %1: + %2 + + + Query Fragments + Statikus szűrÅ‘k + + + + IdxSchedW + + Index scheduling setup + Az indexelés idÅ‘zítése + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Recoll</span> indexing can run permanently, indexing files as they change, or run at discrete intervals. </p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Reading the manual may help you to decide between these approaches (press F1). </p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This tool can help you set up a schedule to automate batch indexing runs, or start real time indexing when you log in (or both, which rarely makes sense). </p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A <span style=" font-weight:600;">Recoll</span> indexelÅ‘ futhat folyamatosan, így a fájlok változásakor az index is azonnal frissül, vagy indulhat meghatározott idÅ‘közönként.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A kézikönyv segítséget nyújt a két eljárás közül a megfelelÅ‘ kiválasztásához (F1).</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">LehetÅ‘ség van az idÅ‘zített indexelés ütemezésére, vagy a valós idejű indexelÅ‘ automatikus indítására bejelentkezéskor (vagy mindkettÅ‘re, bár ez ritkán célszerű).</p></body></html> + + + Cron scheduling + Cron idÅ‘zítÅ‘ + + + The tool will let you decide at what time indexing should run and will install a crontab entry. + Az indexelés kezdÅ‘ idÅ‘pontjainak beállítása egy crontab bejegyzés által. + + + Real time indexing start up + Valós idejű indexelés indítása + + + Decide if real time indexing will be started when you log in (only for the default index). + A valós idejű indexelés indítása bejelentkezéskor (csak az alapértelmezett indexhez). + + + + ListDialog + + Dialog + Párbeszédablak + + + GroupBox + GroupBox + + + + Main + + No db directory in configuration + Nincs adatbáziskönyvtár a beállítófájlban + + + "history" file is damaged or un(read)writeable, please check or remove it: + Az elÅ‘zmények fájlja sérült vagy nem lehet írni/olvasni, ellenÅ‘rizni vagy törölni kell: + + + + Preview + + Close Tab + Lap bezárása + + + Cancel + Mégsem + + + Missing helper program: + Hiányzó segédprogram: + + + Can't turn doc into internal representation for + Nem sikerült értelmezni: + + + Creating preview text + ElÅ‘nézet létrehozása + + + Loading preview text into editor + Az elÅ‘nézet betöltése a megjelenítÅ‘be + + + &Search for: + Kere&sés: + + + &Next + &KövetkezÅ‘ + + + &Previous + &ElÅ‘zÅ‘ + + + Clear + Törlés + + + Match &Case + Kis- és &nagybetűk + + + Error while loading file + Hiba a fájl betöltése közben! + + + + PreviewTextEdit + + Show fields + MezÅ‘k + + + Show main text + Tartalom + + + Print + Nyomtatás + + + Print Current Preview + A jelenlegi nézet nyomtatása + + + Show image + Kép + + + Select All + Mindent kijelöl + + + Copy + Másolás + + + Save document to file + Mentés fájlba + + + Fold lines + Sortörés + + + Preserve indentation + Eredeti tördelés + + + + QObject + + Global parameters + Ãltalános beállítások + + + Local parameters + Helyi beállítások + + + <b>Customised subtrees + <b>Egyedi alkönyvtárak + + + The list of subdirectories in the indexed hierarchy <br>where some parameters need to be redefined. Default: empty. + Az indexelt hierarchián belüli alkönyvtárak listája,<br> melyekre eltérÅ‘ beállítások vonatkoznak. Alapértelmezetten üres. + + + <i>The parameters that follow are set either at the top level, if nothing<br>or an empty line is selected in the listbox above, or for the selected subdirectory.<br>You can add or remove directories by clicking the +/- buttons. + <i>Ha a fenti listából semmi vagy egy üres sor van kijelölve, úgy a következÅ‘ jellemzÅ‘k<br>az indexelendÅ‘ legfelsÅ‘ szintű, egyébként a kijelölt mappára vonatkoznak.<br>A +/- gombokkal lehet a listához könyvtárakat adni vagy onnan törölni. + + + Skipped names + Kizárt nevek + + + These are patterns for file or directory names which should not be indexed. + Mintával megadható fájl- és könyvtárnevek, melyeket nem kell indexelni + + + Follow symbolic links + Szimbolikus linkek követése + + + Follow symbolic links while indexing. The default is no, to avoid duplicate indexing + Indexeléskor kövesse a szimbolikus linkeket.<br>Alapértelmezetten ki van kapcsolva, elkerülendÅ‘ a dupla indexelést. + + + Index all file names + Minden fájlnév indexelése + + + Index the names of files for which the contents cannot be identified or processed (no or unsupported mime type). Default true + A Recoll számára ismeretlen típusú vagy értelmezhetetlen fájlok nevét is indexelje.<br>Alapértelmezetten engedélyezve van. + + + Search parameters + Keresési beállítások + + + Web history + Webes elÅ‘zmények + + + Default<br>character set + Alapértelmezett<br>karakterkódolás + + + Character set used for reading files which do not identify the character set internally, for example pure text files.<br>The default value is empty, and the value from the NLS environnement is used. + A karakterkódolásról információt nem tároló fájlok (például egyszerű szöveges fájlok) kódolása.<br>Alapértelmezetten nincs megadva, és a nyelvi környezet (NLS) alapján lesz beállítva. + + + Ignored endings + Kizárt kiterjesztések + + + These are file name endings for files which will be indexed by name only +(no MIME type identification attempt, no decompression, no content indexing). + Az ilyen fájlnévvégzÅ‘désű fájlok csak a nevük alapján indexelendÅ‘k +(nem történik MIME típusfelismerés, kicsomagolás és tartalomindexelés sem). + + + + QWidget + + Create or choose save directory + Mentési könyvtár megadása + + + Choose exactly one directory + Csak pontosan egy könyvtár adható meg! + + + Could not read directory: + A könyvtár nem olvasható: + + + Unexpected file name collision, cancelling. + A fájl már létezik, ezért ki lesz hagyva. + + + Cannot extract document: + Nem sikerült kicsomagolni a fájlt: + + + &Preview + &ElÅ‘nézet + + + &Open + &Megnyitás + + + Open With + Megnyitás ezzel: + + + Run Script + Szkript futtatása + + + Copy &File Name + &Fájlnév másolása + + + Copy &URL + &URL másolása + + + &Write to File + Menté&s fájlba + + + Save selection to files + A kijelölés mentése fájlba + + + Preview P&arent document/folder + A szülÅ‘ elÅ‘né&zete + + + &Open Parent document/folder + A szülÅ‘ megnyi&tása + + + Find &similar documents + &Hasonló dokumentum keresése + + + Open &Snippets window + Ér&demi részek + + + Show subdocuments / attachments + Aldokumentumok / csatolmányok + + + + QxtConfirmationMessage + + Do not show again. + Ne jelenjen meg újra. + + + + RTIToolW + + Real time indexing automatic start + A valós idejű indexelés automatikus indítása + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Recoll</span> indexing can be set up to run as a daemon, updating the index as files change, in real time. You gain an always up to date index, but system resources are used permanently.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A <span style=" font-weight:600;">Recoll</span> indexelÅ‘je indítható szolgáltatásként, így az index minden fájlváltozáskor azonnal frissül. ElÅ‘nye a mindig naprakész index, de folyamatosan igénybe veszi az erÅ‘forrásokat.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p></body></html> + + + Start indexing daemon with my desktop session. + Az indexelÅ‘ szolgáltatás indítása a munkamenettel + + + Also start indexing daemon right now. + Az indexelÅ‘ szolgáltatás indítása most + + + Replacing: + Csere: + + + Replacing file + Fájl cseréje + + + Can't create: + Nem sikerült létrehozni: + + + Warning + Figyelmeztetés + + + Could not execute recollindex + A recollindex indítása sikertelen + + + Deleting: + Törlés: + + + Deleting file + Fájl törlése + + + Removing autostart + Az autostart kikapcsolása + + + Autostart file deleted. Kill current process too ? + Az autostart fájl törölve lett. A most futó indexelÅ‘t is le kell állítani? + + + + RclMain + + (no stemming) + (nincs szótÅ‘képzés) + + + (all languages) + (minden nyelv) + + + error retrieving stemming languages + hiba a szótÅ‘képzés nyelvének felismerésekor + + + Indexing in progress: + Az indexelés folyamatban: + + + Purge + törlés + + + Stemdb + szótövek adatbázisa + + + Closing + lezárás + + + Unknown + ismeretlen + + + Query results + A keresés eredménye + + + Cannot retrieve document info from database + Nem sikerült az adatbázisban információt találni a dokumentumról. + + + Warning + Figyelmeztetés + + + Can't create preview window + Nem sikerült létrehozni az elÅ‘nézetet + + + This search is not active any more + Ez a keresés már nem aktív. + + + Cannot extract document or create temporary file + Nem sikerült a kicsomagolás vagy az ideiglenes fájl létrehozása. + + + Executing: [ + Végrehajtás: [ + + + About Recoll + A Recoll névjegye + + + History data + ElÅ‘zményadatok + + + Document history + ElÅ‘zmények + + + Update &Index + &Index frissítése + + + Stop &Indexing + Indexelé&s leállítása + + + All + Mind + + + media + Média + + + message + Üzenet + + + other + Egyéb + + + presentation + Prezentáció + + + spreadsheet + Munkafüzet + + + text + Szöveg + + + sorted + rendezett + + + filtered + szűrt + + + No helpers found missing + Nincs hiányzó segédprogram. + + + Missing helper programs + Hiányzó segédprogramok + + + No external viewer configured for mime type [ + Nincs külsÅ‘ megjelenítÅ‘ beállítva ehhez a MIME típushoz [ + + + The viewer specified in mimeview for %1: %2 is not found. +Do you want to start the preferences dialog ? + A mimeview fájlban megadott megjelenítÅ‘ ehhez: %1: %2 nem található. +Megnyissuk a beállítások ablakát? + + + Can't access file: + A fájl nem elérhetÅ‘: + + + Can't uncompress file: + Nem sikerült kicsomagolni a fájlt: + + + Save file + Fájl mentése + + + Result count (est.) + Találatok száma (kb.) + + + Could not open external index. Db not open. Check external indexes list. + Egy külsÅ‘ index megnyitása nem sikerült. EllenÅ‘rizni kell a külsÅ‘ indexek listáját. + + + No results found + Nincs találat + + + None + semmi + + + Updating + frissítés + + + Done + kész + + + Monitor + figyelés + + + Indexing failed + Sikertelen indexelés + + + The current indexing process was not started from this interface. Click Ok to kill it anyway, or Cancel to leave it alone + A jelenleg futó indexelÅ‘ nem errÅ‘l a felületrÅ‘l lett indítva.<br>Az OK gombbal kilÅ‘hetÅ‘, a Mégsem gombbal meghagyható. + + + Erasing index + Index törlése + + + Reset the index and start from scratch ? + Indulhat az index törlése és teljes újraépítése? + + + Query in progress.<br>Due to limitations of the indexing library,<br>cancelling will exit the program + A keresés folyamatban van.<br>Az indexelÅ‘ korlátozásai miatt<br>megszakításkor a program kilép. + + + Error + Hiba + + + Index not open + Nincs megnyitott index + + + Index query error + Indexlekérdezési hiba + + + Content has been indexed for these mime types: + Az alábbi MIME típusok szerepelnek az indexben: + + + Can't update index: indexer running + Nem sikerült frissíteni az indexet: az indexelÅ‘ már fut. + + + Indexed MIME Types + Indexelt MIME típusok + + + Bad viewer command line for %1: [%2] +Please check the mimeview file + Hibás a megjelenítÅ‘ parancssor ehhez: %1: [%2] +EllenÅ‘rizni kell a mimeview fájlt! + + + Viewer command line for %1 specifies both file and parent file value: unsupported + %1 megjelenítÅ‘ parancssora fájlt és szülÅ‘t is megad: ez nem támogatott. + + + Cannot find parent document + Nem található a szülÅ‘dokumentum. + + + Indexing did not run yet + Az indexelÅ‘ jelenleg nem fut. + + + External applications/commands needed for your file types and not found, as stored by the last indexing pass in + Az alábbi külsÅ‘ alkalmazások/parancsok hiányoznak a legutóbbi indexelés során keletkezett napló alapján -----> + + + Sub-documents and attachments + Aldokumentumok és csatolmányok + + + Document filter + DokumentumszűrÅ‘ + + + Index not up to date for this file. Refusing to risk showing the wrong entry. + A fájl bejegyzése az indexben elavult. Esetlegesen téves adatok megjelenítése helyett kihagyva. + + + Click Ok to update the index for this file, then you will need to re-run the query when indexing is done. + Az OK-ra kattintva frissíthetÅ‘ a fájl indexbejegyzése, ennek végeztével újra kell futtatni a keresést. + + + The indexer is running so things should improve when it's done. + Az indexelÅ‘ fut, ennek végeztére a dolgok még helyreállhatnak. + + + Duplicate documents + Másodpéldányok + + + These Urls ( | ipath) share the same content: + Ezek az URL-ek (| ipath) azonos tartalmúak: + + + Bad desktop app spec for %1: [%2] +Please check the desktop file + Hibás alkalmazásbeállítás ehhez:%1: [%2] +EllenÅ‘rizni kell az asztali beállítófájlt! + + + Indexing interrupted + Az indexelés megszakadt. + + + The current indexing process was not started from this interface, can't kill it + A jelenleg futó indexelÅ‘ nem errÅ‘l a felületrÅ‘l lett indítva, nem állítható le. + + + Bad paths + Hibás elérési utak + + + Bad paths in configuration file: + + Hibás elérési utak a beállítófájlban: + + + Selection patterns need topdir + A mintához kezdÅ‘ könyvtár szükséges + + + Selection patterns can only be used with a start directory + Minta használatához kezdÅ‘ könyvtárt is meg kell adni. + + + No search + Nincs keresés + + + No preserved previous search + Nincs elÅ‘zÅ‘leg mentett keresés + + + Choose file to save + Mentés ide + + + Saved Queries (*.rclq) + Mentett keresések (*.rclq) + + + Write failed + Sikertelen írásművelet + + + Could not write to file + A fájl írása sikertelen + + + Read failed + Sikertelen olvasás + + + Could not open file: + Nem sikerült megnyitni a fájlt: + + + Load error + Betöltési hiba + + + Could not load saved query + Nem sikerült betölteni a mentett keresést + + + Index scheduling + Az idÅ‘zítés beállításai + + + Sorry, not available under Windows for now, use the File menu entries to update the index + Sajnos Windows rendszeren még nem vehetÅ‘ igénybe, a Fájl menübÅ‘l lehet frissíteni az indexet. + + + Disabled because the real time indexer was not compiled in. + Nem elérhetÅ‘, mert a valós idejű indexelés nincs a programba fordítva. + + + This configuration tool only works for the main index. + Ez a beállítóeszköz csak az elsÅ‘dleges indexszel használható. + + + Can't set synonyms file (parse error?) + Nem lehet betölteni a szinonímafájlt (értelmezési hiba?) + + + The document belongs to an external index which I can't update. + A dokumentum külsÅ‘ indexhez tartozik, mely innen nem frissíthetÅ‘. + + + Click Cancel to return to the list. <br>Click Ignore to show the preview anyway (and remember for this session). + Visszatérés a listához: Mégsem.<b>Az elÅ‘nézet megnyitása mindenképp (és megjegyzés erre a munkamenetre): MellÅ‘zés. + + + Opening a temporary copy. Edits will be lost if you don't save<br/>them to a permanent location. + Egy ideiglenes másolat lesz megnyitva. A módosítások<br/>megÅ‘rzéséhez a fájlt el kell menteni máshová. + + + Do not show this warning next time (use GUI preferences to restore). + Ne jelenjen meg többször (a GUI beállításaiban visszaállítható). + + + Index locked + Az index zárolva van + + + Unknown indexer state. Can't access webcache file. + Az indexelÅ‘ állapota ismeretlen. A webes gyorstár nem hozzáférhetÅ‘. + + + Indexer is running. Can't access webcache file. + Az indexelÅ‘ fut. A webes gyorstár nem hozzáférhetÅ‘. + + + + RclMainBase + + Recoll + Recoll + + + &File + &Fájl + + + &Tools + &Eszközök + + + &Preferences + &Beállítások + + + &Help + &Súgó + + + E&xit + &Kilépés + + + Ctrl+Q + Ctrl+Q + + + Update &index + Az &index frissítése + + + &Erase document history + &ElÅ‘zmények törlése + + + &About Recoll + A Recoll &névjegye + + + &User manual + &Felhasználói kézikönyv + + + Document &History + &ElÅ‘zmények + + + Document History + ElÅ‘zmények + + + &Advanced Search + Összetett &keresés + + + Advanced/complex Search + Összetett keresés + + + &Sort parameters + &Rendezési beállítások + + + Sort parameters + Rendezési beállítások + + + Term &explorer + &Szóvizsgáló + + + Term explorer tool + Szóvizsgáló + + + Next page + KövetkezÅ‘ oldal + + + Next page of results + KövetkezÅ‘ oldal + + + First page + ElsÅ‘ oldal + + + Go to first page of results + ElsÅ‘ oldal + + + Previous page + ElÅ‘zÅ‘ oldal + + + Previous page of results + ElÅ‘zÅ‘ oldal + + + External index dialog + KülsÅ‘ indexek + + + PgDown + PgDown + + + PgUp + PgUp + + + &Full Screen + &Teljes képernyÅ‘ + + + F11 + F11 + + + Full Screen + Teljes képernyÅ‘ + + + &Erase search history + Keresé&si elÅ‘zmények törlése + + + Sort by dates from oldest to newest + NövekvÅ‘ rendezés dátum szerint + + + Sort by dates from newest to oldest + CsökkenÅ‘ rendezés dátum szerint + + + Show Query Details + A keresés részletei + + + &Rebuild index + Index új&raépítése + + + Shift+PgUp + Shift+PgUp + + + E&xternal index dialog + &KülsÅ‘ indexek + + + &Index configuration + &Indexelés + + + &GUI configuration + &Felhasználói felület + + + &Results + &Találatok + + + Sort by date, oldest first + NövekvÅ‘ rendezés dátum szerint + + + Sort by date, newest first + CsökkenÅ‘ rendezés dátum szerint + + + Show as table + Táblázatos nézet + + + Show results in a spreadsheet-like table + A találatok megjelenítése táblázatban + + + Save as CSV (spreadsheet) file + Mentés CSV (strukturált szöveg) fájlba + + + Saves the result into a file which you can load in a spreadsheet + A találatok mentése egy táblázatkezelÅ‘vel megnyitható fájlba + + + Next Page + KövetkezÅ‘ oldal + + + Previous Page + ElÅ‘zÅ‘ oldal + + + First Page + ElsÅ‘ oldal + + + Query Fragments + Statikus szűrÅ‘k + + + With failed files retrying + A sikerteleneket újra + + + Next update will retry previously failed files + A következÅ‘ frissítéskor újra próbálja a sikertelenül indexelt fájlokat + + + Indexing &schedule + IdÅ‘zí&tés + + + Enable synonyms + Szinonímák engedélyezése + + + Save last query + A legutóbbi keresés mentése + + + Load saved query + Mentett keresés betöltése + + + Special Indexing + Egyedi indexelés + + + Indexing with special options + Indexelés egyedi beállításokkal + + + &View + &Nézet + + + Missing &helpers + &Hiányzó segédprogramok + + + Indexed &MIME types + Indexelt &MIME típusok + + + Index &statistics + &Statisztika + + + Webcache Editor + Webes gyorstár szerkesztése + + + + RclTrayIcon + + Restore + A Recoll megjelenítése + + + Quit + Kilépés + + + + RecollModel + + Abstract + Tartalmi kivonat + + + Author + SzerzÅ‘ + + + Document size + A dokumentum mérete + + + Document date + A dokumentum dátuma + + + File size + A fájl mérete + + + File name + Fájlnév + + + File date + A fájl dátuma + + + Keywords + Kulcsszavak + + + Original character set + Eredeti karakterkódolás + + + Relevancy rating + Relevancia + + + Title + Cím + + + URL + URL + + + Mtime + Módosítás ideje + + + Date + Dátum + + + Date and time + Dátum és idÅ‘ + + + Ipath + BelsÅ‘ elérési út + + + MIME type + MIME típus + + + + ResList + + Result list + Találati lista + + + (show query) + (a&nbsp;keresés&nbsp;részletei) + + + Document history + ElÅ‘zmények + + + <p><b>No results found</b><br> + <p><b>Nincs találat.</b><br> + + + Previous + ElÅ‘zÅ‘ + + + Next + KövetkezÅ‘ + + + Unavailable document + Elérhetetlen dokumentum + + + Preview + ElÅ‘nézet + + + Open + Megnyitás + + + <p><i>Alternate spellings (accents suppressed): </i> + <p><i>Alternatív írásmód (ékezetek nélkül): </i> + + + Documents + Találatok a lapon: + + + out of at least + • Az összes találat: + + + for + + + + <p><i>Alternate spellings: </i> + <p><i>Alternatív írásmód: </i> + + + Result count (est.) + Találatok száma (kb.) + + + Query details + A keresés részletei + + + Snippets + Érdemi részek + + + + ResTable + + &Reset sort + &Rendezés alaphelyzetbe + + + &Delete column + Oszlop &törlése + + + Save table to CSV file + A táblázat mentése CSV fájlba + + + Can't open/create file: + Nem sikerült megnyitni/létrehozni: + + + &Save as CSV + &Mentés CSV fájlba + + + Add "%1" column + „%1†oszlop hozzáadása + + + + SSearch + + Any term + Bármely szó + + + All terms + Minden szó + + + File name + Fájlnév + + + Query language + KeresÅ‘nyelv + + + Bad query string + Hibás keresÅ‘kifejezés + + + Out of memory + Elfogyott a memória + + + Enter file name wildcard expression. + A fájlnév megadásához helyettesítÅ‘ karakterek is használhatók + + + Enter search terms here. Type ESC SPC for completions of current term. + Ide kell írni a keresÅ‘szavakat. +ESC SZÓKÖZ billentyűsorozat: a szó lehetséges kiegészítéseit ajánlja fel. + + + Enter query language expression. Cheat sheet:<br> +<i>term1 term2</i> : 'term1' and 'term2' in any field.<br> +<i>field:term1</i> : 'term1' in field 'field'.<br> + Standard field names/synonyms:<br> + title/subject/caption, author/from, recipient/to, filename, ext.<br> + Pseudo-fields: dir, mime/format, type/rclcat, date, size.<br> + Two date interval exemples: 2009-03-01/2009-05-20 2009-03-01/P2M.<br> +<i>term1 term2 OR term3</i> : term1 AND (term2 OR term3).<br> + You can use parentheses to make things clearer.<br> +<i>"term1 term2"</i> : phrase (must occur exactly). Possible modifiers:<br> +<i>"term1 term2"p</i> : unordered proximity search with default distance.<br> +Use <b>Show Query</b> link when in doubt about result and see manual (&lt;F1>) for more detail. + + KeresÅ‘nyelvi kifejezés megadása. Segítség:<br> +<i>szo1 szo2</i> : 'szo1' és 'szo2' bármely mezÅ‘ben.<br> +<i>mezo:szo1</i> : 'szo1' a 'mezo' nevű mezÅ‘ben.<br> + Szabványos mezÅ‘nevek/szinonímák:<br> + title/subject/caption, author/from, recipient/to, filename, ext.<br> + PszeudómezÅ‘k: dir, mime/format, type/rclcat, date, size.<br> + Péda dátumtartományra: 2009-03-01/2009-05-20 2009-03-01/P2M.<br> +<i>szo1 szo2 OR szo3</i> : szo1 AND (szo2 OR szo3).<br> + A jobb olvashatóság érdekében használhatók zárójelek.<br> +<i>"szo1 szo2"</i> : részmondat (pontosan így kell elÅ‘fordulnia). Lehetséges módosítók:<br> +<i>"szo1 szo2"p</i> : szavak egymáshoz közel, bármilyen sorrendben, alapértelmezett távolsággal.<br> +<b>A keresés részletei</b> segíthet feltárni a nem várt találatok okát. Részletesebb leírás a kézikönyvben (&lt;F1>) található. + + + + Stemming languages for stored query: + A mentett keresés szótÅ‘képzÅ‘ nyelve: + + + differ from current preferences (kept) + eltér a jelenlegi beállítástól (megtartva). + + + Auto suffixes for stored query: + A mentett keresés automatikus toldalékolása: + + + External indexes for stored query: + A mentett keresés külsÅ‘ indexe: + + + Autophrase is set but it was unset for stored query + Az „automatikus részmondat†be van kapcsolva, de a keresés mentésekor tiltva volt. + + + Autophrase is unset but it was set for stored query + Az „automatikus részmondat†ki van kapcsolva, de a keresés mentésekor engedélyezve volt. + + + + SSearchBase + + SSearchBase + SSearchBase + + + Clear + Törlés + + + Ctrl+S + Ctrl+S + + + Erase search entry + A keresÅ‘mezÅ‘ törlése + + + Search + Keresés + + + Start query + A keresés indítása + + + Enter search terms here. Type ESC SPC for completions of current term. + Ide kell írni a keresÅ‘szavakat. +ESC SZÓKÖZ billentyűsorozat: a szó lehetséges kiegészítéseit ajánlja fel. + + + Choose search type. + A keresés módjának kiválasztása + + + + SearchClauseW + + Select the type of query that will be performed with the words + A megadott szavakkal végzett keresés típusának kiválasztása + + + Number of additional words that may be interspersed with the chosen ones + A keresett szavak között található további szavak megengedett száma + + + No field + Nincs mezÅ‘ + + + Any + Bármely szó + + + All + Mind + + + None + semmi + + + Phrase + Részmondat + + + Proximity + Távolság + + + File name + Fájlnév + + + + Snippets + + Snippets + Érdemi részek + + + Find: + Keresés: + + + Next + KövetkezÅ‘ + + + Prev + ElÅ‘zÅ‘ + + + + SnippetsW + + Search + Keresés + + + <p>Sorry, no exact match was found within limits. Probably the document is very big and the snippets generator got lost in a maze...</p> + <p>Sajnos a megadott határok között nincs pontos egyezés. Talán túl nagy a dokumentum, és a feldolgozó elakadt...</p> + + + + SpecIdxW + + Special Indexing + Egyedi indexelés + + + Do not retry previously failed files. + A korábban sikertelenül indexelt fájlok kihagyása + + + Else only modified or failed files will be processed. + Egyébként csak a módosult vagy korábban sikertelenül indexelt fájlok lesznek feldolgozva + + + Erase selected files data before indexing. + A kijelölt fájlok tárolt adatainak törlése indexelés elÅ‘tt + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + A könyvtár, melyet rekurzívan indexelni kell.<br>A beállítófájlban megadott kezdÅ‘ könyvtáron (topdir) belül kell lennie. + + + Browse + Tallózás + + + Start directory (else use regular topdirs): + KezdÅ‘ könyvtár (üresen a rendes kezdÅ‘ könyvtár): + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + Az összes fájl feldolgozásához üresen kell hagyni. Szóközökkel elválasztva több shell típusú minta is megadható.<br>A szóközt tartalmazó mintákat kettÅ‘s idézÅ‘jellel kell védeni.<br>Csak kezdÅ‘ könyvtár megadásával együtt használható. + + + Selection patterns: + Kijelölés mintával: + + + Top indexed entity + Az indexelendÅ‘ kezdÅ‘ könyvtár + + + + SpellBase + + Term Explorer + Szóvizsgáló + + + &Expand + &Listázás + + + Alt+E + Alt+E + + + &Close + &Bezárás + + + Alt+C + Alt+C + + + No db info. + Nincs információ az adatbázisról. + + + Match + Egyéb beállítások + + + Case + Kis-és nagybetű + + + Accents + Ékezetek + + + + SpellW + + Wildcards + HelyettesítÅ‘ karakterek + + + Regexp + Reguláris kifejezés + + + Stem expansion + SzótÅ‘ és toldalékok + + + Spelling/Phonetic + Ãrásmód/fonetika + + + error retrieving stemming languages + hiba a szótÅ‘képzés nyelvének felismerésekor + + + Aspell init failed. Aspell not installed? + Az aspell indítása nem sikerült. Telepítve van? + + + Aspell expansion error. + Aspell toldalékolási hiba. + + + No expansion found + Nincsenek toldalékok. + + + Term + Szó + + + Doc. / Tot. + Dok. / Össz. + + + Index: %1 documents, average length %2 terms.%3 results + Index: %1 dokumentum, átlagosan %2 szó. %3 találat. + + + %1 results + %1 találat + + + List was truncated alphabetically, some frequent + Ez egy rövidített, betűrend szerinti lista, gyakori + + + terms may be missing. Try using a longer root. + szavak hiányozhatnak. Javallott hosszabb szógyök megadása. + + + Show index statistics + Indexstatisztika + + + Number of documents + A dokumentumok száma + + + Average terms per document + A szavak átlagos száma dokumentumonként + + + Database directory size + Az adatbázis mérete + + + MIME types: + MIME típusok: + + + Item + Megnevezés + + + Value + Érték + + + Smallest document length (terms) + A szavak száma a legrövidebb dokumentumban + + + Longest document length (terms) + A szavak száma a leghosszabb dokumentumban + + + Results from last indexing: + A legutóbbi indexelés eredménye: + + + Documents created/updated + létrehozott/frissített dokumentum + + + Files tested + vizsgált fájl + + + Unindexed files + nem indexelt fájl + + + + UIPrefsDialog + + error retrieving stemming languages + hiba a szótÅ‘képzés nyelvének felismerésekor + + + The selected directory does not appear to be a Xapian index + A kijelölt könyvtár nem tartalmaz Xapian indexet. + + + This is the main/local index! + Ez a fÅ‘-/helyi index! + + + The selected directory is already in the index list + A kijelölt könyvtár már szerepel az indexben. + + + Choose + Tallózás + + + Result list paragraph format (erase all to reset to default) + A találati lista bekezdésformátuma (törléssel visszaáll az alapértelmezettre) + + + Result list header (default is empty) + A találati lista fejléce (alapértelmezetten üres) + + + Select recoll config directory or xapian index directory (e.g.: /home/me/.recoll or /home/me/.recoll/xapiandb) + A Recoll beállításainak vagy a Xapian indexnek a könyvtára (pl.: /home/felhasznalo/.recoll vagy /home/felhasznalo/.recoll/xapiandb) + + + The selected directory looks like a Recoll configuration directory but the configuration could not be read + A kijelölt könyvtárban egy olvashatatlan Recoll beállítás található. + + + At most one index should be selected + Csak egy indexet lehet kijelölni. + + + Cant add index with different case/diacritics stripping option + EltérÅ‘ kis-és nagybetű-, ill. ékezetkezelésű index nem adható hozzá. + + + Default QtWebkit font + Alapértelmezett QtWebkit betűkészlet + + + Any term + Bármely szó + + + All terms + Minden szó + + + File name + Fájlnév + + + Query language + KeresÅ‘nyelv + + + Value from previous program exit + A legutóbb használt + + + + ViewAction + + Command + Parancs + + + MIME type + MIME típus + + + Desktop Default + Asztali alapértelmezés + + + Changing entries with different current values + A cserélendÅ‘ mezÅ‘k értéke eltér egymástól. + + + + ViewActionBase + + Native Viewers + Dokumentumtípusok megjelenítÅ‘i + + + Close + Bezárás + + + Select one or several mime types then use the controls in the bottom frame to change how they are processed. + Egy vagy több MIME típus kijelölése után az alsó keretben állítható be az adott típusokhoz elvárt művelet. + + + Use Desktop preferences by default + Az asztali alapértelmezés alkalmazása + + + Select one or several file types, then use the controls in the frame below to change how they are processed + KijelölhetÅ‘ egy vagy több elérési út is + + + Exception to Desktop preferences + Eltérés az asztali beállításoktól + + + Action (empty -> recoll default) + Művelet (üres -> Recoll alapértelmezés) + + + Apply to current selection + Alkalmazás a kijelöltekre + + + Recoll action: + Recoll művelet: + + + current value + jelenlegi érték + + + Select same + Azonosak kijelölése + + + <b>New Values:</b> + <b>Új érték:</b> + + + + Webcache + + Webcache editor + Webes gyorstár szerkesztése + + + Search regexp + Keresés reguláris kifejezéssel + + + + WebcacheEdit + + Copy URL + URL másolása + + + Unknown indexer state. Can't edit webcache file. + Az indexelÅ‘ állapota ismeretlen. A webes gyorstár nem szerkeszthetÅ‘. + + + Indexer is running. Can't edit webcache file. + Az indexelÅ‘ fut. A webes gyorstár nem szerkeszthetÅ‘. + + + Delete selection + A kijelöltek törlése + + + Webcache was modified, you will need to run the indexer after closing this window. + A webes gyorstár módosult. Ezen ablak bezárása után indítani kell az indexelÅ‘t. + + + + WebcacheModel + + MIME + MIME + + + Url + URL + + + + confgui::ConfBeaglePanelW + + Web page store directory name + A weblapokat tároló könyvtár neve + + + The name for a directory where to store the copies of visited web pages.<br>A non-absolute path is taken relative to the configuration directory. + A látogatott weblapok másolatát tároló könyvtár neve.<br>Relatív elérési út a beállításokat tároló könyvtárhoz képest értendÅ‘. + + + Max. size for the web store (MB) + A webes tároló max. mérete (MB) + + + Process the WEB history queue + A webes elÅ‘zmények feldolgozása + + + Enables indexing Firefox visited pages.<br>(you need also install the Firefox Recoll plugin) + A Firefoxszal látogatott oldalak indexelése<br>(a Firefox Recoll kiegészítÅ‘jét is telepíteni kell) + + + Entries will be recycled once the size is reached.<br>Only increasing the size really makes sense because reducing the value will not truncate an existing file (only waste space at the end). + A méret elérésekor a legkorábbi bejegyzések törlÅ‘dnek.<br>Csak a növelésnek van haszna, mivel csökkentéskor a már létezÅ‘ fájl nem lesz kisebb (csak egy része állandóan kihasználatlan marad). + + + + confgui::ConfIndexW + + Can't write configuration file + A beállítófájl írása sikertelen + + + + confgui::ConfParamFNW + + Choose + Tallózás + + + + confgui::ConfParamSLW + + + + + + + + - + - + + + + confgui::ConfSearchPanelW + + Automatic diacritics sensitivity + Automatikus ékezetérzékenység + + + <p>Automatically trigger diacritics sensitivity if the search term has accented characters (not in unac_except_trans). Else you need to use the query language and the <i>D</i> modifier to specify diacritics sensitivity. + <p>Automatikusan különbözÅ‘nek tekinti az ékezetes betűket az ékezet nélküli párjuktól, ha tartalmaz ékezetes betűt a kifejezés (az unac_except_trans kivételével). Egyébként a keresÅ‘nyelv <i>D</i> módosítójával érhetÅ‘ el ugyanez. + + + Automatic character case sensitivity + Kis-és nagybetűk automatikus megkülönböztetése + + + <p>Automatically trigger character case sensitivity if the entry has upper-case characters in any but the first position. Else you need to use the query language and the <i>C</i> modifier to specify character-case sensitivity. + <p>Automatikusan különbözÅ‘nek tekinti a kis-és nagybetűket, ha az elsÅ‘ karakter kivételével bárhol tartalmaz nagybetűt a kifejezés. Egyébként a keresÅ‘nyelv <i>C</i> módosítójával érhetÅ‘ el ugyanez. + + + Maximum term expansion count + A toldalékok maximális száma + + + <p>Maximum expansion count for a single term (e.g.: when using wildcards). The default of 10 000 is reasonable and will avoid queries that appear frozen while the engine is walking the term list. + <p>Egy szó toldalékainak maximális száma (pl. helyettesítÅ‘ karakterek használatakor). Az alapértelmezett 10 000 elfogadható érték, és elkerülhetÅ‘ vele a felhasználói felület idÅ‘leges válaszképtelensége is. + + + Maximum Xapian clauses count + A Xapian feltételek maximális száma + + + <p>Maximum number of elementary clauses we add to a single Xapian query. In some cases, the result of term expansion can be multiplicative, and we want to avoid using excessive memory. The default of 100 000 should be both high enough in most cases and compatible with current typical hardware configurations. + Egy Xapian kereséshez felhasználható elemi feltételek maximális száma. Néha a szavak toldalékolása szorzó hatású, ami túlzott memóriahasználathoz vezethet. Az alapértelmezett 100 000 a legtöbb esetben elegendÅ‘, de nem is támaszt különleges igényeket a hardverrel szemben. + + + + confgui::ConfSubPanelW + + Global + Minden könyvtárra vonatkozik + + + Max. compressed file size (KB) + A tömörített fájlok max. mérete (KB) + + + This value sets a threshold beyond which compressedfiles will not be processed. Set to -1 for no limit, to 0 for no decompression ever. + A tömörített fájlok indexbe kerülésének határértéke. +-1 esetén nincs korlát. +0 esetén soha nem történik kicsomagolás. + + + Max. text file size (MB) + Szövegfájl max. mérete (MB) + + + This value sets a threshold beyond which text files will not be processed. Set to -1 for no limit. +This is for excluding monster log files from the index. + A szövegfájlok indexbe kerülésének határértéke. +-1 esetén nincs korlát. +Az óriásira nÅ‘tt naplófájlok feldolgozása kerülhetÅ‘ el így. + + + Text file page size (KB) + Szövegfájl lapmérete (KB) + + + If this value is set (not equal to -1), text files will be split in chunks of this size for indexing. +This will help searching very big text files (ie: log files). + Ha be van állítva (nem egyenlÅ‘ -1), akkor a szövegfájlok indexelése ilyen méretű darabokban történik. +Ez segítséget nyújt a nagyon nagy méretű szövegfájlokban (pl. naplófájlok) való kereséshez. + + + Max. filter exec. time (S) + A szűrÅ‘ max. futási ideje (s) + + + External filters working longer than this will be aborted. This is for the rare case (ie: postscript) where a document could cause a filter to loop. Set to -1 for no limit. + + A túl hosszú ideig futó külsÅ‘ szűrÅ‘k leállítása +Néha elÅ‘fordul (pl. postscript esetén), hogy a szűrÅ‘ végtelen ciklusba kerül. +-1 esetén nincs korlát. + + + + Only mime types + MIME típusok + + + An exclusive list of indexed mime types.<br>Nothing else will be indexed. Normally empty and inactive + Az indexelendÅ‘ MIME típusok listája.<br>Csak ezek a típusok kerülnek az indexbe. Rendesen üres és inaktív. + + + Exclude mime types + Kizárt MIME típusok + + + Mime types not to be indexed + Ezek a MIME típusok kimaradnak az indexelésbÅ‘l + + + + confgui::ConfTopPanelW + + Top directories + KezdÅ‘ könyvtárak + + + The list of directories where recursive indexing starts. Default: your home. + A megadott könyvtárak rekurzív indexelése. Alapértelmezett értéke a saját könyvtár. + + + Skipped paths + Kizárt elérési utak + + + These are names of directories which indexing will not enter.<br> May contain wildcards. Must match the paths seen by the indexer (ie: if topdirs includes '/home/me' and '/home' is actually a link to '/usr/home', a correct skippedPath entry would be '/home/me/tmp*', not '/usr/home/me/tmp*') + Az indexelÅ‘ által mellÅ‘zendÅ‘ könyvtárak nevei.<br>Használhatók a helyettesítÅ‘ karakterek. Csak az indexelÅ‘ hatókörébe esÅ‘ elérési utakat lehet megadni (pl.: ha a kezdÅ‘ könyvtár a „/home/felhasznalo†és a „/home†egy link a „/usr/homeâ€-ra, akkor helyes elérési út a „/home/felhasznalo/tmp*â€, de nem az a „/usr/home/felhasznalo/tmp*â€). + + + Stemming languages + A szótÅ‘képzés nyelve + + + The languages for which stemming expansion<br>dictionaries will be built. + Ezen nyelvekhez készüljön szótövezÅ‘ és -toldalékoló szótár + + + Log file name + A naplófájl neve + + + The file where the messages will be written.<br>Use 'stderr' for terminal output + Az üzenetek kiírásának a helye.<br>A „stderr†a terminálra küldi az üzeneteket. + + + Log verbosity level + A naplózás szintje + + + This value adjusts the amount of messages,<br>from only errors to a lot of debugging data. + Az üzenetek mennyiségének szabályozása,<br>a hibaüzenetekre szorítkozótól a részletes hibakeresésig. + + + Index flush megabytes interval + Indexírási intervallum (MB) + + + This value adjust the amount of data which is indexed between flushes to disk.<br>This helps control the indexer memory usage. Default 10MB + Az az adatmennyiség, melyet két lemezre írás között az indexelÅ‘ feldolgoz.<br>Segíthet kézben tartani a memóriafoglalást. Alapértelmezett: 10MB + + + Max disk occupation (%) + Max. lemezhasználat (%) + + + This is the percentage of disk occupation where indexing will fail and stop (to avoid filling up your disk).<br>0 means no limit (this is the default). + Százalékos lemezfoglalás, melyen túllépve az indexelÅ‘ nem működik tovább (megelÅ‘zendÅ‘ az összes szabad hely elfoglalását).<br>0 esetén nincs korlát (alapértelmezett). + + + No aspell usage + Az aspell mellÅ‘zése + + + Aspell language + Az aspell nyelve + + + Database directory name + Az adatbázis könyvtárneve + + + Disables use of aspell to generate spelling approximation in the term explorer tool.<br> Useful if aspell is absent or does not work. + A szóvizsgálóban az aspell használatának mellÅ‘zése a hasonló szavak keresésekor.<br>Hasznos, ha az aspell nincs telepítve vagy nem működik. + + + The language for the aspell dictionary. This should look like 'en' or 'fr' ...<br>If this value is not set, the NLS environment will be used to compute it, which usually works. To get an idea of what is installed on your system, type 'aspell config' and look for .dat files inside the 'data-dir' directory. + Az aspell szótár nyelve. pl. „en†vagy „huâ€...<br>Ha nincs megadva, akkor az NLS környezet alapján lesz beállítva, ez általában megfelelÅ‘. A rendszerre telepített nyelveket az „aspell config†parancs kiadása után a „data-dir†könyvtárban található .dat fájlokból lehet megtudni. + + + The name for a directory where to store the index<br>A non-absolute path is taken relative to the configuration directory. The default is 'xapiandb'. + Az indexet tartalmazó könyvtár neve.<br>Relatív elérési út a beállítási könyvtárhoz képest értendÅ‘. Alapértelmezett: „xapiandbâ€. + + + Unac exceptions + Unac kivételek + + + <p>These are exceptions to the unac mechanism which, by default, removes all diacritics, and performs canonic decomposition. You can override unaccenting for some characters, depending on your language, and specify additional decompositions, e.g. for ligatures. In each space-separated entry, the first character is the source one, and the rest is the translation. + <p>Az unac alapértelmezetten eltávolít minden ékezetet és szétbontja a ligatúrákat. Az itt megadott kivételekkel lehetÅ‘ség van adott karakterek esetén tiltani a műveletet, ha a használt nyelv ezt szükségessé teszi. Ezen kívül előírhatók további felbontandó karakterek is. Az egyes elemeket egymástól szóközzel kell elválasztani. Egy elem elsÅ‘ karaktere az eredetit, a további karakterek a várt eredményt határozzák meg. + + + + uiPrefsDialogBase + + User preferences + Beállítások + + + User interface + Felhasználói felület + + + Number of entries in a result page + A találatok száma laponként + + + If checked, results with the same content under different names will only be shown once. + A különbözÅ‘ nevű, de azonos tartalmú találatokból csak egy jelenjen meg + + + Hide duplicate results. + Többszörös találatok elrejtése + + + Highlight color for query terms + A keresÅ‘szavak kiemelésének színe + + + Result list font + A találati lista betűkészlete + + + Opens a dialog to select the result list font + A találati lista betűkészletének kiválasztása + + + Helvetica-10 + Helvetica-10 + + + Resets the result list font to the system default + A találati lista betűkészletének rendszerbeli alapértelmezésére állítása + + + Reset + Alaphelyzet + + + Texts over this size will not be highlighted in preview (too slow). + Ezen méret felett az elÅ‘nézetben nem alkalmaz kiemelést (túl lassú) + + + Maximum text size highlighted for preview (megabytes) + Az elÅ‘nézeti kiemelés korlátja (megabyte) + + + Choose editor applications + A társítások beállítása + + + Auto-start simple search on whitespace entry. + Automatikus keresés szóköz hatására + + + Start with advanced search dialog open. + Az összetett keresés ablaka is legyen nyitva induláskor + + + Remember sort activation state. + A rendezési állapot mentése + + + Prefer Html to plain text for preview. + Az elÅ‘nézetben HTML egyszerű szöveg helyett + + + Search parameters + Keresési beállítások + + + Stemming language + A szótÅ‘képzés nyelve + + + A search for [rolling stones] (2 terms) will be changed to [rolling or stones or (rolling phrase 2 stones)]. +This should give higher precedence to the results where the search terms appear exactly as entered. + Ha például a keresÅ‘kifejezés a [rolling stones] (két szó), akkor helyettesítÅ‘dik +a [rolling OR stones OR (rolling PHRASE 2 stones)] kifejezéssel. +Ãgy elÅ‘bbre kerülnek azok a találatok, meylek a keresett szavakat +pontosan úgy tartalmazzák, ahogyan meg lettek adva. + + + Automatically add phrase to simple searches + Az egyszerű keresés automatikus bÅ‘vítése részmondattal + + + Do we try to build abstracts for result list entries by using the context of query terms ? +May be slow for big documents. + Próbáljon-e tartalmi kivonatot készíteni a keresÅ‘szavak alapján a találati lista elemeihez? +Nagy dokumentumok esetén lassú lehet. + + + Dynamically build abstracts + Dinamikus kivonatolás + + + Do we synthetize an abstract even if the document seemed to have one? + Kivonatoljon akkor is, ha a dokumentum már rendelkezik ezzel? + + + Replace abstracts from documents + A kivonat cseréje + + + Synthetic abstract size (characters) + A kivonat mérete (karakter) + + + Synthetic abstract context words + Az kivonat környezÅ‘ szavainak száma + + + The words in the list will be automatically turned to ext:xxx clauses in the query language entry. + Szavak listája, melyek keresÅ‘szóként megadva +automatikusan ext:xxx keresÅ‘nyelvi kifejezéssé alakíttatnak + + + Query language magic file name suffixes. + KeresÅ‘nyelvi mágikus fájlnévkiterjesztések + + + Enable + Bekapcsolás + + + External Indexes + KülsÅ‘ indexek + + + Toggle selected + A kijelölt váltása + + + Activate All + Mindet bekapcsol + + + Deactivate All + Mindet kikapcsol + + + Remove from list. This has no effect on the disk index. + Törlés a listából. Az index a lemezrÅ‘l nem törlÅ‘dik. + + + Remove selected + A kijelölt törlése + + + Add index + Index hozzáadása + + + Apply changes + A változtatások alkalmazása + + + &OK + &OK + + + Discard changes + A változtatások elvetése + + + &Cancel + &Mégsem + + + Abstract snippet separator + A kivonat elemeinek elválasztója + + + Style sheet + Stíluslap + + + Opens a dialog to select the style sheet file + A megjelenés stílusát leíró fájl kiválasztása + + + Choose + Tallózás + + + Resets the style sheet to default + A stílus visszaállítása az alapértelmezettre + + + Result List + Találati lista + + + Edit result paragraph format string + A találatok bekezdésformátuma + + + Edit result page html header insert + A találatok lapjának fejlécformátuma + + + Date format (strftime(3)) + Dátumformátum (strftime(3)) + + + Frequency percentage threshold over which we do not use terms inside autophrase. +Frequent terms are a major performance issue with phrases. +Skipped terms augment the phrase slack, and reduce the autophrase efficiency. +The default value is 2 (percent). + Egy olyan gyakorisági határérték, mely felett az adott szavak kihagyandók a részmondatokból. +Részmondatkereséskor a gyakori szavak a teljesítménybeli problémák fÅ‘ okai. +A kihagyott szavak lazítják a részek közti kapcsolatot és gyengítik az automatikus részmondat hatásfokát. +Az alapértelmezett érték 2 (százalék). + + + Autophrase term frequency threshold percentage + Az automatikus részmondatok százalékos gyakorisági határértéke + + + Plain text to HTML line style + Az egyszerű szövegbÅ‘l alkotott HTML sor stílusa + + + Lines in PRE text are not folded. Using BR loses some indentation. PRE + Wrap style may be what you want. + A PRE tagok közti sorok nincsenek törve. +BR tag estén a behúzások elveszhetnek. +A PRE+wrap valószínűleg a legjobb választás. + + + <BR> + <BR> + + + <PRE> + <PRE> + + + <PRE> + wrap + <PRE> + wrap + + + Disable Qt autocompletion in search entry. + A Qt automatikus kiegészítésének tiltása a keresÅ‘mezÅ‘ben + + + Search as you type. + Keresés minden leütéskor + + + Paths translations + Elérési út átalakítása + + + Click to add another index directory to the list. You can select either a Recoll configuration directory or a Xapian index. + További index felvétele a listára. Egy Recoll beállítási könyvtárat vagy egy Xapian indexkönyvtárat kell megadni. + + + Snippets window CSS file + CSS az <i>Érdemi részek</i> ablakhoz + + + Opens a dialog to select the Snippets window CSS style sheet file + Az <i>Érdemi részek</i> ablak tartalmának stílusát leíró fájl kiválasztása + + + Resets the Snippets window style + Az <i>Érdemi részek</i> ablak stílusának alaphelyzetbe állítása + + + Decide if document filters are shown as radio buttons, toolbar combobox, or menu. + A szűrÅ‘k megjeleníthetÅ‘k rádiógombokkal, legördülÅ‘ listában az eszköztáron vagy menüben + + + Document filter choice style: + A szűrÅ‘választó stílusa: + + + Buttons Panel + Rádiógombok + + + Toolbar Combobox + LegördülÅ‘ lista + + + Menu + Menü + + + Show system tray icon. + Ikon az értesítési területen + + + Close to tray instead of exiting. + Bezárás az értesítési területre kilépés helyett + + + Start with simple search mode + Az egyszerű keresés módja induláskor + + + User style to apply to the snippets window.<br> Note: the result page header insert is also included in the snippets window header. + Az <i>Érdemi részek</i> ablak tartalmára alkalmazandó stílus.<br>A találati lista fejléce az <i>Érdemi részek</i> ablakban is megjelenik. + + + Synonyms file + Szinonímafájl + + + Show warning when opening temporary file. + Ideiglenes fájlok megnyitásakor figyelmeztetés + + + diff --git a/src/qtgui/i18n/recoll_it.qm b/src/qtgui/i18n/recoll_it.qm index 5bae1401..0737f2fb 100644 Binary files a/src/qtgui/i18n/recoll_it.qm and b/src/qtgui/i18n/recoll_it.qm differ diff --git a/src/qtgui/i18n/recoll_it.ts b/src/qtgui/i18n/recoll_it.ts index b391fa78..a114307a 100644 --- a/src/qtgui/i18n/recoll_it.ts +++ b/src/qtgui/i18n/recoll_it.ts @@ -685,6 +685,13 @@ Click Cancel if you want to edit the configuration file before indexing starts, + + QxtConfirmationMessage + + Do not show again. + + + RTIToolW @@ -1023,14 +1030,6 @@ Please check the mimeview file The indexer is running so things should improve when it's done. - - The document belongs to an external indexwhich I can't update. - - - - Click Cancel to return to the list. Click Ignore to show the preview anyway. - - Duplicate documents @@ -1048,6 +1047,115 @@ Please check the desktop file Indexing interrupted + + The current indexing process was not started from this interface, can't kill it + + + + Bad paths + + + + Bad paths in configuration file: + + + + + Selection patterns need topdir + + + + Selection patterns can only be used with a start directory + + + + No search + + + + No preserved previous search + + + + Choose file to save + + + + Saved Queries (*.rclq) + + + + Write failed + + + + Could not write to file + + + + Read failed + + + + Could not open file: + + + + Load error + + + + Could not load saved query + + + + Index scheduling + + + + Sorry, not available under Windows for now, use the File menu entries to update the index + + + + Disabled because the real time indexer was not compiled in. + + + + This configuration tool only works for the main index. + + + + Can't set synonyms file (parse error?) + + + + The document belongs to an external index which I can't update. + + + + Click Cancel to return to the list. <br>Click Ignore to show the preview anyway (and remember for this session). + + + + Opening a temporary copy. Edits will be lost if you don't save<br/>them to a permanent location. + + + + Do not show this warning next time (use GUI preferences to restore). + + + + Index locked + + + + Unknown indexer state. Can't access webcache file. + + + + Indexer is running. Can't access webcache file. + + RclMainBase @@ -1171,10 +1279,6 @@ Please check the desktop file &Indexing configuration Conf&igurazione indicizzazione - - &Show missing helpers - - PgDown @@ -1215,18 +1319,10 @@ Please check the desktop file &Rebuild index - - &Show indexed types - - Shift+PgUp - - &Indexing schedule - - E&xternal index dialog @@ -1291,6 +1387,50 @@ Please check the desktop file Next update will retry previously failed files + + Indexing &schedule + + + + Enable synonyms + + + + Save last query + + + + Load saved query + + + + Special Indexing + + + + Indexing with special options + + + + &View + + + + Missing &helpers + + + + Indexed &MIME types + + + + Index &statistics + + + + Webcache Editor + + RclTrayIcon @@ -1608,6 +1748,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual + + Stemming languages for stored query: + + + + differ from current preferences (kept) + + + + Auto suffixes for stored query: + + + + External indexes for stored query: + + + + Autophrase is set but it was unset for stored query + + + + Autophrase is unset but it was set for stored query + + SSearchBase @@ -1779,6 +1943,49 @@ Use <b>Show Query</b> link when in doubt about result and see manual Applica + + SpecIdxW + + Special Indexing + + + + Do not retry previously failed files. + + + + Else only modified or failed files will be processed. + + + + Erase selected files data before indexing. + + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + + + + Browse + Esplora + + + Start directory (else use regular topdirs): + + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + + + + Selection patterns: + + + + Top indexed entity + + + SpellBase @@ -1892,14 +2099,6 @@ Use <b>Show Query</b> link when in doubt about result and see manual Average terms per document - - Smallest document length - - - - Longest document length - - Database directory size @@ -1916,6 +2115,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual Value + + Smallest document length (terms) + + + + Longest document length (terms) + + + + Results from last indexing: + + + + Documents created/updated + + + + Files tested + + + + Unindexed files + + UIPrefsDialog @@ -1972,6 +2195,26 @@ Use <b>Show Query</b> link when in doubt about result and see manual Default QtWebkit font + + Any term + Qualsiasi + + + All terms + Tutti + + + File name + Nome file + + + Query language + Linguaggio di interrogazione + + + Value from previous program exit + + UIPrefsDialogBase @@ -2239,11 +2482,52 @@ Questo dovrebbe dare la precedenza ai risultati che contengono i termini esattam - confgui::ConfBeaglePanelW + Webcache - Entries will be recycled once the size is reached + Webcache editor + + Search regexp + + + + + WebcacheEdit + + Copy URL + + + + Unknown indexer state. Can't edit webcache file. + + + + Indexer is running. Can't edit webcache file. + + + + Delete selection + + + + Webcache was modified, you will need to run the indexer after closing this window. + + + + + WebcacheModel + + MIME + + + + Url + + + + + confgui::ConfBeaglePanelW Web page store directory name @@ -2264,6 +2548,10 @@ Questo dovrebbe dare la precedenza ai risultati che contengono i termini esattam Enables indexing Firefox visited pages.<br>(you need also install the Firefox Recoll plugin) + + Entries will be recycled once the size is reached.<br>Only increasing the size really makes sense because reducing the value will not truncate an existing file (only waste space at the end). + + confgui::ConfIndexW @@ -2807,5 +3095,21 @@ The default value is 2 (percent). Close to tray instead of exiting. + + Start with simple search mode + + + + User style to apply to the snippets window.<br> Note: the result page header insert is also included in the snippets window header. + + + + Synonyms file + + + + Show warning when opening temporary file. + + diff --git a/src/qtgui/i18n/recoll_lt.qm b/src/qtgui/i18n/recoll_lt.qm index 7c23f90a..042627d4 100644 Binary files a/src/qtgui/i18n/recoll_lt.qm and b/src/qtgui/i18n/recoll_lt.qm differ diff --git a/src/qtgui/i18n/recoll_lt.ts b/src/qtgui/i18n/recoll_lt.ts index fc0e2a88..db490b90 100644 --- a/src/qtgui/i18n/recoll_lt.ts +++ b/src/qtgui/i18n/recoll_lt.ts @@ -684,6 +684,13 @@ p, li { white-space: pre-wrap; } + + QxtConfirmationMessage + + Do not show again. + + + RTIToolW @@ -1047,14 +1054,6 @@ Please check the mimeview file The indexer is running so things should improve when it's done. - - The document belongs to an external indexwhich I can't update. - - - - Click Cancel to return to the list. Click Ignore to show the preview anyway. - - Duplicate documents @@ -1068,6 +1067,115 @@ Please check the mimeview file Please check the desktop file + + The current indexing process was not started from this interface, can't kill it + + + + Bad paths + + + + Bad paths in configuration file: + + + + + Selection patterns need topdir + + + + Selection patterns can only be used with a start directory + + + + No search + + + + No preserved previous search + + + + Choose file to save + + + + Saved Queries (*.rclq) + + + + Write failed + + + + Could not write to file + + + + Read failed + + + + Could not open file: + + + + Load error + + + + Could not load saved query + + + + Index scheduling + + + + Sorry, not available under Windows for now, use the File menu entries to update the index + + + + Disabled because the real time indexer was not compiled in. + + + + This configuration tool only works for the main index. + + + + Can't set synonyms file (parse error?) + + + + The document belongs to an external index which I can't update. + + + + Click Cancel to return to the list. <br>Click Ignore to show the preview anyway (and remember for this session). + + + + Opening a temporary copy. Edits will be lost if you don't save<br/>them to a permanent location. + + + + Do not show this warning next time (use GUI preferences to restore). + + + + Index locked + + + + Unknown indexer state. Can't access webcache file. + + + + Indexer is running. Can't access webcache file. + + RclMainBase @@ -1197,7 +1305,7 @@ Please check the desktop file &Show missing helpers - &TrÅ«kstamos pagalbinÄ—s programos + &TrÅ«kstamos pagalbinÄ—s programos PgDown @@ -1239,18 +1347,10 @@ Please check the desktop file &Rebuild index - - &Show indexed types - - Shift+PgUp - - &Indexing schedule - - E&xternal index dialog @@ -1315,6 +1415,50 @@ Please check the desktop file Next update will retry previously failed files + + Indexing &schedule + + + + Enable synonyms + + + + Save last query + + + + Load saved query + + + + Special Indexing + + + + Indexing with special options + + + + &View + + + + Missing &helpers + + + + Indexed &MIME types + + + + Index &statistics + + + + Webcache Editor + + RclTrayIcon @@ -1688,6 +1832,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual + + Stemming languages for stored query: + + + + differ from current preferences (kept) + + + + Auto suffixes for stored query: + + + + External indexes for stored query: + + + + Autophrase is set but it was unset for stored query + + + + Autophrase is unset but it was set for stored query + + SSearchBase @@ -1859,6 +2027,49 @@ Use <b>Show Query</b> link when in doubt about result and see manual Uždaryti + + SpecIdxW + + Special Indexing + + + + Do not retry previously failed files. + + + + Else only modified or failed files will be processed. + + + + Erase selected files data before indexing. + + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + + + + Browse + NarÅ¡yti + + + Start directory (else use regular topdirs): + + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + + + + Selection patterns: + + + + Top indexed entity + + + SpellBase @@ -1972,14 +2183,6 @@ Use <b>Show Query</b> link when in doubt about result and see manual Average terms per document - - Smallest document length - - - - Longest document length - - Database directory size @@ -1996,6 +2199,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual Value + + Smallest document length (terms) + + + + Longest document length (terms) + + + + Results from last indexing: + + + + Documents created/updated + + + + Files tested + + + + Unindexed files + + UIPrefsDialog @@ -2051,6 +2278,26 @@ Use <b>Show Query</b> link when in doubt about result and see manual Default QtWebkit font + + Any term + Bet kuris raktinis žodis + + + All terms + Visi raktiniai žodžiai + + + File name + Bylos vardas + + + Query language + Užklausų kalba + + + Value from previous program exit + + UIPrefsDialogBase @@ -2344,6 +2591,51 @@ DidelÄ—s apimties dokumentams gali lÄ—tai veikti. + + Webcache + + Webcache editor + + + + Search regexp + + + + + WebcacheEdit + + Copy URL + + + + Unknown indexer state. Can't edit webcache file. + + + + Indexer is running. Can't edit webcache file. + + + + Delete selection + + + + Webcache was modified, you will need to run the indexer after closing this window. + + + + + WebcacheModel + + MIME + + + + Url + + + confgui::ConfBeaglePanelW @@ -2368,7 +2660,7 @@ DidelÄ—s apimties dokumentams gali lÄ—tai veikti. Entries will be recycled once the size is reached - Ä®raÅ¡ai bus trinami pasiekus nurodytÄ… dydį + Ä®raÅ¡ai bus trinami pasiekus nurodytÄ… dydį Web page store directory name @@ -2390,6 +2682,10 @@ DidelÄ—s apimties dokumentams gali lÄ—tai veikti. Enables indexing Firefox visited pages.<br>(you need also install the Firefox Recoll plugin) + + Entries will be recycled once the size is reached.<br>Only increasing the size really makes sense because reducing the value will not truncate an existing file (only waste space at the end). + + confgui::ConfIndexW @@ -2941,5 +3237,21 @@ The default value is 2 (percent). Close to tray instead of exiting. + + Start with simple search mode + + + + User style to apply to the snippets window.<br> Note: the result page header insert is also included in the snippets window header. + + + + Synonyms file + + + + Show warning when opening temporary file. + + diff --git a/src/qtgui/i18n/recoll_nl.qm b/src/qtgui/i18n/recoll_nl.qm new file mode 100644 index 00000000..b7ba93bb Binary files /dev/null and b/src/qtgui/i18n/recoll_nl.qm differ diff --git a/src/qtgui/i18n/recoll_nl.ts b/src/qtgui/i18n/recoll_nl.ts new file mode 100644 index 00000000..252ec9f9 --- /dev/null +++ b/src/qtgui/i18n/recoll_nl.ts @@ -0,0 +1,2691 @@ + + + + + AdvSearch + + All clauses + Alle termen + + + Any clause + Elke term + + + media + media + + + other + andere + + + Bad multiplier suffix in size filter + Geen juist achtervoegsel in grootte filter + + + text + tekst + + + spreadsheet + spreadsheet + + + presentation + presentatie + + + message + bericht + + + + AdvSearchBase + + Advanced search + geavanceerd zoeken + + + Search for <br>documents<br>satisfying: + Zoek naar<br>documenten<br> die bevatten: + + + Delete clause + verwijder term + + + Add clause + voeg term toe + + + Restrict file types + beperk tot bestandstype + + + Check this to enable filtering on file types + vink dit aan om filetype filtering te activeren + + + By categories + Per categorie + + + Check this to use file categories instead of raw mime types + Vink dit aan om bestands catergorie te gebruiken in plaats van raw mime + + + Save as default + Sla op als standaard + + + Searched file types + Gezochte bestands type + + + All ----> + Alle ----> + + + Sel -----> + Sel ----> + + + <----- Sel + <----- Sel + + + <----- All + alle + + + Ignored file types + negeer bestandstype + + + Enter top directory for search + voer de top bestandsmap in om te doorzoeken + + + Browse + doorbladeren + + + Restrict results to files in subtree: + Beperk de resultaten tot de bestanden in de subtak + + + Start Search + Begin met zoeken + + + Close + sluit + + + All non empty fields on the right will be combined with AND ("All clauses" choice) or OR ("Any clause" choice) conjunctions. <br>"Any" "All" and "None" field types can accept a mix of simple words, and phrases enclosed in double quotes.<br>Fields with no data are ignored. + Elk niet lege veld aan de rechterzijde zal worden gecombineerd met En ("Alle clausules" keuze) en Of ("Bepalingen" keuze) voegwoorden. <br> "Elk", "en" Of "Geen" veldtypen kan een mix van eenvoudige woorden en uitdrukkingen tussen dubbele aanhalingstekens te accepteren. <br> Velden zonder gegevens worden genegeerd. + + + Invert + omkeren + + + Minimum size. You can use k/K,m/M,g/G as multipliers + Minimummaat. U kunt k / K, m / M gebruiken, g / G als multipliers + + + Min. Size + Min Grootte + + + Maximum size. You can use k/K,m/M,g/G as multipliers + Maximale grootte. U kunt k / K, m / M gebruiken, g / G als multipliers + + + Max. Size + Max grootte + + + Filter + Filter + + + From + Van + + + To + Tot + + + Check this to enable filtering on dates + Vink dit aan om op datum te kunnen filteren + + + Filter dates + Filter datums + + + Find + Vind + + + Check this to enable filtering on sizes + Vink dit aan om te filteren op grootte + + + Filter sizes + Filter grootte + + + + CronToolW + + Cron Dialog + Cron dialoogvenster + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Recoll</span> batch indexing schedule (cron) </p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Each field can contain a wildcard (*), a single numeric value, comma-separated lists (1,3,5) and ranges (1-7). More generally, the fields will be used <span style=" font-style:italic;">as is</span> inside the crontab file, and the full crontab syntax can be used, see crontab(5).</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br />For example, entering <span style=" font-family:'Courier New,courier';">*</span> in <span style=" font-style:italic;">Days, </span><span style=" font-family:'Courier New,courier';">12,19</span> in <span style=" font-style:italic;">Hours</span> and <span style=" font-family:'Courier New,courier';">15</span> in <span style=" font-style:italic;">Minutes</span> would start recollindex every day at 12:15 AM and 7:15 PM</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A schedule with very frequent activations is probably less efficient than real time indexing.</p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Recoll</span> batch indexeer schema (cron) </p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Een enkele numerieke waarde, door komma's gescheiden lijsten (1,3,5) en reeksen (1-7). Meer in het algemeen zullen de velden worden gebruikt <span style=" font-style:italic;">als </span> in het crontab bestand, en het volledige crontab syntax kan worden gebruikt, zie crontab (5).</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br />Bijvoorbeeld invoeren <span style=" font-family:'Courier New,courier';">*</span> in <span style=" font-style:italic;">Dagen, </span><span style=" font-family:'Courier New,courier';">12,19</span> in <span style=" font-style:italic;">Uren</span> en <span style=" font-family:'Courier New,courier';">15</span> in <span style=" font-style:italic;">Minuten</span> zal recollindex starten op elke dag om 12:15 AM and 7:15 PM</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Een schema met zeer frequent activering is waarschijnlijk minder efficiënt dan real time indexeren.</p></body></html> + + + Days of week (* or 0-7, 0 or 7 is Sunday) + Dagen van de week (* of 0-7, of 7 is Zondag) + + + Hours (* or 0-23) + Uren (*of 0-23 + + + Minutes (0-59) + Minuten (0-59) + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Click <span style=" font-style:italic;">Disable</span> to stop automatic batch indexing, <span style=" font-style:italic;">Enable</span> to activate it, <span style=" font-style:italic;">Cancel</span> to change nothing.</p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Klik <span style=" font-style:italic;">Uitzetten</span> om automatisch batch indexeren uit te zetten, <span style=" font-style:italic;">Aanzetten</span> om het te activeren, <span style=" font-style:italic;">Annuleren</span> om niets te doen</p></body></html> + + + Enable + Aanzetten + + + Disable + Uitzetten + + + It seems that manually edited entries exist for recollindex, cannot edit crontab + Het lijkt erop dat met de hand bewerkt ingaves bestaan voor recollindex, kan niet crontab bewerken + + + Error installing cron entry. Bad syntax in fields ? + Fout bij het instellen van cron job. Slechte syntax in de ingave? + + + + EditDialog + + Dialog + Dialoog + + + + EditTrans + + Source path + bronpad + + + Local path + lokaal pad + + + Config error + Configuratie fout + + + Original path + Oorspronkelijk pad + + + + EditTransBase + + Path Translations + Pad vertalingen + + + Setting path translations for + zet vertalingspad voor + + + Select one or several file types, then use the controls in the frame below to change how they are processed + Selecteer één of meerdere bestandstypen, gebruik dan de bediening in het kader hieronder om te veranderen hoe ze worden verwerkt + + + Add + toevoegen + + + Delete + Verwijderen + + + Cancel + Annuleer + + + Save + Bewaar + + + + FirstIdxDialog + + First indexing setup + Setup van eerste indexering + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">It appears that the index for this configuration does not exist.</span><br /><br />If you just want to index your home directory with a set of reasonable defaults, press the <span style=" font-style:italic;">Start indexing now</span> button. You will be able to adjust the details later. </p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If you want more control, use the following links to adjust the indexing configuration and schedule.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">These tools can be accessed later from the <span style=" font-style:italic;">Preferences</span> menu.</p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Het blijkt dat de index voor deze configuratie niet bestaat.</span><br /><br />Als u gewoon uw home directory wilt indexeren met een set van redelijke standaardinstellingen, drukt u op de<span style=" font-style:italic;">Start indexeer nu</span>knop. Je zult in staat zijn om de details later aan te passen.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Als u meer controle wil, gebruik dan de volgende links om de indexering configuratie en het schema aan te passen.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Deze tools kunnen later worden geopend vanuit het<span style=" font-style:italic;">Voorkeuren</span> menu.</p></body></html> + + + Indexing configuration + Configuratie inedexering + + + This will let you adjust the directories you want to index, and other parameters like excluded file paths or names, default character sets, etc. + Dit laat u de mappen die u wilt indexeren, en andere parameters aan passen, zoals uitgesloten bestandspaden of namen, standaard character sets, enz. + + + Indexing schedule + Indexerings schema + + + This will let you chose between batch and real-time indexing, and set up an automatic schedule for batch indexing (using cron). + Dit zal u laten kiezen tussen batch en real-time indexering, en het opzetten van een automatisch schema voor batch indexeren (met behulp van cron) + + + Start indexing now + Begin nu met indexering + + + + FragButs + + %1 not found. + %1 niet gevonden. + + + %1: + %2 + %1: %2 + + + Query Fragments + Zoekterm fragmenten + + + + IdxSchedW + + Index scheduling setup + indexing schema setup + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Recoll</span> indexing can run permanently, indexing files as they change, or run at discrete intervals. </p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Reading the manual may help you to decide between these approaches (press F1). </p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This tool can help you set up a schedule to automate batch indexing runs, or start real time indexing when you log in (or both, which rarely makes sense). </p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Recoll</span>indexering kan permanent draaien, het indexeren van bestanden als ze veranderen, of lopen op vaste intervallen.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Het lezen van de handleiding kan helpen om te beslissen tussen deze benaderingen (druk op F1). </p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Deze tool kan u helpen bij het opzetten van een schema om batch indexeren runs te automatiseren, of het starten van real time indexeren wanneer u zich aanmeldt (of beide, dat is echter zelden zinvol). </p></body></html> + + + Cron scheduling + Cron schema + + + The tool will let you decide at what time indexing should run and will install a crontab entry. + Deze tool zal u laten beslissen op welk tijdstip het indexeren moet worden uitgevoerd en zal een crontab installeren. + + + Real time indexing start up + Real time indexering opstart + + + Decide if real time indexing will be started when you log in (only for the default index). + Beslis of real time indexeren wordt gestart wanneer u inlogt (alleen voor de standaard-index). + + + + ListDialog + + Dialog + Dialoog venster + + + GroupBox + GroepVenster + + + + Main + + No db directory in configuration + Geen db bestand in configuratie + + + "history" file is damaged or un(read)writeable, please check or remove it: + Het "Geschiedenis" bestand is beschadigd of on(lees)schrijfbaar geworden, graag controleren of verwijderen: + + + + Preview + + Close Tab + Sluit tab + + + Cancel + Annuleer + + + Missing helper program: + Help programma ontbreekt + + + Can't turn doc into internal representation for + Kan doc omzetten in een interne representatie + + + Creating preview text + preview tekst aan het maken + + + Loading preview text into editor + Preview tekst in editor aan het laden + + + &Search for: + &Zoek naar: + + + &Next + &Volgende + + + &Previous + &Vorige + + + Clear + Wissen + + + Match &Case + Hoofd/kleine letter + + + Error while loading file + Fout bij het laden van bestand + + + + PreviewTextEdit + + Show fields + Toon veld + + + Show main text + Toon hoofd tekst + + + Print + Druk af + + + Print Current Preview + Druk huidige Preview af + + + Show image + Toon afbeelding + + + Select All + Selecteer alles + + + Copy + Kopieer + + + Save document to file + Bewaar document als bestand + + + Fold lines + Vouw lijnen + + + Preserve indentation + Behoud inspringing + + + + QObject + + Global parameters + Globale parameters + + + Local parameters + Lokale parameters + + + <b>Customised subtrees + <b>Aangepaste substructuur + + + The list of subdirectories in the indexed hierarchy <br>where some parameters need to be redefined. Default: empty. + De lijst van de submappen in de geïndexeerde hiërarchie <br> waar sommige parameters moeten worden geherdefinieerd. Standaard: leeg. + + + <i>The parameters that follow are set either at the top level, if nothing<br>or an empty line is selected in the listbox above, or for the selected subdirectory.<br>You can add or remove directories by clicking the +/- buttons. + <i>De parameters die volgen zijn ingesteld, hetzij op het hoogste niveau, als er niets <br>of een lege regel is geselecteerd in de keuzelijst boven, of voor de geselecteerde submap.<br> U kunt mappen toevoegen of verwijderen door op de +/- knoppen te klikken. + + + Skipped names + Overgeslagen namen + + + These are patterns for file or directory names which should not be indexed. + Dit zijn patronen voor bestand of de mappen namen die niet mogen worden geïndexeerd. + + + Follow symbolic links + Volg symbolische links + + + Follow symbolic links while indexing. The default is no, to avoid duplicate indexing + Volg symbolische links tijdens het indexeren. De standaard is niet volgen, om dubbele indexering te voorkomen + + + Index all file names + Indexeer alle bestandsnamen + + + Index the names of files for which the contents cannot be identified or processed (no or unsupported mime type). Default true + Indexeer de namen van bestanden waarvan de inhoud niet kan worden geïdentificeerd of verwerkt (geen of niet-ondersteunde MIME-type). standaard true + + + Search parameters + Zoek parameters + + + Web history + Web geschiedenis + + + Default<br>character set + Standaard<br>karakter set + + + Character set used for reading files which do not identify the character set internally, for example pure text files.<br>The default value is empty, and the value from the NLS environnement is used. + Tekenset die wordt gebruikt voor het lezen van bestanden die het intern tekenset niet worden herkend, bijvoorbeeld pure tekstbestanden. Ondernemingen De standaard waarde is leeg en de waarde van de NLS-omgeving wordt gebruikt. + + + Ignored endings + Genegeerde eindes + + + These are file name endings for files which will be indexed by name only +(no MIME type identification attempt, no decompression, no content indexing). + Dit zijn bestandsnaam eindes voor bestanden die zullen worden geïndexeerd door alleen de naam (geen MIME-type identificatie poging, geen decompressie, geen inhoud indexering). + + + + QWidget + + Create or choose save directory + Maak of kies een bestandsnaam om op te slaan + + + Choose exactly one directory + Kies exact een map + + + Could not read directory: + kon map niet lezen + + + Unexpected file name collision, cancelling. + Onverwachte bestandsnaam botsing, annuleren. + + + Cannot extract document: + Kan het document niet uitpakken + + + &Preview + &Preview + + + &Open + &Openen + + + Open With + Open met + + + Run Script + Voer script uit + + + Copy &File Name + Kopieer &Bestands Naam + + + Copy &URL + Kopieer &URL + + + &Write to File + &Schijf naar Bestand + + + Save selection to files + Bewaar selektie naar bestanden + + + Preview P&arent document/folder + Preview B&ovenliggende document/map + + + &Open Parent document/folder + &Open Bovenliggend document/map + + + Find &similar documents + Vindt &gelijksoortige documenten + + + Open &Snippets window + Open &Knipsel venster + + + Show subdocuments / attachments + Toon subdocumenten / attachments + + + + QxtConfirmationMessage + + Do not show again. + Niet nogmaals tonen + + + + RTIToolW + + Real time indexing automatic start + Automatisch Starten realtime-indexeren + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Recoll</span> indexing can be set up to run as a daemon, updating the index as files change, in real time. You gain an always up to date index, but system resources are used permanently.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Recoll</span>indexering kan worden ingesteld om te draaien als een daemon, het bijwerken van de index als bestanden veranderen, in real time. Je krijgt dan een altijd up-to-date index, maar systeembronnen worden permanent gebruikt.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p></body></html> + + + Start indexing daemon with my desktop session. + Start met indexeren bij mijn desktop-sessie + + + Also start indexing daemon right now. + start nu ook de indexatie daemon + + + Replacing: + Vervanging + + + Replacing file + Vervang bestand + + + Can't create: + Kan niet aanmaken + + + Warning + Waarschuwing + + + Could not execute recollindex + Kon recollindex niet starten + + + Deleting: + Verwijderen + + + Deleting file + Verwijder bestand + + + Removing autostart + Verwijder autostart + + + Autostart file deleted. Kill current process too ? + Autostart ongedaan gemaakt proces ook stoppen ? + + + + RclMain + + (no stemming) + Geen taal + + + (all languages) + alle talen + + + error retrieving stemming languages + Fout bij het ophalen van de stam talen + + + Indexing in progress: + Indexering is bezig + + + Purge + Wissen + + + Stemdb + Stemdb + + + Closing + Sluiten + + + Unknown + Onbekend + + + Query results + Zoekresultaat + + + Cannot retrieve document info from database + kan info van het document uit database niet lezen + + + Warning + Waarschuwing + + + Can't create preview window + kan preview venster niet maken + + + This search is not active any more + Deze zoekopdracht is niet meer aktief + + + Cannot extract document or create temporary file + kan het document niet uitpakken of een tijdelijk bestand maken + + + Executing: [ + Uitvoeren: [ + + + About Recoll + Over Recoll + + + History data + Geschiedenis data + + + Document history + Document geschiedenis + + + Update &Index + Indexeren &bijwerken + + + Stop &Indexing + Stop &Indexing + + + All + Alle + + + media + media + + + message + bericht + + + other + anders + + + presentation + presentatie + + + spreadsheet + spreadsheet + + + text + tekst + + + sorted + gesorteerd + + + filtered + gefilterd + + + No helpers found missing + Alle hulpprogrammas zijn aanwezig + + + Missing helper programs + Missende hulp programmas + + + No external viewer configured for mime type [ + Geen externe viewer voor dit mime type geconfigureerd [ + + + The viewer specified in mimeview for %1: %2 is not found. +Do you want to start the preferences dialog ? + De viewer gespecificeerd in mimeview voor %1: %2 is niet gevonden Wilt u het dialoogvenster voorkeuren openen? + + + Can't access file: + Geen toegang tot het bestand + + + Can't uncompress file: + Kan het bestand niet uitpakken + + + Save file + Bestand opslaan + + + Result count (est.) + Telresultaat(est.) + + + Could not open external index. Db not open. Check external indexes list. + kon externe index niet openen. Db niet geopend. Controleer externe indexlijst + + + No results found + Geen resultaten gevonden + + + None + Geen + + + Updating + Bijwerken + + + Done + afgerond + + + Monitor + Monitoren + + + Indexing failed + Indexering mislukt + + + The current indexing process was not started from this interface. Click Ok to kill it anyway, or Cancel to leave it alone + Het huidige indexering proces werdt niet gestart vanaf deze interface. Klik Ok om het toch te stoppen, of annuleren om het zo te laten + + + Erasing index + Wis index + + + Reset the index and start from scratch ? + De index resetten en geheel opnieuw beginnen? + + + Query in progress.<br>Due to limitations of the indexing library,<br>cancelling will exit the program + Bezig met opdracht <br>Vanwege beperkingen van de indexeerder zal bij,<br>stop het programma in zijn geheel sluiten! + + + Error + Fout + + + Index not open + Index is niet open + + + Index query error + Index vraag fout + + + Content has been indexed for these mime types: + De inhoud is bijgewerkt voor deze mime types + + + Can't update index: indexer running + kan het index niet bijwerken:indexeren is al aktief + + + Indexed MIME Types + Geindexeerd MIME Types + + + Bad viewer command line for %1: [%2] +Please check the mimeview file + Verkeerde command line voor viewer %1:[%2'] controleer mimeview van bestand + + + Viewer command line for %1 specifies both file and parent file value: unsupported + Viewer command line voor %1 specificeerd zowel het bestandtype als het parentfile type waarde: niet ondersteund + + + Cannot find parent document + kan parent van document niet vinden + + + Indexing did not run yet + Indexering is nog niet bezig + + + External applications/commands needed for your file types and not found, as stored by the last indexing pass in + Externe toepassingen / commandos die nodig zijn voor dit bestandstype en niet gevonden, zoals opgeslagen in de laatste indexerings poging + + + Sub-documents and attachments + Sub-documenten en attachments + + + Document filter + Document filter + + + Index not up to date for this file. Refusing to risk showing the wrong entry. + Index voor dit bestand is niet op tu date. geweigerd om verkeerde inforamtie te tonen te riskeren + + + Click Ok to update the index for this file, then you will need to re-run the query when indexing is done. + Klik Ok om de index voor dit bestand bij te werken, daarna moet u de opdracht opnieuw uitvoeren na het indexeren + + + The indexer is running so things should improve when it's done. + De indexeerder is bezig dus er zou een verbetering moeten optreden als hij klaar is. + + + Duplicate documents + Vermenigvuldig documenten + + + These Urls ( | ipath) share the same content: + Deze Urls (ipath) hebben dezelfde inhoud: + + + Bad desktop app spec for %1: [%2] +Please check the desktop file + Verkeerde desktop snelkoppeling for %1:[%2] Graag de desktop snelkoppeling controleren + + + Indexing interrupted + Indexering onderbroken + + + The current indexing process was not started from this interface, can't kill it + Het huidige indexerings proces werdt niet gestart vanaf deze interface, kan het niet stoppen + + + Bad paths + Pad verkeerd + + + Bad paths in configuration file: + + Verkeerd pad in configuratie bestand + + + Selection patterns need topdir + Patronen selecteren vraagt een begin folder + + + Selection patterns can only be used with a start directory + Patronen selecteren kan alleen gebruikt worden met een start folder + + + No search + Niets gezocht + + + No preserved previous search + Geen opgeslagen vorige zoekresultaten + + + Choose file to save + Kies bestand om op te slaan + + + Saved Queries (*.rclq) + Bewaarde Zoekopdrachten (*.rclq) + + + Write failed + Schrijf fout + + + Could not write to file + Kan niet schrijven naar bestand + + + Read failed + Lees fout + + + Could not open file: + Kan bestand niet openen + + + Load error + Laad fout + + + Could not load saved query + Kon bewaarde zoekopdracht niet laden + + + Index scheduling + Index schema + + + Sorry, not available under Windows for now, use the File menu entries to update the index + Het spijt ons, dit is nog niet beschikbaar voor het windows platform, gebruik het bestands ingave menu om de index te updaten + + + Disabled because the real time indexer was not compiled in. + Uitgeschakeld omdat real-time indexering niet ingeschakeld is + + + This configuration tool only works for the main index. + Deze configuratie tool werkt alleen voor de hoofdindex + + + Can't set synonyms file (parse error?) + kan synomiemen bestand niet instellen ( parse error?) + + + The document belongs to an external index which I can't update. + Het document hoort bij een externe index die niet up te daten is + + + Click Cancel to return to the list. <br>Click Ignore to show the preview anyway (and remember for this session). + Klik op annuleren om terug te keren naar de lijst. <br>Klik negeren om het voorbeeld toch te tonen( en te onthouden voor deze sessie) + + + Opening a temporary copy. Edits will be lost if you don't save<br/>them to a permanent location. + Openen van tijdelijke kopie.Alle bewerkingen zullen verloren gaan als u ze niet opslaat naar een permanente lokatie + + + Do not show this warning next time (use GUI preferences to restore). + Laat deze waarschuwing niet meer zien (gebruik GUI voorkeuren om te herstellen) + + + Index locked + index geblokkeerd + + + Unknown indexer state. Can't access webcache file. + De staat van de indexer is onbekend. Kan geen toegang krijgen tot het webcache bestand. + + + Indexer is running. Can't access webcache file. + De indexeerder is bezig. Geen toegang tot webcache + + + + RclMainBase + + Recoll + Recoll + + + &File + &Bestand + + + &Tools + &Gereedschappen + + + &Preferences + &Voorkeuren + + + &Help + &Help + + + E&xit + V&erlaten + + + Ctrl+Q + Crtl+ Q + + + Update &index + Update &indexeerder + + + &Erase document history + &Wis bestands geschiedenis + + + &About Recoll + &Over Recoll + + + &User manual + &Gebruiks handleiding + + + Document &History + Document & Geschiedenis + + + Document History + Document geschiedenis + + + &Advanced Search + &Geavanceerd Zoeken + + + Advanced/complex Search + Uitgebreid/ Geavanceerd Zoeken + + + &Sort parameters + &Sorteer parameters + + + Sort parameters + Sorteer parameters + + + Term &explorer + Term &onderzoeker + + + Term explorer tool + Termen onderzoekers gereedschap + + + Next page + Volgende pagina + + + Next page of results + Volgende resultaten pagina + + + First page + Eerste pagina + + + Go to first page of results + Ga naar de eerste pagina van resultaten + + + Previous page + Vorige pagina + + + Previous page of results + Vorige pagina met resultaten + + + External index dialog + Extern index dialoog + + + PgDown + PgDown + + + PgUp + PgUp + + + &Full Screen + &Volledig Scherm + + + F11 + F11 + + + Full Screen + Volledig Scherm + + + &Erase search history + &Wis zoekgeschiedenis + + + Sort by dates from oldest to newest + Sorteer op datum van oud naar nieuw + + + Sort by dates from newest to oldest + Sorteer op datum van oud naar nieuw + + + Show Query Details + Toon zoek detials + + + &Rebuild index + &Vernieuw de gehele index + + + Shift+PgUp + Shift+PgUp + + + E&xternal index dialog + E&xternal index dialoog + + + &Index configuration + &Index configuratie + + + &GUI configuration + &GUI configuratie + + + &Results + &Resultaten + + + Sort by date, oldest first + Sorteer op datume, oudste eerst + + + Sort by date, newest first + Sorteer op datum, nieuwste eerst + + + Show as table + Toon als tabel + + + Show results in a spreadsheet-like table + Toon het resultaat in een spreadsheet achtig tabel + + + Save as CSV (spreadsheet) file + Bewaar als CVS ( spreadsheet) bestand + + + Saves the result into a file which you can load in a spreadsheet + Bewaar het resultaat naar een bestand die te laden is in een spreadsheet + + + Next Page + Volgende Pagina + + + Previous Page + Vorige Pagina + + + First Page + Eerste Pagina + + + Query Fragments + Zoek fragmenten + + + With failed files retrying + Opnieuw proberen met mislukte bestand + + + Next update will retry previously failed files + De volgende update zal de eerder mislukte bestanden opnieuw proberen + + + Indexing &schedule + Indexing &schema + + + Enable synonyms + Schakel synoniemen in + + + Save last query + Bewaar laatste zoekopdracht + + + Load saved query + Laad bewaarde zoekopdracht + + + Special Indexing + Speciale Indexering + + + Indexing with special options + Indexeren met speciale opties + + + &View + &Bekijken + + + Missing &helpers + Missend & Hulpprogrammas + + + Indexed &MIME types + Geindexeerd &MIME types + + + Index &statistics + Index & statistieken + + + Webcache Editor + Webcache Editor + + + + RclTrayIcon + + Restore + Herstellen + + + Quit + Afsluiten + + + + RecollModel + + Abstract + Uittreksel + + + Author + Auteur + + + Document size + Bestands grootte + + + Document date + Bestands datum + + + File size + Bestands grootte + + + File name + Bestands naam + + + File date + Bestands datum + + + Keywords + Sleutelwoorden + + + Original character set + Origineel karakter set + + + Relevancy rating + relevantiewaarde + + + Title + Titel + + + URL + URL + + + Mtime + Mtijd + + + Date + Datum + + + Date and time + Datum en tijd + + + Ipath + Ipad + + + MIME type + MIME type + + + + ResList + + Result list + Resultaatslijst + + + (show query) + (toon zoekopdracht) + + + Document history + Document historie + + + <p><b>No results found</b><br> + <p><b>Geen resultaat gevonden</b><br> + + + Previous + Vorige + + + Next + Volgende + + + Unavailable document + Document niet beschikbaar + + + Preview + Bekijken + + + Open + Openen + + + <p><i>Alternate spellings (accents suppressed): </i> + <p><i>Alternatieve spellingen (accenten onderdrukken): </i> + + + Documents + Documenten + + + out of at least + van tenminste + + + for + voor + + + <p><i>Alternate spellings: </i> + <p><i>Alternatieve spelling: </i> + + + Result count (est.) + Resultaten telling (est.) + + + Query details + Zoekopdracht details + + + Snippets + Knipsel + + + + ResTable + + &Reset sort + &Opnieuw sorteren + + + &Delete column + &Verwijder kolom + + + Save table to CSV file + Bewaar lijst als cvs bestand + + + Can't open/create file: + Kan bestand niet openen/ bewaren: + + + &Save as CSV + &Bewaar als CVS + + + Add "%1" column + Voeg "%1" kolom toe + + + + SSearch + + Any term + Elke term + + + All terms + Alle termen + + + File name + Bestandsnaam + + + Query language + Zoek taal + + + Bad query string + Foute zoekterm + + + Out of memory + Geen geheugen meer + + + Enter file name wildcard expression. + Voer bestandsnaam wildcard uitdrukking in. + + + Enter search terms here. Type ESC SPC for completions of current term. + Voer zoekterm hier in. Type ESC SPC als aanvulling voor huidige term + + + Enter query language expression. Cheat sheet:<br> +<i>term1 term2</i> : 'term1' and 'term2' in any field.<br> +<i>field:term1</i> : 'term1' in field 'field'.<br> + Standard field names/synonyms:<br> + title/subject/caption, author/from, recipient/to, filename, ext.<br> + Pseudo-fields: dir, mime/format, type/rclcat, date, size.<br> + Two date interval exemples: 2009-03-01/2009-05-20 2009-03-01/P2M.<br> +<i>term1 term2 OR term3</i> : term1 AND (term2 OR term3).<br> + You can use parentheses to make things clearer.<br> +<i>"term1 term2"</i> : phrase (must occur exactly). Possible modifiers:<br> +<i>"term1 term2"p</i> : unordered proximity search with default distance.<br> +Use <b>Show Query</b> link when in doubt about result and see manual (&lt;F1>) for more detail. + + Zoekterm'taal expressie. Cheat sheet: <br> +<i> term1 term2 </i>. 'Term1' en 'term2' op elk gebied <br> +<i> veld: term1 </i>. 'Term1' in 'het veld' veld <br> + Standaard veldnamen / synoniemen: <br> + titel / onderwerp / titel, auteur / uit, ontvanger / to, filename, ext. <br> + Pseudo-velden: dir, mime / format, het type / rclcat, datum, grootte <br>. + Twee datuminterval Voorbeelden: 2009-03-01 / 2009-05-20 2009-03-01 / P2M <br>. +<i> term1 term2 OR term3 </i>: term1 AND (term2 OR term3) <br>. + U kunt haakjes gebruiken om dingen duidelijker te maken. <br> +<i> "term1 term2" </i>: zin (moet precies gebeuren). Mogelijke modifiers: <br> +<i> "term1 term2" p </i>. Ongeordende nabijheid zoeken met de standaard afstand <br> +Gebruik <b> Toon Zoekterm </b> in geval van twijfel over de uitslag en zie handleiding (& lt; F1>) voor meer informatie. + + + + Stemming languages for stored query: + Stam taal voor opgeslagen zoekopdrachten: + + + differ from current preferences (kept) + Afwijken van de uidig (bewaarde) voorkeuren + + + Auto suffixes for stored query: + Automatische aanvullingen voor opgeslagen zoeken + + + External indexes for stored query: + External indexen voor opgeslagen zoekopdrachten: + + + Autophrase is set but it was unset for stored query + Auto aanvullen is ingesteld, maar het was uitgeschakeld voor de opgeslagen zoekopdracht + + + Autophrase is unset but it was set for stored query + Automatisch aanvullen is uitgeschakeld maar was ingesteld voor opegeslagen zoekopdracht + + + + SSearchBase + + SSearchBase + SZoekBasis + + + Clear + Wissen + + + Ctrl+S + Crtl+S + + + Erase search entry + Wis zoekopdracht + + + Search + Zoeken + + + Start query + Start zoekopdracht + + + Enter search terms here. Type ESC SPC for completions of current term. + Voer de zoekopdracht term hier in. Type ESC SPC om huidige termen aan te vullen + + + Choose search type. + Kies zoektype. + + + + SearchClauseW + + Select the type of query that will be performed with the words + Selecteer het type zoekopdracht dat zal worden uitgevoerd met de woorden: + + + Number of additional words that may be interspersed with the chosen ones + Aantal extra woorden die kunnen worden ingevoegd met de gekozen woorden + + + No field + Geen veld + + + Any + Elke + + + All + Alle + + + None + Geen + + + Phrase + Frase + + + Proximity + Ongeveer + + + File name + Bestandsnaam + + + + Snippets + + Snippets + Knipsels + + + Find: + Vindt: + + + Next + Volgende + + + Prev + Vorige + + + + SnippetsW + + Search + Zoek + + + <p>Sorry, no exact match was found within limits. Probably the document is very big and the snippets generator got lost in a maze...</p> + <P> Sorry, niet iets precies kunnen vinden. Waarschijnlijk is het document zeer groot en is de knipsels generator verdwaald in een doolhof ... </ p> + + + + SpecIdxW + + Special Indexing + Speciale indexering + + + Do not retry previously failed files. + Probeerniet nog eens de vorig niet gelukte bestanden + + + Else only modified or failed files will be processed. + Anders zullen alleen de veranderende of gefaalde bestanden verwerkt worden + + + Erase selected files data before indexing. + Wis de geselecteerde bestandens data voor de indexering + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + Map om recursief te indexeren. Dit moet binnen het reguliere geindexeerde gebied zijn<br>zoals ingesteld in het configuratiebestand (hoofdmappen) + + + Browse + Bladeren + + + Start directory (else use regular topdirs): + Begin Map (anders de normale hoofdmappen gebruiken) + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + Laat dit leeg om alle bestanden te kunnen selecteren. U kunt meerdere spaties gescheiden shell-type patronen gebruiken. <br> Patronen met ingesloten ruimtes moeten aangeduid worden met dubbele aanhalingstekens. <br> Kan alleen worden gebruikt als het hoofddoel is ingesteld + + + Selection patterns: + Selecteer patronen + + + Top indexed entity + Hoofd index identiteit + + + + SpellBase + + Term Explorer + Term onderzoeker + + + &Expand + &Uitvouwen + + + Alt+E + Alt+E + + + &Close + &Sluiten + + + Alt+C + Alt+C + + + No db info. + Geen db info. + + + Match + Gelijk + + + Case + Hoofdletter + + + Accents + Accenten + + + + SpellW + + Wildcards + wildcards + + + Regexp + Regexp + + + Stem expansion + Stam expansie + + + Spelling/Phonetic + Spelling/Phonetisch + + + error retrieving stemming languages + Fout bij het ophalen van woordstam talen + + + Aspell init failed. Aspell not installed? + Aspell init faalt. Is Aspell niet geinstalleerd? + + + Aspell expansion error. + Aspell expansie fout. + + + No expansion found + Geen expansie gevonden + + + Term + Term + + + Doc. / Tot. + Doc./Tot. + + + Index: %1 documents, average length %2 terms.%3 results + Index: %1 documenten, wisselende lengte %2 termen.%3 resultaten + + + %1 results + %1 resultaten + + + List was truncated alphabetically, some frequent + De lijst is alfabetisch afgebroken, sommige frequenter + + + terms may be missing. Try using a longer root. + Er kunnen termen ontbreken. Probeer gebruik te maken van een langere root + + + Show index statistics + Toon indexeer statistieken + + + Number of documents + Aantal documenten + + + Average terms per document + Gemiddelde termen per document + + + Database directory size + Database map grootte + + + MIME types: + MIME types + + + Item + Item + + + Value + Waarde + + + Smallest document length (terms) + Kleinste document lengte (termen) + + + Longest document length (terms) + Langste document lengte (termen) + + + Results from last indexing: + resultaten van vorige indexering + + + Documents created/updated + Documenten gemaakt/bijgewerkt + + + Files tested + Bestanden getest + + + Unindexed files + Ongeindexeerde bestanden + + + + UIPrefsDialog + + error retrieving stemming languages + fout bij het ophalen van de stam talen + + + The selected directory does not appear to be a Xapian index + De geselecteerde map schijnt geen Xapian index te zijn + + + This is the main/local index! + Dit is de hoofd/lokale index! + + + The selected directory is already in the index list + De geselecteerde map bestaat al in de index lijst + + + Choose + Kies + + + Result list paragraph format (erase all to reset to default) + Resultaten lijst paragrafen formaat (wist alles en reset naar standaard) + + + Result list header (default is empty) + Resultaten koppen lijst ( is standaard leeg) + + + Select recoll config directory or xapian index directory (e.g.: /home/me/.recoll or /home/me/.recoll/xapiandb) + Selecteer recoll config map of xapian index map (bijv.: /home/me/.recoll of /home/me/.recoll/xapian db) + + + The selected directory looks like a Recoll configuration directory but the configuration could not be read + De geselecteerde map ziet eruit als een Recoll configuratie map, maar de configuratie kon niet worden gelezen + + + At most one index should be selected + Tenminste moet er een index worden geselecteerd + + + Cant add index with different case/diacritics stripping option + Kan index met verschillende hoofdletters/ diakritisch tekens opties niet toevoegen + + + Default QtWebkit font + Standaard QtWebkit lettertype + + + Any term + Elke term + + + All terms + Alle termen + + + File name + Bestandsnaam + + + Query language + Zoek taal + + + Value from previous program exit + Waarde van vorige programma afsluiting + + + + ViewAction + + Command + Opdracht + + + MIME type + MIME type + + + Desktop Default + Desktop Standaard + + + Changing entries with different current values + invoering van verschillende huidige waardes veranderd + + + + ViewActionBase + + Native Viewers + Standaard Viewers + + + Close + Afsluiten + + + Select one or several mime types then use the controls in the bottom frame to change how they are processed. + Slecteer een of meerdere mime types gebruik vervolgens de instellingen onderin het venster om de verwerkingen aan te passen + + + Use Desktop preferences by default + Gebruik Desktop voorkeuren als standaard + + + Select one or several file types, then use the controls in the frame below to change how they are processed + Selecteer een of meerdere bestandstypes, gebruik vervolgens de instellingen onderin het venster hoe ze verwerkt worden + + + Exception to Desktop preferences + Uitzonderingen op Desktop voorkeuren + + + Action (empty -> recoll default) + Aktie (leeg -> recoll standaard) + + + Apply to current selection + Toepassen op huidige selectie + + + Recoll action: + Recoll acties + + + current value + huidige waarde + + + Select same + Selecteer dezelfde + + + <b>New Values:</b> + <b>Nieuwe Waardes:</b> + + + + Webcache + + Webcache editor + Webcache bewerker + + + Search regexp + Zoek regexp + + + + WebcacheEdit + + Copy URL + Kopieer URL + + + Unknown indexer state. Can't edit webcache file. + Status van indexer onbekend. Kan webcache bestand niet bewerken. + + + Indexer is running. Can't edit webcache file. + Indexer is aan het werken. Kan webcache bestand niet bewerken. + + + Delete selection + Verwijder selectie + + + Webcache was modified, you will need to run the indexer after closing this window. + Webcache is gewijzigd, u zult de indexer opnieuw moeten uitvoeren na het sluiten van dit venster + + + + WebcacheModel + + MIME + MIME + + + Url + Url + + + + confgui::ConfBeaglePanelW + + Web page store directory name + Web pagina map naam om op te slaan + + + The name for a directory where to store the copies of visited web pages.<br>A non-absolute path is taken relative to the configuration directory. + De naam voor een map waarin de kopieen van de bezochte webpaginas opgeslagen zullen worden.<br>Een niet absoluut pad zal worden gekozen ten opzichte van de configuratie map + + + Max. size for the web store (MB) + Max. grootte voor het web opslaan (MB) + + + Process the WEB history queue + Verwerk de WEB geschiedenis wachtrij + + + Enables indexing Firefox visited pages.<br>(you need also install the Firefox Recoll plugin) + Zet het indexeren van firefox bezochte paginas aan. <br> (hiervoor zal ook de Firefox Recoll plugin moeten worden geinstalleerd door uzelf) + + + Entries will be recycled once the size is reached.<br>Only increasing the size really makes sense because reducing the value will not truncate an existing file (only waste space at the end). + Invoeringen zullen worden gerecycled zodra de groote is bereikt. <br> Het verhogen van de groote heeft zin omdat het beperken van de waarde de bestaande waardes niet zal afkappen ( er is alleen afval ruimte aan het einde). + + + + confgui::ConfIndexW + + Can't write configuration file + Kan configuratie bestand niet lezen + + + + confgui::ConfParamFNW + + Choose + Kies + + + + confgui::ConfParamSLW + + + + + + + + - + - + + + + confgui::ConfSearchPanelW + + Automatic diacritics sensitivity + Automatische diakritische tekens gevoeligheid + + + <p>Automatically trigger diacritics sensitivity if the search term has accented characters (not in unac_except_trans). Else you need to use the query language and the <i>D</i> modifier to specify diacritics sensitivity. + <P> Automatisch activeren diakritische tekens gevoeligheid als de zoekterm tekens zijn geaccentueerd (niet in unac_except_trans). Wat je nodig hebt om de zoek taal te gebruiken en de <i> D</i> modifier om diakritische tekens gevoeligheid te specificeren. + + + Automatic character case sensitivity + Automatische karakter hoofdletter gevoeligheid + + + <p>Automatically trigger character case sensitivity if the entry has upper-case characters in any but the first position. Else you need to use the query language and the <i>C</i> modifier to specify character-case sensitivity. + <P> Automatisch activeren hoofdletters gevoeligheid als de vermelding hoofdletters heeft in elke, behalve de eerste positie. Anders moet u zoek taal gebruiken en de <i>C</i> modifier karakter-hoofdlettergevoeligheid opgeven. + + + Maximum term expansion count + Maximale term uitbreidings telling + + + <p>Maximum expansion count for a single term (e.g.: when using wildcards). The default of 10 000 is reasonable and will avoid queries that appear frozen while the engine is walking the term list. + <p> Maximale uitbreidingstelling voor een enkele term (bijv.: bij het gebruik van wildcards) Een standaard van 10.000 is redelijk en zal zoekpodrachten die lijken te bevriezen terwijl de zoekmachine loopt door de termlijst vermijden. + + + Maximum Xapian clauses count + Maximaal Xapian clausules telling + + + <p>Maximum number of elementary clauses we add to a single Xapian query. In some cases, the result of term expansion can be multiplicative, and we want to avoid using excessive memory. The default of 100 000 should be both high enough in most cases and compatible with current typical hardware configurations. + <p> Maximale aantal elementaire clausules die we kunnen toevoegen aan een enkele Xapian zoeken. In sommige gevallen kan het resultaatvan de term uitbreiding multiplicatief zijn, en we willen voorkomen dat er overmatig gebruik word gemaakt van het werkgeheugen. De standaard van 100.000 zou hoog genoeg moeten zijn in beidde gevallen en compatible zijn met moderne hardware configuraties. + + + + confgui::ConfSubPanelW + + Global + Globaal + + + Max. compressed file size (KB) + Maximaal gecomprimeerd bestands formaat (KB) + + + This value sets a threshold beyond which compressedfiles will not be processed. Set to -1 for no limit, to 0 for no decompression ever. + Deze waarde stelt een drempel waarboven gecomprimeerde bestanden niet zal worden verwerkt. Ingesteld op -1 voor geen limiet, op 0 voor geen decompressie ooit. + + + Max. text file size (MB) + Max. tekstbestand groote (MB) + + + This value sets a threshold beyond which text files will not be processed. Set to -1 for no limit. +This is for excluding monster log files from the index. + Deze waarde stelt een drempel waarboven tekstbestanden niet zal worden verwerkt. Ingesteld op -1 voor geen limiet. Dit is voor het uitsluiten van monster logbestanden uit de index. + + + Text file page size (KB) + Tekst bestand pagina grootte (KB) + + + If this value is set (not equal to -1), text files will be split in chunks of this size for indexing. +This will help searching very big text files (ie: log files). + Als deze waarde is ingesteld (niet gelijk aan -1), zal tekstbestanden worden opgedeeld in blokken van deze grootte voor indexering. Dit zal helpen bij het zoeken naar zeer grote tekstbestanden (bijv: log-bestanden). + + + Max. filter exec. time (S) + Max. filter executie tijd (S) + + + External filters working longer than this will be aborted. This is for the rare case (ie: postscript) where a document could cause a filter to loop. Set to -1 for no limit. + + Externe filters die langer dan dit werken worden afgebroken. Dit is voor het zeldzame geval (bijv: postscript) wanneer een document een filterlus zou kunnen veroorzaken. Stel in op -1 voor geen limiet. + + + Only mime types + Alleen mime types + + + An exclusive list of indexed mime types.<br>Nothing else will be indexed. Normally empty and inactive + Een exclusieve lijst van geïndexeerde typen mime. <br> Niets anders zal worden geïndexeerd. Normaal gesproken leeg en inactief + + + Exclude mime types + Sluit mime types uit + + + Mime types not to be indexed + Mime types die niet geindexeerd zullen worden + + + + confgui::ConfTopPanelW + + Top directories + Top mappen + + + The list of directories where recursive indexing starts. Default: your home. + Een lijst van mappen waar de recursive indexering gaat starten. Standaard is de thuismap. + + + Skipped paths + Paden overgeslagen + + + These are names of directories which indexing will not enter.<br> May contain wildcards. Must match the paths seen by the indexer (ie: if topdirs includes '/home/me' and '/home' is actually a link to '/usr/home', a correct skippedPath entry would be '/home/me/tmp*', not '/usr/home/me/tmp*') + Dit zijn de namen van de mappen die indexering niet zal doorzoeken. <br> Kan wildcards bevatten. Moet overeenkomen met de paden gezien door de indexer (bijv: als topmappen zoals '/ home/me en '/ home' is eigenlijk een link naar '/usr/home', een correcte overgeslagen pad vermelding zou zijn '/home/me/tmp * ', niet' /usr/home/me/tmp * ') + + + Stemming languages + Stam talen + + + The languages for which stemming expansion<br>dictionaries will be built. + De talen waarvoor de stam uitbreidings<br>wooordenboeken voor zullen worden gebouwd. + + + Log file name + Log bestandsnaam + + + The file where the messages will be written.<br>Use 'stderr' for terminal output + Het bestand waar de boodschappen geschreven zullen worden.<br>Gebruik 'stderr' voor terminal weergave + + + Log verbosity level + Log uitgebreidheids nivo + + + This value adjusts the amount of messages,<br>from only errors to a lot of debugging data. + Deze waarde bepaald het aantal boodschappen,<br>van alleen foutmeldingen tot een hoop debugging data. + + + Index flush megabytes interval + Index verversings megabyte interval + + + This value adjust the amount of data which is indexed between flushes to disk.<br>This helps control the indexer memory usage. Default 10MB + Deze waarde past de hoeveelheid data die zal worden geindexeerd tussen de flushes naar de schijf.<br> Dit helpt bij het controleren van het gebruik van geheugen. Standaad 10MB + + + Max disk occupation (%) + maximale schijf gebruik + + + This is the percentage of disk occupation where indexing will fail and stop (to avoid filling up your disk).<br>0 means no limit (this is the default). + Dit is het precentage van schijfgebruike waar indexering zal falen en stoppen (om te vermijden dat uw schijf volraakt.<br>0 betekend geen limit (dit is standaard). + + + No aspell usage + Gebruik aspell niet + + + Aspell language + Aspell taal + + + Database directory name + Database map naam + + + Disables use of aspell to generate spelling approximation in the term explorer tool.<br> Useful if aspell is absent or does not work. + Schakelt het gebruik van aspell uit om spellings gissingen in het term onderzoeker gereedschap te genereren. <br> Handig als aspell afwezig is of niet werkt. + + + The language for the aspell dictionary. This should look like 'en' or 'fr' ...<br>If this value is not set, the NLS environment will be used to compute it, which usually works. To get an idea of what is installed on your system, type 'aspell config' and look for .dat files inside the 'data-dir' directory. + Taal instelling voor het aspell woordenboek. Dit zou er uit moeten zien als 'en'of 'nl'...<br> als deze waarde niet is ingesteld, zal de NLS omgeving gebruikt worden om het te berekenen, wat meestal werkt. Om een idee te krijgen wat er op uw systeem staat, type 'aspell config' en zoek naar .dat bestanden binnen de 'data-dir'map. + + + The name for a directory where to store the index<br>A non-absolute path is taken relative to the configuration directory. The default is 'xapiandb'. + De naam voor een map om de index in op te slaan<br> Een niet absoluut pad ten opzichte van het configuratie bestand is gekozen. Standaard is het 'xapian db'. + + + Unac exceptions + Unac uitzonderingen + + + <p>These are exceptions to the unac mechanism which, by default, removes all diacritics, and performs canonic decomposition. You can override unaccenting for some characters, depending on your language, and specify additional decompositions, e.g. for ligatures. In each space-separated entry, the first character is the source one, and the rest is the translation. + Dit zijn uitzonderingen op het unac mechanisme dat, standaard, alle diakritische tekens verwijderd, en voert canonische ontbinding door. U kunt unaccenting voor sommige karakters veranderen, afhankelijk van uw taal, en extra decomposities specificeren, bijv. voor ligaturen. In iedere ruimte gescheiden ingave , waar het eerste teken is de bron is, en de rest de vertaling. + + + + uiPrefsDialogBase + + User preferences + Gebruikers voorkeuren + + + User interface + Gebruikers interface + + + Number of entries in a result page + Opgegeven aantal van weergaves per resultaten pagina + + + If checked, results with the same content under different names will only be shown once. + Indien aangevinkt, zullen de resultaten met dezelfde inhoud onder verschillende namen slecht eenmaal worden getoond. + + + Hide duplicate results. + Verberg duplicaat resultaten. + + + Highlight color for query terms + Highlight kleur voor zoektermen + + + Result list font + Resultaten lijst lettertype + + + Opens a dialog to select the result list font + Opent een dialoog om de resultaten lijst lettertype te selecteren + + + Helvetica-10 + Helvetica-10 + + + Resets the result list font to the system default + Reset het resultaten lijst lettertype naar systeem standaardwaarde + + + Reset + Herstel + + + Texts over this size will not be highlighted in preview (too slow). + Teksten groter dan dit zullen niet worden highlighted in previews (te langzaam). + + + Maximum text size highlighted for preview (megabytes) + Maximale tekst groote highlighted voor preview (megabytes) + + + Choose editor applications + Kies editor toepassingen + + + Auto-start simple search on whitespace entry. + Autostart eenvoudige zoekopdracht bij ingave in de witruimte. + + + Start with advanced search dialog open. + Start met geavanceerd zoek dialog open. + + + Remember sort activation state. + Onthoud sorteer activatie status + + + Prefer Html to plain text for preview. + Html voorkeur in plaats van gewoon tekst als preview + + + Search parameters + Zoek parameters + + + Stemming language + Stam taal + + + A search for [rolling stones] (2 terms) will be changed to [rolling or stones or (rolling phrase 2 stones)]. +This should give higher precedence to the results where the search terms appear exactly as entered. + Een zoekopdracht naar '[rollende stenen] (2 termen) wordt gewijzigd in [rollen of stenen of (rollende frase 2 stenen)]. Dit zou een hogere prioriteit moeten geven aan de resultaten, waar de zoektermen precies zoals ingevoerd moeten verschijnen. + + + Automatically add phrase to simple searches + Automatisch aanvullen van eenvoudige zoekopdrachten + + + Do we try to build abstracts for result list entries by using the context of query terms ? +May be slow for big documents. + Moeten we proberen om abstracten voor resultatenlijst invoering op te bouwen met behulp van de context van de zoektermen? Kan traag zijn met grote documenten. + + + Dynamically build abstracts + Dynamisch abstracten bouwen + + + Do we synthetize an abstract even if the document seemed to have one? + Moeten we een abstract maken, zelfs als het document er al een blijkt te hebben? + + + Replace abstracts from documents + Vervang abstracten van documenten + + + Synthetic abstract size (characters) + Synthetische abstractie grootte (tekens) + + + Synthetic abstract context words + Synthetische abstract context woorden + + + The words in the list will be automatically turned to ext:xxx clauses in the query language entry. + De woorden in de lijst zal automatisch omgezet worden naar ext:xxx clausules in de zoektaal ingave. + + + Query language magic file name suffixes. + Zoek taal magic bestandsnaam achtervoegsel + + + Enable + Aanzetten + + + External Indexes + Externe indexen + + + Toggle selected + Toggle geselecteerde + + + Activate All + Alles Activeren + + + Deactivate All + Alles Deactiveren + + + Remove from list. This has no effect on the disk index. + Verwijder van de lijst. Dit heeft geen effect op de schijf index. + + + Remove selected + Geselecteerde verwijderen + + + Add index + Index toevoegen + + + Apply changes + Veranderingen doorvoeren + + + &OK + &OK + + + Discard changes + Veranderingen ongedaan maken + + + &Cancel + &Annuleren + + + Abstract snippet separator + Abstract knipsel scheiding + + + Style sheet + Style sheet + + + Opens a dialog to select the style sheet file + Opend een dialoog venster om style sheet te selecteren + + + Choose + Kies + + + Resets the style sheet to default + Reset de style sheet naar standaard + + + Result List + Resultaten lijst + + + Edit result paragraph format string + Bewerk resultaten paragraaf formaat string + + + Edit result page html header insert + Bewerk resultaat pagina html header invoeg + + + Date format (strftime(3)) + Datum notatie (strftime(3)) + + + Frequency percentage threshold over which we do not use terms inside autophrase. +Frequent terms are a major performance issue with phrases. +Skipped terms augment the phrase slack, and reduce the autophrase efficiency. +The default value is 2 (percent). + Frequentie percentage drempel waarover wij geen termen gebruiken binnen autofrase. Frequente termen zijn een belangrijk prestatie probleem met zinnen en frases. Overgeslagen termen vergroten de zins verslapping, en verminderen de autofrase doeltreffendheid. De standaardwaarde is 2 (procent). + + + Autophrase term frequency threshold percentage + Autofrase term frequentie drempelwaarde percentage + + + Plain text to HTML line style + Platte tekst naar HTML lijn stijl + + + Lines in PRE text are not folded. Using BR loses some indentation. PRE + Wrap style may be what you want. + Lijnen in PRE tekst worden niet gevouwen. Met behulp van BR kan inspringen verwijderen. PRE + Wrap stijl zou wenselijk kunnen zijn. + + + <BR> + <BR> + + + <PRE> + <PRE> + + + <PRE> + wrap + <PRE> + wrap + + + Disable Qt autocompletion in search entry. + Schakel Qt auto-aanvullen uit in zoek invoegveld + + + Search as you type. + Zoek terwijl u typed. + + + Paths translations + Paden vertalingen + + + Click to add another index directory to the list. You can select either a Recoll configuration directory or a Xapian index. + Klik hier om een andere index map toe te voegen aan de lijst. U kunt een Recoll configuratie map of een Xapian index te selecteren. + + + Snippets window CSS file + Knipsel venster CSS bestand + + + Opens a dialog to select the Snippets window CSS style sheet file + Opent een dailoog venster om het knipsel venster CSS stijl sheet bestand te selecteren + + + Resets the Snippets window style + Herstel de Knipsel venster stijl + + + Decide if document filters are shown as radio buttons, toolbar combobox, or menu. + Bepaal of document mappen moeten worden weergegeven als keuzerondjes, gereedschap combinatiebox of menu. + + + Document filter choice style: + Document filter keuze stijl: + + + Buttons Panel + Knoppen Paneel + + + Toolbar Combobox + Gereedschaps-menu combinatiebox + + + Menu + Menu + + + Show system tray icon. + Toon pictogram in het systeemvak. + + + Close to tray instead of exiting. + Sluit naar systeemvak in plaats van sluiten. + + + Start with simple search mode + Start met een eenvoudige zoek modus + + + User style to apply to the snippets window.<br> Note: the result page header insert is also included in the snippets window header. + Gebruiker stijl toe te passen op het knipsel-venster <br>. Let op: het resultaat pagina header invoegen is ook opgenomen in het'kop knipsel-venster . + + + Synonyms file + Synoniemen bestand + + + Show warning when opening temporary file. + Toon waarschuwing bij het openen van een temp bestand. + + + diff --git a/src/qtgui/i18n/recoll_ru.qm b/src/qtgui/i18n/recoll_ru.qm index a23c0b4f..0f1b8ec4 100644 Binary files a/src/qtgui/i18n/recoll_ru.qm and b/src/qtgui/i18n/recoll_ru.qm differ diff --git a/src/qtgui/i18n/recoll_ru.ts b/src/qtgui/i18n/recoll_ru.ts index b9cb7069..b2d3abe5 100644 --- a/src/qtgui/i18n/recoll_ru.ts +++ b/src/qtgui/i18n/recoll_ru.ts @@ -722,6 +722,13 @@ Click Cancel if you want to edit the configuration file before indexing starts, Показать вложенные документы + + QxtConfirmationMessage + + Do not show again. + + + RTIToolW @@ -1111,14 +1118,6 @@ Please check the mimeview file The indexer is running so things should improve when it's done. - - The document belongs to an external indexwhich I can't update. - - - - Click Cancel to return to the list. Click Ignore to show the preview anyway. - - Duplicate documents Дублированные документы @@ -1132,6 +1131,115 @@ Please check the mimeview file Please check the desktop file + + The current indexing process was not started from this interface, can't kill it + + + + Bad paths + + + + Bad paths in configuration file: + + + + + Selection patterns need topdir + + + + Selection patterns can only be used with a start directory + + + + No search + + + + No preserved previous search + + + + Choose file to save + + + + Saved Queries (*.rclq) + + + + Write failed + + + + Could not write to file + + + + Read failed + + + + Could not open file: + + + + Load error + + + + Could not load saved query + + + + Index scheduling + + + + Sorry, not available under Windows for now, use the File menu entries to update the index + + + + Disabled because the real time indexer was not compiled in. + + + + This configuration tool only works for the main index. + + + + Can't set synonyms file (parse error?) + + + + The document belongs to an external index which I can't update. + + + + Click Cancel to return to the list. <br>Click Ignore to show the preview anyway (and remember for this session). + + + + Opening a temporary copy. Edits will be lost if you don't save<br/>them to a permanent location. + + + + Do not show this warning next time (use GUI preferences to restore). + + + + Index locked + + + + Unknown indexer state. Can't access webcache file. + + + + Indexer is running. Can't access webcache file. + + RclMainBase @@ -1261,7 +1369,7 @@ Please check the desktop file &Show missing helpers - &Показать недоÑтающие внешние программы + &Показать недоÑтающие внешние программы PgDown @@ -1317,7 +1425,7 @@ Please check the desktop file &Show indexed types - Показать индекÑируемые &типы + Показать индекÑируемые &типы Shift+PgUp @@ -1325,7 +1433,7 @@ Please check the desktop file &Indexing schedule - &РаÑпиÑание индекÑÐ¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ + &РаÑпиÑание индекÑÐ¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ E&xternal index dialog @@ -1391,6 +1499,50 @@ Please check the desktop file Next update will retry previously failed files + + Indexing &schedule + + + + Enable synonyms + + + + Save last query + + + + Load saved query + + + + Special Indexing + + + + Indexing with special options + + + + &View + + + + Missing &helpers + + + + Indexed &MIME types + + + + Index &statistics + + + + Webcache Editor + + RclTrayIcon @@ -1836,6 +1988,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual + + Stemming languages for stored query: + + + + differ from current preferences (kept) + + + + Auto suffixes for stored query: + + + + External indexes for stored query: + + + + Autophrase is set but it was unset for stored query + + + + Autophrase is unset but it was set for stored query + + SSearchBase @@ -2007,6 +2183,49 @@ Use <b>Show Query</b> link when in doubt about result and see manual Применить + + SpecIdxW + + Special Indexing + + + + Do not retry previously failed files. + + + + Else only modified or failed files will be processed. + + + + Erase selected files data before indexing. + + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + + + + Browse + ПроÑмотр + + + Start directory (else use regular topdirs): + + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + + + + Selection patterns: + + + + Top indexed entity + + + SpellBase @@ -2126,11 +2345,11 @@ Use <b>Show Query</b> link when in doubt about result and see manual Smallest document length - ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° документа + ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° документа Longest document length - МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° документа + МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° документа Database directory size @@ -2148,6 +2367,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual Value Значение + + Smallest document length (terms) + + + + Longest document length (terms) + + + + Results from last indexing: + + + + Documents created/updated + + + + Files tested + + + + Unindexed files + + UIPrefsDialog @@ -2203,6 +2446,26 @@ Use <b>Show Query</b> link when in doubt about result and see manual Default QtWebkit font + + Any term + Любое Ñлово + + + All terms + Ð’Ñе Ñлова + + + File name + + + + Query language + Язык запроÑа + + + Value from previous program exit + + UIPrefsDialogBase @@ -2497,6 +2760,51 @@ This should give higher precedence to the results where the search terms appear + + Webcache + + Webcache editor + + + + Search regexp + + + + + WebcacheEdit + + Copy URL + + + + Unknown indexer state. Can't edit webcache file. + + + + Indexer is running. Can't edit webcache file. + + + + Delete selection + + + + Webcache was modified, you will need to run the indexer after closing this window. + + + + + WebcacheModel + + MIME + + + + Url + + + confgui::ConfBeaglePanelW @@ -2521,7 +2829,7 @@ This should give higher precedence to the results where the search terms appear Entries will be recycled once the size is reached - При доÑтижении указанного размера кÑша Ñтарые запиÑи будут удалÑтьÑÑ + При доÑтижении указанного размера кÑша Ñтарые запиÑи будут удалÑтьÑÑ Web page store directory name @@ -2543,6 +2851,10 @@ This should give higher precedence to the results where the search terms appear Enables indexing Firefox visited pages.<br>(you need also install the Firefox Recoll plugin) Включает индекÑирование Ñтраниц, открывавшихÑÑ Ð² Firefox.<br>(нужно дополнение Recoll Ð´Ð»Ñ Firefox) + + Entries will be recycled once the size is reached.<br>Only increasing the size really makes sense because reducing the value will not truncate an existing file (only waste space at the end). + + confgui::ConfIndexW @@ -3121,5 +3433,21 @@ The default value is 2 (percent). Close to tray instead of exiting. + + Start with simple search mode + + + + User style to apply to the snippets window.<br> Note: the result page header insert is also included in the snippets window header. + + + + Synonyms file + + + + Show warning when opening temporary file. + + diff --git a/src/qtgui/i18n/recoll_tr.qm b/src/qtgui/i18n/recoll_tr.qm index b367e45d..cb4aa591 100644 Binary files a/src/qtgui/i18n/recoll_tr.qm and b/src/qtgui/i18n/recoll_tr.qm differ diff --git a/src/qtgui/i18n/recoll_tr.ts b/src/qtgui/i18n/recoll_tr.ts index 25271c33..0132803b 100644 --- a/src/qtgui/i18n/recoll_tr.ts +++ b/src/qtgui/i18n/recoll_tr.ts @@ -686,6 +686,13 @@ Click Cancel if you want to edit the configuration file before indexing starts, + + QxtConfirmationMessage + + Do not show again. + + + RTIToolW @@ -1024,14 +1031,6 @@ Please check the mimeview file The indexer is running so things should improve when it's done. - - The document belongs to an external indexwhich I can't update. - - - - Click Cancel to return to the list. Click Ignore to show the preview anyway. - - Duplicate documents @@ -1049,6 +1048,115 @@ Please check the desktop file Indexing interrupted + + The current indexing process was not started from this interface, can't kill it + + + + Bad paths + + + + Bad paths in configuration file: + + + + + Selection patterns need topdir + + + + Selection patterns can only be used with a start directory + + + + No search + + + + No preserved previous search + + + + Choose file to save + + + + Saved Queries (*.rclq) + + + + Write failed + + + + Could not write to file + + + + Read failed + + + + Could not open file: + + + + Load error + + + + Could not load saved query + + + + Index scheduling + + + + Sorry, not available under Windows for now, use the File menu entries to update the index + + + + Disabled because the real time indexer was not compiled in. + + + + This configuration tool only works for the main index. + + + + Can't set synonyms file (parse error?) + + + + The document belongs to an external index which I can't update. + + + + Click Cancel to return to the list. <br>Click Ignore to show the preview anyway (and remember for this session). + + + + Opening a temporary copy. Edits will be lost if you don't save<br/>them to a permanent location. + + + + Do not show this warning next time (use GUI preferences to restore). + + + + Index locked + + + + Unknown indexer state. Can't access webcache file. + + + + Indexer is running. Can't access webcache file. + + RclMainBase @@ -1172,10 +1280,6 @@ Please check the desktop file &Indexing configuration İ&ndeksleme yapılandırması - - &Show missing helpers - - PgDown @@ -1216,18 +1320,10 @@ Please check the desktop file &Rebuild index - - &Show indexed types - - Shift+PgUp - - &Indexing schedule - - E&xternal index dialog @@ -1292,6 +1388,50 @@ Please check the desktop file Next update will retry previously failed files + + Indexing &schedule + + + + Enable synonyms + + + + Save last query + + + + Load saved query + + + + Special Indexing + + + + Indexing with special options + + + + &View + + + + Missing &helpers + + + + Indexed &MIME types + + + + Index &statistics + + + + Webcache Editor + + RclTrayIcon @@ -1609,6 +1749,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual + + Stemming languages for stored query: + + + + differ from current preferences (kept) + + + + Auto suffixes for stored query: + + + + External indexes for stored query: + + + + Autophrase is set but it was unset for stored query + + + + Autophrase is unset but it was set for stored query + + SSearchBase @@ -1780,6 +1944,49 @@ Use <b>Show Query</b> link when in doubt about result and see manual Kapat + + SpecIdxW + + Special Indexing + + + + Do not retry previously failed files. + + + + Else only modified or failed files will be processed. + + + + Erase selected files data before indexing. + + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + + + + Browse + Gözat + + + Start directory (else use regular topdirs): + + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + + + + Selection patterns: + + + + Top indexed entity + + + SpellBase @@ -1893,14 +2100,6 @@ Use <b>Show Query</b> link when in doubt about result and see manual Average terms per document - - Smallest document length - - - - Longest document length - - Database directory size @@ -1917,6 +2116,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual Value + + Smallest document length (terms) + + + + Longest document length (terms) + + + + Results from last indexing: + + + + Documents created/updated + + + + Files tested + + + + Unindexed files + + UIPrefsDialog @@ -1972,6 +2195,26 @@ Use <b>Show Query</b> link when in doubt about result and see manual Default QtWebkit font + + Any term + Sözcüklerin herhangi biri + + + All terms + Tüm sözcükler + + + File name + Dosya adı + + + Query language + Arama dili + + + Value from previous program exit + + UIPrefsDialogBase @@ -2239,11 +2482,52 @@ Büyük boyutlu belgelerde yavaÅŸ olabilir. - confgui::ConfBeaglePanelW + Webcache - Entries will be recycled once the size is reached + Webcache editor + + Search regexp + + + + + WebcacheEdit + + Copy URL + + + + Unknown indexer state. Can't edit webcache file. + + + + Indexer is running. Can't edit webcache file. + + + + Delete selection + + + + Webcache was modified, you will need to run the indexer after closing this window. + + + + + WebcacheModel + + MIME + + + + Url + + + + + confgui::ConfBeaglePanelW Web page store directory name @@ -2264,6 +2548,10 @@ Büyük boyutlu belgelerde yavaÅŸ olabilir. Enables indexing Firefox visited pages.<br>(you need also install the Firefox Recoll plugin) + + Entries will be recycled once the size is reached.<br>Only increasing the size really makes sense because reducing the value will not truncate an existing file (only waste space at the end). + + confgui::ConfIndexW @@ -2807,5 +3095,21 @@ The default value is 2 (percent). Close to tray instead of exiting. + + Start with simple search mode + + + + User style to apply to the snippets window.<br> Note: the result page header insert is also included in the snippets window header. + + + + Synonyms file + + + + Show warning when opening temporary file. + + diff --git a/src/qtgui/i18n/recoll_uk.qm b/src/qtgui/i18n/recoll_uk.qm index f3edb3e1..38ddeec8 100644 Binary files a/src/qtgui/i18n/recoll_uk.qm and b/src/qtgui/i18n/recoll_uk.qm differ diff --git a/src/qtgui/i18n/recoll_uk.ts b/src/qtgui/i18n/recoll_uk.ts index 44ffa9e1..89bcbeee 100644 --- a/src/qtgui/i18n/recoll_uk.ts +++ b/src/qtgui/i18n/recoll_uk.ts @@ -687,6 +687,13 @@ Click Cancel if you want to edit the configuration file before indexing starts, + + QxtConfirmationMessage + + Do not show again. + + + RTIToolW @@ -1045,14 +1052,6 @@ Please check the mimeview file The indexer is running so things should improve when it's done. - - The document belongs to an external indexwhich I can't update. - - - - Click Cancel to return to the list. Click Ignore to show the preview anyway. - - Duplicate documents @@ -1066,6 +1065,115 @@ Please check the mimeview file Please check the desktop file + + The current indexing process was not started from this interface, can't kill it + + + + Bad paths + + + + Bad paths in configuration file: + + + + + Selection patterns need topdir + + + + Selection patterns can only be used with a start directory + + + + No search + + + + No preserved previous search + + + + Choose file to save + + + + Saved Queries (*.rclq) + + + + Write failed + + + + Could not write to file + + + + Read failed + + + + Could not open file: + + + + Load error + + + + Could not load saved query + + + + Index scheduling + + + + Sorry, not available under Windows for now, use the File menu entries to update the index + + + + Disabled because the real time indexer was not compiled in. + + + + This configuration tool only works for the main index. + + + + Can't set synonyms file (parse error?) + + + + The document belongs to an external index which I can't update. + + + + Click Cancel to return to the list. <br>Click Ignore to show the preview anyway (and remember for this session). + + + + Opening a temporary copy. Edits will be lost if you don't save<br/>them to a permanent location. + + + + Do not show this warning next time (use GUI preferences to restore). + + + + Index locked + + + + Unknown indexer state. Can't access webcache file. + + + + Indexer is running. Can't access webcache file. + + RclMainBase @@ -1195,7 +1303,7 @@ Please check the desktop file &Show missing helpers - ВідÑутні програми + ВідÑутні програми PgDown @@ -1237,18 +1345,10 @@ Please check the desktop file &Rebuild index - - &Show indexed types - - Shift+PgUp - - &Indexing schedule - - E&xternal index dialog @@ -1313,6 +1413,50 @@ Please check the desktop file Next update will retry previously failed files + + Indexing &schedule + + + + Enable synonyms + + + + Save last query + + + + Load saved query + + + + Special Indexing + + + + Indexing with special options + + + + &View + + + + Missing &helpers + + + + Indexed &MIME types + + + + Index &statistics + + + + Webcache Editor + + RclTrayIcon @@ -1638,6 +1782,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual + + Stemming languages for stored query: + + + + differ from current preferences (kept) + + + + Auto suffixes for stored query: + + + + External indexes for stored query: + + + + Autophrase is set but it was unset for stored query + + + + Autophrase is unset but it was set for stored query + + SSearchBase @@ -1805,6 +1973,49 @@ Use <b>Show Query</b> link when in doubt about result and see manual ЗаÑтоÑувати + + SpecIdxW + + Special Indexing + + + + Do not retry previously failed files. + + + + Else only modified or failed files will be processed. + + + + Erase selected files data before indexing. + + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + + + + Browse + ПереглÑд + + + Start directory (else use regular topdirs): + + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + + + + Selection patterns: + + + + Top indexed entity + + + SpellBase @@ -1918,14 +2129,6 @@ Use <b>Show Query</b> link when in doubt about result and see manual Average terms per document - - Smallest document length - - - - Longest document length - - Database directory size @@ -1942,6 +2145,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual Value + + Smallest document length (terms) + + + + Longest document length (terms) + + + + Results from last indexing: + + + + Documents created/updated + + + + Files tested + + + + Unindexed files + + UIPrefsDialog @@ -1997,6 +2224,26 @@ Use <b>Show Query</b> link when in doubt about result and see manual Default QtWebkit font + + Any term + Будь-Ñке Ñлово + + + All terms + УÑÑ– Ñлова + + + File name + Ім'Ñ Ñ„Ð°Ð¹Ð»Ñƒ + + + Query language + Мова запиту + + + Value from previous program exit + + UIPrefsDialogBase @@ -2284,11 +2531,52 @@ This should give higher precedence to the results where the search terms appear - confgui::ConfBeaglePanelW + Webcache - Entries will be recycled once the size is reached + Webcache editor + + Search regexp + + + + + WebcacheEdit + + Copy URL + + + + Unknown indexer state. Can't edit webcache file. + + + + Indexer is running. Can't edit webcache file. + + + + Delete selection + + + + Webcache was modified, you will need to run the indexer after closing this window. + + + + + WebcacheModel + + MIME + + + + Url + + + + + confgui::ConfBeaglePanelW Web page store directory name @@ -2309,6 +2597,10 @@ This should give higher precedence to the results where the search terms appear Enables indexing Firefox visited pages.<br>(you need also install the Firefox Recoll plugin) + + Entries will be recycled once the size is reached.<br>Only increasing the size really makes sense because reducing the value will not truncate an existing file (only waste space at the end). + + confgui::ConfIndexW @@ -2852,5 +3144,21 @@ The default value is 2 (percent). Close to tray instead of exiting. + + Start with simple search mode + + + + User style to apply to the snippets window.<br> Note: the result page header insert is also included in the snippets window header. + + + + Synonyms file + + + + Show warning when opening temporary file. + + diff --git a/src/qtgui/i18n/recoll_xx.ts b/src/qtgui/i18n/recoll_xx.ts index c9559c08..62e3ed05 100644 --- a/src/qtgui/i18n/recoll_xx.ts +++ b/src/qtgui/i18n/recoll_xx.ts @@ -644,6 +644,13 @@ p, li { white-space: pre-wrap; } + + QxtConfirmationMessage + + Do not show again. + + + RTIToolW @@ -964,14 +971,6 @@ Please check the mimeview file The indexer is running so things should improve when it's done. - - The document belongs to an external indexwhich I can't update. - - - - Click Cancel to return to the list. Click Ignore to show the preview anyway. - - Duplicate documents @@ -989,6 +988,115 @@ Please check the desktop file Indexing interrupted + + The current indexing process was not started from this interface, can't kill it + + + + Bad paths + + + + Bad paths in configuration file: + + + + + Selection patterns need topdir + + + + Selection patterns can only be used with a start directory + + + + No search + + + + No preserved previous search + + + + Choose file to save + + + + Saved Queries (*.rclq) + + + + Write failed + + + + Could not write to file + + + + Read failed + + + + Could not open file: + + + + Load error + + + + Could not load saved query + + + + Index scheduling + + + + Sorry, not available under Windows for now, use the File menu entries to update the index + + + + Disabled because the real time indexer was not compiled in. + + + + This configuration tool only works for the main index. + + + + Can't set synonyms file (parse error?) + + + + The document belongs to an external index which I can't update. + + + + Click Cancel to return to the list. <br>Click Ignore to show the preview anyway (and remember for this session). + + + + Opening a temporary copy. Edits will be lost if you don't save<br/>them to a permanent location. + + + + Do not show this warning next time (use GUI preferences to restore). + + + + Index locked + + + + Unknown indexer state. Can't access webcache file. + + + + Indexer is running. Can't access webcache file. + + RclMainBase @@ -1096,10 +1204,6 @@ Please check the desktop file External index dialog - - &Show missing helpers - - PgDown @@ -1140,18 +1244,10 @@ Please check the desktop file &Rebuild index - - &Show indexed types - - Shift+PgUp - - &Indexing schedule - - E&xternal index dialog @@ -1216,6 +1312,50 @@ Please check the desktop file Next update will retry previously failed files + + Indexing &schedule + + + + Enable synonyms + + + + Save last query + + + + Load saved query + + + + Special Indexing + + + + Indexing with special options + + + + &View + + + + Missing &helpers + + + + Indexed &MIME types + + + + Index &statistics + + + + Webcache Editor + + RclTrayIcon @@ -1447,6 +1587,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual + + Stemming languages for stored query: + + + + differ from current preferences (kept) + + + + Auto suffixes for stored query: + + + + External indexes for stored query: + + + + Autophrase is set but it was unset for stored query + + + + Autophrase is unset but it was set for stored query + + SSearchBase @@ -1552,6 +1716,49 @@ Use <b>Show Query</b> link when in doubt about result and see manual + + SpecIdxW + + Special Indexing + + + + Do not retry previously failed files. + + + + Else only modified or failed files will be processed. + + + + Erase selected files data before indexing. + + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + + + + Browse + + + + Start directory (else use regular topdirs): + + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + + + + Selection patterns: + + + + Top indexed entity + + + SpellBase @@ -1661,14 +1868,6 @@ Use <b>Show Query</b> link when in doubt about result and see manual Average terms per document - - Smallest document length - - - - Longest document length - - Database directory size @@ -1685,6 +1884,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual Value + + Smallest document length (terms) + + + + Longest document length (terms) + + + + Results from last indexing: + + + + Documents created/updated + + + + Files tested + + + + Unindexed files + + UIPrefsDialog @@ -1736,6 +1959,26 @@ Use <b>Show Query</b> link when in doubt about result and see manual Default QtWebkit font + + Any term + + + + All terms + + + + File name + + + + Query language + + + + Value from previous program exit + + ViewAction @@ -1808,11 +2051,52 @@ Use <b>Show Query</b> link when in doubt about result and see manual - confgui::ConfBeaglePanelW + Webcache - Entries will be recycled once the size is reached + Webcache editor + + Search regexp + + + + + WebcacheEdit + + Copy URL + + + + Unknown indexer state. Can't edit webcache file. + + + + Indexer is running. Can't edit webcache file. + + + + Delete selection + + + + Webcache was modified, you will need to run the indexer after closing this window. + + + + + WebcacheModel + + MIME + + + + Url + + + + + confgui::ConfBeaglePanelW Web page store directory name @@ -1833,6 +2117,10 @@ Use <b>Show Query</b> link when in doubt about result and see manual Enables indexing Firefox visited pages.<br>(you need also install the Firefox Recoll plugin) + + Entries will be recycled once the size is reached.<br>Only increasing the size really makes sense because reducing the value will not truncate an existing file (only waste space at the end). + + confgui::ConfIndexW @@ -2338,5 +2626,21 @@ The default value is 2 (percent). Close to tray instead of exiting. + + Start with simple search mode + + + + User style to apply to the snippets window.<br> Note: the result page header insert is also included in the snippets window header. + + + + Synonyms file + + + + Show warning when opening temporary file. + + diff --git a/src/qtgui/i18n/recoll_zh.qm b/src/qtgui/i18n/recoll_zh.qm index 95641b0b..d4349592 100644 Binary files a/src/qtgui/i18n/recoll_zh.qm and b/src/qtgui/i18n/recoll_zh.qm differ diff --git a/src/qtgui/i18n/recoll_zh.ts b/src/qtgui/i18n/recoll_zh.ts index a5aa94fa..b1c1322d 100644 --- a/src/qtgui/i18n/recoll_zh.ts +++ b/src/qtgui/i18n/recoll_zh.ts @@ -755,6 +755,13 @@ p, li { white-space: pre-wrap; } + + QxtConfirmationMessage + + Do not show again. + + + RTIToolW @@ -1121,14 +1128,6 @@ Please check the mimeview file The indexer is running so things should improve when it's done. - - The document belongs to an external indexwhich I can't update. - - - - Click Cancel to return to the list. Click Ignore to show the preview anyway. - - Duplicate documents @@ -1146,6 +1145,115 @@ Please check the desktop file Indexing interrupted + + The current indexing process was not started from this interface, can't kill it + + + + Bad paths + + + + Bad paths in configuration file: + + + + + Selection patterns need topdir + + + + Selection patterns can only be used with a start directory + + + + No search + + + + No preserved previous search + + + + Choose file to save + + + + Saved Queries (*.rclq) + + + + Write failed + + + + Could not write to file + + + + Read failed + + + + Could not open file: + + + + Load error + + + + Could not load saved query + + + + Index scheduling + + + + Sorry, not available under Windows for now, use the File menu entries to update the index + + + + Disabled because the real time indexer was not compiled in. + + + + This configuration tool only works for the main index. + + + + Can't set synonyms file (parse error?) + + + + The document belongs to an external index which I can't update. + + + + Click Cancel to return to the list. <br>Click Ignore to show the preview anyway (and remember for this session). + + + + Opening a temporary copy. Edits will be lost if you don't save<br/>them to a permanent location. + + + + Do not show this warning next time (use GUI preferences to restore). + + + + Index locked + + + + Unknown indexer state. Can't access webcache file. + + + + Indexer is running. Can't access webcache file. + + RclMainBase @@ -1275,7 +1383,7 @@ Please check the desktop file &Show missing helpers - 显示缺少的辅助程åºåˆ—表(&S) + 显示缺少的辅助程åºåˆ—表(&S) PgDown @@ -1331,7 +1439,7 @@ Please check the desktop file &Show indexed types - 显示已索引的文件类型(&S) + 显示已索引的文件类型(&S) Shift+PgUp @@ -1339,7 +1447,7 @@ Please check the desktop file &Indexing schedule - 定时索引(&I) + 定时索引(&I) E&xternal index dialog @@ -1405,6 +1513,50 @@ Please check the desktop file Next update will retry previously failed files + + Indexing &schedule + + + + Enable synonyms + + + + Save last query + + + + Load saved query + + + + Special Indexing + + + + Indexing with special options + + + + &View + + + + Missing &helpers + + + + Indexed &MIME types + + + + Index &statistics + + + + Webcache Editor + + RclTrayIcon @@ -1819,6 +1971,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual + + Stemming languages for stored query: + + + + differ from current preferences (kept) + + + + Auto suffixes for stored query: + + + + External indexes for stored query: + + + + Autophrase is set but it was unset for stored query + + + + Autophrase is unset but it was set for stored query + + SSearchBase @@ -1924,6 +2100,49 @@ Use <b>Show Query</b> link when in doubt about result and see manual + + SpecIdxW + + Special Indexing + + + + Do not retry previously failed files. + + + + Else only modified or failed files will be processed. + + + + Erase selected files data before indexing. + + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + + + + Browse + æµè§ˆ + + + Start directory (else use regular topdirs): + + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + + + + Selection patterns: + + + + Top indexed entity + + + SpellBase @@ -2037,14 +2256,6 @@ Use <b>Show Query</b> link when in doubt about result and see manual Average terms per document - - Smallest document length - - - - Longest document length - - Database directory size @@ -2061,6 +2272,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual Value + + Smallest document length (terms) + + + + Longest document length (terms) + + + + Results from last indexing: + + + + Documents created/updated + + + + Files tested + + + + Unindexed files + + UIPrefsDialog @@ -2116,6 +2351,26 @@ Use <b>Show Query</b> link when in doubt about result and see manual Default QtWebkit font + + Any term + 任一è¯è¯­ + + + All terms + 全部è¯è¯­ + + + File name + 文件å + + + Query language + 查询语言 + + + Value from previous program exit + + ViewAction @@ -2203,6 +2458,51 @@ Use <b>Show Query</b> link when in doubt about result and see manual + + Webcache + + Webcache editor + + + + Search regexp + + + + + WebcacheEdit + + Copy URL + + + + Unknown indexer state. Can't edit webcache file. + + + + Indexer is running. Can't edit webcache file. + + + + Delete selection + + + + Webcache was modified, you will need to run the indexer after closing this window. + + + + + WebcacheModel + + MIME + + + + Url + + + confgui::ConfBeaglePanelW @@ -2215,7 +2515,7 @@ Use <b>Show Query</b> link when in doubt about result and see manual Entries will be recycled once the size is reached - 当尺寸达到设定值时,这些æ¡ç›®ä¼šè¢«å¾ªçŽ¯ä½¿ç”¨ + 当尺寸达到设定值时,这些æ¡ç›®ä¼šè¢«å¾ªçŽ¯ä½¿ç”¨ Web page store directory name @@ -2237,6 +2537,10 @@ Use <b>Show Query</b> link when in doubt about result and see manual Enables indexing Firefox visited pages.<br>(you need also install the Firefox Recoll plugin) + + Entries will be recycled once the size is reached.<br>Only increasing the size really makes sense because reducing the value will not truncate an existing file (only waste space at the end). + + confgui::ConfIndexW @@ -2791,5 +3095,21 @@ The default value is 2 (percent). Close to tray instead of exiting. + + Start with simple search mode + + + + User style to apply to the snippets window.<br> Note: the result page header insert is also included in the snippets window header. + + + + Synonyms file + + + + Show warning when opening temporary file. + + diff --git a/src/qtgui/i18n/recoll_zh_CN.qm b/src/qtgui/i18n/recoll_zh_CN.qm index 5a974e5e..cc5ecfd1 100644 Binary files a/src/qtgui/i18n/recoll_zh_CN.qm and b/src/qtgui/i18n/recoll_zh_CN.qm differ diff --git a/src/qtgui/i18n/recoll_zh_CN.ts b/src/qtgui/i18n/recoll_zh_CN.ts index 42b1e90f..45a2ab69 100644 --- a/src/qtgui/i18n/recoll_zh_CN.ts +++ b/src/qtgui/i18n/recoll_zh_CN.ts @@ -755,6 +755,13 @@ p, li { white-space: pre-wrap; } æ˜¾ç¤ºå­æ–‡æ¡£/附件 + + QxtConfirmationMessage + + Do not show again. + + + RTIToolW @@ -1138,14 +1145,6 @@ Please check the mimeview file The indexer is running so things should improve when it's done. - - The document belongs to an external indexwhich I can't update. - - - - Click Cancel to return to the list. Click Ignore to show the preview anyway. - - Duplicate documents é‡å¤æ–‡æ¡£ @@ -1159,6 +1158,115 @@ Please check the mimeview file Please check the desktop file + + The current indexing process was not started from this interface, can't kill it + + + + Bad paths + + + + Bad paths in configuration file: + + + + + Selection patterns need topdir + + + + Selection patterns can only be used with a start directory + + + + No search + + + + No preserved previous search + + + + Choose file to save + + + + Saved Queries (*.rclq) + + + + Write failed + + + + Could not write to file + + + + Read failed + + + + Could not open file: + + + + Load error + + + + Could not load saved query + + + + Index scheduling + + + + Sorry, not available under Windows for now, use the File menu entries to update the index + + + + Disabled because the real time indexer was not compiled in. + + + + This configuration tool only works for the main index. + + + + Can't set synonyms file (parse error?) + + + + The document belongs to an external index which I can't update. + + + + Click Cancel to return to the list. <br>Click Ignore to show the preview anyway (and remember for this session). + + + + Opening a temporary copy. Edits will be lost if you don't save<br/>them to a permanent location. + + + + Do not show this warning next time (use GUI preferences to restore). + + + + Index locked + + + + Unknown indexer state. Can't access webcache file. + + + + Indexer is running. Can't access webcache file. + + RclMainBase @@ -1288,7 +1396,7 @@ Please check the desktop file &Show missing helpers - 显示缺少的辅助程åºåˆ—表(&S) + 显示缺少的辅助程åºåˆ—表(&S) PgDown @@ -1344,7 +1452,7 @@ Please check the desktop file &Show indexed types - 显示已索引的文件类型(&S) + 显示已索引的文件类型(&S) Shift+PgUp @@ -1352,7 +1460,7 @@ Please check the desktop file &Indexing schedule - 定时索引(&I) + 定时索引(&I) E&xternal index dialog @@ -1418,6 +1526,50 @@ Please check the desktop file Next update will retry previously failed files + + Indexing &schedule + + + + Enable synonyms + + + + Save last query + + + + Load saved query + + + + Special Indexing + + + + Indexing with special options + + + + &View + + + + Missing &helpers + + + + Indexed &MIME types + + + + Index &statistics + + + + Webcache Editor + + RclTrayIcon @@ -1852,6 +2004,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual + + Stemming languages for stored query: + + + + differ from current preferences (kept) + + + + Auto suffixes for stored query: + + + + External indexes for stored query: + + + + Autophrase is set but it was unset for stored query + + + + Autophrase is unset but it was set for stored query + + SSearchBase @@ -1957,6 +2133,49 @@ Use <b>Show Query</b> link when in doubt about result and see manual + + SpecIdxW + + Special Indexing + + + + Do not retry previously failed files. + + + + Else only modified or failed files will be processed. + + + + Erase selected files data before indexing. + + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + + + + Browse + æµè§ˆ + + + Start directory (else use regular topdirs): + + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + + + + Selection patterns: + + + + Top indexed entity + + + SpellBase @@ -2072,11 +2291,11 @@ Use <b>Show Query</b> link when in doubt about result and see manual Smallest document length - æœ€å°æ–‡æ¡£é•¿åº¦ + æœ€å°æ–‡æ¡£é•¿åº¦ Longest document length - 最大文档长度 + 最大文档长度 Database directory size @@ -2094,6 +2313,30 @@ Use <b>Show Query</b> link when in doubt about result and see manual Value 值 + + Smallest document length (terms) + + + + Longest document length (terms) + + + + Results from last indexing: + + + + Documents created/updated + + + + Files tested + + + + Unindexed files + + UIPrefsDialog @@ -2149,6 +2392,26 @@ Use <b>Show Query</b> link when in doubt about result and see manual Default QtWebkit font + + Any term + 任一è¯è¯­ + + + All terms + 全部è¯è¯­ + + + File name + 文件å + + + Query language + 查询语言 + + + Value from previous program exit + + ViewAction @@ -2236,6 +2499,51 @@ Use <b>Show Query</b> link when in doubt about result and see manual <b>新的值:</b> + + Webcache + + Webcache editor + + + + Search regexp + + + + + WebcacheEdit + + Copy URL + + + + Unknown indexer state. Can't edit webcache file. + + + + Indexer is running. Can't edit webcache file. + + + + Delete selection + + + + Webcache was modified, you will need to run the indexer after closing this window. + + + + + WebcacheModel + + MIME + + + + Url + + + confgui::ConfBeaglePanelW @@ -2248,7 +2556,7 @@ Use <b>Show Query</b> link when in doubt about result and see manual Entries will be recycled once the size is reached - 当尺寸达到设定值时,这些æ¡ç›®ä¼šè¢«å¾ªçŽ¯ä½¿ç”¨ + 当尺寸达到设定值时,这些æ¡ç›®ä¼šè¢«å¾ªçŽ¯ä½¿ç”¨ Web page store directory name @@ -2270,6 +2578,10 @@ Use <b>Show Query</b> link when in doubt about result and see manual Enables indexing Firefox visited pages.<br>(you need also install the Firefox Recoll plugin) å¯ç”¨å¯¹ç«ç‹çš„已访问页é¢è¿›è¡Œç´¢å¼•。<br>(妳还需è¦å®‰è£…ç«ç‹çš„Recollæ’件) + + Entries will be recycled once the size is reached.<br>Only increasing the size really makes sense because reducing the value will not truncate an existing file (only waste space at the end). + + confgui::ConfIndexW @@ -2825,5 +3137,21 @@ The default value is 2 (percent). Close to tray instead of exiting. + + Start with simple search mode + + + + User style to apply to the snippets window.<br> Note: the result page header insert is also included in the snippets window header. + + + + Synonyms file + + + + Show warning when opening temporary file. + + diff --git a/src/qtgui/images/cancel.png b/src/qtgui/images/cancel.png old mode 100755 new mode 100644 diff --git a/src/qtgui/images/close.png b/src/qtgui/images/close.png old mode 100755 new mode 100644 diff --git a/src/qtgui/images/down.png b/src/qtgui/images/down.png old mode 100755 new mode 100644 diff --git a/src/qtgui/images/table.png b/src/qtgui/images/table.png old mode 100755 new mode 100644 diff --git a/src/qtgui/images/up.png b/src/qtgui/images/up.png old mode 100755 new mode 100644 diff --git a/src/qtgui/main.cpp b/src/qtgui/main.cpp index 93986ffe..e3ba48f1 100644 --- a/src/qtgui/main.cpp +++ b/src/qtgui/main.cpp @@ -14,11 +14,8 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ - - #include "autoconfig.h" -#include #include #include @@ -31,6 +28,7 @@ #include #include #include +#include #include "rcldb.h" #include "rclconfig.h" @@ -38,7 +36,7 @@ #include "recoll.h" #include "smallut.h" #include "rclinit.h" -#include "debuglog.h" +#include "log.h" #include "rclmain_w.h" #include "ssearch_w.h" #include "guiutils.h" @@ -52,14 +50,14 @@ extern RclConfig *theconfig; -PTMutexInit thetempfileslock; +std::mutex thetempfileslock; static vector o_tempfiles; /* Keep an array of temporary files for deletion at exit. It happens that we erase some of them before exiting (ie: when closing a preview tab), we don't reuse the array holes for now */ void rememberTempFile(TempFile temp) { - PTMutexLocker locker(thetempfileslock); + std::unique_lock locker(thetempfileslock); o_tempfiles.push_back(temp); } @@ -67,16 +65,21 @@ void forgetTempFile(string &fn) { if (fn.empty()) return; - PTMutexLocker locker(thetempfileslock); + std::unique_lock locker(thetempfileslock); for (vector::iterator it = o_tempfiles.begin(); it != o_tempfiles.end(); it++) { - if ((*it).isNotNull() && !fn.compare((*it)->filename())) { - it->release(); + if ((*it) && !fn.compare((*it)->filename())) { + it->reset(); } } fn.erase(); } +void deleteAllTempFiles() +{ + std::unique_lock locker(thetempfileslock); + o_tempfiles.clear(); +} Rcl::Db *rcldb; @@ -95,7 +98,7 @@ void startManual(const string& helpindex) bool maybeOpenDb(string &reason, bool force, bool *maindberror) { - LOGDEB2(("maybeOpenDb: force %d\n", force)); + LOGDEB2("maybeOpenDb: force " << (force) << "\n" ); if (!rcldb) { reason = "Internal error: db not created"; return false; @@ -106,7 +109,7 @@ bool maybeOpenDb(string &reason, bool force, bool *maindberror) rcldb->rmQueryDb(""); for (list::const_iterator it = prefs.activeExtraDbs.begin(); it != prefs.activeExtraDbs.end(); it++) { - LOGDEB(("main: adding [%s]\n", it->c_str())); + LOGDEB("main: adding [" << *it << "]\n" ); rcldb->addQueryDb(*it); } Rcl::Db::OpenError error; @@ -133,8 +136,7 @@ bool getStemLangs(vector& vlangs) string reason; if (maybeOpenDb(reason)) { vlangs = rcldb->getStemLangs(); - LOGDEB0(("getStemLangs: from index: %s\n", - stringsToString(vlangs).c_str())); + LOGDEB0("getStemLangs: from index: " << (stringsToString(vlangs)) << "\n" ); return true; } else { // Cant get the langs from the index. Maybe it just does not @@ -150,32 +152,23 @@ bool getStemLangs(vector& vlangs) static void recollCleanup() { - LOGDEB2(("recollCleanup: closing database\n")); + LOGDEB2("recollCleanup: closing database\n" ); deleteZ(rcldb); deleteZ(theconfig); - PTMutexLocker locker(thetempfileslock); - o_tempfiles.clear(); - + deleteAllTempFiles(); + #ifdef RCL_USE_ASPELL deleteZ(aspell); #endif - LOGDEB2(("recollCleanup: done\n")); -} - -static void sigcleanup(int) -{ - // We used to not call exit from here, because of the idxthread, but - // this is now gone, so... - recollNeedsExit = 1; - exit(1); + LOGDEB2("recollCleanup: done\n" ); } void applyStyleSheet(const QString& ssfname) { const char *cfname = (const char *)ssfname.toLocal8Bit(); - LOGDEB0(("Applying style sheet: [%s]\n", cfname)); + LOGDEB0("Applying style sheet: [" << (cfname) << "]\n" ); if (cfname && *cfname) { string stylesheet; file_to_string(cfname, stylesheet); @@ -305,7 +298,7 @@ int main(int argc, char **argv) string reason; - theconfig = recollinit(recollCleanup, sigcleanup, reason, &a_config); + theconfig = recollinit(recollCleanup, 0, reason, &a_config); if (!theconfig || !theconfig->ok()) { QString msg = "Configuration problem: "; msg += QString::fromUtf8(reason.c_str()); @@ -333,7 +326,7 @@ int main(int argc, char **argv) aspell = new Aspell(theconfig); aspell->init(reason); if (!aspell || !aspell->ok()) { - LOGDEB(("Aspell speller creation failed %s\n", reason.c_str())); + LOGDEB("Aspell speller creation failed " << (reason) << "\n" ); aspell = 0; } #endif @@ -402,8 +395,38 @@ int main(int argc, char **argv) mainWindow-> sSearch->setSearchString(QString::fromLocal8Bit(question.c_str())); } else if (!urltoview.empty()) { - LOGDEB(("MAIN: got urltoview [%s]\n", urltoview.c_str())); + LOGDEB("MAIN: got urltoview [" << (urltoview) << "]\n" ); mainWindow->setUrlToView(QString::fromLocal8Bit(urltoview.c_str())); } return app.exec(); } + +QString myGetFileName(bool isdir, QString caption, bool filenosave) +{ + LOGDEB1("myFileDialog: isdir " << (isdir) << "\n" ); + QFileDialog dialog(0, caption); + + if (isdir) { + dialog.setFileMode(QFileDialog::Directory); + dialog.setOptions(QFileDialog::ShowDirsOnly); + } else { + dialog.setFileMode(QFileDialog::AnyFile); + if (filenosave) + dialog.setAcceptMode(QFileDialog::AcceptOpen); + else + dialog.setAcceptMode(QFileDialog::AcceptSave); + } + dialog.setViewMode(QFileDialog::List); + QFlags flags = QDir::NoDotAndDotDot | QDir::Hidden; + if (isdir) + flags |= QDir::Dirs; + else + flags |= QDir::Dirs | QDir::Files; + dialog.setFilter(flags); + + if (dialog.exec() == QDialog::Accepted) { + return dialog.selectedFiles().value(0); + } + return QString(); +} + diff --git a/src/qtgui/mtpics/aptosid-book.png b/src/qtgui/mtpics/aptosid-book.png old mode 100755 new mode 100644 diff --git a/src/qtgui/mtpics/image.png b/src/qtgui/mtpics/image.png old mode 100755 new mode 100644 diff --git a/src/qtgui/mtpics/source.png b/src/qtgui/mtpics/source.png old mode 100755 new mode 100644 diff --git a/src/qtgui/mtpics/sownd.png b/src/qtgui/mtpics/sownd.png old mode 100755 new mode 100644 diff --git a/src/qtgui/multisave.cpp b/src/qtgui/multisave.cpp index 5f779165..00849d66 100644 --- a/src/qtgui/multisave.cpp +++ b/src/qtgui/multisave.cpp @@ -30,7 +30,7 @@ using namespace std; #include "recoll.h" #include "multisave.h" #include "smallut.h" -#include "debuglog.h" +#include "log.h" #include "pathut.h" #include "internfile.h" @@ -52,7 +52,7 @@ void multiSave(QWidget *p, vector& docs) return; } string dir((const char *)dirl[0].toLocal8Bit()); - LOGDEB2(("multiSave: got dir %s\n", dir.c_str())); + LOGDEB2("multiSave: got dir " << (dir) << "\n" ); /* Save doc to files in target directory. Issues: - It is quite common to have docs in the array with the save @@ -90,13 +90,13 @@ void multiSave(QWidget *p, vector& docs) string utf8fn; it->getmeta(Rcl::Doc::keyfn, &utf8fn); string suffix = path_suffix(utf8fn); - LOGDEB(("Multisave: [%s] suff [%s]\n", utf8fn.c_str(), suffix.c_str())); + LOGDEB("Multisave: [" << (utf8fn) << "] suff [" << (suffix) << "]\n" ); if (suffix.empty() || suffix.size() > 10) { suffix = theconfig->getSuffixFromMimeType(it->mimetype); - LOGDEB(("Multisave: suff from config [%s]\n", suffix.c_str())); + LOGDEB("Multisave: suff from config [" << (suffix) << "]\n" ); } string simple = path_basename(utf8fn, string(".") + suffix); - LOGDEB(("Multisave: simple [%s]\n", simple.c_str())); + LOGDEB("Multisave: simple [" << (simple) << "]\n" ); if (simple.empty()) simple = "rclsave"; if (simple.size() > maxlen) { @@ -123,7 +123,7 @@ void multiSave(QWidget *p, vector& docs) for (unsigned int i = 0; i != docs.size(); i++) { string fn = path_cat(dir, filenames[i]); - if (access(fn.c_str(), 0) == 0) { + if (path_exists(fn)) { QMessageBox::warning(0, "Recoll", QWidget::tr("Unexpected file name collision, " "cancelling.")); @@ -141,3 +141,4 @@ void multiSave(QWidget *p, vector& docs) } } } + diff --git a/src/qtgui/preview_load.cpp b/src/qtgui/preview_load.cpp new file mode 100644 index 00000000..2e732264 --- /dev/null +++ b/src/qtgui/preview_load.cpp @@ -0,0 +1,72 @@ +/* Copyright (C) 2014 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include + +#include "log.h" +#include "preview_load.h" +#include "internfile.h" +#include "rcldoc.h" +#include "pathut.h" +#include "cancelcheck.h" +#include "rclconfig.h" + +LoadThread::LoadThread(RclConfig *config, const Rcl::Doc& idc, + bool pvhtm, QObject *parent) + : QThread(parent), status(1), m_idoc(idc), m_previewHtml(pvhtm), + m_config(*config) +{ +} + +void LoadThread::run() +{ + FileInterner interner(m_idoc, &m_config, FileInterner::FIF_forPreview); + FIMissingStore mst; + interner.setMissingStore(&mst); + + // Even when previewHtml is set, we don't set the interner's + // target mtype to html because we do want the html filter to + // do its work: we won't use the text/plain, but we want the + // text/html to be converted to utf-8 (for highlight processing) + try { + string ipath = m_idoc.ipath; + FileInterner::Status ret = interner.internfile(fdoc, ipath); + if (ret == FileInterner::FIDone || ret == FileInterner::FIAgain) { + // FIAgain is actually not nice here. It means that the record + // for the *file* of a multidoc was selected. Actually this + // shouldn't have had a preview link at all, but we don't know + // how to handle it now. Better to show the first doc than + // a mysterious error. Happens when the file name matches a + // a search term. + status = 0; + // If we prefer HTML and it is available, replace the + // text/plain document text + if (m_previewHtml && !interner.get_html().empty()) { + fdoc.text = interner.get_html(); + fdoc.mimetype = "text/html"; + } + tmpimg = interner.get_imgtmp(); + } else { + fdoc.mimetype = interner.getMimetype(); + mst.getMissingExternal(missing); + status = -1; + } + } catch (CancelExcept) { + LOGDEB("LoadThread: cancelled\n" ); + status = -1; + } +} + diff --git a/src/qtgui/preview_load.h b/src/qtgui/preview_load.h new file mode 100644 index 00000000..970a6a29 --- /dev/null +++ b/src/qtgui/preview_load.h @@ -0,0 +1,59 @@ +/* Copyright (C) 2015 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _PVW_LOAD_H_INCLUDED_ +#define _PVW_LOAD_H_INCLUDED_ + +#include + +#include + +#include "rcldoc.h" +#include "pathut.h" +#include "rclutil.h" +#include "rclconfig.h" + +/* + * A thread to perform the file reading / format conversion work for preview + */ +class LoadThread : public QThread { + + Q_OBJECT; + +public: + LoadThread(RclConfig *conf, + const Rcl::Doc& idoc, bool pvhtml, QObject *parent = 0); + + virtual ~LoadThread() { + } + + virtual void run(); + +public: + // The results are returned through public members. + int status; + Rcl::Doc fdoc; + TempFile tmpimg; + std::string missing; + +private: + Rcl::Doc m_idoc; + bool m_previewHtml; + RclConfig m_config; +}; + + +#endif /* _PVW_LOAD_H_INCLUDED_ */ diff --git a/src/qtgui/preview_plaintorich.cpp b/src/qtgui/preview_plaintorich.cpp new file mode 100644 index 00000000..ebcddc9e --- /dev/null +++ b/src/qtgui/preview_plaintorich.cpp @@ -0,0 +1,185 @@ +/* Copyright (C) 2014 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include "autoconfig.h" + +#include + +#include + +#include +#include + +#include "preview_plaintorich.h" + +#include "plaintorich.h" +#include "log.h" +#include "guiutils.h" +#include "cancelcheck.h" + +using namespace std; + +PlainToRichQtPreview::PlainToRichQtPreview() +{ + clear(); +} + +void PlainToRichQtPreview::clear() +{ + m_curanchor = 1; + m_lastanchor = 0; + m_groupanchors.clear(); + m_groupcuranchors.clear(); +} + +bool PlainToRichQtPreview::haveAnchors() +{ + return m_lastanchor != 0; +} + +string PlainToRichQtPreview::PlainToRichQtPreview::header() +{ + if (!m_inputhtml) { + switch (prefs.previewPlainPre) { + case PrefsPack::PP_BR: + m_eolbr = true; + return ""; + case PrefsPack::PP_PRE: + m_eolbr = false; + return "
          ";
          +        case PrefsPack::PP_PREWRAP:
          +            m_eolbr = false;
          +            return ""
          +                "
          ";
          +        }
          +    }
          +    return cstr_null;
          +}
          +
          +string PlainToRichQtPreview::startMatch(unsigned int grpidx)
          +{
          +    LOGDEB2("startMatch, grpidx "  << (grpidx) << "\n" );
          +    grpidx = m_hdata->grpsugidx[grpidx];
          +    LOGDEB2("startMatch, ugrpidx "  << (grpidx) << "\n" );
          +    m_groupanchors[grpidx].push_back(++m_lastanchor);
          +    m_groupcuranchors[grpidx] = 0; 
          +    return string("").
          +        append("");
          +}
          +
          +string  PlainToRichQtPreview::endMatch()
          +{
          +    return string("");
          +}
          +
          +string  PlainToRichQtPreview::termAnchorName(int i) const
          +{
          +    static const char *termAnchorNameBase = "TRM";
          +    char acname[sizeof(termAnchorNameBase) + 20];
          +    sprintf(acname, "%s%d", termAnchorNameBase, i);
          +    return string(acname);
          +}
          +
          +string  PlainToRichQtPreview::startChunk()
          +{
          +    return "
          ";
          +}
          +
          +int  PlainToRichQtPreview::nextAnchorNum(int grpidx)
          +{
          +    LOGDEB2("nextAnchorNum: group "  << (grpidx) << "\n" );
          +    map::iterator curit = 
          +        m_groupcuranchors.find(grpidx);
          +    map >::iterator vecit = 
          +        m_groupanchors.find(grpidx);
          +    if (grpidx == -1 || curit == m_groupcuranchors.end() ||
          +        vecit == m_groupanchors.end()) {
          +        if (m_curanchor >= m_lastanchor)
          +            m_curanchor = 1;
          +        else
          +            m_curanchor++;
          +    } else {
          +        if (curit->second >= vecit->second.size() -1)
          +            m_groupcuranchors[grpidx] = 0;
          +        else 
          +            m_groupcuranchors[grpidx]++;
          +        m_curanchor = vecit->second[m_groupcuranchors[grpidx]];
          +        LOGDEB2("nextAnchorNum: curanchor now "  << (m_curanchor) << "\n" );
          +    }
          +    return m_curanchor;
          +}
          +
          +int  PlainToRichQtPreview::prevAnchorNum(int grpidx)
          +{
          +    map::iterator curit = 
          +        m_groupcuranchors.find(grpidx);
          +    map >::iterator vecit = 
          +        m_groupanchors.find(grpidx);
          +    if (grpidx == -1 || curit == m_groupcuranchors.end() ||
          +        vecit == m_groupanchors.end()) {
          +        if (m_curanchor <= 1)
          +            m_curanchor = m_lastanchor;
          +        else
          +            m_curanchor--;
          +    } else {
          +        if (curit->second <= 0)
          +            m_groupcuranchors[grpidx] = vecit->second.size() -1;
          +        else 
          +            m_groupcuranchors[grpidx]--;
          +        m_curanchor = vecit->second[m_groupcuranchors[grpidx]];
          +    }
          +    return m_curanchor;
          +}
          +
          +QString  PlainToRichQtPreview::curAnchorName() const
          +{
          +    return QString::fromUtf8(termAnchorName(m_curanchor).c_str());
          +}
          +
          +
          +ToRichThread::ToRichThread(const string &i, const HighlightData& hd,
          +                           std::shared_ptr ptr,
          +                           QStringList& qrichlist,
          +                           QObject *parent)
          +    : QThread(parent), m_input(i), m_hdata(hd), m_ptr(ptr), m_output(qrichlist)
          +{
          +}
          +
          +// Insert into editor by chunks so that the top becomes visible
          +// earlier for big texts. This provokes some artifacts (adds empty line),
          +// so we can't set it too low.
          +#define CHUNKL 500*1000
          +
          +void ToRichThread::run()
          +{
          +    list out;
          +    try {
          +        m_ptr->plaintorich(m_input, out, m_hdata, CHUNKL);
          +    } catch (CancelExcept) {
          +        return;
          +    }
          +
          +    // Convert C++ string list to QString list
          +    for (list::iterator it = out.begin(); 
          +         it != out.end(); it++) {
          +        m_output.push_back(QString::fromUtf8(it->c_str(), it->length()));
          +    }
          +}
          +
          diff --git a/src/qtgui/preview_plaintorich.h b/src/qtgui/preview_plaintorich.h
          new file mode 100644
          index 00000000..ab508bba
          --- /dev/null
          +++ b/src/qtgui/preview_plaintorich.h
          @@ -0,0 +1,73 @@
          +/* Copyright (C) 2015 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
          + *   (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 General Public License for more details.
          + *
          + *   You should have received a copy of the GNU General Public License
          + *   along with this program; if not, write to the
          + *   Free Software Foundation, Inc.,
          + *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
          + */
          +#ifndef _PREVIEW_PLAINTORICH_H_INCLUDED_
          +#define _PREVIEW_PLAINTORICH_H_INCLUDED_
          +#include "autoconfig.h"
          +
          +#include 
          +#include 
          +#include 
          +#include 
          +
          +#include 
          +#include 
          +
          +#include "plaintorich.h"
          +
          +/** Preview text highlighter */
          +class PlainToRichQtPreview : public PlainToRich {
          +public:
          +    PlainToRichQtPreview();
          +    void clear();
          +    bool haveAnchors();
          +    virtual string header();
          +    virtual string startMatch(unsigned int grpidx);
          +    virtual string endMatch();
          +    virtual string termAnchorName(int i) const;
          +    virtual string startChunk();
          +    int nextAnchorNum(int grpidx);
          +    int prevAnchorNum(int grpidx);
          +    QString curAnchorName() const;
          +
          +private:
          +    int m_curanchor;
          +    int m_lastanchor;
          +    // Lists of anchor numbers (match locations) for the term (groups)
          +    // in the query (the map key is and index into HighlightData.groups).
          +    std::map > m_groupanchors;
          +    std::map m_groupcuranchors;
          +};
          +
          +/* A thread to convert to rich text (mark search terms) */
          +class ToRichThread : public QThread {
          +    Q_OBJECT;
          +    
          +public:
          +    ToRichThread(const string &i, const HighlightData& hd,
          +                 std::shared_ptr ptr,
          +                 QStringList& qrichlst, // Output
          +                 QObject *parent = 0);
          +    virtual void run();
          +
          +private:
          +    const string &m_input;
          +    const HighlightData &m_hdata;
          +    std::shared_ptr m_ptr;
          +    QStringList &m_output;
          +};
          +
          +#endif /* _PREVIEW_PLAINTORICH_H_INCLUDED_ */
          diff --git a/src/qtgui/preview_w.cpp b/src/qtgui/preview_w.cpp
          index cb2e3825..52d1e65a 100644
          --- a/src/qtgui/preview_w.cpp
          +++ b/src/qtgui/preview_w.cpp
          @@ -14,16 +14,10 @@
            *   Free Software Foundation, Inc.,
            *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
            */
          -#include 
          -#include 
          -#include 
          -#include 
          +#include "autoconfig.h"
           
           #include 
           #include 
          -#ifndef NO_NAMESPACES
          -using std::pair;
          -#endif /* NO_NAMESPACES */
           
           #include 
           #include 
          @@ -48,146 +42,29 @@ using std::pair;
           #include 
           #include 
           #include 
          +#include 
          +#include 
           
          -#include "debuglog.h"
          +#include "log.h"
           #include "pathut.h"
           #include "internfile.h"
           #include "recoll.h"
          -#include "plaintorich.h"
           #include "smallut.h"
          +#include "chrono.h"
           #include "wipedir.h"
           #include "cancelcheck.h"
           #include "preview_w.h"
           #include "guiutils.h"
           #include "docseqhist.h"
           #include "rclhelp.h"
          +#include "preview_load.h"
          +#include "preview_plaintorich.h"
           
          -// Subclass plainToRich to add s and anchors to the preview text
          -class PlainToRichQtPreview : public PlainToRich {
          -public:
          -
          -    PlainToRichQtPreview()
          -	: m_curanchor(1), m_lastanchor(0)
          -    {
          -    }    
          -
          -    bool haveAnchors()
          -    {
          -	return m_lastanchor != 0;
          -    }
          -
          -    virtual string header() 
          -    {
          -	if (!m_inputhtml) {
          -	    switch (prefs.previewPlainPre) {
          -	    case PrefsPack::PP_BR:
          -		m_eolbr = true;
          -		return "";
          -	    case PrefsPack::PP_PRE:
          -		m_eolbr = false;
          -		return "
          ";
          -	    case PrefsPack::PP_PREWRAP:
          -		m_eolbr = false;
          -		return ""
          -		    "
          ";
          -	    }
          -	}
          -	return cstr_null;
          -    }
          -
          -    virtual string startMatch(unsigned int grpidx)
          -    {
          -	LOGDEB2(("startMatch, grpidx %u\n", grpidx));
          -	grpidx = m_hdata->grpsugidx[grpidx];
          -	LOGDEB2(("startMatch, ugrpidx %u\n", grpidx));
          -	m_groupanchors[grpidx].push_back(++m_lastanchor);
          -	m_groupcuranchors[grpidx] = 0; 
          -	return string("").
          -	    append("");
          -    }
          -
          -    virtual string endMatch() 
          -    {
          -	return string("");
          -    }
          -
          -    virtual string termAnchorName(int i) const
          -    {
          -	static const char *termAnchorNameBase = "TRM";
          -	char acname[sizeof(termAnchorNameBase) + 20];
          -	sprintf(acname, "%s%d", termAnchorNameBase, i);
          -	return string(acname);
          -    }
          -
          -    virtual string startChunk() 
          -    { 
          -	return "
          ";
          -    }
          -
          -    int nextAnchorNum(int grpidx)
          -    {
          -	LOGDEB2(("nextAnchorNum: group %d\n", grpidx));
          -	map::iterator curit = 
          -	    m_groupcuranchors.find(grpidx);
          -	map >::iterator vecit = 
          -	    m_groupanchors.find(grpidx);
          -	if (grpidx == -1 || curit == m_groupcuranchors.end() ||
          -	    vecit == m_groupanchors.end()) {
          -	    if (m_curanchor >= m_lastanchor)
          -		m_curanchor = 1;
          -	    else
          -		m_curanchor++;
          -	} else {
          -	    if (curit->second >= vecit->second.size() -1)
          -		m_groupcuranchors[grpidx] = 0;
          -	    else 
          -		m_groupcuranchors[grpidx]++;
          -	    m_curanchor = vecit->second[m_groupcuranchors[grpidx]];
          -	    LOGDEB2(("nextAnchorNum: curanchor now %d\n", m_curanchor));
          -	}
          -	return m_curanchor;
          -    }
          -
          -    int prevAnchorNum(int grpidx)
          -    {
          -	map::iterator curit = 
          -	    m_groupcuranchors.find(grpidx);
          -	map >::iterator vecit = 
          -	    m_groupanchors.find(grpidx);
          -	if (grpidx == -1 || curit == m_groupcuranchors.end() ||
          -	    vecit == m_groupanchors.end()) {
          -	    if (m_curanchor <= 1)
          -		m_curanchor = m_lastanchor;
          -	    else
          -		m_curanchor--;
          -	} else {
          -	    if (curit->second <= 0)
          -		m_groupcuranchors[grpidx] = vecit->second.size() -1;
          -	    else 
          -		m_groupcuranchors[grpidx]--;
          -	    m_curanchor = vecit->second[m_groupcuranchors[grpidx]];
          -	}
          -	return m_curanchor;
          -    }
          -
          -    QString curAnchorName() const
          -    {
          -	return QString::fromUtf8(termAnchorName(m_curanchor).c_str());
          -    }
          -
          -private:
          -    int m_curanchor;
          -    int m_lastanchor;
          -    // Lists of anchor numbers (match locations) for the term (groups)
          -    // in the query (the map key is and index into HighlightData.groups).
          -    map > m_groupanchors;
          -    map m_groupcuranchors;
          -};
          +static const QKeySequence closeKS(Qt::Key_Escape);
          +static const QKeySequence nextDocInTabKS(Qt::ShiftModifier+Qt::Key_Down);
          +static const QKeySequence prevDocInTabKS(Qt::ShiftModifier+Qt::Key_Up);
          +static const QKeySequence closeTabKS(Qt::ControlModifier+Qt::Key_W);
          +static const QKeySequence printTabKS(Qt::ControlModifier+Qt::Key_P);
           
           void Preview::init()
           {
          @@ -218,13 +95,13 @@ void Preview::init()
               searchTextCMB->setInsertPolicy(QComboBox::NoInsert);
               searchTextCMB->setDuplicatesEnabled(false);
               for (unsigned int i = 0; i < m_hData.ugroups.size(); i++) {
          -	QString s;
          -	for (unsigned int j = 0; j < m_hData.ugroups[i].size(); j++) {
          -	    s.append(QString::fromUtf8(m_hData.ugroups[i][j].c_str()));
          -	    if (j != m_hData.ugroups[i].size()-1)
          -		s.append(" ");
          -	}
          -	searchTextCMB->addItem(s);
          +        QString s;
          +        for (unsigned int j = 0; j < m_hData.ugroups[i].size(); j++) {
          +            s.append(QString::fromUtf8(m_hData.ugroups[i][j].c_str()));
          +            if (j != m_hData.ugroups[i].size()-1)
          +                s.append(" ");
          +        }
          +        searchTextCMB->addItem(s);
               }
               searchTextCMB->setEditText("");
               searchTextCMB->setCompleter(0);
          @@ -261,37 +138,68 @@ void Preview::init()
           
               (void)new HelpClient(this);
               HelpClient::installMap((const char *)objectName().toUtf8(), 
          -			   "RCL.SEARCH.PREVIEW");
          +                           "RCL.SEARCH.GUI.PREVIEW");
           
               // signals and slots connections
               connect(searchTextCMB, SIGNAL(activated(int)), 
          -	    this, SLOT(searchTextFromIndex(int)));
          +            this, SLOT(searchTextFromIndex(int)));
               connect(searchTextCMB, SIGNAL(editTextChanged(const QString&)), 
          -	    this, SLOT(searchTextChanged(const QString&)));
          +            this, SLOT(searchTextChanged(const QString&)));
               connect(nextButton, SIGNAL(clicked()), this, SLOT(nextPressed()));
               connect(prevButton, SIGNAL(clicked()), this, SLOT(prevPressed()));
               connect(clearPB, SIGNAL(clicked()), searchTextCMB, SLOT(clearEditText()));
               connect(pvTab, SIGNAL(currentChanged(int)), 
          -	    this, SLOT(currentChanged(int)));
          +            this, SLOT(currentChanged(int)));
               connect(bt, SIGNAL(clicked()), this, SLOT(closeCurrentTab()));
           
          +    connect(new QShortcut(closeKS, this), SIGNAL (activated()), 
          +            this, SLOT (close()));
          +    connect(new QShortcut(nextDocInTabKS, this), SIGNAL (activated()), 
          +            this, SLOT (emitShowNext()));
          +    connect(new QShortcut(prevDocInTabKS, this), SIGNAL (activated()), 
          +            this, SLOT (emitShowPrev()));
          +    connect(new QShortcut(closeTabKS, this), SIGNAL (activated()), 
          +            this, SLOT (closeCurrentTab()));
          +    connect(new QShortcut(printTabKS, this), SIGNAL (activated()), 
          +            this, SIGNAL (printCurrentPreviewRequest()));
          +
               m_dynSearchActive = false;
               m_canBeep = true;
               if (prefs.pvwidth > 100) {
          -	resize(prefs.pvwidth, prefs.pvheight);
          +        resize(prefs.pvwidth, prefs.pvheight);
               }
               m_loading = false;
               currentChanged(pvTab->currentIndex());
               m_justCreated = true;
           }
           
          +void Preview::emitShowNext()
          +{
          +    if (m_loading)
          +        return;
          +    PreviewTextEdit *edit = currentEditor();
          +    if (edit) {
          +        emit(showNext(this, m_searchId, edit->m_docnum));
          +    }
          +}
          +
          +void Preview::emitShowPrev()
          +{
          +    if (m_loading)
          +        return;
          +    PreviewTextEdit *edit = currentEditor();
          +    if (edit) {
          +        emit(showPrev(this, m_searchId, edit->m_docnum));
          +    }
          +}
          +
           void Preview::closeEvent(QCloseEvent *e)
           {
          -    LOGDEB(("Preview::closeEvent. m_loading %d\n", m_loading));
          +    LOGDEB("Preview::closeEvent. m_loading "  << (m_loading) << "\n" );
               if (m_loading) {
          -	CancelCheck::instance().setCancel();
          -	e->ignore();
          -	return;
          +        CancelCheck::instance().setCancel();
          +        e->ignore();
          +        return;
               }
               prefs.pvwidth = width();
               prefs.pvheight = height();
          @@ -300,10 +208,10 @@ void Preview::closeEvent(QCloseEvent *e)
               for (int i = 0; i < pvTab->count(); i++) {
                   QWidget *tw = pvTab->widget(i);
                   if (tw) {
          -	    PreviewTextEdit *edit = 
          -		tw->findChild("pvEdit");
          +            PreviewTextEdit *edit = 
          +                tw->findChild("pvEdit");
                       if (edit) {
          -		forgetTempFile(edit->m_tmpfilename);
          +                forgetTempFile(edit->m_tmpfilename);
                       }
                   }
               }
          @@ -318,77 +226,51 @@ bool Preview::eventFilter(QObject *target, QEvent *event)
           {
               if (event->type() != QEvent::KeyPress) {
           #if 0
          -    LOGDEB(("Preview::eventFilter(): %s\n", eventTypeToStr(event->type())));
          -	if (event->type() == QEvent::MouseButtonRelease) {
          -	    QMouseEvent *mev = (QMouseEvent *)event;
          -	    LOGDEB(("Mouse: GlobalY %d y %d\n", mev->globalY(),
          -		    mev->y()));
          -	}
          +        LOGDEB("Preview::eventFilter(): "  << (eventTypeToStr(event->type())) << "\n" );
          +        if (event->type() == QEvent::MouseButtonRelease) {
          +            QMouseEvent *mev = (QMouseEvent *)event;
          +            LOGDEB("Mouse: GlobalY "  << (mev->globalY()) << " y "  << (mev->y()) << "\n" );
          +        }
           #endif
          -	return false;
          +        return false;
               }
          -    
          -    LOGDEB2(("Preview::eventFilter: keyEvent\n"));
           
               PreviewTextEdit *edit = currentEditor();
               QKeyEvent *keyEvent = (QKeyEvent *)event;
          -    if (keyEvent->key() == Qt::Key_Escape) {
          -	close();
          -	return true;
          -    } else if (keyEvent->key() == Qt::Key_Down &&
          -	       (keyEvent->modifiers() & Qt::ShiftModifier)) {
          -	LOGDEB2(("Preview::eventFilter: got Shift-Up\n"));
          -	if (edit) 
          -	    emit(showNext(this, m_searchId, edit->m_docnum));
          -	return true;
          -    } else if (keyEvent->key() == Qt::Key_Up &&
          -	       (keyEvent->modifiers() & Qt::ShiftModifier)) {
          -	LOGDEB2(("Preview::eventFilter: got Shift-Down\n"));
          -	if (edit) 
          -	    emit(showPrev(this, m_searchId, edit->m_docnum));
          -	return true;
          -    } else if (keyEvent->key() == Qt::Key_W &&
          -	       (keyEvent->modifiers() & Qt::ControlModifier)) {
          -	LOGDEB2(("Preview::eventFilter: got ^W\n"));
          -	closeCurrentTab();
          -	return true;
          -    } else if (keyEvent->key() == Qt::Key_P &&
          -	       (keyEvent->modifiers() & Qt::ControlModifier)) {
          -	LOGDEB2(("Preview::eventFilter: got ^P\n"));
          -	emit(printCurrentPreviewRequest());
          -	return true;
          -    } else if (m_dynSearchActive) {
          -	if (keyEvent->key() == Qt::Key_F3) {
          -	    LOGDEB2(("Preview::eventFilter: got F3\n"));
          -	    doSearch(searchTextCMB->currentText(), true, false);
          -	    return true;
          -	}
          -	if (target != searchTextCMB)
          -	    return QApplication::sendEvent(searchTextCMB, event);
          +
          +    if (m_dynSearchActive) {
          +        if (keyEvent->key() == Qt::Key_F3) {
          +            LOGDEB2("Preview::eventFilter: got F3\n" );
          +            doSearch(searchTextCMB->currentText(), true, 
          +                     (keyEvent->modifiers() & Qt::ShiftModifier) != 0);
          +            return true;
          +        }
          +        if (target != searchTextCMB)
          +            return QApplication::sendEvent(searchTextCMB, event);
               } else {
          -	if (edit && 
          -	    (target == edit || target == edit->viewport())) {
          -	    if (keyEvent->key() == Qt::Key_Slash ||
          -		(keyEvent->key() == Qt::Key_F &&
          -		 (keyEvent->modifiers() & Qt::ControlModifier))) {
          -		LOGDEB2(("Preview::eventFilter: got / or C-F\n"));
          -		searchTextCMB->setFocus();
          -		m_dynSearchActive = true;
          -		return true;
          -	    } else if (keyEvent->key() == Qt::Key_Space) {
          -		LOGDEB2(("Preview::eventFilter: got Space\n"));
          -		int value = edit->verticalScrollBar()->value();
          -		value += edit->verticalScrollBar()->pageStep();
          -		edit->verticalScrollBar()->setValue(value);
          -		return true;
          -	    } else if (keyEvent->key() == Qt::Key_Backspace) {
          -		LOGDEB2(("Preview::eventFilter: got Backspace\n"));
          -		int value = edit->verticalScrollBar()->value();
          -		value -= edit->verticalScrollBar()->pageStep();
          -		edit->verticalScrollBar()->setValue(value);
          -		return true;
          -	    }
          -	}
          +        if (edit && 
          +            (target == edit || target == edit->viewport())) {
          +            if (keyEvent->key() == Qt::Key_Slash ||
          +                (keyEvent->key() == Qt::Key_F &&
          +                 (keyEvent->modifiers() & Qt::ControlModifier))) {
          +                LOGDEB2("Preview::eventFilter: got / or C-F\n" );
          +                searchTextCMB->setFocus();
          +                m_dynSearchActive = true;
          +                return true;
          +            } else if (keyEvent->key() == Qt::Key_Space) {
          +                LOGDEB2("Preview::eventFilter: got Space\n" );
          +                int value = edit->verticalScrollBar()->value();
          +                value += edit->verticalScrollBar()->pageStep();
          +                edit->verticalScrollBar()->setValue(value);
          +                return true;
          +            } else if (keyEvent->key() == Qt::Key_Backspace) {
          +                LOGDEB2("Preview::eventFilter: got Backspace\n" );
          +                int value = edit->verticalScrollBar()->value();
          +                value -= edit->verticalScrollBar()->pageStep();
          +                edit->verticalScrollBar()->setValue(value);
          +                return true;
          +            }
          +        }
               }
           
               return false;
          @@ -396,32 +278,31 @@ bool Preview::eventFilter(QObject *target, QEvent *event)
           
           void Preview::searchTextChanged(const QString & text)
           {
          -    LOGDEB1(("Search line text changed. text: '%s'\n", 
          -	     (const char *)text.toUtf8()));
          +    LOGDEB1("Search line text changed. text: '"  << ((const char *)text.toUtf8()) << "'\n" );
               m_searchTextFromIndex = -1;
               if (text.isEmpty()) {
          -	m_dynSearchActive = false;
          -	clearPB->setEnabled(false);
          +        m_dynSearchActive = false;
          +        clearPB->setEnabled(false);
               } else {
          -	m_dynSearchActive = true;
          -	clearPB->setEnabled(true);
          -	doSearch(text, false, false);
          +        m_dynSearchActive = true;
          +        clearPB->setEnabled(true);
          +        doSearch(text, false, false);
               }
           }
           
           void Preview::searchTextFromIndex(int idx)
           {
          -    LOGDEB1(("search line from index %d\n", idx));
          +    LOGDEB1("search line from index "  << (idx) << "\n" );
               m_searchTextFromIndex = idx;
           }
           
           PreviewTextEdit *Preview::currentEditor()
           {
          -    LOGDEB2(("Preview::currentEditor()\n"));
          +    LOGDEB2("Preview::currentEditor()\n" );
               QWidget *tw = pvTab->currentWidget();
               PreviewTextEdit *edit = 0;
               if (tw) {
          -	edit = tw->findChild("pvEdit");
          +        edit = tw->findChild("pvEdit");
               }
               return edit;
           }
          @@ -431,7 +312,7 @@ void Preview::emitSaveDocToFile()
           {
               PreviewTextEdit *ce = currentEditor();
               if (ce && !ce->m_dbdoc.url.empty()) {
          -	emit saveDocToFile(ce->m_dbdoc);
          +        emit saveDocToFile(ce->m_dbdoc);
               }
           }
           
          @@ -440,40 +321,38 @@ void Preview::emitSaveDocToFile()
           // false, the search string has been modified, we search for the new string, 
           // starting from the current position
           void Preview::doSearch(const QString &_text, bool next, bool reverse, 
          -		       bool wordOnly)
          +                       bool wordOnly)
           {
          -    LOGDEB(("Preview::doSearch: text [%s] idx %d next %d rev %d word %d\n", 
          -	    (const char *)_text.toUtf8(), m_searchTextFromIndex, int(next), 
          -	    int(reverse), int(wordOnly)));
          +    LOGDEB("Preview::doSearch: text ["  << ((const char *)_text.toUtf8()) << "] idx "  << (m_searchTextFromIndex) << " next "  << (int(next)) << " rev "  << (int(reverse)) << " word "  << (int(wordOnly)) << "\n" );
               QString text = _text;
           
               bool matchCase = matchCheck->isChecked();
               PreviewTextEdit *edit = currentEditor();
               if (edit == 0) {
          -	// ??
          -	return;
          +        // ??
          +        return;
               }
           
               if (text.isEmpty() || m_searchTextFromIndex != -1) {
          -	if (!edit->m_plaintorich->haveAnchors()) {
          -	    LOGDEB(("NO ANCHORS\n"));
          -	    return;
          -	}
          -	// The combobox indices are equal to the search ugroup indices
          -	// in hldata, that's how we built the list.
          -	if (reverse) {
          -	    edit->m_plaintorich->prevAnchorNum(m_searchTextFromIndex);
          -	} else {
          -	    edit->m_plaintorich->nextAnchorNum(m_searchTextFromIndex);
          -	}
          -	QString aname = edit->m_plaintorich->curAnchorName();
          -	LOGDEB(("Calling scrollToAnchor(%s)\n", (const char *)aname.toUtf8()));
          -	edit->scrollToAnchor(aname);
          -	// Position the cursor approximately at the anchor (top of
          -	// viewport) so that searches start from here
          -	QTextCursor cursor = edit->cursorForPosition(QPoint(0, 0));
          -	edit->setTextCursor(cursor);
          -	return;
          +        if (!edit->m_plaintorich->haveAnchors()) {
          +            LOGDEB("NO ANCHORS\n" );
          +            return;
          +        }
          +        // The combobox indices are equal to the search ugroup indices
          +        // in hldata, that's how we built the list.
          +        if (reverse) {
          +            edit->m_plaintorich->prevAnchorNum(m_searchTextFromIndex);
          +        } else {
          +            edit->m_plaintorich->nextAnchorNum(m_searchTextFromIndex);
          +        }
          +        QString aname = edit->m_plaintorich->curAnchorName();
          +        LOGDEB("Calling scrollToAnchor("  << ((const char *)aname.toUtf8()) << ")\n" );
          +        edit->scrollToAnchor(aname);
          +        // Position the cursor approximately at the anchor (top of
          +        // viewport) so that searches start from here
          +        QTextCursor cursor = edit->cursorForPosition(QPoint(0, 0));
          +        edit->setTextCursor(cursor);
          +        return;
               }
           
               // If next is false, the user added characters to the current
          @@ -482,75 +361,73 @@ void Preview::doSearch(const QString &_text, bool next, bool reverse,
               // to look for the next occurrence instead of trying to lenghten
               // the current match
               if (!next) {
          -	QTextCursor cursor = edit->textCursor();
          -	cursor.setPosition(cursor.anchor(), QTextCursor::KeepAnchor);
          -	edit->setTextCursor(cursor);
          +        QTextCursor cursor = edit->textCursor();
          +        cursor.setPosition(cursor.anchor(), QTextCursor::KeepAnchor);
          +        edit->setTextCursor(cursor);
               }
               Chrono chron;
          -    LOGDEB(("Preview::doSearch: first find call\n"));
          +    LOGDEB("Preview::doSearch: first find call\n" );
               QTextDocument::FindFlags flags = 0;
               if (reverse)
          -	flags |= QTextDocument::FindBackward;
          +        flags |= QTextDocument::FindBackward;
               if (wordOnly)
          -	flags |= QTextDocument::FindWholeWords;
          +        flags |= QTextDocument::FindWholeWords;
               if (matchCase)
          -	flags |= QTextDocument::FindCaseSensitively;
          +        flags |= QTextDocument::FindCaseSensitively;
               bool found = edit->find(text, flags);
          -    LOGDEB(("Preview::doSearch: first find call return: found %d %.2f S\n", 
          -            found, chron.secs()));
          +    LOGDEB("Preview::doSearch: first find call return: found "  << (found) << " "  << (chron.secs()) << " S\n" );
               // If not found, try to wrap around. 
               if (!found) { 
          -	LOGDEB(("Preview::doSearch: wrapping around\n"));
          -	if (reverse) {
          -	    edit->moveCursor (QTextCursor::End);
          -	} else {
          -	    edit->moveCursor (QTextCursor::Start);
          -	}
          -	LOGDEB(("Preview::doSearch: 2nd find call\n"));
          +        LOGDEB("Preview::doSearch: wrapping around\n" );
          +        if (reverse) {
          +            edit->moveCursor (QTextCursor::End);
          +        } else {
          +            edit->moveCursor (QTextCursor::Start);
          +        }
          +        LOGDEB("Preview::doSearch: 2nd find call\n" );
                   chron.restart();
          -	found = edit->find(text, flags);
          -	LOGDEB(("Preview::doSearch: 2nd find call return found %d %.2f S\n",
          -                found, chron.secs()));
          +        found = edit->find(text, flags);
          +        LOGDEB("Preview::doSearch: 2nd find call return found "  << (found) << " "  << (chron.secs()) << " S\n" );
               }
           
               if (found) {
          -	m_canBeep = true;
          +        m_canBeep = true;
               } else {
          -	if (m_canBeep)
          -	    QApplication::beep();
          -	m_canBeep = false;
          +        if (m_canBeep)
          +            QApplication::beep();
          +        m_canBeep = false;
               }
          -    LOGDEB(("Preview::doSearch: return\n"));
          +    LOGDEB("Preview::doSearch: return\n" );
           }
           
           void Preview::nextPressed()
           {
          -    LOGDEB2(("Preview::nextPressed\n"));
          +    LOGDEB2("Preview::nextPressed\n" );
               doSearch(searchTextCMB->currentText(), true, false);
           }
           
           void Preview::prevPressed()
           {
          -    LOGDEB2(("Preview::prevPressed\n"));
          +    LOGDEB2("Preview::prevPressed\n" );
               doSearch(searchTextCMB->currentText(), true, true);
           }
           
           // Called when user clicks on tab
           void Preview::currentChanged(int index)
           {
          -    LOGDEB2(("PreviewTextEdit::currentChanged\n"));
          +    LOGDEB2("PreviewTextEdit::currentChanged\n" );
               QWidget *tw = pvTab->widget(index);
               PreviewTextEdit *edit = 
          -	tw->findChild("pvEdit");
          -    LOGDEB1(("Preview::currentChanged(). Editor: %p\n", edit));
          +        tw->findChild("pvEdit");
          +    LOGDEB1("Preview::currentChanged(). Editor: "  << (edit) << "\n" );
               
               if (edit == 0) {
          -	LOGERR(("Editor child not found\n"));
          -	return;
          +        LOGERR("Editor child not found\n" );
          +        return;
               }
               edit->setFocus();
               // Disconnect the print signal and reconnect it to the current editor
          -    LOGDEB(("Disconnecting reconnecting print signal\n"));
          +    LOGDEB("Disconnecting reconnecting print signal\n" );
               disconnect(this, SIGNAL(printCurrentPreviewRequest()), 0, 0);
               connect(this, SIGNAL(printCurrentPreviewRequest()), edit, SLOT(print()));
               edit->installEventFilter(this);
          @@ -561,24 +438,24 @@ void Preview::currentChanged(int index)
           
           void Preview::closeCurrentTab()
           {
          -    LOGDEB1(("Preview::closeCurrentTab: m_loading %d\n", m_loading));
          +    LOGDEB1("Preview::closeCurrentTab: m_loading "  << (m_loading) << "\n" );
               if (m_loading) {
          -	CancelCheck::instance().setCancel();
          -	return;
          +        CancelCheck::instance().setCancel();
          +        return;
               }
               PreviewTextEdit *e = currentEditor();
               if (e)
          -	forgetTempFile(e->m_tmpfilename);
          +        forgetTempFile(e->m_tmpfilename);
               if (pvTab->count() > 1) {
          -	pvTab->removeTab(pvTab->currentIndex());
          +        pvTab->removeTab(pvTab->currentIndex());
               } else {
          -	close();
          +        close();
               }
           }
           
           PreviewTextEdit *Preview::addEditorTab()
           {
          -    LOGDEB1(("PreviewTextEdit::addEditorTab()\n"));
          +    LOGDEB1("PreviewTextEdit::addEditorTab()\n" );
               QWidget *anon = new QWidget((QWidget *)pvTab);
               QVBoxLayout *anonLayout = new QVBoxLayout(anon); 
               PreviewTextEdit *editor = new PreviewTextEdit(anon, "pvEdit", this);
          @@ -592,16 +469,16 @@ PreviewTextEdit *Preview::addEditorTab()
           
           void Preview::setCurTabProps(const Rcl::Doc &doc, int docnum)
           {
          -    LOGDEB1(("Preview::setCurTabProps\n"));
          +    LOGDEB1("Preview::setCurTabProps\n" );
               QString title;
               string ctitle;
               if (doc.getmeta(Rcl::Doc::keytt, &ctitle) && !ctitle.empty()) {
          -	title = QString::fromUtf8(ctitle.c_str(), ctitle.length());
          +        title = QString::fromUtf8(ctitle.c_str(), ctitle.length());
               } else {
                   title = QString::fromLocal8Bit(path_getsimple(doc.url).c_str());
               }
               if (title.length() > 20) {
          -	title = title.left(10) + "..." + title.right(10);
          +        title = title.left(10) + "..." + title.right(10);
               }
               int curidx = pvTab->currentIndex();
               pvTab->setTabText(curidx, title);
          @@ -609,44 +486,44 @@ void Preview::setCurTabProps(const Rcl::Doc &doc, int docnum)
               char datebuf[100];
               datebuf[0] = 0;
               if (!doc.fmtime.empty() || !doc.dmtime.empty()) {
          -	time_t mtime = doc.dmtime.empty() ? 
          -	    atoll(doc.fmtime.c_str()) : atoll(doc.dmtime.c_str());
          -	struct tm *tm = localtime(&mtime);
          -	strftime(datebuf, 99, "%Y-%m-%d %H:%M:%S", tm);
          +        time_t mtime = doc.dmtime.empty() ? 
          +            atoll(doc.fmtime.c_str()) : atoll(doc.dmtime.c_str());
          +        struct tm *tm = localtime(&mtime);
          +        strftime(datebuf, 99, "%Y-%m-%d %H:%M:%S", tm);
               }
          -    LOGDEB(("Doc.url: [%s]\n", doc.url.c_str()));
          +    LOGDEB("Doc.url: ["  << (doc.url) << "]\n" );
               string url;
               printableUrl(theconfig->getDefCharset(), doc.url, url);
               string tiptxt = url + string("\n");
               tiptxt += doc.mimetype + " " + string(datebuf) + "\n";
               if (!ctitle.empty())
          -	tiptxt += ctitle + "\n";
          +        tiptxt += ctitle + "\n";
               pvTab->setTabToolTip(curidx,
          -			 QString::fromUtf8(tiptxt.c_str(), tiptxt.length()));
          +                         QString::fromUtf8(tiptxt.c_str(), tiptxt.length()));
           
               PreviewTextEdit *e = currentEditor();
               if (e) {
          -	e->m_url = doc.url;
          -	e->m_ipath = doc.ipath;
          -	e->m_docnum = docnum;
          +        e->m_url = doc.url;
          +        e->m_ipath = doc.ipath;
          +        e->m_docnum = docnum;
               }
           }
           
           bool Preview::makeDocCurrent(const Rcl::Doc& doc, int docnum, bool sametab)
           {
          -    LOGDEB(("Preview::makeDocCurrent: %s\n", doc.url.c_str()));
          +    LOGDEB("Preview::makeDocCurrent: "  << (doc.url) << "\n" );
           
               if (m_loading) {
          -	LOGERR(("Already loading\n"));
          -	return false;
          +        LOGERR("Already loading\n" );
          +        return false;
               }
           
               /* Check if we already have this page */
               for (int i = 0; i < pvTab->count(); i++) {
                   QWidget *tw = pvTab->widget(i);
                   if (tw) {
          -	    PreviewTextEdit *edit = 
          -		tw->findChild("pvEdit");
          +            PreviewTextEdit *edit = 
          +                tw->findChild("pvEdit");
                       if (edit && !edit->m_url.compare(doc.url) && 
                           !edit->m_ipath.compare(doc.ipath)) {
                           pvTab->setCurrentIndex(i);
          @@ -657,12 +534,12 @@ bool Preview::makeDocCurrent(const Rcl::Doc& doc, int docnum, bool sametab)
           
               // if just created the first tab was created during init
               if (!sametab && !m_justCreated && !addEditorTab()) {
          -	return false;
          +        return false;
               }
               m_justCreated = false;
               if (!loadDocInCurrentTab(doc, docnum)) {
          -	closeCurrentTab();
          -	return false;
          +        closeCurrentTab();
          +        return false;
               }
               raise();
               return true;
          @@ -671,20 +548,20 @@ void Preview::togglePlainPre()
           {
               switch (prefs.previewPlainPre) {
               case PrefsPack::PP_BR:
          -	prefs.previewPlainPre = PrefsPack::PP_PRE;
          -	break;
          +        prefs.previewPlainPre = PrefsPack::PP_PRE;
          +        break;
               case PrefsPack::PP_PRE:
          -	prefs.previewPlainPre = PrefsPack::PP_BR;
          -	break;
          +        prefs.previewPlainPre = PrefsPack::PP_BR;
          +        break;
               case PrefsPack::PP_PREWRAP:
               default:
          -	prefs.previewPlainPre = PrefsPack::PP_PRE;
          -	break;
          +        prefs.previewPlainPre = PrefsPack::PP_PRE;
          +        break;
               }
               
               PreviewTextEdit *editor = currentEditor();
               if (editor)
          -	loadDocInCurrentTab(editor->m_dbdoc, editor->m_docnum);
          +        loadDocInCurrentTab(editor->m_dbdoc, editor->m_docnum);
           }
           
           void Preview::emitWordSelect(QString word)
          @@ -708,92 +585,15 @@ void Preview::emitWordSelect(QString word)
             beginning of the text displayed faster
           */
           
          -/* A thread to to the file reading / format conversion */
          -class LoadThread : public QThread {
          -    int *statusp;
          -    Rcl::Doc& out;
          -    const Rcl::Doc& idoc;
          -    int loglevel;
          - public: 
          -    string missing;
          -    TempFile imgtmp;
          -
          -    LoadThread(int *stp, Rcl::Doc& odoc, const Rcl::Doc& idc) 
          -	: statusp(stp), out(odoc), idoc(idc)
          -	{
          -	    loglevel = DebugLog::getdbl()->getlevel();
          -	}
          -    ~LoadThread() {
          -    }
          -    virtual void run() {
          -	DebugLog::getdbl()->setloglevel(loglevel);
          -
          -	FileInterner interner(idoc, theconfig, FileInterner::FIF_forPreview);
          -	FIMissingStore mst;
          -	interner.setMissingStore(&mst);
          -	// Even when previewHtml is set, we don't set the interner's
          -	// target mtype to html because we do want the html filter to
          -	// do its work: we won't use the text/plain, but we want the
          -	// text/html to be converted to utf-8 (for highlight processing)
          -	try {
          -            string ipath = idoc.ipath;
          -	    FileInterner::Status ret = interner.internfile(out, ipath);
          -	    if (ret == FileInterner::FIDone || ret == FileInterner::FIAgain) {
          -		// FIAgain is actually not nice here. It means that the record
          -		// for the *file* of a multidoc was selected. Actually this
          -		// shouldn't have had a preview link at all, but we don't know
          -		// how to handle it now. Better to show the first doc than
          -		// a mysterious error. Happens when the file name matches a
          -		// a search term.
          -		*statusp = 0;
          -		// If we prefer html and it is available, replace the
          -		// text/plain document text
          -		if (prefs.previewHtml && !interner.get_html().empty()) {
          -		    out.text = interner.get_html();
          -		    out.mimetype = "text/html";
          -		}
          -		imgtmp = interner.get_imgtmp();
          -	    } else {
          -		out.mimetype = interner.getMimetype();
          -		mst.getMissingExternal(missing);
          -		*statusp = -1;
          -	    }
          -	} catch (CancelExcept) {
          -	    *statusp = -1;
          -	}
          -    }
          -};
          -
           
           // Insert into editor by chunks so that the top becomes visible
           // earlier for big texts. This provokes some artifacts (adds empty line),
           // so we can't set it too low.
           #define CHUNKL 500*1000
           
          -/* A thread to convert to rich text (mark search terms) */
          -class ToRichThread : public QThread {
          -    string ∈
          -    const HighlightData &hdata;
          -    list &out;
          -    int loglevel;
          -    PlainToRichQtPreview *ptr;
          - public:
          -    ToRichThread(string &i, const HighlightData& hd, list &o, 
          -		 PlainToRichQtPreview *_ptr)
          -	: in(i), hdata(hd), out(o), ptr(_ptr)
          -    {
          -	    loglevel = DebugLog::getdbl()->getlevel();
          -    }
          -    virtual void run()
          -    {
          -	DebugLog::getdbl()->setloglevel(loglevel);
          -	try {
          -	    ptr->plaintorich(in, out, hdata, CHUNKL);
          -	} catch (CancelExcept) {
          -	}
          -    }
          -};
          -
          +// Make sure we don't ever reenter loadDocInCurrentTab: note that I
          +// don't think it's actually possible, this must be the result of a
          +// misguided debug session.
           class LoadGuard {
               bool *m_bp;
           public:
          @@ -803,7 +603,7 @@ public:
           
           bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum)
           {
          -    LOGDEB1(("Preview::loadDocInCurrentTab()\n"));
          +    LOGDEB1("Preview::loadDocInCurrentTab()\n" );
           
               LoadGuard guard(&m_loading);
               CancelCheck::instance().setCancel(false);
          @@ -811,57 +611,61 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum)
               setCurTabProps(idoc, docnum);
           
               QString msg = QString("Loading: %1 (size %2 bytes)")
          -	.arg(QString::fromLocal8Bit(idoc.url.c_str()))
          -	.arg(QString::fromUtf8(idoc.fbytes.c_str()));
          +        .arg(QString::fromLocal8Bit(idoc.url.c_str()))
          +        .arg(QString::fromUtf8(idoc.fbytes.c_str()));
           
          -    // Create progress dialog and aux objects
          -    const int nsteps = 20;
          -    QProgressDialog progress(msg, tr("Cancel"), 0, nsteps, this);
          +    QProgressDialog progress(msg, tr("Cancel"), 0, 0, this);
               progress.setMinimumDuration(2000);
          +    QEventLoop loop;
          +    QTimer tT;
          +    tT.setSingleShot(true);
          +    connect(&tT, SIGNAL(timeout()), &loop, SLOT(quit()));
           
               ////////////////////////////////////////////////////////////////////////
               // Load and convert document
               // idoc came out of the index data (main text and some fields missing). 
               // fdoc is the complete one what we are going to extract from storage.
          -    Rcl::Doc fdoc;
          -    int status = 1;
          -    LoadThread lthr(&status, fdoc, idoc);
          +    LoadThread lthr(theconfig, idoc, prefs.previewHtml, this);
          +    connect(<hr, SIGNAL(finished()), &loop, SLOT(quit()));
          +
               lthr.start();
          -    int prog;
          -    for (prog = 1;;prog++) {
          -	if (lthr.wait(100))
          -	    break;
          -	progress.setValue(prog);
          -	qApp->processEvents();
          -	if (progress.wasCanceled()) {
          -	    CancelCheck::instance().setCancel();
          -	}
          -	if (prog >= 5)
          -	    sleep(1);
          +    for (int i = 0;;i++) {
          +        tT.start(1000); 
          +        loop.exec();
          +        if (lthr.isFinished())
          +            break;
          +        if (progress.wasCanceled()) {
          +            CancelCheck::instance().setCancel();
          +        }
          +        if (i == 1)
          +            progress.show();
               }
           
          -    LOGDEB(("LoadFileInCurrentTab: after file load: cancel %d status %d"
          -	    " text length %d\n", 
          -	    CancelCheck::instance().cancelState(), status, fdoc.text.length()));
          +    LOGDEB("loadDocInCurrentTab: after file load: cancel "  << (CancelCheck::instance().cancelState()) << " status "  << (lthr.status) << " text length "  << (lthr.fdoc.text.length()) << "\n" );
           
               if (CancelCheck::instance().cancelState())
          -	return false;
          -    if (status != 0) {
          +        return false;
          +    if (lthr.status != 0) {
          +        progress.close();
                   QString explain;
          -	if (!lthr.missing.empty()) {
          +        if (!lthr.missing.empty()) {
                       explain = QString::fromUtf8("
          ") + tr("Missing helper program: ") + QString::fromLocal8Bit(lthr.missing.c_str()); - QMessageBox::warning(0, "Recoll", - tr("Can't turn doc into internal " - "representation for ") + - fdoc.mimetype.c_str() + explain); + QMessageBox::warning(0, "Recoll", + tr("Can't turn doc into internal " + "representation for ") + + lthr.fdoc.mimetype.c_str() + explain); } else { - QMessageBox::warning(0, "Recoll", - tr("Error while loading file")); - } + if (progress.wasCanceled()) { + //QMessageBox::warning(0, "Recoll", tr("Canceled")); + } else { + QMessageBox::warning(0, "Recoll", + tr("Error while loading file")); + } + } - return false; + return false; } // Reset config just in case. theconfig->setKeyDir(""); @@ -871,14 +675,15 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum) // We don't do the highlighting for very big texts: too long. We // should at least do special char escaping, in case a '&' or '<' // somehow slipped through previous processing. - bool highlightTerms = fdoc.text.length() < - (unsigned long)prefs.maxhltextmbs * 1024 * 1024; + bool highlightTerms = lthr.fdoc.text.length() < + (unsigned long)prefs.maxhltextmbs * 1024 * 1024; // Final text is produced in chunks so that we can display the top // while still inserting at bottom - list qrichlst; PreviewTextEdit *editor = currentEditor(); + editor->m_plaintorich->clear(); + // For an actual html file, if we want to have the images and // style loaded in the preview, we need to set the search // path. Not too sure this is a good idea as I find them rather @@ -888,118 +693,110 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum) #if 0 string path = fileurltolocalpath(idoc.url); if (!path.empty()) { - path = path_getfather(path); - QStringList paths(QString::fromLocal8Bit(path.c_str())); - editor->setSearchPaths(paths); + path = path_getfather(path); + QStringList paths(QString::fromLocal8Bit(path.c_str())); + editor->setSearchPaths(paths); } #endif editor->setHtml(""); editor->m_format = Qt::RichText; - bool inputishtml = !fdoc.mimetype.compare("text/html"); + bool inputishtml = !lthr.fdoc.mimetype.compare("text/html"); + QStringList qrichlst; + +#if 1 + if (highlightTerms) { + progress.setLabelText(tr("Creating preview text")); + qApp->processEvents(); -#if 0 - // For testing qtextedit bugs... + if (inputishtml) { + LOGDEB1("Preview: got html " << (lthr.fdoc.text) << "\n" ); + editor->m_plaintorich->set_inputhtml(true); + } else { + LOGDEB1("Preview: got plain " << (lthr.fdoc.text) << "\n" ); + editor->m_plaintorich->set_inputhtml(false); + } + + ToRichThread rthr(lthr.fdoc.text, m_hData, editor->m_plaintorich, + qrichlst, this); + connect(&rthr, SIGNAL(finished()), &loop, SLOT(quit())); + rthr.start(); + + for (;;) { + tT.start(1000); + loop.exec(); + if (rthr.isFinished()) + break; + if (progress.wasCanceled()) { + CancelCheck::instance().setCancel(); + } + } + + // Conversion to rich text done + if (CancelCheck::instance().cancelState()) { + if (qrichlst.size() == 0 || qrichlst.front().size() == 0) { + // We can't call closeCurrentTab here as it might delete + // the object which would be a nasty surprise to our + // caller. + return false; + } else { + qrichlst.back() += "Cancelled !"; + } + } + } else { + LOGDEB("Preview: no hilighting, loading " << (int(lthr.fdoc.text.size())) << " bytes\n" ); + // No plaintorich() call. In this case, either the text is + // html and the html quoting is hopefully correct, or it's + // plain-text and there is no need to escape special + // characters. We'd still want to split in chunks (so that the + // top is displayed faster), but we must not cut tags, and + // it's too difficult on html. For text we do the splitting on + // a QString to avoid utf8 issues. + QString qr = QString::fromUtf8(lthr.fdoc.text.c_str(), + lthr.fdoc.text.length()); + int l = 0; + if (inputishtml) { + qrichlst.push_back(qr); + } else { + editor->setPlainText(""); + editor->m_format = Qt::PlainText; + for (int pos = 0; pos < (int)qr.length(); pos += l) { + l = MIN(CHUNKL, qr.length() - pos); + qrichlst.push_back(qr.mid(pos, l)); + } + } + } +#else // For testing qtextedit bugs... highlightTerms = true; const char *textlist[] = - { - "Du plain text avec un\n termtag fin de ligne:", - "texte apres le tag\n", - }; + { + "Du plain text avec un\n termtag fin de ligne:", + "texte apres le tag\n", + }; const int listl = sizeof(textlist) / sizeof(char*); for (int i = 0 ; i < listl ; i++) qrichlst.push_back(QString::fromUtf8(textlist[i])); -#else - if (highlightTerms) { - progress.setLabelText(tr("Creating preview text")); - qApp->processEvents(); - - if (inputishtml) { - LOGDEB1(("Preview: got html %s\n", fdoc.text.c_str())); - editor->m_plaintorich->set_inputhtml(true); - } else { - LOGDEB1(("Preview: got plain %s\n", fdoc.text.c_str())); - editor->m_plaintorich->set_inputhtml(false); - } - list richlst; - ToRichThread rthr(fdoc.text, m_hData, richlst, editor->m_plaintorich); - rthr.start(); - - for (;;prog++) { - if (rthr.wait(100)) - break; - progress.setValue(nsteps); - qApp->processEvents(); - if (progress.wasCanceled()) { - CancelCheck::instance().setCancel(); - } - if (prog >= 5) - sleep(1); - } - - // Conversion to rich text done - if (CancelCheck::instance().cancelState()) { - if (richlst.size() == 0 || richlst.front().length() == 0) { - // We can't call closeCurrentTab here as it might delete - // the object which would be a nasty surprise to our - // caller. - return false; - } else { - richlst.back() += "Cancelled !"; - } - } - // Convert C++ string list to QString list - for (list::iterator it = richlst.begin(); - it != richlst.end(); it++) { - qrichlst.push_back(QString::fromUtf8(it->c_str(), it->length())); - } - } else { - LOGDEB(("Preview: no hilighting\n")); - // No plaintorich() call. In this case, either the text is - // html and the html quoting is hopefully correct, or it's - // plain-text and there is no need to escape special - // characters. We'd still want to split in chunks (so that the - // top is displayed faster), but we must not cut tags, and - // it's too difficult on html. For text we do the splitting on - // a QString to avoid utf8 issues. - QString qr = QString::fromUtf8(fdoc.text.c_str(), fdoc.text.length()); - int l = 0; - if (inputishtml) { - qrichlst.push_back(qr); - } else { - editor->setPlainText(""); - editor->m_format = Qt::PlainText; - for (int pos = 0; pos < (int)qr.length(); pos += l) { - l = MIN(CHUNKL, qr.length() - pos); - qrichlst.push_back(qr.mid(pos, l)); - } - } - } #endif - /////////////////////////////////////////////////////////// // Load text into editor window. - prog = 2 * nsteps / 3; progress.setLabelText(tr("Loading preview text into editor")); qApp->processEvents(); - int instep = 0; - for (list::iterator it = qrichlst.begin(); - it != qrichlst.end(); it++, prog++, instep++) { - progress.setValue(prog); - qApp->processEvents(); + for (QStringList::iterator it = qrichlst.begin(); + it != qrichlst.end(); it++) { + qApp->processEvents(); - editor->append(*it); + editor->append(*it); // We need to save the rich text for printing, the editor does // not do it consistently for us. editor->m_richtxt.append(*it); - if (progress.wasCanceled()) { + if (progress.wasCanceled()) { editor->append("Cancelled !"); - LOGDEB(("LoadFileInCurrentTab: cancelled in editor load\n")); - break; - } + LOGDEB("loadDocInCurrentTab: cancelled in editor load\n" ); + break; + } } progress.close(); @@ -1011,63 +808,63 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum) // Maybe the text was actually empty ? Switch to fields then. Else free-up // the text memory in the loaded document. We still have a copy of the text // in editor->m_richtxt - bool textempty = fdoc.text.empty(); + bool textempty = lthr.fdoc.text.empty(); if (!textempty) - fdoc.text.clear(); - editor->m_fdoc = fdoc; + lthr.fdoc.text.clear(); + editor->m_fdoc = lthr.fdoc; editor->m_dbdoc = idoc; if (textempty) editor->displayFields(); // If this is an image, display it instead of the text. if (!idoc.mimetype.compare(0, 6, "image/")) { - string fn = fileurltolocalpath(idoc.url); + string fn = fileurltolocalpath(idoc.url); - // 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: - theconfig->setKeyDir(path_getfather(fn)); - if (fn.empty() || !idoc.ipath.empty()) { - TempFile temp = lthr.imgtmp; - if (temp.isNotNull()) { - LOGDEB1(("Preview: load: got temp file from internfile\n")); - } else if (!FileInterner::idocToFile(temp, string(), - theconfig, idoc)) { - temp.release(); // just in case. - } - if (temp.isNotNull()) { - rememberTempFile(temp); - fn = temp->filename(); - editor->m_tmpfilename = fn; - } else { - editor->m_tmpfilename.erase(); - fn.erase(); - } - } + // 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: + theconfig->setKeyDir(fn.empty() ? "" : path_getfather(fn)); + if (fn.empty() || !idoc.ipath.empty()) { + TempFile temp = lthr.tmpimg; + if (temp) { + LOGDEB1("Preview: load: got temp file from internfile\n" ); + } else if (!FileInterner::idocToFile(temp, string(), + theconfig, idoc)) { + temp.reset(); // just in case. + } + if (temp) { + rememberTempFile(temp); + fn = temp->filename(); + editor->m_tmpfilename = fn; + } else { + editor->m_tmpfilename.erase(); + fn.erase(); + } + } - if (!fn.empty()) { - editor->m_image = QImage(fn.c_str()); - if (!editor->m_image.isNull()) - editor->displayImage(); - } - } + if (!fn.empty()) { + editor->m_image = QImage(fn.c_str()); + if (!editor->m_image.isNull()) + editor->displayImage(); + } + } // Position the editor so that the first search term is visible if (searchTextCMB->currentText().length() != 0) { - // If there is a current search string, perform the search - m_canBeep = true; - doSearch(searchTextCMB->currentText(), true, false); + // If there is a current search string, perform the search + m_canBeep = true; + doSearch(searchTextCMB->currentText(), true, false); } else { - // Position to the first query term - if (editor->m_plaintorich->haveAnchors()) { - QString aname = editor->m_plaintorich->curAnchorName(); - LOGDEB2(("Call movetoanchor(%s)\n", (const char *)aname.toUtf8())); - editor->scrollToAnchor(aname); - // Position the cursor approximately at the anchor (top of - // viewport) so that searches start from here - QTextCursor cursor = editor->cursorForPosition(QPoint(0, 0)); - editor->setTextCursor(cursor); - } + // Position to the first query term + if (editor->m_plaintorich->haveAnchors()) { + QString aname = editor->m_plaintorich->curAnchorName(); + LOGDEB2("Call movetoanchor(" << ((const char *)aname.toUtf8()) << ")\n" ); + editor->scrollToAnchor(aname); + // Position the cursor approximately at the anchor (top of + // viewport) so that searches start from here + QTextCursor cursor = editor->cursorForPosition(QPoint(0, 0)); + editor->setTextCursor(cursor); + } } @@ -1079,94 +876,89 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum) editor->setFocus(); emit(previewExposed(this, m_searchId, docnum)); - LOGDEB(("LoadFileInCurrentTab: returning true\n")); + LOGDEB("loadDocInCurrentTab: returning true\n" ); return true; } PreviewTextEdit::PreviewTextEdit(QWidget* parent, const char* nm, Preview *pv) - : QTextBrowser(parent), m_preview(pv), - m_plaintorich(new PlainToRichQtPreview()), + : QTextBrowser(parent), m_preview(pv), + m_plaintorich(new PlainToRichQtPreview()), m_dspflds(false), m_docnum(-1) { setContextMenuPolicy(Qt::CustomContextMenu); setObjectName(nm); connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), - this, SLOT(createPopupMenu(const QPoint&))); + this, SLOT(createPopupMenu(const QPoint&))); setOpenExternalLinks(false); setOpenLinks(false); } -PreviewTextEdit::~PreviewTextEdit() -{ - delete m_plaintorich; -} - void PreviewTextEdit::createPopupMenu(const QPoint& pos) { - LOGDEB1(("PreviewTextEdit::createPopupMenu()\n")); + LOGDEB1("PreviewTextEdit::createPopupMenu()\n" ); QMenu *popup = new QMenu(this); switch (m_curdsp) { case PTE_DSPTXT: - popup->addAction(tr("Show fields"), this, SLOT(displayFields())); - if (!m_image.isNull()) - popup->addAction(tr("Show image"), this, SLOT(displayImage())); - break; + popup->addAction(tr("Show fields"), this, SLOT(displayFields())); + if (!m_image.isNull()) + popup->addAction(tr("Show image"), this, SLOT(displayImage())); + break; case PTE_DSPFLDS: - popup->addAction(tr("Show main text"), this, SLOT(displayText())); - if (!m_image.isNull()) - popup->addAction(tr("Show image"), this, SLOT(displayImage())); - break; + popup->addAction(tr("Show main text"), this, SLOT(displayText())); + if (!m_image.isNull()) + popup->addAction(tr("Show image"), this, SLOT(displayImage())); + break; case PTE_DSPIMG: default: - popup->addAction(tr("Show fields"), this, SLOT(displayFields())); - popup->addAction(tr("Show main text"), this, SLOT(displayText())); - break; + popup->addAction(tr("Show fields"), this, SLOT(displayFields())); + popup->addAction(tr("Show main text"), this, SLOT(displayText())); + break; } popup->addAction(tr("Select All"), this, SLOT(selectAll())); popup->addAction(tr("Copy"), this, SLOT(copy())); popup->addAction(tr("Print"), this, SLOT(print())); if (prefs.previewPlainPre) { - popup->addAction(tr("Fold lines"), m_preview, SLOT(togglePlainPre())); + popup->addAction(tr("Fold lines"), m_preview, SLOT(togglePlainPre())); } else { - popup->addAction(tr("Preserve indentation"), - m_preview, SLOT(togglePlainPre())); + popup->addAction(tr("Preserve indentation"), + m_preview, SLOT(togglePlainPre())); } // Need to check ipath until we fix the internfile bug that always // has it convert to html for top level docs if (!m_dbdoc.url.empty() && !m_dbdoc.ipath.empty()) - popup->addAction(tr("Save document to file"), - m_preview, SLOT(emitSaveDocToFile())); + popup->addAction(tr("Save document to file"), + m_preview, SLOT(emitSaveDocToFile())); popup->popup(mapToGlobal(pos)); } // Display main text void PreviewTextEdit::displayText() { - LOGDEB1(("PreviewTextEdit::displayText()\n")); + LOGDEB1("PreviewTextEdit::displayText()\n" ); if (m_format == Qt::PlainText) - setPlainText(m_richtxt); + setPlainText(m_richtxt); else - setHtml(m_richtxt); + setHtml(m_richtxt); m_curdsp = PTE_DSPTXT; } // Display field values void PreviewTextEdit::displayFields() { - LOGDEB1(("PreviewTextEdit::displayFields()\n")); + LOGDEB1("PreviewTextEdit::displayFields()\n" ); QString txt = "\n"; txt += "" + QString::fromLocal8Bit(m_url.c_str()); if (!m_ipath.empty()) - txt += "|" + QString::fromUtf8(m_ipath.c_str()); + txt += "|" + QString::fromUtf8(m_ipath.c_str()); txt += "

          "; txt += "
          \n"; for (map::const_iterator it = m_fdoc.meta.begin(); - it != m_fdoc.meta.end(); it++) { - if (!it->second.empty()) - txt += "
          " + QString::fromUtf8(it->first.c_str()) + "
          " - + "
          " + QString::fromUtf8(escapeHtml(it->second).c_str()) - + "
          \n"; + it != m_fdoc.meta.end(); it++) { + if (!it->second.empty()) + txt += "
          " + QString::fromUtf8(it->first.c_str()) + "
          " + + "
          " + QString::fromUtf8(escapeHtml(it->second).c_str()) + + "
          \n"; } txt += "
          "; setHtml(txt); @@ -1175,17 +967,17 @@ void PreviewTextEdit::displayFields() void PreviewTextEdit::displayImage() { - LOGDEB1(("PreviewTextEdit::displayImage()\n")); + LOGDEB1("PreviewTextEdit::displayImage()\n" ); if (m_image.isNull()) - displayText(); + displayText(); setPlainText(""); if (m_image.width() > width() || - m_image.height() > height()) { - m_image = m_image.scaled(width(), height(), Qt::KeepAspectRatio); + m_image.height() > height()) { + m_image = m_image.scaled(width(), height(), Qt::KeepAspectRatio); } document()->addResource(QTextDocument::ImageResource, QUrl("image"), - m_image); + m_image); QTextCursor cursor = textCursor(); cursor.insertImage("image"); m_curdsp = PTE_DSPIMG; @@ -1193,18 +985,18 @@ void PreviewTextEdit::displayImage() void PreviewTextEdit::mouseDoubleClickEvent(QMouseEvent *event) { - LOGDEB2(("PreviewTextEdit::mouseDoubleClickEvent\n")); + LOGDEB2("PreviewTextEdit::mouseDoubleClickEvent\n" ); QTextEdit::mouseDoubleClickEvent(event); if (textCursor().hasSelection() && m_preview) - m_preview->emitWordSelect(textCursor().selectedText()); + m_preview->emitWordSelect(textCursor().selectedText()); } void PreviewTextEdit::print() { - LOGDEB(("PreviewTextEdit::print\n")); + LOGDEB("PreviewTextEdit::print\n" ); if (!m_preview) return; - + #ifndef QT_NO_PRINTER QPrinter printer; QPrintDialog *dialog = new QPrintDialog(&printer, this); @@ -1214,3 +1006,4 @@ void PreviewTextEdit::print() QTextEdit::print(&printer); #endif } + diff --git a/src/qtgui/preview_w.h b/src/qtgui/preview_w.h index 5d880033..d46a73e4 100644 --- a/src/qtgui/preview_w.h +++ b/src/qtgui/preview_w.h @@ -16,6 +16,7 @@ */ #ifndef _PREVIEW_W_H_INCLUDED_ #define _PREVIEW_W_H_INCLUDED_ +#include "autoconfig.h" // Always use a qtextbrowser for now, there is no compelling reason to // switch to webkit here @@ -25,6 +26,8 @@ #include +#include + #include #include #include @@ -39,7 +42,6 @@ #include #include "rcldb.h" -#include "refcntr.h" #include "plaintorich.h" #include "rclmain_w.h" @@ -54,7 +56,6 @@ class PreviewTextEdit : public PREVIEW_PARENTCLASS { Q_OBJECT; public: PreviewTextEdit(QWidget* parent, const char* name, Preview *pv); - virtual ~PreviewTextEdit(); void moveToAnchor(const QString& name); enum DspType {PTE_DSPTXT, PTE_DSPFLDS, PTE_DSPIMG}; @@ -72,7 +73,7 @@ protected: private: Preview *m_preview; - PlainToRichQtPreview *m_plaintorich; + std::shared_ptr m_plaintorich; bool m_dspflds; string m_url; // filename for this tab @@ -130,14 +131,20 @@ class Preview : public QWidget { friend class PreviewTextEdit; public slots: + // Search stuff virtual void searchTextChanged(const QString& text); virtual void searchTextFromIndex(int); virtual void doSearch(const QString& str, bool next, bool reverse, bool wo = false); virtual void nextPressed(); virtual void prevPressed(); + + // Tabs management virtual void currentChanged(int); virtual void closeCurrentTab(); + virtual void emitShowNext(); + virtual void emitShowPrev(); + virtual void emitSaveDocToFile(); virtual void togglePlainPre(); diff --git a/src/qtgui/ptrans_w.cpp b/src/qtgui/ptrans_w.cpp index c1338f2a..0253a5ab 100644 --- a/src/qtgui/ptrans_w.cpp +++ b/src/qtgui/ptrans_w.cpp @@ -14,6 +14,7 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#include "autoconfig.h" #include @@ -33,7 +34,7 @@ using namespace std; #include #include "recoll.h" -#include "debuglog.h" +#include "log.h" #include "guiutils.h" #include "conftree.h" @@ -132,3 +133,4 @@ void EditTrans::on_transTW_itemSelectionChanged() else delPB->setEnabled(1); } + diff --git a/src/qtgui/rclhelp.cpp b/src/qtgui/rclhelp.cpp index 026466e6..34ba5698 100644 --- a/src/qtgui/rclhelp.cpp +++ b/src/qtgui/rclhelp.cpp @@ -22,7 +22,7 @@ #include "recoll.h" #include "rclhelp.h" -#include "debuglog.h" +#include "log.h" map HelpClient::helpmap; @@ -42,7 +42,7 @@ bool HelpClient::eventFilter(QObject *obj, QEvent *event) static time_t last_start; if (event->type() == QEvent::KeyPress || event->type() == QEvent::ShortcutOverride) { - // LOGDEB(("HelpClient::eventFilter: %d\n", (int)event->type())); + // LOGDEB("HelpClient::eventFilter: " << ((int)event->type()) << "\n" ); QKeyEvent *ke = static_cast(event); if (ke->key() == Qt::Key_F1 || ke->key() == Qt::Key_Help) { if (obj->isWidgetType()) { @@ -57,11 +57,10 @@ bool HelpClient::eventFilter(QObject *obj, QEvent *event) if (time(0) - last_start > 5) { last_start = time(0); if (it != helpmap.end()) { - LOGDEB(("HelpClient::eventFilter: %s->%s\n", - it->first.c_str(), it->second.c_str())); + LOGDEB("HelpClient::eventFilter: " << (it->first) << "->" << (it->second) << "\n" ); startManual(it->second); } else { - LOGDEB(("HelpClient::eventFilter: no help section\n")); + LOGDEB("HelpClient::eventFilter: no help section\n" ); startManual(""); } } @@ -71,3 +70,4 @@ bool HelpClient::eventFilter(QObject *obj, QEvent *event) } return false; } + diff --git a/src/qtgui/rclm_idx.cpp b/src/qtgui/rclm_idx.cpp index 18d28a71..c4b15173 100644 --- a/src/qtgui/rclm_idx.cpp +++ b/src/qtgui/rclm_idx.cpp @@ -16,18 +16,22 @@ */ #include "autoconfig.h" +#include +#include "safeunistd.h" + #include #include #include "execmd.h" -#include "debuglog.h" +#include "log.h" #include "transcode.h" #include "indexer.h" #include "rclmain_w.h" +#include "specialindex.h" using namespace std; -void RclMain::idxStatus() +void RclMain::updateIdxStatus() { ConfSimple cs(theconfig->getIdxStatusFile().c_str(), 1); QString msg = tr("Indexing in progress: "); @@ -36,12 +40,11 @@ void RclMain::idxStatus() cs.get("phase", val); status.phase = DbIxStatus::Phase(atoi(val.c_str())); cs.get("fn", status.fn); - cs.get("docsdone", val); - status.docsdone = atoi(val.c_str()); - cs.get("filesdone", val); - status.filesdone = atoi(val.c_str()); - cs.get("dbtotdocs", val); - status.dbtotdocs = atoi(val.c_str()); + cs.get("docsdone", &status.docsdone); + cs.get("filesdone", &status.filesdone); + cs.get("fileerrors", &status.fileerrors); + cs.get("dbtotdocs", &status.dbtotdocs); + cs.get("totfiles", &status.totfiles); QString phs; switch (status.phase) { @@ -57,11 +60,14 @@ void RclMain::idxStatus() msg += phs + " "; if (status.phase == DbIxStatus::DBIXS_FILES) { char cnts[100]; - if (status.dbtotdocs > 0) - sprintf(cnts,"(%d/%d/%d) ", status.docsdone, status.filesdone, - status.dbtotdocs); - else - sprintf(cnts, "(%d/%d) ", status.docsdone, status.filesdone); + if (status.dbtotdocs > 0) { + sprintf(cnts, "(%d docs/%d files/%d errors/%d tot files) ", + status.docsdone, status.filesdone, status.fileerrors, + status.totfiles); + } else { + sprintf(cnts, "(%d docs/%d files/%d errors) ", + status.docsdone, status.filesdone, status.fileerrors); + } msg += QString::fromUtf8(cnts) + " "; } string mf;int ecnt = 0; @@ -77,7 +83,7 @@ void RclMain::idxStatus() // indexing, a possible need to exit, and cleanup exited viewers void RclMain::periodic100() { - LOGDEB2(("Periodic100\n")); + LOGDEB2("Periodic100\n" ); if (m_idxproc) { // An indexing process was launched. If its' done, see status. int status; @@ -104,7 +110,7 @@ void RclMain::periodic100() // update/show status even if the status file did not // change (else the status line goes blank during // lengthy operations). - idxStatus(); + updateIdxStatus(); } } // Update the "start/stop indexing" menu entry, can't be done from @@ -114,25 +120,34 @@ void RclMain::periodic100() m_indexerState = IXST_RUNNINGMINE; fileToggleIndexingAction->setText(tr("Stop &Indexing")); fileToggleIndexingAction->setEnabled(true); - fileRetryFailedAction->setEnabled(false); fileRebuildIndexAction->setEnabled(false); + actionSpecial_Indexing->setEnabled(false); periodictimer->setInterval(200); } else { Pidfile pidfile(theconfig->getPidfile()); - if (pidfile.open() == 0) { + pid_t pid = pidfile.open(); + if (pid == getpid()) { + // Locked by me + m_indexerState = IXST_NOTRUNNING; + fileToggleIndexingAction->setText(tr("Index locked")); + fileToggleIndexingAction->setEnabled(false); + fileRebuildIndexAction->setEnabled(false); + actionSpecial_Indexing->setEnabled(false); + periodictimer->setInterval(1000); + } else if (pid == 0) { m_indexerState = IXST_NOTRUNNING; fileToggleIndexingAction->setText(tr("Update &Index")); - fileRetryFailedAction->setEnabled(true); fileToggleIndexingAction->setEnabled(true); fileRebuildIndexAction->setEnabled(true); + actionSpecial_Indexing->setEnabled(true); periodictimer->setInterval(1000); } else { // Real time or externally started batch indexer running m_indexerState = IXST_RUNNINGNOTMINE; fileToggleIndexingAction->setText(tr("Stop &Indexing")); fileToggleIndexingAction->setEnabled(true); - fileRetryFailedAction->setEnabled(false); fileRebuildIndexAction->setEnabled(false); + actionSpecial_Indexing->setEnabled(false); periodictimer->setInterval(200); } } @@ -162,6 +177,22 @@ void RclMain::periodic100() fileExit(); } +bool RclMain::checkIdxPaths() +{ + string badpaths; + vector args {"recollindex", "-c", theconfig->getConfDir(), "-E"}; + ExecCmd::backtick(args, badpaths); + if (!badpaths.empty()) { + int rep = QMessageBox::warning( + 0, tr("Bad paths"), tr("Bad paths in configuration file:\n") + + QString::fromLocal8Bit(badpaths.c_str()), + QMessageBox::Ok, QMessageBox::Cancel, QMessageBox::NoButton); + if (rep == QMessageBox::Cancel) + return false; + } + return true; +} + // This gets called when the "update index" action is activated. It executes // the requested action, and disables the menu entry. This will be // re-enabled by the indexing status check @@ -172,31 +203,35 @@ void RclMain::toggleIndexing() if (m_idxproc) { // Indexing was in progress, request stop. Let the periodic // routine check for the results. - int pid = m_idxproc->getChildPid(); - if (pid > 0) { - kill(pid, SIGTERM); + if (m_idxproc->requestChildExit()) { m_idxkilled = true; } } break; case IXST_RUNNINGNOTMINE: { +#ifdef _WIN32 + QMessageBox::warning(0, tr("Warning"), + tr("The current indexing process " + "was not started from this " + "interface, can't kill it"), + QMessageBox::Ok, QMessageBox::NoButton); +#else int rep = - QMessageBox::information(0, tr("Warning"), - tr("The current indexing process " - "was not started from this " - "interface. Click Ok to kill it " - "anyway, or Cancel to leave it alone"), - QMessageBox::Ok, - QMessageBox::Cancel, - QMessageBox::NoButton); + QMessageBox::information( + 0, tr("Warning"), + tr("The current indexing process was not started from this " + "interface. Click Ok to kill it " + "anyway, or Cancel to leave it alone"), + QMessageBox::Ok, QMessageBox::Cancel, QMessageBox::NoButton); if (rep == QMessageBox::Ok) { Pidfile pidfile(theconfig->getPidfile()); pid_t pid = pidfile.open(); if (pid > 0) kill(pid, SIGTERM); } - } +#endif + } break; case IXST_NOTRUNNING: { @@ -206,29 +241,10 @@ void RclMain::toggleIndexing() string mhd; m_firstIndexing = !theconfig->getMissingHelperDesc(mhd); - vector args; - - string badpaths; - args.push_back("recollindex"); - args.push_back("-E"); - ExecCmd::backtick(args, badpaths); - if (!badpaths.empty()) { - int rep = - QMessageBox::warning(0, tr("Bad paths"), - tr("Bad paths in configuration file:\n") + - QString::fromLocal8Bit(badpaths.c_str()), - QMessageBox::Ok, - QMessageBox::Cancel, - QMessageBox::NoButton); - if (rep == QMessageBox::Cancel) - return; + if (!checkIdxPaths()) { + return; } - - args.clear(); - args.push_back("-c"); - args.push_back(theconfig->getConfDir()); - if (fileRetryFailedAction->isChecked()) - args.push_back("-k"); + vector args{"-c", theconfig->getConfDir()}; m_idxproc = new ExecCmd; m_idxproc->startExec("recollindex", args, false, false); } @@ -247,6 +263,10 @@ void RclMain::rebuildIndex() return; //?? Should not have been called case IXST_NOTRUNNING: { + if (m_idxproc) { + LOGERR("RclMain::rebuildIndex: current indexer exec not null\n" ); + return; + } int rep = QMessageBox::warning(0, tr("Erasing index"), tr("Reset the index and start " @@ -255,15 +275,25 @@ void RclMain::rebuildIndex() QMessageBox::Cancel, QMessageBox::NoButton); if (rep == QMessageBox::Ok) { +#ifdef _WIN32 + // Under windows, it's necessary to close the db here, + // else Xapian won't be able to do what it wants with the + // (open) files. Of course if there are several GUI + // instances, this won't work... + if (rcldb) + rcldb->close(); +#endif // _WIN32 // Could also mean that no helpers are missing, but then we // won't try to show a message anyway (which is what // firstIndexing is used for) string mhd; m_firstIndexing = !theconfig->getMissingHelperDesc(mhd); - vector args; - args.push_back("-c"); - args.push_back(theconfig->getConfDir()); - args.push_back("-z"); + + if (!checkIdxPaths()) { + return; + } + + vector args{"-c", theconfig->getConfDir(), "-z"}; m_idxproc = new ExecCmd; m_idxproc->startExec("recollindex", args, false, false); } @@ -272,29 +302,139 @@ void RclMain::rebuildIndex() } } +void SpecIdxW::onBrowsePB_clicked() +{ + QString dir = myGetFileName(true, tr("Top indexed entity"), true); + targLE->setText(dir); +} + +bool SpecIdxW::noRetryFailed() +{ + return noRetryFailedCB->isChecked(); +} + +bool SpecIdxW::eraseFirst() +{ + return eraseBeforeCB->isChecked(); +} + +std::vector SpecIdxW::selpatterns() +{ + vector pats; + string text = qs2utf8s(selPatsLE->text()); + if (!text.empty()) { + stringToStrings(text, pats); + } + return pats; +} + +std::string SpecIdxW::toptarg() +{ + return qs2utf8s(targLE->text()); +} + +void SpecIdxW::onTargLE_textChanged(const QString& text) +{ + if (text.isEmpty()) + selPatsLE->setEnabled(false); + else + selPatsLE->setEnabled(true); +} + +static string execToString(const string& cmd, const vector& args) +{ + string command = cmd + " "; + for (vector::const_iterator it = args.begin(); + it != args.end(); it++) { + command += "{" + *it + "} "; + } + return command; +} + +void RclMain::specialIndex() +{ + LOGDEB("RclMain::specialIndex\n" ); + switch (m_indexerState) { + case IXST_UNKNOWN: + case IXST_RUNNINGMINE: + case IXST_RUNNINGNOTMINE: + return; //?? Should not have been called + case IXST_NOTRUNNING: + default: + break; + } + if (m_idxproc) { + LOGERR("RclMain::rebuildIndex: current indexer exec not null\n" ); + return; + } + if (!specidx) // ?? + return; + + vector args{"-c", theconfig->getConfDir()}; + + string top = specidx->toptarg(); + if (!top.empty()) { + args.push_back("-r"); + } + + if (specidx->eraseFirst()) { + if (top.empty()) { + args.push_back("-Z"); + } else { + args.push_back("-e"); + } + } + + // The default for retrying differ depending if -r is set + if (top.empty()) { + if (!specidx->noRetryFailed()) { + args.push_back("-k"); + } + } else { + if (specidx->noRetryFailed()) { + args.push_back("-K"); + } + } + + vector selpats = specidx->selpatterns(); + if (!selpats.empty() && top.empty()) { + QMessageBox::warning(0, tr("Selection patterns need topdir"), + tr("Selection patterns can only be used with a " + "start directory"), + QMessageBox::Ok, QMessageBox::NoButton); + return; + } + + for (vector::const_iterator it = selpats.begin(); + it != selpats.end(); it++) { + args.push_back("-p"); + args.push_back(*it); + } + if (!top.empty()) { + args.push_back(top); + } + m_idxproc = new ExecCmd; + LOGINFO("specialIndex: exec: " << execToString("recollindex", args) <startExec("recollindex", args, false, false); +} + void RclMain::updateIdxForDocs(vector& docs) { if (m_idxproc) { QMessageBox::warning(0, tr("Warning"), tr("Can't update index: indexer running"), - QMessageBox::Ok, - QMessageBox::NoButton); + QMessageBox::Ok, QMessageBox::NoButton); return; } vector paths; if (ConfIndexer::docsToPaths(docs, paths)) { - vector args; - args.push_back("-c"); - args.push_back(theconfig->getConfDir()); - args.push_back("-e"); - args.push_back("-i"); + vector args{"-c", theconfig->getConfDir(), "-e", "-i"}; args.insert(args.end(), paths.begin(), paths.end()); m_idxproc = new ExecCmd; m_idxproc->startExec("recollindex", args, false, false); fileToggleIndexingAction->setText(tr("Stop &Indexing")); } fileToggleIndexingAction->setEnabled(false); - fileRetryFailedAction->setEnabled(false); + actionSpecial_Indexing->setEnabled(false); } - diff --git a/src/qtgui/rclm_preview.cpp b/src/qtgui/rclm_preview.cpp index 34ff56ad..7f56f6f3 100644 --- a/src/qtgui/rclm_preview.cpp +++ b/src/qtgui/rclm_preview.cpp @@ -19,7 +19,7 @@ #include #include -#include "debuglog.h" +#include "log.h" #include "internfile.h" #include "rclzg.h" #include "rclmain_w.h" @@ -31,12 +31,12 @@ static const QKeySequence quitKeySeq("Ctrl+q"); // where the current one is closed void RclMain::previewClosed(Preview *w) { - LOGDEB(("RclMain::previewClosed(%p)\n", w)); + LOGDEB("RclMain::previewClosed(" << (w) << ")\n" ); if (w == curPreview) { - LOGDEB(("Active preview closed\n")); + LOGDEB("Active preview closed\n" ); curPreview = 0; } else { - LOGDEB(("Old preview closed\n")); + LOGDEB("Old preview closed\n" ); } delete w; } @@ -53,8 +53,10 @@ void RclMain::previewClosed(Preview *w) // config) bool RclMain::containerUpToDate(Rcl::Doc& doc) { + static bool ignore_out_of_date_preview = false; + // If ipath is empty, we decide we don't care. Also, we need an index, - if (doc.ipath.empty() || rcldb == 0) + if (ignore_out_of_date_preview || doc.ipath.empty() || rcldb == 0) return true; string udi; @@ -93,11 +95,12 @@ bool RclMain::containerUpToDate(Rcl::Doc& doc) "improve when it's done. "); } else if (ixnotact) { // Not main index - msg += tr("The document belongs to an external index" + msg += tr("The document belongs to an external index " "which I can't update. "); } - msg += tr("Click Cancel to return to the list. " - "Click Ignore to show the preview anyway. "); + msg += tr("Click Cancel to return to the list.
          " + "Click Ignore to show the preview anyway (and remember for " + "this session)."); QMessageBox::StandardButtons bts = QMessageBox::Ignore | QMessageBox::Cancel; @@ -111,13 +114,16 @@ bool RclMain::containerUpToDate(Rcl::Doc& doc) QMessageBox::Cancel : QMessageBox::NoButton); if (m_indexerState == IXST_NOTRUNNING && rep == QMessageBox::Ok) { - LOGDEB(("Requesting index update for %s\n", doc.url.c_str())); + LOGDEB("Requesting index update for " << (doc.url) << "\n" ); vector docs(1, doc); updateIdxForDocs(docs); } - if (rep != QMessageBox::Ignore) + if (rep == QMessageBox::Ignore) { + ignore_out_of_date_preview = true; + return true; + } else { return false; - return true; + } } /** @@ -129,7 +135,7 @@ bool RclMain::containerUpToDate(Rcl::Doc& doc) */ void RclMain::startPreview(int docnum, Rcl::Doc doc, int mod) { - LOGDEB(("startPreview(%d, doc, %d)\n", docnum, mod)); + LOGDEB("startPreview(" << (docnum) << ", doc, " << (mod) << ")\n" ); if (!containerUpToDate(doc)) return; @@ -215,8 +221,7 @@ void RclMain::previewPrevInTab(Preview * w, int sid, int docnum) // Combined next/prev from result list in current preview tab void RclMain::previewPrevOrNextInTab(Preview * w, int sid, int docnum, bool nxt) { - LOGDEB(("RclMain::previewNextInTab sid %d docnum %d, listId %d\n", - sid, docnum, reslist->listId())); + LOGDEB("RclMain::previewNextInTab sid " << (sid) << " docnum " << (docnum) << ", listId " << (reslist->listId()) << "\n" ); if (w == 0) // ?? return; @@ -231,7 +236,7 @@ void RclMain::previewPrevOrNextInTab(Preview * w, int sid, int docnum, bool nxt) docnum++; else docnum--; - if (docnum < 0 || m_source.isNull() || docnum >= m_source->getResCnt()) { + if (docnum < 0 || !m_source || docnum >= m_source->getResCnt()) { QApplication::beep(); return; } @@ -250,10 +255,10 @@ void RclMain::previewPrevOrNextInTab(Preview * w, int sid, int docnum, bool nxt) // displayed result list, tell reslist (to color the paragraph) void RclMain::previewExposed(Preview *, int sid, int docnum) { - LOGDEB2(("RclMain::previewExposed: sid %d docnum %d, m_sid %d\n", - sid, docnum, reslist->listId())); + LOGDEB2("RclMain::previewExposed: sid " << (sid) << " docnum " << (docnum) << ", m_sid " << (reslist->listId()) << "\n" ); if (sid != reslist->listId()) { return; } reslist->previewExposed(docnum); } + diff --git a/src/qtgui/rclm_saveload.cpp b/src/qtgui/rclm_saveload.cpp index cc17c396..90702ec0 100644 --- a/src/qtgui/rclm_saveload.cpp +++ b/src/qtgui/rclm_saveload.cpp @@ -18,16 +18,14 @@ /** Saving and restoring named queries */ -#include -#include -#include +#include "safesysstat.h" #include #include #include #include "rclmain_w.h" -#include "debuglog.h" +#include "log.h" #include "readfile.h" #include "xmltosd.h" #include "searchdata.h" @@ -43,7 +41,7 @@ static QString prevDir() settings.value("/Recoll/prefs/lastQuerySaveDir").toString(); string defpath = path_cat(theconfig->getConfDir(), "saved_queries"); if (prevdir.isEmpty()) { - if (access(defpath.c_str(), 0) != 0) { + if (!path_exists(defpath)) { mkdir(defpath.c_str(), 0700); } return QString::fromLocal8Bit(defpath.c_str()); @@ -59,7 +57,7 @@ void RclMain::saveLastQuery() xml = sSearch->asXML(); } else { if (g_advshistory) { - RefCntr sd; + std::shared_ptr sd; sd = g_advshistory->getnewest(); if (sd) { xml = sd->asXML(); @@ -90,7 +88,7 @@ void RclMain::saveLastQuery() string tofile((const char *)s.toLocal8Bit()); - LOGDEB(("RclMain::saveLastQuery: XML: [%s]\n", xml.c_str())); + LOGDEB("RclMain::saveLastQuery: XML: [" << (xml) << "]\n" ); string reason; if (!stringtofile(xml, tofile.c_str(), reason)) { QMessageBox::warning(this, tr("Write failed"), @@ -118,7 +116,7 @@ void RclMain::loadSavedQuery() } // Try to parse as SearchData - RefCntr sd = xmlToSearchData(xml); + std::shared_ptr sd = xmlToSearchData(xml); if (sd) { showAdvSearchDialog(); asearchform->fromSearch(sd); @@ -134,3 +132,4 @@ void RclMain::loadSavedQuery() QMessageBox::warning(this, tr("Load error"), tr("Could not load saved query")); } + diff --git a/src/qtgui/rclm_view.cpp b/src/qtgui/rclm_view.cpp index 8881f94d..cc2bab52 100644 --- a/src/qtgui/rclm_view.cpp +++ b/src/qtgui/rclm_view.cpp @@ -16,11 +16,15 @@ */ #include "autoconfig.h" +#include "safeunistd.h" + #include #include -#include "debuglog.h" +#include "qxtconfirmationmessage.h" + +#include "log.h" #include "fileudi.h" #include "execmd.h" #include "transcode.h" @@ -29,6 +33,7 @@ #include "internfile.h" #include "rclmain_w.h" #include "rclzg.h" +#include "pathut.h" using namespace std; @@ -43,9 +48,7 @@ void RclMain::viewUrl() return; QUrl qurl(m_urltoview); - LOGDEB(("RclMain::viewUrl: Path [%s] fragment [%s]\n", - (const char *)qurl.path().toLocal8Bit(), - (const char *)qurl.fragment().toLocal8Bit())); + LOGDEB("RclMain::viewUrl: Path [" << ((const char *)qurl.path().toLocal8Bit()) << "] fragment [" << ((const char *)qurl.fragment().toLocal8Bit()) << "]\n" ); /* In theory, the url might not be for a file managed by the fs indexer so that the make_udi() call here would be @@ -64,9 +67,9 @@ void RclMain::viewUrl() // StartNativeViewer needs a db source to call getEnclosing() on. Rcl::Query *query = new Rcl::Query(rcldb); DocSequenceDb *src = - new DocSequenceDb(RefCntr(query), "", - RefCntr(new Rcl::SearchData)); - m_source = RefCntr(src); + new DocSequenceDb(std::shared_ptr(query), "", + std::shared_ptr(new Rcl::SearchData)); + m_source = std::shared_ptr(src); // Start a native viewer if the mimetype has one defined, else a @@ -120,7 +123,7 @@ static bool lookForHtmlBrowser(string &exefile) void RclMain::openWith(Rcl::Doc doc, string cmdspec) { - LOGDEB(("RclMain::openWith: %s\n", cmdspec.c_str())); + LOGDEB("RclMain::openWith: " << (cmdspec) << "\n" ); // Split the command line vector lcmd; @@ -143,9 +146,12 @@ void RclMain::openWith(Rcl::Doc doc, string cmdspec) // Try to keep the letters used more or less consistent with the reslist // paragraph format. map subs; +#ifdef _WIN32 + path_backslashize(fn); +#endif subs["F"] = fn; subs["f"] = fn; - subs["U"] = url; + subs["U"] = url_encode(url); subs["u"] = url; execViewer(subs, false, execname, lcmd, cmdspec, doc); @@ -155,11 +161,10 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term) { string 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() - )); + LOGDEB("RclMain::startNativeViewer: mtype [" << doc.mimetype << + "] apptag [" << apptag << "] page " << pagenum << " term [" << + qs2utf8s(term) << "] url [" << doc.url << "] ipath [" << + doc.ipath << "]\n"); // Look for appropriate viewer string cmdplusattr = theconfig->getMimeViewerDef(doc.mimetype, apptag, @@ -176,9 +181,15 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term) string cmd; theconfig->valueSplitAttributes(cmdplusattr, cmd, viewerattrs); bool ignoreipath = false; + int execwflags = 0; if (viewerattrs.get("ignoreipath", cmdplusattr)) ignoreipath = stringToBool(cmdplusattr); - + if (viewerattrs.get("maximize", cmdplusattr)) { + if (stringToBool(cmdplusattr)) { + execwflags |= ExecCmd::EXF_MAXIMIZED; + } + } + // Split the command line vector lcmd; if (!stringToStrings(cmd, lcmd)) { @@ -269,7 +280,7 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term) // 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)) { + if (!m_source || !m_source->getEnclosing(doc, pdoc)) { QMessageBox::warning(0, "Recoll", tr("Cannot find parent document")); return; @@ -286,14 +297,14 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term) } } + bool enterHistory = false; bool istempfile = false; - - LOGDEB(("RclMain::startNV: groksipath %d wantsf %d wantsparentf %d\n", - groksipath, wantsfile, wantsparentfile)); + + LOGDEB("RclMain::startNV: groksipath " << (groksipath) << " wantsf " << (wantsfile) << " wantsparentf " << (wantsparentfile) << "\n" ); // 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: - theconfig->setKeyDir(path_getfather(fn)); + theconfig->setKeyDir(fn.empty() ? "" : path_getfather(fn)); if (!doc.isFsFile() || ((wantsfile || wantsparentfile) && fn.empty()) || (!groksipath && !doc.ipath.empty()) ) { @@ -305,10 +316,11 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term) "temporary file")); return; } - istempfile = true; + enterHistory = true; + istempfile = true; rememberTempFile(temp); fn = temp->filename(); - url = string("file://") + fn; + url = path_pathtofileurl(fn); } // If using an actual file, check that it exists, and if it is @@ -330,19 +342,32 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term) return; } } - if (!temp.isNull()) { + if (temp) { + istempfile = true; rememberTempFile(temp); fn = temp->filename(); - url = string("file://") + fn; + url = path_pathtofileurl(fn); } } + if (istempfile) { + QxtConfirmationMessage confirm( + QMessageBox::Warning, + "Recoll", + tr("Opening a temporary copy. Edits will be lost if you don't save" + "
          them to a permanent location."), + tr("Do not show this warning next time (use GUI preferences to restore).")); + confirm.setSettingsPath("Recoll/prefs"); + confirm.setOverrideSettingsKey("showTempFileWarning"); + confirm.exec(); + } + // If we are not called with a page number (which would happen for a call // from the snippets window), see if we can compute a page number anyway. if (pagenum == -1) { pagenum = 1; string lterm; - if (m_source.isNotNull()) + if (m_source) pagenum = m_source->getFirstMatchPage(doc, lterm); if (pagenum == -1) pagenum = 1; @@ -364,40 +389,45 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term) // paragraph format. map subs; subs["D"] = efftime; +#ifdef _WIN32 + path_backslashize(fn); +#endif subs["f"] = fn; subs["F"] = fn; subs["i"] = FileInterner::getLastIpathElt(doc.ipath); subs["M"] = doc.mimetype; subs["p"] = cpagenum; subs["s"] = (const char*)term.toLocal8Bit(); - subs["U"] = url; + subs["U"] = url_encode(url); subs["u"] = url; // Let %(xx) access all metadata. for (map::const_iterator it = doc.meta.begin(); it != doc.meta.end(); it++) { subs[it->first] = it->second; } - execViewer(subs, istempfile, execpath, lcmd, cmd, doc); + execViewer(subs, enterHistory, execpath, lcmd, cmd, doc, execwflags); } -void RclMain::execViewer(const map& subs, bool istempfile, +void RclMain::execViewer(const map& subs, bool enterHistory, const string& execpath, const vector& _lcmd, const string& cmd, - Rcl::Doc doc) + Rcl::Doc doc, int flags) { string ncmd; vector lcmd; for (vector::const_iterator it = _lcmd.begin(); it != _lcmd.end(); it++) { pcSubst(*it, ncmd, subs); - LOGDEB(("%s->%s\n", it->c_str(), ncmd.c_str())); + LOGDEB("" << *it << "->" << (ncmd) << "\n" ); lcmd.push_back(ncmd); } // Also substitute inside the unsplitted command line and display // in status bar pcSubst(cmd, ncmd, subs); +#ifndef _WIN32 ncmd += " &"; +#endif QStatusBar *stb = statusBar(); if (stb) { string fcharset = theconfig->getDefCharset(true); @@ -408,14 +438,14 @@ void RclMain::execViewer(const map& subs, bool istempfile, stb->showMessage(msg, 10000); } - if (!istempfile) + if (!enterHistory) historyEnterDoc(g_dynconf, doc.meta[Rcl::Doc::keyudi]); // Do the zeitgeist thing zg_send_event(ZGSEND_OPEN, doc); // We keep pushing back and never deleting. This can't be good... - ExecCmd *ecmd = new ExecCmd; + ExecCmd *ecmd = new ExecCmd(ExecCmd::EXF_SHOWWINDOW | flags); m_viewers.push_back(ecmd); ecmd->startExec(execpath, lcmd, false, false); } @@ -427,17 +457,36 @@ void RclMain::startManual() void RclMain::startManual(const string& index) { + string docdir = path_cat(theconfig->getDatadir(), "doc"); + + // The single page user manual is nicer if we have an index. Else + // the webhelp one is nicer if it is present + string usermanual = path_cat(docdir, "usermanual.html"); + string webhelp = path_cat(docdir, "webhelp"); + webhelp = path_cat(webhelp, "index.html"); + bool has_wh = path_exists(webhelp); + + LOGDEB("RclMain::startManual: help index is " << (index.empty()?"(null)":index) << "\n" ); + bool indexempty = index.empty(); + +#ifdef _WIN32 + // On Windows I could not find any way to pass the fragment through + // rclstartw (tried to set text/html as exception with rclstartw %u). + // So always start the webhelp + indexempty = true; +#endif + + if (!indexempty) { + usermanual += "#"; + usermanual += index; + } Rcl::Doc doc; - doc.url = "file://"; - doc.url = path_cat(doc.url, theconfig->getDatadir()); - doc.url = path_cat(doc.url, "doc"); - doc.url = path_cat(doc.url, "usermanual.html"); - LOGDEB(("RclMain::startManual: help index is %s\n", - index.empty()?"(null)":index.c_str())); - if (!index.empty()) { - doc.url += "#"; - doc.url += index; + if (has_wh && indexempty) { + doc.url = path_pathtofileurl(webhelp); + } else { + doc.url = path_pathtofileurl(usermanual); } doc.mimetype = "text/html"; startNativeViewer(doc); } + diff --git a/src/qtgui/rclm_wins.cpp b/src/qtgui/rclm_wins.cpp index d9cb87d1..54ae49e2 100644 --- a/src/qtgui/rclm_wins.cpp +++ b/src/qtgui/rclm_wins.cpp @@ -19,7 +19,7 @@ #include #include -#include "debuglog.h" +#include "log.h" #include "internfile.h" #include "listdialog.h" #include "confgui/confguiindex.h" @@ -28,7 +28,9 @@ #include "rtitool.h" #include "snippets_w.h" #include "fragbuts.h" +#include "specialindex.h" #include "rclmain_w.h" +#include "webcache.h" using namespace std; @@ -47,8 +49,8 @@ void RclMain::showAdvSearchDialog() this, SLOT (fileExit())); connect(asearchform, - SIGNAL(startSearch(RefCntr, bool)), - this, SLOT(startSearch(RefCntr, bool))); + SIGNAL(startSearch(std::shared_ptr, bool)), + this, SLOT(startSearch(std::shared_ptr, bool))); asearchform->show(); } else { // Close and reopen, in hope that makes us visible... @@ -73,6 +75,58 @@ void RclMain::showSpellDialog() } } +void RclMain::showWebcacheDialog() +{ + switch (indexerState()) { + case RclMain::IXST_UNKNOWN: + QMessageBox::warning(0, "Recoll", tr("Unknown indexer state. " + "Can't access webcache file.")); + return; + case RclMain::IXST_RUNNINGMINE: + case RclMain::IXST_RUNNINGNOTMINE: + QMessageBox::warning(0, "Recoll", tr("Indexer is running. " + "Can't access webcache file.")); + return; + case RclMain::IXST_NOTRUNNING: + break; + } + + if (!m_pidfile) { + m_pidfile = new Pidfile(theconfig->getPidfile()); + if (m_pidfile->open() != 0) { + deleteZ(m_pidfile); + return; + } + if (m_pidfile->write_pid() != 0) { + deleteZ(m_pidfile); + return; + } + } + + if (webcache == 0) { + webcache = new WebcacheEdit(this); + webcache->setAttribute(Qt::WA_DeleteOnClose); + connect(new QShortcut(quitKeySeq, webcache), SIGNAL (activated()), + this, SLOT (fileExit())); + connect(webcache, SIGNAL(destroyed(QObject*)), + this, SLOT(onWebcacheDestroyed(QObject*)) ); + webcache->show(); + } +} +void RclMain::onWebcacheDestroyed(QObject *) +{ + deleteZ(m_pidfile); + webcache = 0; +} + +void RclMain::showIndexStatistics() +{ + showSpellDialog(); + if (spellform == 0) + return; + spellform->setMode(SpellW::TYPECMB_STATS); +} + void RclMain::showFragButs() { if (fragbuts && fragbuts->isStale(0)) { @@ -85,8 +139,7 @@ void RclMain::showFragButs() connect(fragbuts, SIGNAL(fragmentsChanged()), this, SLOT(onFragmentsChanged())); } else { - delete fragbuts; - fragbuts = 0; + deleteZ(fragbuts); } } else { // Close and reopen, in hope that makes us visible... @@ -95,6 +148,19 @@ void RclMain::showFragButs() } } +void RclMain::showSpecIdx() +{ + if (specidx == 0) { + specidx = new SpecIdxW(0); + connect(specidx, SIGNAL(accepted()), this, SLOT(specialIndex())); + specidx->show(); + } else { + // Close and reopen, in hope that makes us visible... + specidx->close(); + specidx->show(); + } +} + void RclMain::showIndexConfig() { showIndexConfig(false); @@ -105,7 +171,7 @@ void RclMain::execIndexConfig() } void RclMain::showIndexConfig(bool modal) { - LOGDEB(("showIndexConfig()\n")); + LOGDEB("showIndexConfig()\n" ); if (indexConfig == 0) { indexConfig = new ConfIndexW(0, theconfig); connect(new QShortcut(quitKeySeq, indexConfig), SIGNAL (activated()), @@ -133,7 +199,13 @@ void RclMain::execIndexSched() } void RclMain::showIndexSched(bool modal) { - LOGDEB(("showIndexSched()\n")); +#ifdef _WIN32 + QMessageBox::information(this, tr("Index scheduling"), + tr("Sorry, not available under Windows for now, use the File menu entries " + "to update the index")); + return; +#endif + LOGDEB("showIndexSched()\n" ); if (indexSched == 0) { indexSched = new IdxSchedW(this); connect(new QShortcut(quitKeySeq, indexSched), SIGNAL (activated()), @@ -174,7 +246,7 @@ void RclMain::execCronTool() } void RclMain::showCronTool(bool modal) { - LOGDEB(("showCronTool()\n")); + LOGDEB("showCronTool()\n" ); if (cronTool == 0) { cronTool = new CronToolW(0); connect(new QShortcut(quitKeySeq, cronTool), SIGNAL (activated()), @@ -201,7 +273,7 @@ void RclMain::execRTITool() } void RclMain::showRTITool(bool modal) { - LOGDEB(("showRTITool()\n")); + LOGDEB("showRTITool()\n" ); if (rtiTool == 0) { rtiTool = new RTIToolW(0); connect(new QShortcut(quitKeySeq, rtiTool), SIGNAL (activated()), @@ -230,23 +302,16 @@ void RclMain::showUIPrefs() } else { // Close and reopen, in hope that makes us visible... uiprefs->close(); + rwSettings(false); + uiprefs->setFromPrefs(); } uiprefs->show(); } void RclMain::showExtIdxDialog() { - if (uiprefs == 0) { - uiprefs = new UIPrefsDialog(this); - connect(new QShortcut(quitKeySeq, uiprefs), SIGNAL (activated()), - this, SLOT (fileExit())); - connect(uiprefs, SIGNAL(uiprefsDone()), this, SLOT(setUIPrefs())); - } else { - // Close and reopen, in hope that makes us visible... - uiprefs->close(); - } + showUIPrefs(); uiprefs->tabWidget->setCurrentIndex(3); - uiprefs->show(); } void RclMain::showAboutDialog() @@ -388,3 +453,4 @@ void RclMain::showSnippets(Rcl::Doc doc) sp, SLOT (close())); sp->show(); } + diff --git a/src/qtgui/rclmain.ui b/src/qtgui/rclmain.ui index bb690cbc..71558876 100644 --- a/src/qtgui/rclmain.ui +++ b/src/qtgui/rclmain.ui @@ -73,8 +73,8 @@ &File - + @@ -82,12 +82,17 @@ + + + + + &View + + - - @@ -98,6 +103,7 @@ + @@ -109,6 +115,8 @@ + + @@ -136,6 +144,7 @@ + @@ -187,7 +196,7 @@ - &Show missing helpers + Missing &helpers showMissingHelpers_Action @@ -195,7 +204,7 @@ - &Show indexed types + Indexed &MIME types showActiveTypes_Action @@ -346,7 +355,7 @@ - &Indexing schedule + Indexing &schedule indexScheduleAction @@ -371,6 +380,20 @@ extIdxAction + + + true + + + Enable synonyms + + + Enable synonyms + + + enbSynAction + + &Full Screen @@ -497,6 +520,24 @@ Load saved query + + + Special Indexing + + + Indexing with special options + + + + + Index &statistics + + + + + Webcache Editor + + diff --git a/src/qtgui/rclmain_w.cpp b/src/qtgui/rclmain_w.cpp index efcc0663..41438f65 100644 --- a/src/qtgui/rclmain_w.cpp +++ b/src/qtgui/rclmain_w.cpp @@ -16,9 +16,9 @@ */ #include "autoconfig.h" -#include - #include +#include +#include #include #include @@ -43,7 +43,7 @@ #include #include "recoll.h" -#include "debuglog.h" +#include "log.h" #include "mimehandler.h" #include "pathut.h" #include "smallut.h" @@ -52,7 +52,6 @@ #include "uiprefs_w.h" #include "guiutils.h" #include "reslist.h" -#include "refcntr.h" #include "ssearch_w.h" #include "internfile.h" #include "docseqdb.h" @@ -137,20 +136,28 @@ void RclMain::init() // idxstatus file. Make sure it exists before trying to watch it // (case where we're started on an older index, or if the status - // file was deleted since indexing - ::close(::open(theconfig->getIdxStatusFile().c_str(), O_CREAT, 0600)); - m_watcher.addPath(QString::fromLocal8Bit( - theconfig->getIdxStatusFile().c_str())); + // file was deleted since indexing) + QString idxfn = + QString::fromLocal8Bit(theconfig->getIdxStatusFile().c_str()); + QFile qf(idxfn); + qf.open(QIODevice::ReadWrite); + qf.setPermissions(QFile::ReadOwner|QFile::WriteOwner); + qf.close(); + m_watcher.addPath(idxfn); // At least some versions of qt4 don't display the status bar if // it's not created here. (void)statusBar(); (void)new HelpClient(this); - HelpClient::installMap((const char *)this->objectName().toUtf8(), "RCL.SEARCH.SIMPLE"); + HelpClient::installMap((const char *)this->objectName().toUtf8(), + "RCL.SEARCH.GUI.SIMPLE"); // Set the focus to the search terms entry: sSearch->queryText->setFocus(); + enbSynAction->setDisabled(prefs.synFile.isEmpty()); + enbSynAction->setChecked(prefs.synFileEnable); + // Stemming language menu g_stringNoStem = tr("(no stemming)"); g_stringAllStem = tr("(all languages)"); @@ -283,13 +290,14 @@ void RclMain::init() sc = new QShortcut(seql, this); connect(sc, SIGNAL (activated()), sSearch, SLOT (takeFocus())); - connect(&m_watcher, SIGNAL(fileChanged(QString)), - this, SLOT(idxStatus())); - connect(sSearch, SIGNAL(startSearch(RefCntr, bool)), - this, SLOT(startSearch(RefCntr, bool))); + connect(&m_watcher, SIGNAL(fileChanged(QString)), + this, SLOT(updateIdxStatus())); + + connect(sSearch, + SIGNAL(startSearch(std::shared_ptr, bool)), + this, SLOT(startSearch(std::shared_ptr, bool))); connect(sSearch, SIGNAL(clearSearch()), this, SLOT(resetSearch())); - connect(preferencesMenu, SIGNAL(triggered(QAction*)), this, SLOT(setStemLang(QAction*))); connect(preferencesMenu, SIGNAL(aboutToShow()), @@ -308,7 +316,8 @@ void RclMain::init() this, SLOT(saveLastQuery())); connect(actionLoad_saved_query, SIGNAL(triggered()), this, SLOT(loadSavedQuery())); - + connect(actionShow_index_statistics, SIGNAL(triggered()), + this, SLOT(showIndexStatistics())); connect(helpAbout_RecollAction, SIGNAL(triggered()), this, SLOT(showAboutDialog())); connect(showMissingHelpers_Action, SIGNAL(triggered()), @@ -323,8 +332,16 @@ void RclMain::init() this, SLOT(showAdvSearchDialog())); connect(toolsSpellAction, SIGNAL(triggered()), this, SLOT(showSpellDialog())); +#ifdef _WIN32 + actionWebcache_Editor->setEnabled(false); +#else + connect(actionWebcache_Editor, SIGNAL(triggered()), + this, SLOT(showWebcacheDialog())); +#endif connect(actionQuery_Fragments, SIGNAL(triggered()), this, SLOT(showFragButs())); + connect(actionSpecial_Indexing, SIGNAL(triggered()), + this, SLOT(showSpecIdx())); connect(indexConfigAction, SIGNAL(triggered()), this, SLOT(showIndexConfig())); connect(indexScheduleAction, SIGNAL(triggered()), @@ -333,6 +350,8 @@ void RclMain::init() this, SLOT(showUIPrefs())); connect(extIdxAction, SIGNAL(triggered()), this, SLOT(showExtIdxDialog())); + connect(enbSynAction, SIGNAL(toggled(bool)), + this, SLOT(setSynEnabled(bool))); connect(toggleFullScreenAction, SIGNAL(triggered()), this, SLOT(toggleFullScreen())); @@ -344,8 +363,8 @@ void RclMain::init() restable->setRclMain(this, true); connect(actionSaveResultsAsCSV, SIGNAL(triggered()), restable, SLOT(saveAsCSV())); - connect(this, SIGNAL(docSourceChanged(RefCntr)), - restable, SLOT(setDocSource(RefCntr))); + connect(this, SIGNAL(docSourceChanged(std::shared_ptr)), + restable, SLOT(setDocSource(std::shared_ptr))); connect(this, SIGNAL(searchReset()), restable, SLOT(resetSource())); connect(this, SIGNAL(resultsReady()), @@ -374,8 +393,8 @@ void RclMain::init() this, SLOT(showSnippets(Rcl::Doc))); reslist->setRclMain(this, true); - connect(this, SIGNAL(docSourceChanged(RefCntr)), - reslist, SLOT(setDocSource(RefCntr))); + connect(this, SIGNAL(docSourceChanged(std::shared_ptr)), + reslist, SLOT(setDocSource(std::shared_ptr))); connect(firstPageAction, SIGNAL(triggered()), reslist, SLOT(resultPageFirst())); connect(prevPageAction, SIGNAL(triggered()), @@ -440,6 +459,13 @@ void RclMain::init() periodictimer->start(1000); } +void RclMain::setSynEnabled(bool on) +{ + prefs.synFileEnable = on; + if (uiprefs) + uiprefs->synFileCB->setChecked(prefs.synFileEnable); +} + void RclMain::resultCount(int n) { actionSortByDateAsc->setEnabled(n>0); @@ -528,7 +554,7 @@ void RclMain::initDbOpen() void RclMain::setStemLang(QAction *id) { - LOGDEB(("RclMain::setStemLang(%p)\n", id)); + LOGDEB("RclMain::setStemLang(" << id << ")\n"); // Check that the menu entry is for a stemming language change // (might also be "show prefs" etc. bool isLangId = false; @@ -558,8 +584,8 @@ void RclMain::setStemLang(QAction *id) lang = id->text(); } prefs.queryStemLang = lang; - LOGDEB(("RclMain::setStemLang(%d): lang [%s]\n", - id, (const char *)prefs.queryStemLang.toUtf8())); + LOGDEB("RclMain::setStemLang(" << id << "): lang [" << + qs2utf8s(prefs.queryStemLang) << "]\n"); rwSettings(true); emit stemLangChanged(lang); } @@ -567,7 +593,7 @@ void RclMain::setStemLang(QAction *id) // Set the checked stemming language item before showing the prefs menu void RclMain::setStemLang(const QString& lang) { - LOGDEB(("RclMain::setStemLang(%s)\n", (const char *)lang.toUtf8())); + LOGDEB("RclMain::setStemLang(" << qs2utf8s(lang) << ")\n"); QAction *id; if (lang == "") { id = m_idNoStem; @@ -601,7 +627,7 @@ void RclMain::showTrayMessage(const QString& text) void RclMain::closeEvent(QCloseEvent *ev) { - LOGDEB(("RclMain::closeEvent\n")); + LOGDEB("RclMain::closeEvent\n"); if (prefs.closeToTray && m_trayicon && m_trayicon->isVisible()) { hide(); ev->ignore(); @@ -612,7 +638,7 @@ void RclMain::closeEvent(QCloseEvent *ev) void RclMain::fileExit() { - LOGDEB(("RclMain: fileExit\n")); + LOGDEB("RclMain: fileExit\n"); // Don't save geometry if we're currently fullscreened if (!isFullScreen()) { prefs.mainwidth = width(); @@ -626,27 +652,29 @@ void RclMain::fileExit() prefs.ssearchTyp = sSearch->searchTypCMB->currentIndex(); } - if (asearchform) - delete asearchform; - rwSettings(true); - // Let the exit handler clean up the rest (internal recoll stuff). - exit(0); + // We should do the right thing and let exit() call all the + // cleanup handlers. But we have few persistent resources and qt + // exit is a great source of crashes and pita. So do our own + // cleanup: + deleteAllTempFiles(); + // and scram out + _Exit(0); } // Start a db query and set the reslist docsource -void RclMain::startSearch(RefCntr sdata, bool issimple) +void RclMain::startSearch(std::shared_ptr sdata, bool issimple) { - LOGDEB(("RclMain::startSearch. Indexing %s Active %d\n", - m_idxproc?"on":"off", m_queryActive)); + LOGDEB("RclMain::startSearch. Indexing " << (m_idxproc?"on":"off") << + " Active " << m_queryActive << "\n"); if (m_queryActive) { - LOGDEB(("startSearch: already active\n")); + LOGDEB("startSearch: already active\n"); return; } m_queryActive = true; restable->setEnabled(false); - m_source = RefCntr(); + m_source = std::shared_ptr(); m_searchIsSimple = issimple; @@ -660,16 +688,27 @@ void RclMain::startSearch(RefCntr sdata, bool issimple) return; } + if (prefs.synFileEnable && !prefs.synFile.isEmpty()) { + string sf = (const char *)prefs.synFile.toLocal8Bit(); + if (!rcldb->setSynGroupsFile(sf)) { + QMessageBox::warning(0, "Recoll", + tr("Can't set synonyms file (parse error?)")); + return; + } + } else { + rcldb->setSynGroupsFile(""); + } + Rcl::Query *query = new Rcl::Query(rcldb); query->setCollapseDuplicates(prefs.collapseDuplicates); curPreview = 0; DocSequenceDb *src = - new DocSequenceDb(RefCntr(query), + new DocSequenceDb(std::shared_ptr(query), string(tr("Query results").toUtf8()), sdata); src->setAbstractParams(prefs.queryBuildAbstract, prefs.queryReplaceAbstract); - m_source = RefCntr(src); + m_source = std::shared_ptr(src); m_source->setSortSpec(m_sortspec); m_source->setFiltSpec(m_filtspec); @@ -679,18 +718,15 @@ void RclMain::startSearch(RefCntr sdata, bool issimple) } class QueryThread : public QThread { - int loglevel; - RefCntr m_source; + std::shared_ptr m_source; public: - QueryThread(RefCntr source) + QueryThread(std::shared_ptr source) : m_source(source) { - loglevel = DebugLog::getdbl()->getlevel(); } ~QueryThread() { } virtual void run() { - DebugLog::getdbl()->setloglevel(loglevel); cnt = m_source->getResCnt(); } int cnt; @@ -698,7 +734,7 @@ class QueryThread : public QThread { void RclMain::initiateQuery() { - if (m_source.isNull()) + if (!m_source) return; QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); @@ -727,7 +763,7 @@ void RclMain::initiateQuery() qApp->processEvents(); if (progress.wasCanceled()) { // Just get out of there asap. - _exit(1); + exit(1); } qApp->processEvents(); @@ -760,7 +796,7 @@ void RclMain::onSortCtlChanged() if (m_sortspecnochange) return; - LOGDEB(("RclMain::onSortCtlChanged()\n")); + LOGDEB("RclMain::onSortCtlChanged()\n"); m_sortspec.reset(); if (actionSortByDateAsc->isChecked()) { m_sortspec.field = "mtime"; @@ -778,7 +814,7 @@ void RclMain::onSortCtlChanged() prefs.sortActive = prefs.sortDesc = false; prefs.sortField = ""; } - if (m_source.isNotNull()) + if (m_source) m_source->setSortSpec(m_sortspec); emit sortDataChanged(m_sortspec); initiateQuery(); @@ -786,7 +822,7 @@ void RclMain::onSortCtlChanged() void RclMain::onSortDataChanged(DocSeqSortSpec spec) { - LOGDEB(("RclMain::onSortDataChanged\n")); + LOGDEB("RclMain::onSortDataChanged\n"); m_sortspecnochange = true; if (spec.field.compare("mtime")) { actionSortByDateDesc->setChecked(false); @@ -796,7 +832,7 @@ void RclMain::onSortDataChanged(DocSeqSortSpec spec) actionSortByDateAsc->setChecked(!spec.desc); } m_sortspecnochange = false; - if (m_source.isNotNull()) + if (m_source) m_source->setSortSpec(spec); m_sortspec = spec; @@ -809,7 +845,7 @@ void RclMain::onSortDataChanged(DocSeqSortSpec spec) void RclMain::on_actionShowResultsAsTable_toggled(bool on) { - LOGDEB(("RclMain::on_actionShowResultsAsTable_toggled(%d)\n", int(on))); + LOGDEB("RclMain::on_actionShowResultsAsTable_toggled(" << on << ")\n"); prefs.showResultsAsTable = on; displayingTable = on; restable->setVisible(on); @@ -818,8 +854,9 @@ void RclMain::on_actionShowResultsAsTable_toggled(bool on) static QShortcut tablefocseq(QKeySequence("Ctrl+r"), this); if (!on) { int docnum = restable->getDetailDocNumOrTopRow(); - if (docnum >= 0) - reslist->resultPageFor(docnum); + if (docnum >= 0) { + reslist->resultPageFor(docnum); + } disconnect(&tablefocseq, SIGNAL(activated()), restable, SLOT(takeFocus())); sSearch->takeFocus(); @@ -838,7 +875,7 @@ void RclMain::on_actionShowResultsAsTable_toggled(bool on) void RclMain::on_actionSortByDateAsc_toggled(bool on) { - LOGDEB(("RclMain::on_actionSortByDateAsc_toggled(%d)\n", int(on))); + LOGDEB("RclMain::on_actionSortByDateAsc_toggled(" << on << ")\n"); if (on) { if (actionSortByDateDesc->isChecked()) { actionSortByDateDesc->setChecked(false); @@ -851,7 +888,7 @@ void RclMain::on_actionSortByDateAsc_toggled(bool on) void RclMain::on_actionSortByDateDesc_toggled(bool on) { - LOGDEB(("RclMain::on_actionSortByDateDesc_toggled(%d)\n", int(on))); + LOGDEB("RclMain::on_actionSortByDateDesc_toggled(" << on << ")\n"); if (on) { if (actionSortByDateAsc->isChecked()) { actionSortByDateAsc->setChecked(false); @@ -881,7 +918,7 @@ void RclMain::saveDocToFile(Rcl::Doc doc) void RclMain::showSubDocs(Rcl::Doc doc) { - LOGDEB(("RclMain::showSubDocs\n")); + LOGDEB("RclMain::showSubDocs\n"); string reason; if (!maybeOpenDb(reason)) { QMessageBox::critical(0, "Recoll", QString(reason.c_str())); @@ -896,8 +933,8 @@ void RclMain::showSubDocs(Rcl::Doc doc) new DocSequenceDocs(rcldb, docs, qs2utf8s(tr("Sub-documents and attachments"))); src->setDescription(qs2utf8s(tr("Sub-documents and attachments"))); - RefCntr - source(new DocSource(theconfig, RefCntr(src))); + std::shared_ptr + source(new DocSource(theconfig, std::shared_ptr(src))); ResTable *res = new ResTable(); res->setRclMain(this, false); @@ -910,14 +947,14 @@ void RclMain::showSubDocs(Rcl::Doc doc) // significant terms, and add them to the simple search entry. void RclMain::docExpand(Rcl::Doc doc) { - LOGDEB(("RclMain::docExpand()\n")); + LOGDEB("RclMain::docExpand()\n"); if (!rcldb) return; list terms; terms = m_source->expand(doc); if (terms.empty()) { - LOGDEB(("RclMain::docExpand: no terms\n")); + LOGDEB("RclMain::docExpand: no terms\n"); return; } // Do we keep the original query. I think we'd better not. @@ -936,9 +973,9 @@ void RclMain::docExpand(Rcl::Doc doc) void RclMain::showDocHistory() { - LOGDEB(("RclMain::showDocHistory\n")); + LOGDEB("RclMain::showDocHistory\n"); emit searchReset(); - m_source = RefCntr(); + m_source = std::shared_ptr(); curPreview = 0; string reason; @@ -947,8 +984,8 @@ void RclMain::showDocHistory() return; } // Construct a bogus SearchData structure - RefCntrsearchdata = - RefCntr(new Rcl::SearchData(Rcl::SCLT_AND, cstr_null)); + std::shared_ptrsearchdata = + std::shared_ptr(new Rcl::SearchData(Rcl::SCLT_AND, cstr_null)); searchdata->setDescription((const char *)tr("History data").toUtf8()); @@ -957,8 +994,8 @@ void RclMain::showDocHistory() new DocSequenceHistory(rcldb, g_dynconf, string(tr("Document history").toUtf8())); src->setDescription((const char *)tr("History data").toUtf8()); - DocSource *source = new DocSource(theconfig, RefCntr(src)); - m_source = RefCntr(source); + DocSource *source = new DocSource(theconfig, std::shared_ptr(src)); + m_source = std::shared_ptr(source); m_source->setSortSpec(m_sortspec); m_source->setFiltSpec(m_filtspec); emit docSourceChanged(m_source); @@ -992,9 +1029,11 @@ void RclMain::setUIPrefs() { if (!uiprefs) return; - LOGDEB(("Recollmain::setUIPrefs\n")); + LOGDEB("Recollmain::setUIPrefs\n"); reslist->setFont(); sSearch->setPrefs(); + enbSynAction->setDisabled(prefs.synFile.isEmpty()); + enbSynAction->setChecked(prefs.synFileEnable); } void RclMain::enableNextPage(bool yesno) @@ -1013,7 +1052,7 @@ void RclMain::enablePrevPage(bool yesno) QString RclMain::getQueryDescription() { - if (m_source.isNull()) + if (!m_source) return ""; return QString::fromUtf8(m_source->getDescription().c_str()); } @@ -1028,7 +1067,7 @@ void RclMain::catgFilter(QAction *act) // User pressed a filter button: set filter params in reslist void RclMain::catgFilter(int id) { - LOGDEB(("RclMain::catgFilter: id %d\n", id)); + LOGDEB("RclMain::catgFilter: id " << id << "\n"); if (id < 0 || id >= int(m_catgbutvec.size())) return; @@ -1073,7 +1112,7 @@ void RclMain::setFiltSpec() } } - if (m_source.isNotNull()) + if (m_source) m_source->setFiltSpec(m_filtspec); initiateQuery(); } @@ -1101,3 +1140,4 @@ void RclMain::applyStyleSheet() { ::applyStyleSheet(prefs.qssFile); } + diff --git a/src/qtgui/rclmain_w.h b/src/qtgui/rclmain_w.h index 974d8fec..b791095a 100644 --- a/src/qtgui/rclmain_w.h +++ b/src/qtgui/rclmain_w.h @@ -16,6 +16,7 @@ */ #ifndef RCLMAIN_W_H #define RCLMAIN_W_H +#include "autoconfig.h" #include #include @@ -29,7 +30,7 @@ #include "rcldb.h" #include "searchdata.h" #include "spell_w.h" -#include "refcntr.h" +#include #include "pathut.h" #include "guiutils.h" @@ -40,85 +41,97 @@ class ResTable; class CronToolW; class RTIToolW; class FragButs; +class SpecIdxW; +class WebcacheEdit; #include "ui_rclmain.h" namespace confgui { - class ConfIndexW; +class ConfIndexW; } using confgui::ConfIndexW; class RclTrayIcon; -class RclMain : public QMainWindow, public Ui::RclMainBase -{ - Q_OBJECT +class RclMain : public QMainWindow, public Ui::RclMainBase { + Q_OBJECT; public: - enum IndexerState {IXST_UNKNOWN, IXST_NOTRUNNING, - IXST_RUNNINGMINE, IXST_RUNNINGNOTMINE}; - RclMain(QWidget * parent = 0) - : QMainWindow(parent), - curPreview(0), - asearchform(0), - uiprefs(0), - indexConfig(0), - indexSched(0), - cronTool(0), - rtiTool(0), - spellform(0), + RclMain(QWidget * parent = 0) + : QMainWindow(parent), + curPreview(0), + asearchform(0), + uiprefs(0), + indexConfig(0), + indexSched(0), + cronTool(0), + rtiTool(0), + spellform(0), fragbuts(0), - periodictimer(0), - restable(0), - displayingTable(0), + specidx(0), + periodictimer(0), + webcache(0), + restable(0), + displayingTable(0), m_idNoStem(0), m_idAllStem(0), - m_toolsTB(0), m_resTB(0), + m_toolsTB(0), m_resTB(0), m_filtFRM(0), m_filtCMB(0), m_filtBGRP(0), m_filtMN(0), - m_idxproc(0), + m_idxproc(0), m_idxkilled(false), m_catgbutvecidx(0), - m_sortspecnochange(false), - m_indexerState(IXST_UNKNOWN), - m_queryActive(false), - m_firstIndexing(false), - m_searchIsSimple(false) - { - setupUi(this); - init(); + m_sortspecnochange(false), + m_indexerState(IXST_UNKNOWN), + m_queryActive(false), + m_firstIndexing(false), + m_searchIsSimple(false), + m_pidfile(0) { + setupUi(this); + init(); } ~RclMain() {} + QString getQueryDescription(); /** This is only called from main() to set an URL to be displayed (using - recoll as a doc extracter for embedded docs */ - virtual void setUrlToView(const QString& u) {m_urltoview = u;} + recoll as a doc extracter for embedded docs */ + virtual void setUrlToView(const QString& u) { + m_urltoview = u; + } /** Same usage: actually display the current urltoview */ virtual void viewUrl(); - bool lastSearchSimple() - { - return m_searchIsSimple; + bool lastSearchSimple() { + return m_searchIsSimple; } // Takes copies of the args instead of refs. Lazy and safe. void newDupsW(const Rcl::Doc doc, const std::vector dups); -protected: - virtual void showEvent(QShowEvent *); + enum IndexerState {IXST_UNKNOWN, IXST_NOTRUNNING, + IXST_RUNNINGMINE, IXST_RUNNINGNOTMINE}; + IndexerState indexerState() const { + return m_indexerState; + } + + public slots: virtual void fileExit(); - virtual void idxStatus(); virtual void periodic100(); virtual void toggleIndexing(); virtual void rebuildIndex(); - virtual void startSearch(RefCntr sdata, bool issimple); + virtual void specialIndex(); + virtual void startSearch(std::shared_ptr sdata, + bool issimple); virtual void previewClosed(Preview *w); virtual void showAdvSearchDialog(); virtual void showSpellDialog(); + virtual void showWebcacheDialog(); + virtual void showIndexStatistics(); virtual void showFragButs(); + virtual void showSpecIdx(); virtual void showAboutDialog(); virtual void showMissingHelpers(); virtual void showActiveTypes(); @@ -126,6 +139,7 @@ public slots: virtual void startManual(const string&); virtual void showDocHistory(); virtual void showExtIdxDialog(); + virtual void setSynEnabled(bool); virtual void showUIPrefs(); virtual void showIndexConfig(); virtual void execIndexConfig(); @@ -143,8 +157,8 @@ public slots: virtual void showSnippets(Rcl::Doc); virtual void startPreview(int docnum, Rcl::Doc doc, int keymods); virtual void startPreview(Rcl::Doc); - virtual void startNativeViewer(Rcl::Doc, int pagenum = -1, - QString term=QString()); + virtual void startNativeViewer(Rcl::Doc, int pagenum = -1, + QString term = QString()); virtual void openWith(Rcl::Doc, string); virtual void saveDocToFile(Rcl::Doc); virtual void previewNextInTab(Preview *, int sid, int docnum); @@ -171,15 +185,20 @@ public slots: virtual void setFilterCtlStyle(int stl); virtual void showTrayMessage(const QString& text); +private slots: + virtual void updateIdxStatus(); + virtual void onWebcacheDestroyed(QObject *); signals: - void docSourceChanged(RefCntr); + void docSourceChanged(std::shared_ptr); void stemLangChanged(const QString& lang); void sortDataChanged(DocSeqSortSpec); void resultsReady(); void searchReset(); protected: - virtual void closeEvent( QCloseEvent * ); + virtual void closeEvent(QCloseEvent *); + virtual void showEvent(QShowEvent *); + private: Preview *curPreview; @@ -191,7 +210,9 @@ private: RTIToolW *rtiTool; SpellW *spellform; FragButs *fragbuts; + SpecIdxW *specidx; QTimer *periodictimer; + WebcacheEdit *webcache; ResTable *restable; bool displayingTable; QAction *m_idNoStem; @@ -212,7 +233,7 @@ private: DocSeqFiltSpec m_filtspec; bool m_sortspecnochange; DocSeqSortSpec m_sortspec; - RefCntr m_source; + std::shared_ptr m_source; IndexerState m_indexerState; bool m_queryActive; bool m_firstIndexing; @@ -224,13 +245,17 @@ private: RclTrayIcon *m_trayicon; + // We sometimes take the indexer lock (e.g.: when editing the webcache) + Pidfile *m_pidfile; + virtual void init(); virtual void setupResTB(bool combo); - virtual void previewPrevOrNextInTab(Preview *, int sid, int docnum, - bool next); - virtual void execViewer(const map& subs, bool istempfile, + virtual void previewPrevOrNextInTab(Preview *, int sid, int docnum, + bool next); + // flags may contain ExecCmd::EXF_xx values + virtual void execViewer(const map& subs, bool enterHistory, const string& execpath, const vector& lcmd, - const string& cmd, Rcl::Doc doc); + const string& cmd, Rcl::Doc doc, int flags=0); virtual void setStemLang(const QString& lang); virtual void onSortCtlChanged(); virtual void showIndexConfig(bool modal); @@ -241,6 +266,7 @@ private: virtual void initiateQuery(); virtual bool containerUpToDate(Rcl::Doc& doc); virtual void setFiltSpec(); + virtual bool checkIdxPaths(); }; #endif // RCLMAIN_W_H diff --git a/src/qtgui/rclzg.cpp b/src/qtgui/rclzg.cpp index 845c1a14..3c3e8071 100644 --- a/src/qtgui/rclzg.cpp +++ b/src/qtgui/rclzg.cpp @@ -16,9 +16,11 @@ */ #ifdef USE_ZEITGEIST +#include "autoconfig.h" + #include "rclzg.h" -#include "debuglog.h" +#include "log.h" #include "pathut.h" #include @@ -76,9 +78,9 @@ void zg_send_event(ZgSendType, const Rcl::Doc& doc) QtZeitgeist::DataModel::EventList events; events.push_back(event); - LOGDEB(("zg_send_event, sending for %s %s\n", - doc.mimetype.c_str(), doc.url.c_str())); + LOGDEB("zg_send_event, sending for " << (doc.mimetype) << " " << (doc.url) << "\n" ); zglogger.insertEvents(events); } #endif + diff --git a/src/qtgui/recoll-win.pro b/src/qtgui/recoll-win.pro new file mode 100644 index 00000000..487fba3f --- /dev/null +++ b/src/qtgui/recoll-win.pro @@ -0,0 +1,137 @@ +# Note this is generated by configure on Linux (see recoll.pro.in). +# recoll.pro is under version control anyway and used on Windows + +TEMPLATE = app +LANGUAGE = C++ +TARGET = recoll + +QT += webkit + +DEFINES += BUILDING_RECOLL +DEFINES -= UNICODE +DEFINES -= _UNICODE +DEFINES += _MBCS +DEFINES += PSAPI_VERSION=1 + +QT += xml +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets webkitwidgets printsupport + +HEADERS += \ + advsearch_w.h \ + advshist.h \ + confgui/confgui.h \ + confgui/confguiindex.h \ + crontool.h \ + widgets/editdialog.h \ + firstidx.h \ + fragbuts.h \ + idxsched.h \ + widgets/listdialog.h \ + widgets/qxtconfirmationmessage.h \ + preview_w.h \ + preview_load.h \ + preview_plaintorich.h \ + ptrans_w.h \ + rclhelp.h \ + rclmain_w.h \ + reslist.h \ + restable.h \ + rtitool.h \ + searchclause_w.h \ + snippets_w.h \ + specialindex.h \ + spell_w.h \ + ssearch_w.h \ + systray.h \ + uiprefs_w.h \ + viewaction_w.h \ + webcache.h + +SOURCES += \ + advsearch_w.cpp \ + advshist.cpp \ + confgui/confgui.cpp \ + confgui/confguiindex.cpp \ + crontool.cpp \ + fragbuts.cpp \ + guiutils.cpp \ + main.cpp \ + multisave.cpp \ + preview_w.cpp \ + preview_load.cpp \ + preview_plaintorich.cpp \ + ptrans_w.cpp \ + rclhelp.cpp \ + rclmain_w.cpp \ + rclm_idx.cpp \ + rclm_preview.cpp \ + rclm_saveload.cpp \ + rclm_view.cpp \ + rclm_wins.cpp \ + rclzg.cpp \ + respopup.cpp \ + reslist.cpp \ + restable.cpp \ + rtitool.cpp \ + searchclause_w.cpp \ + snippets_w.cpp \ + spell_w.cpp \ + ssearch_w.cpp \ + systray.cpp \ + uiprefs_w.cpp \ + viewaction_w.cpp \ + webcache.cpp \ + widgets/qxtconfirmationmessage.cpp \ + xmltosd.cpp + +FORMS = \ + advsearch.ui \ + crontool.ui \ + widgets/editdialog.ui \ + firstidx.ui \ + idxsched.ui \ + widgets/listdialog.ui \ + ptrans.ui \ + rclmain.ui \ + restable.ui \ + rtitool.ui \ + specialindex.ui \ + spell.ui \ + snippets.ui \ + ssearchb.ui \ + uiprefs.ui \ + viewaction.ui \ + webcache.ui + +RESOURCES = recoll.qrc + +INCLUDEPATH += ../common ../index ../internfile ../query ../unac \ + ../utils ../aspell ../rcldb ../qtgui ../xaposix \ + confgui widgets +windows { + RC_FILE = recoll.rc + contains(QMAKE_CC, gcc){ + # MingW + QMAKE_CXXFLAGS += -std=c++11 -Wno-unused-parameter + } + contains(QMAKE_CC, cl){ + # Visual Studio + } + LIBS += C:/recoll/src/windows/build-librecoll-Desktop_Qt_5_5_0_MinGW_32bit-Release/release/librecoll.dll +} + +TRANSLATIONS = \ + i18n/recoll_cs.ts \ + i18n/recoll_da.ts \ + i18n/recoll_de.ts \ + i18n/recoll_el.ts \ + i18n/recoll_es.ts \ + i18n/recoll_fr.ts \ + i18n/recoll_it.ts \ + i18n/recoll_lt.ts \ + i18n/recoll_ru.ts \ + i18n/recoll_tr.ts \ + i18n/recoll_uk.ts \ + i18n/recoll_xx.ts \ + i18n/recoll_zh_CN.ts \ + i18n/recoll_zh.ts \ diff --git a/src/qtgui/recoll.h b/src/qtgui/recoll.h index 429dab88..e78a0025 100644 --- a/src/qtgui/recoll.h +++ b/src/qtgui/recoll.h @@ -21,7 +21,7 @@ #include "rclconfig.h" #include "rcldb.h" -#include "ptmutex.h" +#include "rclutil.h" #include @@ -38,6 +38,7 @@ extern RclConfig *theconfig; extern void rememberTempFile(TempFile); extern void forgetTempFile(string &fn); +extern void deleteAllTempFiles(); extern Rcl::Db *rcldb; extern int recollNeedsExit; @@ -54,4 +55,10 @@ inline std::string qs2utf8s(const QString& qs) { return std::string((const char *)qs.toUtf8()); } + +/** Specialized version of the qt file dialog. Can't use getOpenFile() + etc. cause they hide dot files... */ +extern QString myGetFileName(bool isdir, QString caption = QString(), + bool filenosave = false); + #endif /* _RECOLL_H_INCLUDED_ */ diff --git a/src/qtgui/recoll.pro.in b/src/qtgui/recoll.pro.in index 13f9c0c8..c58105f0 100644 --- a/src/qtgui/recoll.pro.in +++ b/src/qtgui/recoll.pro.in @@ -1,11 +1,15 @@ TEMPLATE = app LANGUAGE = C++ -@QMAKE_ENABLE_WEBKIT@QT += webkit -@QMAKE_DISABLE_WEBKIT@QMAKE_CXXFLAGS += -DRESLIST_TEXTBROWSER -DSNIPPETS_TEXTBROWSER +VPATH = @srcdir@ +DEFINES += BUILDING_RECOLL -@QMAKE_ENABLE_ZEITGEIST@QT += dbus -@QMAKE_ENABLE_ZEITGEIST@QMAKE_CXXFLAGS += -DUSE_ZEITGEIST +@QMAKE_ENABLE_WEBKIT@ QT += webkit +@QMAKE_DISABLE_WEBKIT@ QMAKE_CXXFLAGS += -DRESLIST_TEXTBROWSER -DSNIPPETS_TEXTBROWSER +QMAKE_CXXFLAGS += -std=c++11 + +@QMAKE_ENABLE_ZEITGEIST@ QT += dbus +@QMAKE_ENABLE_ZEITGEIST@ QMAKE_CXXFLAGS += -DUSE_ZEITGEIST QT += xml greaterThan(QT_MAJOR_VERSION, 4): QT += widgets webkitwidgets printsupport @@ -18,11 +22,11 @@ HEADERS += \ confgui/confgui.h \ confgui/confguiindex.h \ crontool.h \ - editdialog.h \ firstidx.h \ fragbuts.h \ idxsched.h \ - listdialog.h \ + preview_load.h \ + preview_plaintorich.h \ preview_w.h \ ptrans_w.h \ rclhelp.h \ @@ -32,11 +36,16 @@ HEADERS += \ rtitool.h \ searchclause_w.h \ snippets_w.h \ + specialindex.h \ spell_w.h \ ssearch_w.h \ systray.h \ uiprefs_w.h \ viewaction_w.h \ + webcache.h \ + widgets/editdialog.h \ + widgets/listdialog.h \ + widgets/qxtconfirmationmessage.h SOURCES += \ advsearch_w.cpp \ @@ -48,18 +57,20 @@ SOURCES += \ guiutils.cpp \ main.cpp \ multisave.cpp \ + preview_load.cpp \ + preview_plaintorich.cpp \ preview_w.cpp \ ptrans_w.cpp \ rclhelp.cpp \ - rclmain_w.cpp \ rclm_idx.cpp \ rclm_preview.cpp \ rclm_saveload.cpp \ rclm_view.cpp \ rclm_wins.cpp \ + rclmain_w.cpp \ rclzg.cpp \ - respopup.cpp \ reslist.cpp \ + respopup.cpp \ restable.cpp \ rtitool.cpp \ searchclause_w.cpp \ @@ -69,48 +80,53 @@ SOURCES += \ systray.cpp \ uiprefs_w.cpp \ viewaction_w.cpp \ + webcache.cpp \ + widgets/qxtconfirmationmessage.cpp \ xmltosd.cpp FORMS = \ advsearch.ui \ crontool.ui \ - editdialog.ui \ + widgets/editdialog.ui \ firstidx.ui \ idxsched.ui \ - listdialog.ui \ + widgets/listdialog.ui \ ptrans.ui \ rclmain.ui \ restable.ui \ rtitool.ui \ + specialindex.ui \ spell.ui \ snippets.ui \ ssearchb.ui \ uiprefs.ui \ viewaction.ui \ - + webcache.ui + RESOURCES = recoll.qrc unix { UI_DIR = .ui MOC_DIR = .moc OBJECTS_DIR = .obj - # Note: libdir may be substituted with sthing like $(exec_prefix)/lib - # at this point and will go as such in the Makefile. Expansion will be - # completed at make time. - LIBS += $(BSTATIC) -L../lib -lrecoll + LIBS += -L../.libs -lrecoll !macx { + # Note: libdir may be substituted with sthing like $(exec_prefix)/lib + # at this point and will go as such in the Makefile. Expansion will be + # completed at make time. LIBS += -Wl,-rpath=@libdir@/recoll } LIBS += @LIBXAPIAN@ $(LIBXAPIANSTATICEXTRA) \ @LIBICONV@ $(BDYNAMIC) @LIBQZEITGEIST@ -lz - INCLUDEPATH += ../common ../index ../internfile ../query ../unac \ - ../utils ../aspell ../rcldb ../qtgui \ - confgui + INCLUDEPATH += ../common @srcdir@/../common @srcdir@/../index \ + @srcdir@/../internfile @srcdir@/../query @srcdir@/../unac \ + @srcdir@/../utils @srcdir@/../aspell @srcdir@/../rcldb \ + @srcdir@/../qtgui @srcdir@/../xaposix @srcdir@/confgui \ + @srcdir@/widgets DEPENDPATH += $$INCLUDEPATH - POST_TARGETDEPS = ../lib/librecoll.a } UNAME = $$system(uname -s) @@ -141,3 +157,33 @@ TRANSLATIONS = \ i18n/recoll_xx.ts \ i18n/recoll_zh_CN.ts \ i18n/recoll_zh.ts \ + +unix { + isEmpty(PREFIX) { + PREFIX = /usr/local + } + message("Prefix is $$PREFIX") + DEFINES += PREFIX=\\\"$$PREFIX\\\" + + # Installation stuff + target.path = "$$PREFIX/bin" + + imdata.files = @srcdir@/mtpics/*.png + imdata.path = $$PREFIX/share/recoll/images + trdata.files = @srcdir@/i18n/*.qm + trdata.path = $$PREFIX/share/recoll/translations + desktop.files += @srcdir@/../desktop/recoll-searchgui.desktop + desktop.path = $$PREFIX/share/applications/ + icona.files += @srcdir@/../desktop/recoll.png + icona.path = $$PREFIX/share/icons/hicolor/48x48/apps/ + iconb.files += @srcdir@/../desktop/recoll.png + iconb.path = $$PREFIX/share/pixmaps/ + appdata.files = @srcdir@/../desktop/recoll.appdata.xml + appdata.path = $$PREFIX/share/appdata/ + INSTALLS += target imdata trdata desktop icona iconb appdata + + # The recollinstall script used to do the following to install zh_CN as + # zh. Is this still needed? + #${INSTALL} -m 0444 ${I18N}/recoll_zh_CN.qm \ + # ${datadir}/recoll/translations/recoll_zh.qm || exit 1 +} diff --git a/src/qtgui/recoll.rc b/src/qtgui/recoll.rc new file mode 100644 index 00000000..906e9267 --- /dev/null +++ b/src/qtgui/recoll.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "../desktop/recoll.ico" diff --git a/src/qtgui/reslist.cpp b/src/qtgui/reslist.cpp index 4cad0651..a7c1448f 100644 --- a/src/qtgui/reslist.cpp +++ b/src/qtgui/reslist.cpp @@ -38,7 +38,7 @@ //#include #endif -#include "debuglog.h" +#include "log.h" #include "smallut.h" #include "recoll.h" #include "guiutils.h" @@ -47,7 +47,7 @@ #include "pathut.h" #include "mimehandler.h" #include "plaintorich.h" -#include "refcntr.h" +#include #include "internfile.h" #include "indexer.h" #include "snippets_w.h" @@ -116,7 +116,7 @@ void logdata(const char *data) // /// QtGuiResListPager methods: bool QtGuiResListPager::append(const string& data) { - LOGDEB2(("QtGuiReslistPager::appendString : %s\n", data.c_str())); + LOGDEB2("QtGuiReslistPager::appendString : " << (data) << "\n" ); logdata(data.c_str()); m_reslist->append(QString::fromUtf8(data.c_str())); return true; @@ -125,8 +125,7 @@ bool QtGuiResListPager::append(const string& data) bool QtGuiResListPager::append(const string& data, int docnum, const Rcl::Doc&) { - LOGDEB2(("QtGuiReslistPager::appendDoc: blockCount %d, %s\n", - m_reslist->document()->blockCount(), data.c_str())); + LOGDEB2("QtGuiReslistPager::appendDoc: blockCount " << (m_reslist->document()->blockCount()) << ", " << (data) << "\n" ); logdata(data.c_str()); #ifdef RESLIST_TEXTBROWSER int blkcnt0 = m_reslist->document()->blockCount(); @@ -208,7 +207,7 @@ void QtGuiResListPager::suggest(const vectoruterms, if (noaspell) return; if (!aspell) { - LOGERR(("QtGuiResListPager:: aspell not initialized\n")); + LOGERR("QtGuiResListPager:: aspell not initialized\n" ); return; } @@ -225,8 +224,7 @@ void QtGuiResListPager::suggest(const vectoruterms, // frequencies and propose something anyway if a possible // variation is much more common (as google does) ? if (!aspell->suggest(*rcldb, *uit, asuggs, reason)) { - LOGERR(("QtGuiResListPager::suggest: aspell failed: %s\n", - reason.c_str())); + LOGERR("QtGuiResListPager::suggest: aspell failed: " << (reason) << "\n" ); continue; } @@ -261,16 +259,15 @@ string QtGuiResListPager::iconUrl(RclConfig *config, Rcl::Doc& doc) ConfIndexer::docsToPaths(docs, paths); if (!paths.empty()) { string path; - LOGDEB0(("ResList::iconUrl: source path [%s]\n", paths[0].c_str())); + LOGDEB2("ResList::iconUrl: source path [" << paths[0] << "]\n"); if (thumbPathForUrl(cstr_fileu + paths[0], 128, path)) { - LOGDEB0(("ResList::iconUrl: icon path [%s]\n", path.c_str())); + LOGDEB2("ResList::iconUrl: icon path [" << path << "]\n"); return cstr_fileu + path; } else { - LOGDEB0(("ResList::iconUrl: no icon: path [%s]\n", - path.c_str())); + LOGDEB2("ResList::iconUrl: no icon: path [" << path << "]\n"); } } else { - LOGDEB(("ResList::iconUrl: docsToPaths failed\n")); + LOGDEB("ResList::iconUrl: docsToPaths failed\n"); } } return ResListPager::iconUrl(config, doc); @@ -286,11 +283,12 @@ public: string s1, s2; stringsToString >(m_hdata->groups[idx], s1); stringsToString >(m_hdata->ugroups[m_hdata->grpsugidx[idx]], s2); - LOGDEB(("Reslist startmatch: group %s user group %s\n", s1.c_str(), s2.c_str())); + LOGDEB2("Reslist startmatch: group " << s1 << " user group " << + s2 << "\n"); } - return string(""); + return string(""); } virtual string endMatch() { @@ -310,7 +308,7 @@ ResList::ResList(QWidget* parent, const char* name) else setObjectName(name); #ifdef RESLIST_TEXTBROWSER - LOGDEB(("Reslist: using QTextBrowser\n")); + LOGDEB("Reslist: using QTextBrowser\n" ); setReadOnly(TRUE); setUndoRedoEnabled(FALSE); setOpenLinks(FALSE); @@ -319,7 +317,7 @@ ResList::ResList(QWidget* parent, const char* name) connect(this, SIGNAL(anchorClicked(const QUrl &)), this, SLOT(linkWasClicked(const QUrl &))); #else - LOGDEB(("Reslist: using QWebView\n")); + LOGDEB("Reslist: using QWebView\n" ); // signals and slots connections connect(this, SIGNAL(linkClicked(const QUrl &)), this, SLOT(linkWasClicked(const QUrl &))); @@ -331,7 +329,8 @@ ResList::ResList(QWidget* parent, const char* name) languageChange(); (void)new HelpClient(this); - HelpClient::installMap(qs2utf8s(this->objectName()), "RCL.SEARCH.RESLIST"); + HelpClient::installMap(qs2utf8s(this->objectName()), + "RCL.SEARCH.GUI.RESLIST"); #if 0 // See comments in "highlighted @@ -423,19 +422,19 @@ int ResList::newListId() extern "C" int XFlush(void *); -void ResList::setDocSource(RefCntr nsource) +void ResList::setDocSource(std::shared_ptr nsource) { - LOGDEB(("ResList::setDocSource()\n")); - m_source = RefCntr(new DocSource(theconfig, nsource)); + LOGDEB("ResList::setDocSource()\n" ); + m_source = std::shared_ptr(new DocSource(theconfig, nsource)); } // A query was executed, or the filtering/sorting parameters changed, // re-read the results. void ResList::readDocSource() { - LOGDEB(("ResList::readDocSource()\n")); + LOGDEB("ResList::readDocSource()\n" ); resetView(); - if (m_source.isNull()) + if (!m_source) return; m_listId = newListId(); @@ -448,8 +447,8 @@ void ResList::readDocSource() void ResList::resetList() { - LOGDEB(("ResList::resetList()\n")); - setDocSource(RefCntr()); + LOGDEB("ResList::resetList()\n" ); + setDocSource(std::shared_ptr()); resetView(); } @@ -481,7 +480,7 @@ bool ResList::displayingHistory() // We want to reset the displayed history if it is currently // shown. Using the title value is an ugly hack string htstring = string((const char *)tr("Document history").toUtf8()); - if (m_source.isNull() || m_source->title().empty()) + if (!m_source || m_source->title().empty()) return false; return m_source->title().find(htstring) == 0; } @@ -512,15 +511,14 @@ int ResList::docnumfromparnum(int block) // Get range of paragraph numbers which make up the result for document number pair ResList::parnumfromdocnum(int docnum) { - LOGDEB(("parnumfromdocnum: docnum %d\n", docnum)); + LOGDEB("parnumfromdocnum: docnum " << (docnum) << "\n" ); if (m_pager->pageNumber() < 0) { - LOGDEB(("parnumfromdocnum: no page return -1,-1\n")); + LOGDEB("parnumfromdocnum: no page return -1,-1\n" ); return pair(-1,-1); } int winfirst = pageFirstDocNum(); if (docnum - winfirst < 0) { - LOGDEB(("parnumfromdocnum: docnum %d < winfirst %d return -1,-1\n", - docnum, winfirst)); + LOGDEB("parnumfromdocnum: docnum " << (docnum) << " < winfirst " << (winfirst) << " return -1,-1\n" ); return pair(-1,-1); } docnum -= winfirst; @@ -534,11 +532,11 @@ pair ResList::parnumfromdocnum(int docnum) m_pageParaToReldocnums.end() && it1->second == docnum) { last++; } - LOGDEB(("parnumfromdocnum: return %d,%d\n", first, last)); + LOGDEB("parnumfromdocnum: return " << (first) << "," << (last) << "\n" ); return pair(first, last); } } - LOGDEB(("parnumfromdocnum: not found return -1,-1\n")); + LOGDEB("parnumfromdocnum: not found return -1,-1\n" ); return pair(-1,-1); } #endif // TEXTBROWSER @@ -549,8 +547,7 @@ pair ResList::parnumfromdocnum(int docnum) // result in a one-page change. bool ResList::getDoc(int docnum, Rcl::Doc &doc) { - LOGDEB(("ResList::getDoc: docnum %d winfirst %d\n", docnum, - pageFirstDocNum())); + LOGDEB("ResList::getDoc: docnum " << (docnum) << " winfirst " << (pageFirstDocNum()) << "\n" ); int winfirst = pageFirstDocNum(); int winlast = m_pager->pageLastDocNum(); if (docnum < 0 || winfirst < 0 || winlast < 0) @@ -640,8 +637,7 @@ void ResList::resPageDownOrNext() #ifdef RESLIST_TEXTBROWSER int vpos = verticalScrollBar()->value(); verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd); - LOGDEB(("ResList::resPageDownOrNext: vpos before %d, after %d\n", - vpos, verticalScrollBar()->value())); + LOGDEB("ResList::resPageDownOrNext: vpos before " << (vpos) << ", after " << (verticalScrollBar()->value()) << "\n" ); if (vpos == verticalScrollBar()->value()) resultPageNext(); #else @@ -674,9 +670,9 @@ bool ResList::scrollIsAtBottom() int max = frame->scrollBarMaximum(Qt::Vertical); int cur = frame->scrollBarValue(Qt::Vertical); ret = (max != 0) && (cur == max); - LOGDEB2(("Scrollatbottom: cur %d max %d\n", cur, max)); + LOGDEB2("Scrollatbottom: cur " << (cur) << " max " << (max) << "\n" ); } - LOGDEB2(("scrollIsAtBottom: returning %d\n", ret)); + LOGDEB2("scrollIsAtBottom: returning " << (ret) << "\n" ); return ret; #endif } @@ -693,10 +689,10 @@ bool ResList::scrollIsAtTop() } else { int cur = frame->scrollBarValue(Qt::Vertical); int min = frame->scrollBarMinimum(Qt::Vertical); - LOGDEB(("Scrollattop: cur %d min %d\n", cur, min)); + LOGDEB("Scrollattop: cur " << (cur) << " min " << (min) << "\n" ); ret = (cur == min); } - LOGDEB2(("scrollIsAtTop: returning %d\n", ret)); + LOGDEB2("scrollIsAtTop: returning " << (ret) << "\n" ); return ret; #endif } @@ -737,8 +733,7 @@ void ResList::resultPageFor(int docnum) void ResList::append(const QString &text) { - LOGDEB2(("QtGuiReslistPager::appendQString : %s\n", - (const char*)text.toUtf8())); + LOGDEB2("QtGuiReslistPager::appendQString : " << qs2utf8s(text) << "\n"); #ifdef RESLIST_TEXTBROWSER QTextBrowser::append(text); #else @@ -756,9 +751,9 @@ void ResList::displayPage() setHtml(m_text); #endif - LOGDEB0(("ResList::displayPg: hasNext %d atBot %d hasPrev %d at Top %d \n", - m_pager->hasPrev(), scrollIsAtBottom(), - m_pager->hasNext(), scrollIsAtTop())); + LOGDEB0("ResList::displayPg: hasNext " << m_pager->hasNext() << + " atBot " << scrollIsAtBottom() << " hasPrev " << + m_pager->hasPrev() << " at Top " << scrollIsAtTop() << " \n"); setupArrows(); // Possibly color paragraph of current preview if any @@ -768,7 +763,7 @@ void ResList::displayPage() // Color paragraph (if any) of currently visible preview void ResList::previewExposed(int docnum) { - LOGDEB(("ResList::previewExposed: doc %d\n", docnum)); + LOGDEB("ResList::previewExposed: doc " << (docnum) << "\n" ); // Possibly erase old one to white if (m_curPvDoc != -1) { @@ -787,14 +782,13 @@ void ResList::previewExposed(int docnum) #else QString sel = QString("div[rcldocnum=\"%1\"]").arg(m_curPvDoc - pageFirstDocNum()); - LOGDEB2(("Searching for element, selector: [%s]\n", - qs2utf8s(sel).c_str())); + LOGDEB2("Searching for element, selector: [" << (qs2utf8s(sel)) << "]\n" ); QWebElement elt = page()->mainFrame()->findFirstElement(sel); if (!elt.isNull()) { - LOGDEB2(("Found\n")); + LOGDEB2("Found\n" ); elt.removeAttribute("style"); } else { - LOGDEB2(("Not Found\n")); + LOGDEB2("Not Found\n" ); } #endif m_curPvDoc = -1; @@ -824,14 +818,13 @@ void ResList::previewExposed(int docnum) #else QString sel = QString("div[rcldocnum=\"%1\"]").arg(docnum - pageFirstDocNum()); - LOGDEB2(("Searching for element, selector: [%s]\n", - qs2utf8s(sel).c_str())); + LOGDEB2("Searching for element, selector: [" << (qs2utf8s(sel)) << "]\n" ); QWebElement elt = page()->mainFrame()->findFirstElement(sel); if (!elt.isNull()) { - LOGDEB2(("Found\n")); + LOGDEB2("Found\n" ); elt.setAttribute("style", "background: LightBlue;}"); } else { - LOGDEB2(("Not Found\n")); + LOGDEB2("Not Found\n" ); } #endif } @@ -850,7 +843,7 @@ void ResList::mouseDoubleClickEvent(QMouseEvent *event) void ResList::showQueryDetails() { - if (m_source.isNull()) + if (!m_source) return; string oq = breakIntoLines(m_source->getDescription(), 100, 50); QString str; @@ -863,7 +856,7 @@ void ResList::showQueryDetails() void ResList::linkWasClicked(const QUrl &url) { string ascurl = qs2utf8s(url.toString()); - LOGDEB(("ResList::linkWasClicked: [%s]\n", ascurl.c_str())); + LOGDEB("ResList::linkWasClicked: [" << (ascurl) << "]\n" ); int what = ascurl[0]; switch (what) { @@ -871,12 +864,12 @@ void ResList::linkWasClicked(const QUrl &url) // Open abstract/snippets window case 'A': { - if (m_source.isNull()) + if (!m_source) return; int i = atoi(ascurl.c_str()+1) - 1; Rcl::Doc doc; if (!getDoc(i, doc)) { - LOGERR(("ResList::linkWasClicked: can't get doc for %d\n", i)); + LOGERR("ResList::linkWasClicked: can't get doc for " << (i) << "\n" ); return; } emit(showSnippets(doc)); @@ -886,12 +879,12 @@ void ResList::linkWasClicked(const QUrl &url) // Show duplicates case 'D': { - if (m_source.isNull()) + if (!m_source) return; int i = atoi(ascurl.c_str()+1) - 1; Rcl::Doc doc; if (!getDoc(i, doc)) { - LOGERR(("ResList::linkWasClicked: can't get doc for %d\n", i)); + LOGERR("ResList::linkWasClicked: can't get doc for " << (i) << "\n" ); return; } vector dups; @@ -907,10 +900,10 @@ void ResList::linkWasClicked(const QUrl &url) int i = atoi(ascurl.c_str()+1) - 1; Rcl::Doc doc; if (!getDoc(i, doc)) { - LOGERR(("ResList::linkWasClicked: can't get doc for %d\n", i)); + LOGERR("ResList::linkWasClicked: can't get doc for " << (i) << "\n" ); return; } - emit editRequested(ResultPopup::getParent(RefCntr(), + emit editRequested(ResultPopup::getParent(std::shared_ptr(), doc)); } break; @@ -929,7 +922,7 @@ void ResList::linkWasClicked(const QUrl &url) int i = atoi(ascurl.c_str()+1) - 1; Rcl::Doc doc; if (!getDoc(i, doc)) { - LOGERR(("ResList::linkWasClicked: can't get doc for %d\n", i)); + LOGERR("ResList::linkWasClicked: can't get doc for " << (i) << "\n" ); return; } if (what == 'P') { @@ -989,18 +982,18 @@ void ResList::linkWasClicked(const QUrl &url) break; default: - LOGERR(("ResList::linkWasClicked: bad link [%s]\n", ascurl.c_str())); + LOGERR("ResList::linkWasClicked: bad link [" << (ascurl) << "]\n" ); break;// ?? } } void ResList::createPopupMenu(const QPoint& pos) { - LOGDEB(("ResList::createPopupMenu(%d, %d)\n", pos.x(), pos.y())); + LOGDEB("ResList::createPopupMenu(" << (pos.x()) << ", " << (pos.y()) << ")\n" ); #ifdef RESLIST_TEXTBROWSER QTextCursor cursor = cursorForPosition(pos); int blocknum = cursor.blockNumber(); - LOGDEB(("ResList::createPopupMenu(): block %d\n", blocknum)); + LOGDEB("ResList::createPopupMenu(): block " << (blocknum) << "\n" ); m_popDoc = docnumfromparnum(blocknum); #else QWebHitTestResult htr = page()->mainFrame()->hitTestContent(pos); @@ -1050,7 +1043,7 @@ void ResList::menuSaveToFile() void ResList::menuPreviewParent() { Rcl::Doc doc; - if (getDoc(m_popDoc, doc) && !m_source.isNull()) { + if (getDoc(m_popDoc, doc) && m_source) { Rcl::Doc pdoc = ResultPopup::getParent(m_source, doc); if (pdoc.mimetype == "inode/directory") { emit editRequested(pdoc); @@ -1063,7 +1056,7 @@ void ResList::menuPreviewParent() void ResList::menuOpenParent() { Rcl::Doc doc; - if (getDoc(m_popDoc, doc) && m_source.isNotNull()) + if (getDoc(m_popDoc, doc) && m_source) emit editRequested(ResultPopup::getParent(m_source, doc)); } @@ -1121,3 +1114,4 @@ int ResList::pageFirstDocNum() { return m_pager->pageFirstDocNum(); } + diff --git a/src/qtgui/reslist.h b/src/qtgui/reslist.h index 0f627a4d..f8254d03 100644 --- a/src/qtgui/reslist.h +++ b/src/qtgui/reslist.h @@ -17,15 +17,11 @@ #ifndef _RESLIST_H_INCLUDED_ #define _RESLIST_H_INCLUDED_ +#include "autoconfig.h" #include #include -#ifndef NO_NAMESPACES -using std::list; -using std::pair; -#endif - #ifdef RESLIST_TEXTBROWSER #include #define RESLIST_PARENTCLASS QTextBrowser @@ -37,7 +33,7 @@ using std::pair; #include "docseq.h" #include "sortseq.h" #include "filtseq.h" -#include "refcntr.h" +#include #include "rcldoc.h" #include "reslistpager.h" @@ -70,7 +66,7 @@ class ResList : public RESLIST_PARENTCLASS void setRclMain(RclMain *m, bool ismain); public slots: - virtual void setDocSource(RefCntr nsource); + virtual void setDocSource(std::shared_ptr nsource); virtual void resetList(); // Erase current list virtual void resPageUpOrBack(); // Page up pressed virtual void resPageDownOrNext(); // Page down pressed @@ -122,7 +118,7 @@ class ResList : public RESLIST_PARENTCLASS private: QtGuiResListPager *m_pager; - RefCntr m_source; + std::shared_ptr m_source; int m_popDoc; // Docnum for the popup menu. int m_curPvDoc;// Docnum for current preview int m_lstClckMod; // Last click modifier. @@ -133,7 +129,7 @@ class ResList : public RESLIST_PARENTCLASS // docnum. Built while we insert text into the qtextedit std::map m_pageParaToReldocnums; virtual int docnumfromparnum(int); - virtual pair parnumfromdocnum(int); + virtual std::pair parnumfromdocnum(int); #else // Webview makes it more difficult to append text incrementally, // so we store the page and display it when done. diff --git a/src/qtgui/respopup.cpp b/src/qtgui/respopup.cpp index 97b0024a..509ea108 100644 --- a/src/qtgui/respopup.cpp +++ b/src/qtgui/respopup.cpp @@ -20,7 +20,7 @@ #include #include -#include "debuglog.h" +#include "log.h" #include "smallut.h" #include "recoll.h" #include "docseq.h" @@ -29,15 +29,12 @@ namespace ResultPopup { -QMenu *create(QWidget *me, int opts, RefCntr source, Rcl::Doc& doc) +QMenu *create(QWidget *me, int opts, std::shared_ptr source, Rcl::Doc& doc) { QMenu *popup = new QMenu(me); - LOGDEB(("ResultPopup::create: opts %x haspages %d %s %s\n", opts, - doc.haspages, source.isNull() ? - "Source is Null" : "Source not null", - source.isNull() ? "" : source->snippetsCapable() ? - "snippetsCapable" : "not snippetsCapable")); + LOGDEB("ResultPopup::create: opts " << (opts) << " haspages " << (doc.haspages) << " " << (source ? "Source not null" : "Source is Null") << " " << (source ? (source->snippetsCapable() ? + "snippetsCapable" : "not snippetsCapable") : "") << "\n" ); string apptag; doc.getmeta(Rcl::Doc::keyapptg, &apptag); @@ -112,7 +109,7 @@ QMenu *create(QWidget *me, int opts, RefCntr source, Rcl::Doc& doc) me, SLOT(menuSaveSelection())); Rcl::Doc pdoc; - if (source.isNotNull() && source->getEnclosing(doc, pdoc)) { + if (source && source->getEnclosing(doc, pdoc)) { popup->addAction(QWidget::tr("Preview P&arent document/folder"), me, SLOT(menuPreviewParent())); } @@ -126,7 +123,7 @@ QMenu *create(QWidget *me, int opts, RefCntr source, Rcl::Doc& doc) popup->addAction(QWidget::tr("Find &similar documents"), me, SLOT(menuExpand())); - if (doc.haspages && source.isNotNull() && source->snippetsCapable()) + if (doc.haspages && source && source->snippetsCapable()) popup->addAction(QWidget::tr("Open &Snippets window"), me, SLOT(menuShowSnippets())); @@ -137,10 +134,10 @@ QMenu *create(QWidget *me, int opts, RefCntr source, Rcl::Doc& doc) return popup; } -Rcl::Doc getParent(RefCntr source, Rcl::Doc& doc) +Rcl::Doc getParent(std::shared_ptr source, Rcl::Doc& doc) { Rcl::Doc pdoc; - if (source.isNull() || !source->getEnclosing(doc, pdoc)) { + if (!source || !source->getEnclosing(doc, pdoc)) { // No parent doc: show enclosing folder with app configured for // directories pdoc.url = url_parentfolder(doc.url); @@ -176,3 +173,4 @@ void copyURL(const Rcl::Doc &doc) } } + diff --git a/src/qtgui/respopup.h b/src/qtgui/respopup.h index b1141fe7..3dd9c464 100644 --- a/src/qtgui/respopup.h +++ b/src/qtgui/respopup.h @@ -16,14 +16,15 @@ */ #ifndef _RESPOPUP_H_INCLUDED_ #define _RESPOPUP_H_INCLUDED_ +#include "autoconfig.h" namespace ResultPopup { enum Options {showExpand = 0x1, showSubs = 0x2, isMain = 0x3, showSaveOne = 0x4, showSaveSel = 0x8}; extern QMenu *create(QWidget *me, int opts, - RefCntr source, + std::shared_ptr source, Rcl::Doc& doc); - extern Rcl::Doc getParent(RefCntr source, + extern Rcl::Doc getParent(std::shared_ptr source, Rcl::Doc& doc); extern void copyFN(const Rcl::Doc &doc); extern void copyURL(const Rcl::Doc &doc); diff --git a/src/qtgui/restable.cpp b/src/qtgui/restable.cpp index b85da2f2..e4f2578f 100644 --- a/src/qtgui/restable.cpp +++ b/src/qtgui/restable.cpp @@ -35,9 +35,9 @@ #include #include "recoll.h" -#include "refcntr.h" +#include #include "docseq.h" -#include "debuglog.h" +#include "log.h" #include "restable.h" #include "guiutils.h" #include "reslistpager.h" @@ -49,6 +49,7 @@ #include "rclmain_w.h" #include "multisave.h" #include "appformime.h" +#include "transcode.h" static const QKeySequence quitKeySeq("Ctrl+q"); static const QKeySequence closeKeySeq("Ctrl+w"); @@ -66,8 +67,8 @@ public: virtual ~PlainToRichQtReslist() {} virtual string startMatch(unsigned int) { - return string(""); + return string(""); } virtual string endMatch() {return string("");} }; @@ -177,15 +178,17 @@ static string sizegetter(const string& fld, const Rcl::Doc& doc) static string dategetter(const string&, const Rcl::Doc& doc) { - char datebuf[100]; - datebuf[0] = 0; + string sdate; if (!doc.dmtime.empty() || !doc.fmtime.empty()) { + char datebuf[100]; + datebuf[0] = 0; time_t mtime = doc.dmtime.empty() ? atoll(doc.fmtime.c_str()) : atoll(doc.dmtime.c_str()); struct tm *tm = localtime(&mtime); strftime(datebuf, 99, "%Y-%m-%d", tm); + transcode(datebuf, sdate, RclConfig::getLocaleCharset(), "UTF-8"); } - return datebuf; + return sdate; } static string datetimegetter(const string&, const Rcl::Doc& doc) @@ -196,7 +199,7 @@ static string datetimegetter(const string&, const Rcl::Doc& doc) time_t mtime = doc.dmtime.empty() ? atoll(doc.fmtime.c_str()) : atoll(doc.dmtime.c_str()); struct tm *tm = localtime(&mtime); - strftime(datebuf, 99, "%Y-%m-%d %H:%M:%S %z", tm); + strftime(datebuf, 99, prefs.creslistdateformat.c_str(), tm); } return datebuf; } @@ -271,32 +274,32 @@ RecollModel::RecollModel(const QStringList fields, QObject *parent) int RecollModel::rowCount(const QModelIndex&) const { - LOGDEB2(("RecollModel::rowCount\n")); - if (m_source.isNull()) + LOGDEB2("RecollModel::rowCount\n"); + if (!m_source) return 0; return m_source->getResCnt(); } int RecollModel::columnCount(const QModelIndex&) const { - LOGDEB2(("RecollModel::columnCount\n")); + LOGDEB2("RecollModel::columnCount\n"); return m_fields.size(); } void RecollModel::readDocSource() { - LOGDEB(("RecollModel::readDocSource()\n")); + LOGDEB("RecollModel::readDocSource()\n"); beginResetModel(); endResetModel(); } -void RecollModel::setDocSource(RefCntr nsource) +void RecollModel::setDocSource(std::shared_ptr nsource) { - LOGDEB(("RecollModel::setDocSource\n")); - if (nsource.isNull()) { - m_source = RefCntr(); + LOGDEB("RecollModel::setDocSource\n"); + if (!nsource) { + m_source = std::shared_ptr(); } else { - m_source = RefCntr(new DocSource(theconfig, nsource)); + m_source = std::shared_ptr(new DocSource(theconfig, nsource)); m_hdata.clear(); m_source->getTerms(m_hdata); } @@ -317,7 +320,7 @@ void RecollModel::deleteColumn(int col) void RecollModel::addColumn(int col, const string& field) { - LOGDEB(("AddColumn: col %d fld [%s]\n", col, field.c_str())); + LOGDEB("AddColumn: col " << col << " fld [" << field << "]\n"); if (col >= 0 && col < int(m_fields.size())) { col++; vector::iterator it = m_fields.begin(); @@ -335,8 +338,9 @@ void RecollModel::addColumn(int col, const string& field) QVariant RecollModel::headerData(int idx, Qt::Orientation orientation, int role) const { - LOGDEB2(("RecollModel::headerData: idx %d orientation %s role %d\n", - idx, orientation == Qt::Vertical ? "vertical":"horizontal", role)); + LOGDEB2("RecollModel::headerData: idx " << idx << " orientation " << + (orientation == Qt::Vertical ? "vertical":"horizontal") << + " role " << role << "\n"); if (orientation == Qt::Vertical && role == Qt::DisplayRole) { return idx; } @@ -354,9 +358,9 @@ QVariant RecollModel::headerData(int idx, Qt::Orientation orientation, QVariant RecollModel::data(const QModelIndex& index, int role) const { - LOGDEB2(("RecollModel::data: row %d col %d role %d\n", index.row(), - index.column(), role)); - if (m_source.isNull() || role != Qt::DisplayRole || !index.isValid() || + LOGDEB2("RecollModel::data: row " << index.row() << " col " << + index.column() << " role " << role << "\n"); + if (!m_source || role != Qt::DisplayRole || !index.isValid() || index.column() >= int(m_fields.size())) { return QVariant(); } @@ -375,7 +379,7 @@ QVariant RecollModel::data(const QModelIndex& index, int role) const void RecollModel::saveAsCSV(FILE *fp) { - if (m_source.isNull()) + if (!m_source) return; int cols = columnCount(); @@ -408,9 +412,10 @@ void RecollModel::saveAsCSV(FILE *fp) // This gets called when the column headers are clicked void RecollModel::sort(int column, Qt::SortOrder order) { - if (m_ignoreSort) + if (m_ignoreSort) { return; - LOGDEB(("RecollModel::sort(%d, %d)\n", column, int(order))); + } + LOGDEB("RecollModel::sort(" << column << ", " << order << ")\n"); DocSeqSortSpec spec; if (column >= 0 && column < int(m_fields.size())) { @@ -419,7 +424,7 @@ void RecollModel::sort(int column, Qt::SortOrder order) !stringlowercmp("datetime", spec.field)) spec.field = "mtime"; spec.desc = order == Qt::AscendingOrder ? false : true; - } + } emit sortDataChanged(spec); } @@ -523,8 +528,8 @@ void ResTable::init() QKeySequence seq("Esc"); QShortcut *sc = new QShortcut(seq, this); - connect(sc, SIGNAL (activated()), - tableView->selectionModel(), SLOT (clear())); + connect(sc, SIGNAL(activated()), + tableView->selectionModel(), SLOT(clear())); connect(tableView->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex &)), this, SLOT(onTableView_currentChanged(const QModelIndex&))); @@ -592,7 +597,7 @@ int ResTable::getDetailDocNumOrTopRow() void ResTable::makeRowVisible(int row) { - LOGDEB(("ResTable::showRow(%d)\n", row)); + LOGDEB("ResTable::showRow(" << row << ")\n"); QModelIndex modelIndex = m_model->index(row, 0); tableView->scrollTo(modelIndex, QAbstractItemView::PositionAtTop); tableView->selectionModel()->clear(); @@ -611,7 +616,7 @@ void ResTable::saveColState() QHeaderView *header = tableView->horizontalHeader(); const vector& vf = m_model->getFields(); if (!header) { - LOGERR(("ResTable::saveColState: no table header ??\n")); + LOGERR("ResTable::saveColState: no table header ??\n"); return; } @@ -622,7 +627,7 @@ void ResTable::saveColState() for (int vi = 0; vi < header->count(); vi++) { int li = header->logicalIndex(vi); if (li < 0 || li >= int(vf.size())) { - LOGERR(("saveColState: logical index beyond list size!\n")); + LOGERR("saveColState: logical index beyond list size!\n"); continue; } newfields.push_back(QString::fromUtf8(vf[li].c_str())); @@ -634,10 +639,10 @@ void ResTable::saveColState() void ResTable::onTableView_currentChanged(const QModelIndex& index) { - LOGDEB2(("ResTable::onTableView_currentChanged(%d, %d)\n", - index.row(), index.column())); + LOGDEB2("ResTable::onTableView_currentChanged(" << index.row() << ", " << + index.column() << ")\n"); - if (!m_model || m_model->getDocSource().isNull()) + if (!m_model || !m_model->getDocSource()) return; Rcl::Doc doc; if (m_model->getDocSource()->getDoc(index.row(), doc)) { @@ -653,21 +658,21 @@ void ResTable::onTableView_currentChanged(const QModelIndex& index) void ResTable::on_tableView_entered(const QModelIndex& index) { - LOGDEB2(("ResTable::on_tableView_entered(%d, %d)\n", - index.row(), index.column())); + LOGDEB2("ResTable::on_tableView_entered(" << index.row() << ", " << + index.column() << ")\n"); if (!tableView->selectionModel()->hasSelection()) onTableView_currentChanged(index); } void ResTable::takeFocus() { -// LOGDEB(("resTable: take focus\n")); +// LOGDEB("resTable: take focus\n"); tableView->setFocus(Qt::ShortcutFocusReason); } -void ResTable::setDocSource(RefCntr nsource) +void ResTable::setDocSource(std::shared_ptr nsource) { - LOGDEB(("ResTable::setDocSource\n")); + LOGDEB("ResTable::setDocSource\n"); if (m_model) m_model->setDocSource(nsource); if (m_pager) @@ -679,13 +684,13 @@ void ResTable::setDocSource(RefCntr nsource) void ResTable::resetSource() { - LOGDEB(("ResTable::resetSource\n")); - setDocSource(RefCntr()); + LOGDEB("ResTable::resetSource\n"); + setDocSource(std::shared_ptr()); } void ResTable::saveAsCSV() { - LOGDEB(("ResTable::saveAsCSV\n")); + LOGDEB("ResTable::saveAsCSV\n"); if (!m_model) return; QString s = @@ -709,8 +714,8 @@ void ResTable::saveAsCSV() // This is called when the sort order is changed from another widget void ResTable::onSortDataChanged(DocSeqSortSpec spec) { - LOGDEB(("ResTable::onSortDataChanged: [%s] desc %d\n", - spec.field.c_str(), int(spec.desc))); + LOGDEB("ResTable::onSortDataChanged: [" << spec.field << "] desc " << + spec.desc << "\n"); QHeaderView *header = tableView->horizontalHeader(); if (!header || !m_model) return; @@ -734,7 +739,7 @@ void ResTable::onSortDataChanged(DocSeqSortSpec spec) void ResTable::resetSort() { - LOGDEB(("ResTable::resetSort()\n")); + LOGDEB("ResTable::resetSort()\n"); QHeaderView *header = tableView->horizontalHeader(); if (header) header->setSortIndicator(-1, Qt::AscendingOrder); @@ -745,7 +750,7 @@ void ResTable::resetSort() void ResTable::readDocSource(bool resetPos) { - LOGDEB(("ResTable::readDocSource(%d)\n", int(resetPos))); + LOGDEB("ResTable::readDocSource(" << resetPos << ")\n"); if (resetPos) tableView->verticalScrollBar()->setSliderPosition(0); @@ -761,7 +766,7 @@ void ResTable::linkWasClicked(const QUrl &url) } QString s = url.toString(); const char *ascurl = s.toUtf8(); - LOGDEB(("ResTable::linkWasClicked: [%s]\n", ascurl)); + LOGDEB("ResTable::linkWasClicked: [" << ascurl << "]\n"); int i = atoi(ascurl+1) -1; int what = ascurl[0]; @@ -782,7 +787,7 @@ void ResTable::linkWasClicked(const QUrl &url) // Open parent folder case 'F': { - emit editRequested(ResultPopup::getParent(RefCntr(), + emit editRequested(ResultPopup::getParent(std::shared_ptr(), m_detaildoc)); } break; @@ -790,10 +795,15 @@ void ResTable::linkWasClicked(const QUrl &url) case 'P': case 'E': { - if (what == 'P') - emit docPreviewClicked(i, m_detaildoc, 0); - else + if (what == 'P') { + if (m_ismainres) { + emit docPreviewClicked(i, m_detaildoc, 0); + } else { + emit previewRequested(m_detaildoc); + } + } else { emit editRequested(m_detaildoc); + } } break; @@ -816,14 +826,14 @@ void ResTable::linkWasClicked(const QUrl &url) break; default: - LOGERR(("ResTable::linkWasClicked: bad link [%s]\n", ascurl)); + LOGERR("ResTable::linkWasClicked: bad link [" << ascurl << "]\n"); break;// ?? } } void ResTable::onDoubleClick(const QModelIndex& index) { - if (!m_model || m_model->getDocSource().isNull()) + if (!m_model || !m_model->getDocSource()) return; Rcl::Doc doc; if (m_model->getDocSource()->getDoc(index.row(), doc)) { @@ -839,7 +849,8 @@ void ResTable::onDoubleClick(const QModelIndex& index) void ResTable::createPopupMenu(const QPoint& pos) { - LOGDEB(("ResTable::createPopupMenu: m_detaildocnum %d\n", m_detaildocnum)); + LOGDEB("ResTable::createPopupMenu: m_detaildocnum " << m_detaildocnum << + "\n"); if (m_detaildocnum >= 0 && m_model) { int opts = m_ismainres? ResultPopup::isMain : 0; @@ -877,7 +888,7 @@ void ResTable::menuSaveToFile() void ResTable::menuSaveSelection() { - if (m_model == 0 || m_model->getDocSource().isNull()) + if (m_model == 0 || !m_model->getDocSource()) return; QModelIndexList indexl = tableView->selectionModel()->selectedRows(); @@ -899,7 +910,7 @@ void ResTable::menuSaveSelection() void ResTable::menuPreviewParent() { if (m_detaildocnum >= 0 && m_model && - m_model->getDocSource().isNotNull()) { + m_model->getDocSource()) { Rcl::Doc pdoc = ResultPopup::getParent(m_model->getDocSource(), m_detaildoc); if (pdoc.mimetype == "inode/directory") { @@ -912,7 +923,7 @@ void ResTable::menuPreviewParent() void ResTable::menuOpenParent() { - if (m_detaildocnum >= 0 && m_model && m_model->getDocSource().isNotNull()) + if (m_detaildocnum >= 0 && m_model && m_model->getDocSource()) emit editRequested( ResultPopup::getParent(m_model->getDocSource(), m_detaildoc)); } @@ -970,7 +981,8 @@ void ResTable::menuShowSubDocs() void ResTable::createHeaderPopupMenu(const QPoint& pos) { - LOGDEB(("ResTable::createHeaderPopupMenu(%d, %d)\n", pos.x(), pos.y())); + LOGDEB("ResTable::createHeaderPopupMenu(" << pos.x() << ", " << + pos.y() << ")\n"); QHeaderView *header = tableView->horizontalHeader(); if (!header || !m_model) return; @@ -1016,7 +1028,8 @@ void ResTable::addColumn() if (!m_model) return; QAction *action = (QAction *)sender(); - LOGDEB(("addColumn: text %s, data %s\n", qs2utf8s(action->text()).c_str(), - qs2utf8s(action->data().toString()).c_str())); + LOGDEB("addColumn: text " << qs2utf8s(action->text()) << ", data " << + qs2utf8s(action->data().toString()) << "\n"); m_model->addColumn(m_popcolumn, qs2utf8s(action->data().toString())); } + diff --git a/src/qtgui/restable.h b/src/qtgui/restable.h index 9e19ea28..d032939e 100644 --- a/src/qtgui/restable.h +++ b/src/qtgui/restable.h @@ -16,15 +16,16 @@ */ #ifndef _RESTABLE_H_INCLUDED_ #define _RESTABLE_H_INCLUDED_ +#include "autoconfig.h" #include #include #include #include +#include #include "ui_restable.h" -#include "refcntr.h" #include "docseq.h" #include "plaintorich.h" @@ -50,8 +51,8 @@ public: virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); // Specific methods virtual void readDocSource(); - virtual void setDocSource(RefCntr nsource); - virtual RefCntr getDocSource() {return m_source;} + virtual void setDocSource(std::shared_ptr nsource); + virtual std::shared_ptr getDocSource() {return m_source;} virtual void deleteColumn(int); virtual const std::vector& getFields() {return m_fields;} virtual const std::map& getAllFields() @@ -72,7 +73,7 @@ signals: void sortDataChanged(DocSeqSortSpec); private: - mutable RefCntr m_source; + mutable std::shared_ptr m_source; std::vector m_fields; std::vector m_getters; static std::map o_displayableFields; @@ -126,7 +127,7 @@ public: public slots: virtual void onTableView_currentChanged(const QModelIndex&); virtual void on_tableView_entered(const QModelIndex& index); - virtual void setDocSource(RefCntr nsource); + virtual void setDocSource(std::shared_ptr nsource); virtual void saveColState(); virtual void resetSource(); virtual void readDocSource(bool resetPos = true); diff --git a/src/qtgui/rtitool.cpp b/src/qtgui/rtitool.cpp index 4e46e4a4..085fd5f1 100644 --- a/src/qtgui/rtitool.cpp +++ b/src/qtgui/rtitool.cpp @@ -1,3 +1,4 @@ +#ifndef _WIN32 /* Copyright (C) 2005 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 @@ -17,12 +18,11 @@ #include "autoconfig.h" #include -#include -#include +#include "safesysstat.h" +#include "safeunistd.h" #include -#include + #include -using std::string; #include #include @@ -35,6 +35,8 @@ using std::string; #include "readfile.h" #include "execmd.h" +using std::string; + static const char *rautostartfile = ".config/autostart/recollindex.desktop"; // Just in case we don't find the file in the shared dir, have a @@ -61,7 +63,7 @@ void RTIToolW::init() connect(this->sesCB, SIGNAL(clicked(bool)), this, SLOT(sesclicked(bool))); string autostartfile = path_cat(path_home(), rautostartfile); - if (access(autostartfile.c_str(), 0) == 0) { + if (path_exists(autostartfile)) { sesCB->setChecked(true); } } @@ -81,7 +83,7 @@ void RTIToolW::accept() if (sesCB->isChecked()) { // Setting up daemon indexing autostart - if (::access(autostartfile.c_str(), 0) == 0) { + if (path_exists(autostartfile)) { QString msg = tr("Replacing: ") + QString::fromLocal8Bit(autostartfile.c_str()); @@ -97,7 +99,7 @@ void RTIToolW::accept() if (theconfig) { string sourcefile = path_cat(theconfig->getDatadir(), "examples"); sourcefile = path_cat(sourcefile, "recollindex.desktop"); - if (::access(sourcefile.c_str(), 0) == 0) { + if (path_exists(sourcefile)) { file_to_string(sourcefile, text); } } @@ -139,7 +141,7 @@ void RTIToolW::accept() exitdial = true; } else { // Turning autostart off - if (::access(autostartfile.c_str(), 0) == 0) { + if (path_exists(autostartfile)) { QString msg = tr("Deleting: ") + QString::fromLocal8Bit(autostartfile.c_str()); @@ -173,3 +175,4 @@ out: if (exitdial) QDialog::accept(); } +#endif diff --git a/src/qtgui/rtitool.h b/src/qtgui/rtitool.h index 8b64e890..baa89036 100644 --- a/src/qtgui/rtitool.h +++ b/src/qtgui/rtitool.h @@ -31,10 +31,17 @@ class RTIToolW : public QDialog, public Ui::RTIToolW { init(); } public slots: +#ifdef _WIN32 + void sesclicked(bool) {} + void accept() {} +private: + void init() {} +#else void sesclicked(bool); void accept(); private: void init(); +#endif }; diff --git a/src/qtgui/searchclause_w.cpp b/src/qtgui/searchclause_w.cpp index 6d4fa584..8f6be03d 100644 --- a/src/qtgui/searchclause_w.cpp +++ b/src/qtgui/searchclause_w.cpp @@ -17,8 +17,7 @@ #include "autoconfig.h" #include "recoll.h" -#include "debuglog.h" - +#include "log.h" #include "searchclause_w.h" #include @@ -142,7 +141,7 @@ SearchDataClause *SearchClauseW::getClause() void SearchClauseW::setFromClause(SearchDataClauseSimple *cl) { - LOGDEB(("SearchClauseW::setFromClause\n")); + LOGDEB("SearchClauseW::setFromClause\n" ); switch(cl->getTp()) { case SCLT_OR: if (cl->getexclude()) tpChange(2); else tpChange(0); break; case SCLT_AND: tpChange(1); break; @@ -151,7 +150,7 @@ void SearchClauseW::setFromClause(SearchDataClauseSimple *cl) case SCLT_FILENAME: tpChange(5); break; default: return; } - LOGDEB(("SearchClauseW::setFromClause: calling erase\n")); + LOGDEB("SearchClauseW::setFromClause: calling erase\n" ); clear(); QString text = QString::fromUtf8(cl->gettext().c_str()); @@ -220,3 +219,4 @@ void SearchClauseW::tpChange(int index) fldCMB->show(); } } + diff --git a/src/qtgui/snippets_w.cpp b/src/qtgui/snippets_w.cpp index 4672fef8..6e4dd0ac 100644 --- a/src/qtgui/snippets_w.cpp +++ b/src/qtgui/snippets_w.cpp @@ -16,7 +16,6 @@ */ #include "autoconfig.h" -#include #include #include @@ -33,7 +32,7 @@ using namespace std; #endif #include -#include "debuglog.h" +#include "log.h" #include "recoll.h" #include "snippets_w.h" #include "guiutils.h" @@ -53,8 +52,8 @@ class PlainToRichQtSnippets : public PlainToRich { public: virtual string startMatch(unsigned int) { - return string(""); + return string(""); } virtual string endMatch() { @@ -65,7 +64,7 @@ static PlainToRichQtSnippets g_hiliter; void SnippetsW::init() { - if (m_source.isNull()) + if (!m_source) return; QPushButton *searchButton = new QPushButton(tr("Search")); @@ -171,7 +170,7 @@ void SnippetsW::init() } list lr; if (!g_hiliter.plaintorich(it->snippet, lr, hdata)) { - LOGDEB1(("No match for [%s]\n", it->snippet.c_str())); + LOGDEB1("No match for [" << (it->snippet) << "]\n" ); continue; } nomatch = false; @@ -239,7 +238,7 @@ void SnippetsW::slotSearchTextChanged(const QString& txt) void SnippetsW::linkWasClicked(const QUrl &url) { string ascurl = (const char *)url.toString().toUtf8(); - LOGDEB(("Snippets::linkWasClicked: [%s]\n", ascurl.c_str())); + LOGDEB("Snippets::linkWasClicked: [" << (ascurl) << "]\n" ); if (ascurl.size() > 3) { int what = ascurl[0]; @@ -260,6 +259,7 @@ void SnippetsW::linkWasClicked(const QUrl &url) } } } - LOGERR(("Snippets::linkWasClicked: bad link [%s]\n", ascurl.c_str())); + LOGERR("Snippets::linkWasClicked: bad link [" << (ascurl) << "]\n" ); } + diff --git a/src/qtgui/snippets_w.h b/src/qtgui/snippets_w.h index 87ea2947..8c7dff86 100644 --- a/src/qtgui/snippets_w.h +++ b/src/qtgui/snippets_w.h @@ -16,10 +16,14 @@ */ #ifndef _SNIPPETS_W_H_INCLUDED_ #define _SNIPPETS_W_H_INCLUDED_ + +#include "autoconfig.h" + +#include + #include #include "rcldoc.h" -#include "refcntr.h" #include "docseq.h" #include "rclmain_w.h" @@ -29,7 +33,7 @@ class SnippetsW : public QWidget, public Ui::Snippets { Q_OBJECT public: - SnippetsW(Rcl::Doc doc, RefCntr source, QWidget* parent = 0) + SnippetsW(Rcl::Doc doc, std::shared_ptr source, QWidget* parent = 0) : QWidget(parent), m_doc(doc), m_source(source) { setupUi((QDialog*)this); @@ -48,7 +52,7 @@ signals: private: void init(); Rcl::Doc m_doc; - RefCntr m_source; + std::shared_ptr m_source; }; #endif /* _SNIPPETS_W_H_INCLUDED_ */ diff --git a/src/qtgui/specialindex.h b/src/qtgui/specialindex.h new file mode 100644 index 00000000..1ed775e3 --- /dev/null +++ b/src/qtgui/specialindex.h @@ -0,0 +1,53 @@ +/* + * 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _SPECIDX_W_H_INCLUDED_ +#define _SPECIDX_W_H_INCLUDED_ + +#include +#include + +#include "ui_specialindex.h" + +class QPushButton; + +class SpecIdxW : public QDialog, public Ui::SpecIdxW { + Q_OBJECT + +public: + + SpecIdxW(QWidget * parent = 0) + : QDialog(parent) + { + setupUi(this); + selPatsLE->setEnabled(false); + connect(browsePB, SIGNAL(clicked()), this, SLOT(onBrowsePB_clicked())); + connect(targLE, SIGNAL(textChanged(const QString&)), + this, SLOT(onTargLE_textChanged(const QString&))); + } + bool noRetryFailed(); + bool eraseFirst(); + std::vector selpatterns(); + std::string toptarg(); + +public slots: + + void onTargLE_textChanged(const QString&); + void onBrowsePB_clicked(); +}; + + +#endif /* _SPECIDX_W_H_INCLUDED_ */ diff --git a/src/qtgui/specialindex.ui b/src/qtgui/specialindex.ui new file mode 100644 index 00000000..a462f413 --- /dev/null +++ b/src/qtgui/specialindex.ui @@ -0,0 +1,148 @@ + + + SpecIdxW + + + Qt::WindowModal + + + + 0 + 0 + 413 + 191 + + + + Special Indexing + + + + + + + + Do not retry previously failed files. + + + + + + + Else only modified or failed files will be processed. + + + Erase selected files data before indexing. + + + + + + + + + + 8 + 0 + + + + + 300 + 0 + + + + Directory to recursively index. This must be inside the regular indexed area<br> as defined in the configuration file (topdirs). + + + + + + + Browse + + + false + + + + + + + Start directory (else use regular topdirs): + + + false + + + + + + + + + + + Leave empty to select all files. You can use multiple space-separated shell-type patterns.<br>Patterns with embedded spaces should be quoted with double quotes.<br>Can only be used if the start target is set. + + + Selection patterns: + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SpecIdxW + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SpecIdxW + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/qtgui/spell_w.cpp b/src/qtgui/spell_w.cpp index c8fad345..4d743f6e 100644 --- a/src/qtgui/spell_w.cpp +++ b/src/qtgui/spell_w.cpp @@ -17,15 +17,11 @@ #include "autoconfig.h" #include -#include #include #include #include #include -using std::list; -using std::multimap; -using std::string; #include #include @@ -39,7 +35,7 @@ using std::string; #include #include -#include "debuglog.h" +#include "log.h" #include "recoll.h" #include "spell_w.h" #include "guiutils.h" @@ -49,11 +45,16 @@ using std::string; #include "rclhelp.h" #include "wasatorcl.h" #include "execmd.h" +#include "indexer.h" #ifdef RCL_USE_ASPELL #include "rclaspell.h" #endif +using std::list; +using std::multimap; +using std::string; + void SpellW::init() { m_c2t.clear(); @@ -74,15 +75,6 @@ void SpellW::init() expTypeCMB->addItem(tr("Show index statistics")); m_c2t.push_back(TYPECMB_STATS); - int typ = prefs.termMatchType; - vector::const_iterator it = - std::find(m_c2t.begin(), m_c2t.end(), typ); - if (it == m_c2t.end()) - it = m_c2t.begin(); - int cmbidx = it - m_c2t.begin(); - - expTypeCMB->setCurrentIndex(cmbidx); - // Stemming language combobox stemLangCMB->clear(); vector langs; @@ -98,7 +90,7 @@ void SpellW::init() (void)new HelpClient(this); HelpClient::installMap((const char *)this->objectName().toUtf8(), - "RCL.SEARCH.TERMEXPLORER"); + "RCL.SEARCH.GUI.TERMEXPLORER"); // signals and slots connections connect(baseWordLE, SIGNAL(textChanged(const QString&)), @@ -106,7 +98,7 @@ void SpellW::init() connect(baseWordLE, SIGNAL(returnPressed()), this, SLOT(doExpand())); connect(expandPB, SIGNAL(clicked()), this, SLOT(doExpand())); connect(dismissPB, SIGNAL(clicked()), this, SLOT(close())); - connect(expTypeCMB, SIGNAL(activated(int)), this, SLOT(modeSet(int))); + connect(expTypeCMB, SIGNAL(activated(int)), this, SLOT(onModeChanged(int))); resTW->setShowGrid(0); #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) @@ -123,11 +115,18 @@ void SpellW::init() resTW->setColumnWidth(1, 150); resTW->installEventFilter(this); - if (o_index_stripchars) { - caseSensCB->setEnabled(false); - caseSensCB->setEnabled(false); - } - modeSet(cmbidx); + int idx = cmbIdx((comboboxchoice)prefs.termMatchType); + expTypeCMB->setCurrentIndex(idx); + onModeChanged(idx); +} + +int SpellW::cmbIdx(comboboxchoice mode) +{ + vector::const_iterator it = + std::find(m_c2t.begin(), m_c2t.end(), mode); + if (it == m_c2t.end()) + it = m_c2t.begin(); + return it - m_c2t.begin(); } static const int maxexpand = 10000; @@ -148,7 +147,7 @@ void SpellW::doExpand() string reason; if (!maybeOpenDb(reason)) { QMessageBox::critical(0, "Recoll", QString(reason.c_str())); - LOGDEB(("SpellW::doExpand: db error: %s\n", reason.c_str())); + LOGDEB("SpellW::doExpand: db error: " << (reason) << "\n" ); return; } @@ -179,12 +178,12 @@ void SpellW::doExpand() string l_stemlang = qs2utf8s(stemLangCMB->currentText()); if (!rcldb->termMatch(mt, l_stemlang, expr, res, maxexpand)) { - LOGERR(("SpellW::doExpand:rcldb::termMatch failed\n")); + LOGERR("SpellW::doExpand:rcldb::termMatch failed\n" ); return; } statsLBL->setText(tr("Index: %1 documents, average length %2 terms." "%3 results") - .arg(dbs.dbdoccount).arg(dbs.dbavgdoclen, 0, 'f', 1) + .arg(dbs.dbdoccount).arg(dbs.dbavgdoclen, 0, 'f', 0) .arg(res.entries.size())); } @@ -193,19 +192,19 @@ void SpellW::doExpand() #ifdef RCL_USE_ASPELL case TYPECMB_ASPELL: { - LOGDEB(("SpellW::doExpand: aspelling\n")); + LOGDEB("SpellW::doExpand: aspelling\n" ); if (!aspell) { QMessageBox::warning(0, "Recoll", tr("Aspell init failed. " "Aspell not installed?")); - LOGDEB(("SpellW::doExpand: aspell init error\n")); + LOGDEB("SpellW::doExpand: aspell init error\n" ); return; } list suggs; if (!aspell->suggest(*rcldb, expr, suggs, reason)) { QMessageBox::warning(0, "Recoll", tr("Aspell expansion error. ")); - LOGERR(("SpellW::doExpand:suggest failed: %s\n", reason.c_str())); + LOGERR("SpellW::doExpand:suggest failed: " << (reason) << "\n" ); } for (list::const_iterator it = suggs.begin(); it != suggs.end(); it++) @@ -254,7 +253,7 @@ void SpellW::doExpand() for (vector::iterator it = res.entries.begin(); it != res.entries.end(); it++) { - LOGDEB2(("SpellW::expand: %6d [%s]\n", it->wcf, it->term.c_str())); + LOGDEB2("SpellW::expand: " << (it->wcf) << " [" << (it->term) << "]\n" ); char num[30]; if (it->wcf) sprintf(num, "%d / %d", it->docs, it->wcf); @@ -276,7 +275,7 @@ void SpellW::showStats() Rcl::DbStats res; if (!rcldb->dbStats(res)) { - LOGERR(("SpellW::doExpand:rcldb::dbStats failed\n")); + LOGERR("SpellW::doExpand:rcldb::dbStats failed\n" ); return; } @@ -290,23 +289,54 @@ void SpellW::showStats() resTW->setItem(row, 0, new QTableWidgetItem(tr("Average terms per document"))); resTW->setItem(row++, 1, new QTableWidgetItem( - QString::number(res.dbavgdoclen))); + QString::number(res.dbavgdoclen, 'f', 0))); resTW->setRowCount(row+1); resTW->setItem(row, 0, - new QTableWidgetItem(tr("Smallest document length"))); + new QTableWidgetItem(tr("Smallest document length (terms)"))); resTW->setItem(row++, 1, new QTableWidgetItem( QString::number(res.mindoclen))); resTW->setRowCount(row+1); resTW->setItem(row, 0, - new QTableWidgetItem(tr("Longest document length"))); + new QTableWidgetItem(tr("Longest document length (terms)"))); resTW->setItem(row++, 1, new QTableWidgetItem( QString::number(res.maxdoclen))); if (!theconfig) return; + ConfSimple cs(theconfig->getIdxStatusFile().c_str(), 1); + DbIxStatus st; + cs.get("fn", st.fn); + cs.get("docsdone", &st.docsdone); + cs.get("filesdone", &st.filesdone); + cs.get("fileerrors", &st.fileerrors); + cs.get("dbtotdocs", &st.dbtotdocs); + cs.get("totfiles", &st.totfiles); + + resTW->setRowCount(row+1); + resTW->setItem(row, 0, + new QTableWidgetItem(tr("Results from last indexing:"))); + resTW->setItem(row++, 1, new QTableWidgetItem("")); + resTW->setRowCount(row+1); + resTW->setItem(row, 0, + new QTableWidgetItem(tr(" Documents created/updated"))); + resTW->setItem(row++, 1, + new QTableWidgetItem(QString::number(st.docsdone))); + resTW->setRowCount(row+1); + resTW->setItem(row, 0, + new QTableWidgetItem(tr(" Files tested"))); + resTW->setItem(row++, 1, + new QTableWidgetItem(QString::number(st.filesdone))); + resTW->setRowCount(row+1); + resTW->setItem(row, 0, + new QTableWidgetItem(tr(" Unindexed files"))); + resTW->setItem(row++, 1, + new QTableWidgetItem(QString::number(st.fileerrors))); + + baseWordLE->setText(QString::fromLocal8Bit(theconfig->getDbDir().c_str())); + ExecCmd cmd; vector args; int status; @@ -314,9 +344,9 @@ void SpellW::showStats() args.push_back(theconfig->getDbDir()); string output; status = cmd.doexec("du", args, 0, &output); - int dbkbytes = 0; + long long dbkbytes = 0; if (!status) { - dbkbytes = atoi(output.c_str()); + dbkbytes = atoll(output.c_str()); } resTW->setRowCount(row+1); resTW->setItem(row, 0, @@ -332,10 +362,10 @@ void SpellW::showStats() string reason; string q = string("mime:") + *it; Rcl::SearchData *sd = wasaStringToRcl(theconfig, "", q, reason); - RefCntr rq(sd); + std::shared_ptr rq(sd); Rcl::Query query(rcldb); if (!query.setQuery(rq)) { - LOGERR(("Query setup failed: %s",query.getReason().c_str())); + LOGERR("Query setup failed: " << (query.getReason()) << "" ); return; } int cnt = query.getResCnt(); @@ -348,7 +378,7 @@ void SpellW::showStats() for (multimap::const_reverse_iterator it = mtbycnt.rbegin(); it != mtbycnt.rend(); it++) { resTW->setRowCount(row+1); - resTW->setItem(row, 0, new QTableWidgetItem( + resTW->setItem(row, 0, new QTableWidgetItem(QString(" ") + QString::fromUtf8(it->second.c_str()))); resTW->setItem(row++, 1, new QTableWidgetItem( QString::number(it->first))); @@ -373,12 +403,34 @@ void SpellW::textDoubleClicked(int row, int) emit(wordSelect(item->text())); } -void SpellW::modeSet(int idx) +void SpellW::onModeChanged(int idx) { if (idx < 0 || idx > int(m_c2t.size())) return; - comboboxchoice mode = m_c2t[idx]; + comboboxchoice mode = m_c2t[idx]; + setModeCommon(mode); +} + +void SpellW::setMode(comboboxchoice mode) +{ + expTypeCMB->setCurrentIndex(cmbIdx(mode)); + setModeCommon(mode); +} + +void SpellW::setModeCommon(comboboxchoice mode) +{ + if (m_prevmode == TYPECMB_STATS) { + baseWordLE->setText(""); + } + m_prevmode = mode; resTW->setRowCount(0); + if (o_index_stripchars) { + caseSensCB->setEnabled(false); + diacSensCB->setEnabled(false); + } else { + caseSensCB->setEnabled(true); + diacSensCB->setEnabled(true); + } if (mode == TYPECMB_STEM) { stemLangCMB->setEnabled(true); @@ -388,8 +440,6 @@ void SpellW::modeSet(int idx) caseSensCB->setEnabled(false); } else { stemLangCMB->setEnabled(false); - diacSensCB->setEnabled(true); - caseSensCB->setEnabled(true); } if (mode == TYPECMB_STATS) baseWordLE->setEnabled(false); @@ -401,6 +451,8 @@ void SpellW::modeSet(int idx) QStringList labels(tr("Item")); labels.push_back(tr("Value")); resTW->setHorizontalHeaderLabels(labels); + diacSensCB->setEnabled(false); + caseSensCB->setEnabled(false); doExpand(); } else { QStringList labels(tr("Term")); @@ -469,3 +521,4 @@ bool SpellW::eventFilter(QObject *target, QEvent *event) } return false; } + diff --git a/src/qtgui/spell_w.h b/src/qtgui/spell_w.h index faa99419..d1cfd6a3 100644 --- a/src/qtgui/spell_w.h +++ b/src/qtgui/spell_w.h @@ -25,34 +25,41 @@ #include "ui_spell.h" class SpellW : public QWidget, public Ui::SpellBase { - Q_OBJECT + Q_OBJECT; public: SpellW(QWidget* parent = 0) - : QWidget(parent) - { + : QWidget(parent), m_prevmode(TYPECMB_NONE) { setupUi(this); init(); } virtual bool eventFilter(QObject *target, QEvent *event ); + + enum comboboxchoice {TYPECMB_NONE, TYPECMB_WILD, TYPECMB_REG, TYPECMB_STEM, + TYPECMB_ASPELL, TYPECMB_STATS}; public slots: virtual void doExpand(); virtual void wordChanged(const QString&); virtual void textDoubleClicked(); virtual void textDoubleClicked(int, int); - virtual void modeSet(int); + virtual void setMode(comboboxchoice); +private slots: + virtual void onModeChanged(int); + signals: void wordSelect(QString); private: - enum comboboxchoice {TYPECMB_WILD, TYPECMB_REG, TYPECMB_STEM, - TYPECMB_ASPELL, TYPECMB_STATS}; // combobox index to expansion type - std::vector m_c2t; + std::vector m_c2t; + comboboxchoice m_prevmode; + void init(); void copy(); void showStats(); + int cmbIdx(comboboxchoice mode); + void setModeCommon(comboboxchoice mode); }; #endif /* _ASPELL_W_H_INCLUDED_ */ diff --git a/src/qtgui/ssearch_w.cpp b/src/qtgui/ssearch_w.cpp index d2e714eb..aa7bb01c 100644 --- a/src/qtgui/ssearch_w.cpp +++ b/src/qtgui/ssearch_w.cpp @@ -14,8 +14,11 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#include "autoconfig.h" + #include #include +#include #include #include @@ -30,11 +33,10 @@ #include #include -#include "debuglog.h" +#include "log.h" #include "guiutils.h" #include "searchdata.h" #include "ssearch_w.h" -#include "refcntr.h" #include "textsplit.h" #include "wasatorcl.h" #include "rclhelp.h" @@ -93,7 +95,7 @@ void SSearch::init() void SSearch::takeFocus() { - LOGDEB2(("SSearch: take focus\n")); + LOGDEB2("SSearch: take focus\n" ); queryText->setFocus(Qt::ShortcutFocusReason); // If the focus was already in the search entry, the text is not selected. // Do it for consistency @@ -103,15 +105,14 @@ void SSearch::takeFocus() void SSearch::timerDone() { QString qs = queryText->currentText(); - LOGDEB1(("SSearch::timerDone: qs [%s]\n", qs2utf8s(qs).c_str())); + LOGDEB1("SSearch::timerDone: qs [" << (qs2utf8s(qs)) << "]\n" ); searchTextChanged(qs); } void SSearch::searchTextChanged(const QString& text) { QString qs = queryText->currentText(); - LOGDEB1(("SSearch::searchTextChanged. ks %d qs [%s]\n", - m_keystroke, qs2utf8s(text).c_str())); + LOGDEB1("SSearch::searchTextChanged. ks " << (m_keystroke) << " qs [" << (qs2utf8s(text)) << "]\n" ); if (text.isEmpty()) { searchPB->setEnabled(false); clearqPB->setEnabled(false); @@ -128,8 +129,7 @@ void SSearch::searchTextChanged(const QString& text) m_disableAutosearch = true; string s; int cs = partialWord(s); - LOGDEB1(("SSearch::searchTextChanged: autosearch. cs %d s [%s]\n", - cs, s.c_str())); + LOGDEB1("SSearch::searchTextChanged: autosearch. cs " << (cs) << " s [" << (s) << "]\n" ); if (cs < 0) { startSimpleSearch(); } else if (!m_stroketimeout->isActive() && s.size() >= 2) { @@ -144,14 +144,14 @@ void SSearch::searchTextChanged(const QString& text) void SSearch::searchTypeChanged(int typ) { - LOGDEB(("Search type now %d\n", typ)); + LOGDEB("Search type now " << (typ) << "\n" ); // Adjust context help if (typ == SST_LANG) HelpClient::installMap((const char *)this->objectName().toUtf8(), "RCL.SEARCH.LANG"); else HelpClient::installMap((const char *)this->objectName().toUtf8(), - "RCL.SEARCH.SIMPLE"); + "RCL.SEARCH.GUI.SIMPLE"); // Also fix tooltips switch (typ) { @@ -186,7 +186,7 @@ void SSearch::searchTypeChanged(int typ) void SSearch::startSimpleSearch() { QString qs = queryText->currentText(); - LOGDEB(("SSearch::startSimpleSearch(): qs [%s]\n", qs2utf8s(qs).c_str())); + LOGDEB("SSearch::startSimpleSearch(): qs [" << (qs2utf8s(qs)) << "]\n" ); if (qs.length() == 0) return; @@ -199,7 +199,7 @@ void SSearch::startSimpleSearch() if (!startSimpleSearch(u8)) return; - LOGDEB(("startSimpleSearch: updating history\n")); + LOGDEB("startSimpleSearch: updating history\n" ); // Search terms history: // We want to have the new text at the top and any older identical // entry to be erased. There is no standard qt policy to do this ? @@ -240,7 +240,7 @@ string SSearch::asXML() bool SSearch::startSimpleSearch(const string& u8, int maxexp) { - LOGDEB(("SSearch::startSimpleSearch(%s)\n", u8.c_str())); + LOGDEB("SSearch::startSimpleSearch(" << (u8) << ")\n" ); string stemlang = prefs.stemlang(); ostringstream xml; @@ -307,9 +307,9 @@ bool SSearch::startSimpleSearch(const string& u8, int maxexp) xml << "\n"; m_xml = xml.str(); - LOGDEB(("SSearch::startSimpleSearch:xml:[%s]\n", m_xml.c_str())); + LOGDEB("SSearch::startSimpleSearch:xml:[" << (m_xml) << "]\n" ); - RefCntr rsdata(sdata); + std::shared_ptr rsdata(sdata); emit startSearch(rsdata, true); return true; } @@ -390,7 +390,7 @@ bool SSearch::hasSearchString() static const char* punct = " \t()<>\"'[]{}!^*.,:;\n\r"; void SSearch::addTerm(QString term) { - LOGDEB(("SSearch::AddTerm: [%s]\n", (const char *)term.toUtf8())); + LOGDEB("SSearch::AddTerm: [" << ((const char *)term.toUtf8()) << "]\n" ); string t = (const char *)term.toUtf8(); string::size_type pos = t.find_last_not_of(punct); if (pos == string::npos) @@ -412,8 +412,7 @@ void SSearch::addTerm(QString term) void SSearch::onWordReplace(const QString& o, const QString& n) { - LOGDEB(("SSearch::onWordReplace: o [%s] n [%s]\n", - qs2utf8s(o).c_str(), qs2utf8s(n).c_str())); + LOGDEB("SSearch::onWordReplace: o [" << (qs2utf8s(o)) << "] n [" << (qs2utf8s(n)) << "]\n" ); QString txt = queryText->currentText(); QRegExp exp = QRegExp(QString("\\b") + o + QString("\\b")); exp.setCaseSensitivity(Qt::CaseInsensitive); @@ -474,7 +473,7 @@ int SSearch::completionList(string s, QStringList& lst, int max) // Complete last word in input by querying db for all possible terms. void SSearch::completion() { - LOGDEB(("SSearch::completion\n")); + LOGDEB("SSearch::completion\n" ); m_disableAutosearch = true; m_stroketimeout->stop(); @@ -504,7 +503,7 @@ void SSearch::completion() return; } if (lst.size() >= maxdpy) { - LOGDEB0(("SSearch::completion(): truncating list\n")); + LOGDEB0("SSearch::completion(): truncating list\n" ); lst = lst.mid(0, maxdpy); lst.append("[...]"); } @@ -540,7 +539,7 @@ void SSearch::completionTermChosen(const QString& text) void SSearch::wrapupCompletion() { - LOGDEB(("SSearch::wrapupCompletion\n")); + LOGDEB("SSearch::wrapupCompletion\n" ); queryText->clear(); queryText->addItems(prefs.ssearchHistory); @@ -705,9 +704,7 @@ bool SSearch::eventFilter(QObject *target, QEvent *event) event->type() == QEvent::UpdateRequest || event->type() == QEvent::Paint) return false; - LOGDEB2(("SSearch::eventFilter: target %p (%p) type %s\n", - target, queryText->lineEdit(), - eventTypeToStr(event->type()))); + LOGDEB2("SSearch::eventFilter: target " << (target) << " (" << (queryText->lineEdit()) << ") type " << (eventTypeToStr(event->type())) << "\n" ); #endif if (target == queryText->view()) { @@ -723,16 +720,15 @@ bool SSearch::eventFilter(QObject *target, QEvent *event) if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = (QKeyEvent *)event; - LOGDEB1(("SSearch::eventFilter: keyPress (m_escape %d) key %d\n", - m_escape, ke->key())); + LOGDEB1("SSearch::eventFilter: keyPress (m_escape " << (m_escape) << ") key " << (ke->key()) << "\n" ); if (ke->key() == Qt::Key_Escape) { - LOGDEB(("Escape\n")); + LOGDEB("Escape\n" ); m_escape = true; m_disableAutosearch = true; m_stroketimeout->stop(); return true; } else if (m_escape && ke->key() == Qt::Key_Space) { - LOGDEB(("Escape space\n")); + LOGDEB("Escape space\n" ); ke->accept(); completion(); m_escape = false; @@ -748,11 +744,11 @@ bool SSearch::eventFilter(QObject *target, QEvent *event) if (prefs.ssearchAsYouType) { m_disableAutosearch = false; QString qs = queryText->currentText(); - LOGDEB0(("SSearch::eventFilter: start timer, qs [%s]\n", - qs2utf8s(qs).c_str())); + LOGDEB0("SSearch::eventFilter: start timer, qs [" << (qs2utf8s(qs)) << "]\n" ); m_stroketimeout->start(strokeTimeoutMS); } } } return false; } + diff --git a/src/qtgui/ssearch_w.h b/src/qtgui/ssearch_w.h index cb9b2841..91d721eb 100644 --- a/src/qtgui/ssearch_w.h +++ b/src/qtgui/ssearch_w.h @@ -16,6 +16,7 @@ */ #ifndef _SSEARCH_W_H_INCLUDED_ #define _SSEARCH_W_H_INCLUDED_ +#include "autoconfig.h" #include @@ -26,7 +27,7 @@ class QTimer; #include "recoll.h" #include "searchdata.h" -#include "refcntr.h" +#include #include "ui_ssearchb.h" @@ -72,7 +73,7 @@ public slots: virtual void takeFocus(); signals: - void startSearch(RefCntr, bool); + void startSearch(std::shared_ptr, bool); void clearSearch(); private: bool m_escape; diff --git a/src/qtgui/uiprefs.ui b/src/qtgui/uiprefs.ui index b6ee9dfd..01708237 100644 --- a/src/qtgui/uiprefs.ui +++ b/src/qtgui/uiprefs.ui @@ -36,7 +36,7 @@ - Highlight color for query terms + Highlight CSS style for query terms false @@ -44,7 +44,7 @@ - + 50 @@ -314,6 +314,16 @@ + + + + Show warning when opening temporary file. + + + true + + + @@ -778,6 +788,56 @@ May be slow for big documents. + + + + QFrame::HLine + + + QFrame::Sunken + + + + + + + + + + 1 + 0 + + + + Synonyms file + + + false + + + + + + + Enable + + + + + + + + 30 + 0 + + + + Choose + + + + + diff --git a/src/qtgui/uiprefs_w.cpp b/src/qtgui/uiprefs_w.cpp index b1f685f2..72a9ae70 100644 --- a/src/qtgui/uiprefs_w.cpp +++ b/src/qtgui/uiprefs_w.cpp @@ -16,7 +16,7 @@ */ #include "autoconfig.h" -#include +#include "safesysstat.h" #include #include @@ -45,12 +45,11 @@ #include "recoll.h" #include "guiutils.h" -#include "rcldb.h" #include "rclconfig.h" #include "pathut.h" #include "uiprefs_w.h" #include "viewaction_w.h" -#include "debuglog.h" +#include "log.h" #include "editdialog.h" #include "rclmain_w.h" #include "ptrans_w.h" @@ -73,6 +72,7 @@ void UIPrefsDialog::init() connect(stylesheetPB, SIGNAL(clicked()),this, SLOT(showStylesheetDialog())); connect(resetSSPB, SIGNAL(clicked()), this, SLOT(resetStylesheet())); connect(snipCssPB, SIGNAL(clicked()),this, SLOT(showSnipCssDialog())); + connect(synFilePB, SIGNAL(clicked()),this, SLOT(showSynFileDialog())); connect(resetSnipCssPB, SIGNAL(clicked()), this, SLOT(resetSnipCss())); connect(idxLV, SIGNAL(itemSelectionChanged()), @@ -139,6 +139,7 @@ void UIPrefsDialog::setFromPrefs() keepSortCB->setChecked(prefs.keepSort); showTrayIconCB->setChecked(prefs.showTrayIcon); closeToTrayCB->setChecked(prefs.closeToTray); + showTempFileWarningCB->setChecked(prefs.showTempFileWarning == -1); previewHtmlCB->setChecked(prefs.previewHtml); switch (prefs.previewPlainPre) { case PrefsPack::PP_BR: @@ -153,7 +154,7 @@ void UIPrefsDialog::setFromPrefs() break; } // Query terms color - qtermColorLE->setText(prefs.qtermcolor); + qtermStyleLE->setText(prefs.qtermstyle); // Abstract snippet separator string abssepLE->setText(prefs.abssep); dateformatLE->setText(prefs.reslistdateformat); @@ -214,6 +215,15 @@ void UIPrefsDialog::setFromPrefs() autoSuffsCB->setChecked(prefs.autoSuffsEnable); autoSuffsLE->setText(prefs.autoSuffs); + synFileCB->setChecked(prefs.synFileEnable); + synFile = prefs.synFile; + if (synFile.isEmpty()) { + synFilePB->setText(tr("Choose")); + } else { + string nm = path_getsimple((const char *)synFile.toLocal8Bit()); + synFilePB->setText(QString::fromLocal8Bit(nm.c_str())); + } + // Initialize the extra indexes listboxes idxLV->clear(); for (list::iterator it = prefs.allExtraDbs.begin(); @@ -276,7 +286,7 @@ void UIPrefsDialog::accept() prefs.collapseDuplicates = collapseDupsCB->isChecked(); prefs.maxhltextmbs = maxHLTSB->value(); - prefs.qtermcolor = qtermColorLE->text(); + prefs.qtermstyle = qtermStyleLE->text(); prefs.abssep = abssepLE->text(); prefs.reslistdateformat = dateformatLE->text(); prefs.creslistdateformat = (const char*)prefs.reslistdateformat.toUtf8(); @@ -313,6 +323,8 @@ void UIPrefsDialog::accept() prefs.keepSort = keepSortCB->isChecked(); prefs.showTrayIcon = showTrayIconCB->isChecked(); prefs.closeToTray = closeToTrayCB->isChecked(); + prefs.showTempFileWarning = showTempFileWarningCB->isChecked() ? + -1 : 1024; prefs.previewHtml = previewHtmlCB->isChecked(); if (plainBRRB->isChecked()) { @@ -326,10 +338,12 @@ void UIPrefsDialog::accept() prefs.syntAbsLen = syntlenSB->value(); prefs.syntAbsCtx = syntctxSB->value(); - prefs.autoSuffsEnable = autoSuffsCB->isChecked(); prefs.autoSuffs = autoSuffsLE->text(); + prefs.synFileEnable = synFileCB->isChecked(); + prefs.synFile = synFile; + prefs.allExtraDbs.clear(); prefs.activeExtraDbs.clear(); for (int i = 0; i < idxLV->count(); i++) { @@ -360,6 +374,7 @@ void UIPrefsDialog::editParaFormat() if (result == QDialog::Accepted) paraFormat = dialog.plainTextEdit->toPlainText(); } + void UIPrefsDialog::editHeaderText() { EditDialog dialog(this); @@ -441,6 +456,15 @@ void UIPrefsDialog::resetSnipCss() snipCssPB->setText(tr("Choose")); } +void UIPrefsDialog::showSynFileDialog() +{ + synFile = myGetFileName(false, "Select synonyms file", true); + if (synFile.isEmpty()) + return; + string nm = path_getsimple((const char *)synFile.toLocal8Bit()); + synFilePB->setText(QString::fromLocal8Bit(nm.c_str())); +} + void UIPrefsDialog::resetReslistFont() { reslistFontFamily = ""; @@ -533,6 +557,9 @@ void UIPrefsDialog::delExtraDbPB_clicked() static bool samedir(const string& dir1, const string& dir2) { +#ifdef _WIN32 + return !dir1.compare(dir2); +#else struct stat st1, st2; if (stat(dir1.c_str(), &st1)) return false; @@ -542,6 +569,7 @@ static bool samedir(const string& dir1, const string& dir2) return true; } return false; +#endif } void UIPrefsDialog::on_showTrayIconCB_clicked() @@ -565,7 +593,7 @@ void UIPrefsDialog::addExtraDbPB_clicked() if (input.isEmpty()) return; string dbdir = (const char *)input.toLocal8Bit(); - if (access(path_cat(dbdir, "recoll.conf").c_str(), 0) == 0) { + if (path_exists(path_cat(dbdir, "recoll.conf"))) { // Chosen dir is config dir. RclConfig conf(&dbdir); dbdir = conf.getDbDir(); @@ -578,8 +606,7 @@ void UIPrefsDialog::addExtraDbPB_clicked() } } - LOGDEB(("ExtraDbDial: got: [%s]\n", dbdir.c_str())); - path_catslash(dbdir); + LOGDEB("ExtraDbDial: got: [" << (dbdir) << "]\n" ); bool stripped; if (!Rcl::Db::testDbDir(dbdir, &stripped)) { QMessageBox::warning(0, "Recoll", @@ -614,3 +641,4 @@ void UIPrefsDialog::addExtraDbPB_clicked() item->setCheckState(Qt::Checked); idxLV->sortItems(); } + diff --git a/src/qtgui/uiprefs_w.h b/src/qtgui/uiprefs_w.h index a422c0d5..62077c2b 100644 --- a/src/qtgui/uiprefs_w.h +++ b/src/qtgui/uiprefs_w.h @@ -42,13 +42,16 @@ public: int reslistFontSize; QString qssFile; QString snipCssFile; + QString synFile; virtual void init(); + void setFromPrefs(); public slots: virtual void showFontDialog(); virtual void resetReslistFont(); virtual void showStylesheetDialog(); + virtual void showSynFileDialog(); virtual void showSnipCssDialog(); virtual void resetStylesheet(); virtual void resetSnipCss(); @@ -65,7 +68,7 @@ public slots: virtual void editHeaderText(); virtual void extradDbSelectChanged(); virtual void extraDbEditPtrans(); - + signals: void uiprefsDone(); @@ -77,7 +80,6 @@ private: // Locally stored data (pending ok/cancel) QString paraFormat; QString headerText; - void setFromPrefs(); ViewAction *m_viewAction; RclMain *m_mainWindow; }; diff --git a/src/qtgui/viewaction_w.cpp b/src/qtgui/viewaction_w.cpp index e3cb4792..97a52fe9 100644 --- a/src/qtgui/viewaction_w.cpp +++ b/src/qtgui/viewaction_w.cpp @@ -14,6 +14,7 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#include "autoconfig.h" #include @@ -33,7 +34,7 @@ using namespace std; #include #include "recoll.h" -#include "debuglog.h" +#include "log.h" #include "guiutils.h" #include "viewaction_w.h" @@ -212,6 +213,9 @@ void ViewAction::editActions() string sact = (const char *)newActionLE->text().toLocal8Bit(); trimstring(sact); +#ifdef _WIN32 + path_slashize(sact); +#endif for (list::const_iterator mit = mtypes.begin(); mit != mtypes.end(); mit++) { set::iterator xit = viewerXs.find(*mit); @@ -233,3 +237,4 @@ void ViewAction::editActions() theconfig->setMimeViewerAllEx(s); fillLists(); } + diff --git a/src/qtgui/webcache.cpp b/src/qtgui/webcache.cpp new file mode 100644 index 00000000..2c75bd46 --- /dev/null +++ b/src/qtgui/webcache.cpp @@ -0,0 +1,365 @@ +/* Copyright (C) 2016 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include "autoconfig.h" + +#include +#include +#include +#include + +#ifdef _WIN32 +#define USING_STD_REGEX +#endif + +#ifndef USING_STD_REGEX +#include +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "recoll.h" +#include "webcache.h" +#include "beaglequeuecache.h" +#include "circache.h" +#include "conftree.h" +#include "rclmain_w.h" + +using namespace std; + +class CEnt { +public: + CEnt(const string& ud, const string& ur, const string& mt) + : udi(ud), url(ur), mimetype(mt) { + } + string udi; + string url; + string mimetype; +}; + +class WebcacheModelInternal { +public: + std::shared_ptr cache; + vector all; + vector disp; +}; + +WebcacheModel::WebcacheModel(QObject *parent) + : QAbstractTableModel(parent), m(new WebcacheModelInternal()) +{ + //qDebug() << "WebcacheModel::WebcacheModel()"; + reload(); +} +WebcacheModel::~WebcacheModel() +{ + delete m; +} + +void WebcacheModel::reload() +{ + m->cache = + std::shared_ptr(new BeagleQueueCache(theconfig)); + m->all.clear(); + m->disp.clear(); + + if (m->cache) { + bool eof; + m->cache->cc()->rewind(eof); + while (!eof) { + string udi, sdic; + m->cache->cc()->getCurrent(udi, sdic); + ConfSimple dic(sdic); + string mime, url; + dic.get("mimetype", mime); + dic.get("url", url); + if (!udi.empty()) { + m->all.push_back(CEnt(udi, url, mime)); + m->disp.push_back(CEnt(udi, url, mime)); + } + if (!m->cache->cc()->next(eof)) + break; + } + } + emit dataChanged(createIndex(0,0), createIndex(1, m->all.size())); +} + +bool WebcacheModel::deleteIdx(unsigned int idx) +{ + if (idx > m->disp.size() || !m->cache) + return false; + return m->cache->cc()->erase(m->disp[idx].udi, true); +} + +string WebcacheModel::getURL(unsigned int idx) +{ + if (idx > m->disp.size() || !m->cache) + return string(); + return m->disp[idx].url; +} + +int WebcacheModel::rowCount(const QModelIndex&) const +{ + //qDebug() << "WebcacheModel::rowCount(): " << m->disp.size(); + return int(m->disp.size()); +} + +int WebcacheModel::columnCount(const QModelIndex&) const +{ + //qDebug() << "WebcacheModel::columnCount()"; + return 2; +} + +QVariant WebcacheModel::headerData (int col, Qt::Orientation orientation, + int role) const +{ + // qDebug() << "WebcacheModel::headerData()"; + if (orientation != Qt::Horizontal || role != Qt::DisplayRole) { + return QVariant(); + } + switch (col) { + case 0: return QVariant(tr("MIME")); + case 1: return QVariant(tr("Url")); + default: return QVariant(); + } +} + +QVariant WebcacheModel::data(const QModelIndex& index, int role) const +{ + //qDebug() << "WebcacheModel::data()"; + Q_UNUSED(index); + if (role != Qt::DisplayRole) { + return QVariant(); + } + int row = index.row(); + if (row < 0 || row >= int(m->disp.size())) { + return QVariant(); + } + + /* We now read the data on init */ +#if 0 + string sdic; + if (!m->cache->cc()->get(m->disp[row].udi, sdic)) { + return QVariant(); + } + ConfSimple dic(sdic); + //ostringstream os; dic.write(os); cerr << "DIC: " << os.str() << endl; + string mime, url; + dic.get("mimetype", mime); + dic.get("url", url); +#else + const string& mime = m->disp[row].mimetype; + const string& url = m->disp[row].url; +#endif + + switch (index.column()) { + case 0: return QVariant(QString::fromUtf8(mime.c_str())); + case 1: return QVariant(QString::fromUtf8(url.c_str())); + default: return QVariant(); + } +} + +#ifndef USING_STD_REGEX +#define M_regexec(A,B,C,D,E) regexec(&(A),B,C,D,E) +#else +#define M_regexec(A,B,C,D,E) (!regex_match(B,A)) +#endif + +void WebcacheModel::setSearchFilter(const QString& _txt) +{ + string txt = qs2utf8s(_txt); + +#ifndef USING_STD_REGEX + regex_t exp; + if (regcomp(&exp, txt.c_str(), REG_NOSUB|REG_EXTENDED)) { + //qDebug() << "regcomp failed for " << _txt; + return; + } +#else + basic_regex exp; + try { + exp = basic_regex(txt, std::regex_constants::nosubs | + std::regex_constants::extended); + } catch(...) { + return; + } +#endif + + m->disp.clear(); + for (unsigned int i = 0; i < m->all.size(); i++) { + if (!M_regexec(exp, m->all[i].url.c_str(), 0, 0, 0)) { + m->disp.push_back(m->all[i]); + } else { + //qDebug() << "match failed. exp" << _txt << "data" << + // m->all[i].url.c_str(); + } + } + emit dataChanged(createIndex(0,0), createIndex(1, m->all.size())); +} + +static const int ROWHEIGHTPAD = 2; +static const char *cwnm = "/Recoll/prefs/webcachecolw"; +static const char *wwnm = "/Recoll/prefs/webcachew"; +static const char *whnm = "/Recoll/prefs/webcacheh"; +static const QKeySequence closeKS(Qt::ControlModifier+Qt::Key_W); + +WebcacheEdit::WebcacheEdit(RclMain *parent) + : QDialog(parent), m_recoll(parent), m_modified(false) +{ + //qDebug() << "WebcacheEdit::WebcacheEdit()"; + setupUi(this); + m_model = new WebcacheModel(this); + tableview->setModel(m_model); + tableview->setSelectionBehavior(QAbstractItemView::SelectRows); + tableview->setSelectionMode(QAbstractItemView::ExtendedSelection); + tableview->setContextMenuPolicy(Qt::CustomContextMenu); + tableview->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + QSettings settings; + QStringList wl; + wl = settings.value(cwnm).toStringList(); + QHeaderView *header = tableview->horizontalHeader(); + if (header) { + if (int(wl.size()) == header->count()) { + for (int i = 0; i < header->count(); i++) { + header->resizeSection(i, wl[i].toInt()); + } + } + } + connect(header, SIGNAL(sectionResized(int,int,int)), + this, SLOT(saveColState())); + + header = tableview->verticalHeader(); + if (header) { + header->setDefaultSectionSize(QApplication::fontMetrics().height() + + ROWHEIGHTPAD); + } + + int width = settings.value(wwnm, 0).toInt(); + int height = settings.value(whnm, 0).toInt(); + if (width && height) { + resize(QSize(width, height)); + } + + connect(searchLE, SIGNAL(textEdited(const QString&)), + m_model, SLOT(setSearchFilter(const QString&))); + connect(new QShortcut(closeKS, this), SIGNAL (activated()), + this, SLOT (close())); + connect(tableview, SIGNAL(customContextMenuRequested(const QPoint&)), + this, SLOT(createPopupMenu(const QPoint&))); + +} + +void WebcacheEdit::createPopupMenu(const QPoint& pos) +{ + int selsz = tableview->selectionModel()->selectedRows().size(); + if (selsz <= 0) { + return; + } + QMenu *popup = new QMenu(this); + if (selsz == 1) { + popup->addAction(tr("Copy URL"), this, SLOT(copyURL())); + } + if (m_recoll) { + RclMain::IndexerState ixstate = m_recoll->indexerState(); + switch (ixstate) { + case RclMain::IXST_UNKNOWN: + QMessageBox::warning(0, "Recoll", + tr("Unknown indexer state. " + "Can't edit webcache file.")); + break; + case RclMain::IXST_RUNNINGMINE: + case RclMain::IXST_RUNNINGNOTMINE: + QMessageBox::warning(0, "Recoll", + tr("Indexer is running. " + "Can't edit webcache file.")); + break; + case RclMain::IXST_NOTRUNNING: + popup->addAction(tr("Delete selection"), + this, SLOT(deleteSelected())); + break; + } + } + + popup->popup(tableview->mapToGlobal(pos)); +} + +void WebcacheEdit::deleteSelected() +{ + QModelIndexList selection = tableview->selectionModel()->selectedRows(); + for (int i = 0; i < selection.size(); i++) { + if (m_model->deleteIdx(selection[i].row())) { + m_modified = true; + } + } + m_model->reload(); + m_model->setSearchFilter(searchLE->text()); + tableview->clearSelection(); +} + +void WebcacheEdit::copyURL() +{ + QModelIndexList selection = tableview->selectionModel()->selectedRows(); + if (selection.size() != 1) + return; + string url = m_model->getURL(selection[0].row()); + if (!url.empty()) { + url = url_encode(url, 7); + QApplication::clipboard()->setText(url.c_str(), + QClipboard::Selection); + QApplication::clipboard()->setText(url.c_str(), + QClipboard::Clipboard); + } +} + +void WebcacheEdit::saveColState() +{ + //qDebug() << "void WebcacheEdit::saveColState()"; + QHeaderView *header = tableview->horizontalHeader(); + QStringList newwidths; + for (int vi = 0; vi < header->count(); vi++) { + int li = header->logicalIndex(vi); + newwidths.push_back(lltodecstr(header->sectionSize(li)).c_str()); + } + + QSettings settings; + settings.setValue(cwnm, newwidths); +} + +void WebcacheEdit::closeEvent(QCloseEvent *event) +{ + if (m_modified) { + QMessageBox::information(0, "Recoll", + tr("Webcache was modified, you will need " + "to run the indexer after closing this " + "window.")); + } + if (!isFullScreen()) { + QSettings settings; + settings.setValue(wwnm, width()); + settings.setValue(whnm, height()); + } + event->accept(); +} diff --git a/src/qtgui/webcache.h b/src/qtgui/webcache.h new file mode 100644 index 00000000..c0b3a900 --- /dev/null +++ b/src/qtgui/webcache.h @@ -0,0 +1,78 @@ +/* Copyright (C) 2016 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _WEBCACHE_H_INCLUDED_ +#define _WEBCACHE_H_INCLUDED_ +#include "autoconfig.h" + +#include +#include +#include + +#include "ui_webcache.h" + +#include + +class WebcacheModelInternal; +class QCloseEvent; + +class WebcacheModel : public QAbstractTableModel { + Q_OBJECT; + +public: + WebcacheModel(QObject *parent = 0); + ~WebcacheModel(); + + // Reimplemented methods + virtual int rowCount (const QModelIndex& = QModelIndex()) const; + virtual int columnCount(const QModelIndex& = QModelIndex()) const; + virtual QVariant headerData (int col, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + virtual QVariant data(const QModelIndex& index, + int role = Qt::DisplayRole ) const; + bool deleteIdx(unsigned int idx); + std::string getURL(unsigned int idx); + +public slots: + void setSearchFilter(const QString&); + void reload(); + +private: + WebcacheModelInternal *m; +}; + +class RclMain; + +class WebcacheEdit : public QDialog, public Ui::Webcache { + Q_OBJECT; + +public: + WebcacheEdit(RclMain *parent); +public slots: + void saveColState(); + void createPopupMenu(const QPoint&); + void deleteSelected(); + void copyURL(); +protected: + void closeEvent(QCloseEvent *); +private: + WebcacheModel *m_model; + RclMain *m_recoll; + bool m_modified; +}; + + +#endif /* _WEBCACHE_H_INCLUDED_ */ diff --git a/src/qtgui/webcache.ui b/src/qtgui/webcache.ui new file mode 100644 index 00000000..bdd2400a --- /dev/null +++ b/src/qtgui/webcache.ui @@ -0,0 +1,51 @@ + + + Webcache + + + + 0 + 0 + 400 + 300 + + + + Webcache editor + + + + + + + + Search regexp + + + searchLE + + + + + + + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + false + + + + + + + + diff --git a/src/qtgui/editdialog.h b/src/qtgui/widgets/editdialog.h similarity index 100% rename from src/qtgui/editdialog.h rename to src/qtgui/widgets/editdialog.h diff --git a/src/qtgui/editdialog.ui b/src/qtgui/widgets/editdialog.ui similarity index 100% rename from src/qtgui/editdialog.ui rename to src/qtgui/widgets/editdialog.ui diff --git a/src/qtgui/listdialog.h b/src/qtgui/widgets/listdialog.h similarity index 100% rename from src/qtgui/listdialog.h rename to src/qtgui/widgets/listdialog.h diff --git a/src/qtgui/listdialog.ui b/src/qtgui/widgets/listdialog.ui similarity index 100% rename from src/qtgui/listdialog.ui rename to src/qtgui/widgets/listdialog.ui diff --git a/src/qtgui/widgets/qxtconfirmationmessage.cpp b/src/qtgui/widgets/qxtconfirmationmessage.cpp new file mode 100644 index 00000000..cc718db6 --- /dev/null +++ b/src/qtgui/widgets/qxtconfirmationmessage.cpp @@ -0,0 +1,438 @@ +#include "qxtconfirmationmessage.h" +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of the LibQxt project nor the +** names of its contributors may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +** +*****************************************************************************/ + + +#include +#include +#include +#include +#include + +static const QLatin1String DEFAULT_ORGANIZATION("QxtWidgets"); +static const QLatin1String DEFAULT_APPLICATION("QxtConfirmationMessage"); + +class QxtConfirmationMessagePrivate : public QxtPrivate +{ +public: + QXT_DECLARE_PUBLIC(QxtConfirmationMessage) + void init(const QString& message = QString()); + + QString key() const; + QString applicationName() const; + QString organizationName() const; + + int showAgain(); + void doNotShowAgain(int result); + void reset(); + + bool remember; + QCheckBox* confirm; + QString overrideApp; + QString overrideKey; + QString overrideOrg; + + static QString path; + static QSettings::Scope scope; + static QSettings::Format format; +}; + +QString QxtConfirmationMessagePrivate::path; +QSettings::Scope QxtConfirmationMessagePrivate::scope = QSettings::UserScope; +QSettings::Format QxtConfirmationMessagePrivate::format = QSettings::NativeFormat; + +void QxtConfirmationMessagePrivate::init(const QString& message) +{ + remember = false; + confirm = new QCheckBox(&qxt_p()); + if (!message.isNull()) + confirm->setText(message); + else + confirm->setText(QxtConfirmationMessage::tr("Do not show again.")); + + QGridLayout* grid = qobject_cast(qxt_p().layout()); + QDialogButtonBox* buttons = qxt_p().findChild(); + if (grid && buttons) + { + const int idx = grid->indexOf(buttons); + int row, column, rowSpan, columnSpan = 0; + grid->getItemPosition(idx, &row, &column, &rowSpan, &columnSpan); + QLayoutItem* buttonsItem = grid->takeAt(idx); + grid->addWidget(confirm, row, column, rowSpan, columnSpan, Qt::AlignLeft | Qt::AlignTop); + grid->addItem(buttonsItem, ++row, column, rowSpan, columnSpan); + } +} + +QString QxtConfirmationMessagePrivate::key() const +{ + QString value = overrideKey; + if (value.isEmpty()) + { + const QString all = qxt_p().windowTitle() + qxt_p().text() + qxt_p().informativeText(); + const QByteArray data = all.toLocal8Bit(); + value = QString::number(qChecksum(data.constData(), data.length())); + } + return value; +} + +QString QxtConfirmationMessagePrivate::applicationName() const +{ + QString name = overrideApp; + if (name.isEmpty()) + name = QCoreApplication::applicationName(); + if (name.isEmpty()) + name = DEFAULT_APPLICATION; + return name; +} + +QString QxtConfirmationMessagePrivate::organizationName() const +{ + QString name = overrideOrg; + if (name.isEmpty()) + name = QCoreApplication::organizationName(); + if (name.isEmpty()) + name = DEFAULT_ORGANIZATION; + return name; +} + +int QxtConfirmationMessagePrivate::showAgain() +{ + QSettings settings(format, scope, organizationName(), applicationName()); + if (!path.isEmpty()) + settings.beginGroup(path); + return settings.value(key(), -1).toInt(); +} + +void QxtConfirmationMessagePrivate::doNotShowAgain(int result) +{ + QSettings settings(format, scope, organizationName(), applicationName()); + if (!path.isEmpty()) + settings.beginGroup(path); + settings.setValue(key(), result); +} + +void QxtConfirmationMessagePrivate::reset() +{ + QSettings settings(format, scope, organizationName(), applicationName()); + if (!path.isEmpty()) + settings.beginGroup(path); + settings.remove(key()); +} + +/*! + \class QxtConfirmationMessage + \inmodule QxtWidgets + \brief The QxtConfirmationMessage class provides a confirmation message. + + QxtConfirmationMessage is a confirmation message with checkable + \bold {"Do not show again."} option. A checked and accepted confirmation + message is no more shown until reseted. + + Example usage: + \code + void MainWindow::closeEvent(QCloseEvent* event) + { + static const QString text(tr("Are you sure you want to quit?")); + if (QxtConfirmationMessage::confirm(this, tr("Confirm"), text) == QMessageBox::No) + event->ignore(); + } + \endcode + + \image qxtconfirmationmessage.png "QxtConfirmationMessage in action." + + \bold {Note:} QCoreApplication::organizationName and QCoreApplication::applicationName + are used for storing settings. In case these properties are empty, \bold "QxtWidgets" and + \bold "QxtConfirmationMessage" are used, respectively. + */ + +/*! + Constructs a new QxtConfirmationMessage with \a parent. + */ +QxtConfirmationMessage::QxtConfirmationMessage(QWidget* parent) + : QMessageBox(parent) +{ + QXT_INIT_PRIVATE(QxtConfirmationMessage); + qxt_d().init(); +} + +/*! + Constructs a new QxtConfirmationMessage with \a icon, \a title, \a text, \a confirmation, \a buttons, \a parent and \a flags. + */ +QxtConfirmationMessage::QxtConfirmationMessage(QMessageBox::Icon icon, const QString& title, const QString& text, const QString& confirmation, + QMessageBox::StandardButtons buttons, QWidget* parent, Qt::WindowFlags flags) + : QMessageBox(icon, title, text, buttons, parent, flags) +{ + QXT_INIT_PRIVATE(QxtConfirmationMessage); + qxt_d().init(confirmation); +} + +/*! + Destructs the confirmation message. + */ +QxtConfirmationMessage::~QxtConfirmationMessage() +{ +} + +/*! + Opens an confirmation message box with the specified \a title, \a text and \a confirmation. + The standard \a buttons are added to the message box. \a defaultButton specifies + the button used when Enter is pressed. \a defaultButton must refer to a button that + was given in \a buttons. If \a defaultButton is QMessageBox::NoButton, QMessageBox + chooses a suitable default automatically. + + Returns the identity of the standard button that was clicked. + If Esc was pressed instead, the escape button is returned. + + If \a parent is \c 0, the message box is an application modal dialog box. + If \a parent is a widget, the message box is window modal relative to \a parent. + */ +QMessageBox::StandardButton QxtConfirmationMessage::confirm(QWidget* parent, + const QString& title, const QString& text, const QString& confirmation, + QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) +{ + QxtConfirmationMessage msgBox(QMessageBox::NoIcon, title, text, confirmation, QMessageBox::NoButton, parent); + QDialogButtonBox* buttonBox = msgBox.findChild(); + Q_ASSERT(buttonBox != 0); + + uint mask = QMessageBox::FirstButton; + while (mask <= QMessageBox::LastButton) + { + uint sb = buttons & mask; + mask <<= 1; + if (!sb) + continue; + QPushButton* button = msgBox.addButton((QMessageBox::StandardButton)sb); + // Choose the first accept role as the default + if (msgBox.defaultButton()) + continue; + if ((defaultButton == QMessageBox::NoButton && buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) + || (defaultButton != QMessageBox::NoButton && sb == uint(defaultButton))) + msgBox.setDefaultButton(button); + } + if (msgBox.exec() == -1) + return QMessageBox::Cancel; + return msgBox.standardButton(msgBox.clickedButton()); +} + +/*! + \property QxtConfirmationMessage::confirmationText + \brief the confirmation text + + The default value is \bold {"Do not show again."} + */ +QString QxtConfirmationMessage::confirmationText() const +{ + return qxt_d().confirm->text(); +} + +void QxtConfirmationMessage::setConfirmationText(const QString& confirmation) +{ + qxt_d().confirm->setText(confirmation); +} + +/*! + \property QxtConfirmationMessage::overrideSettingsApplication + \brief the override application name used for settings + + QCoreApplication::applicationName is used when no \bold overrideSettingsApplication + has been set. The application name falls back to \bold "QxtConfirmationMessage" + when no QCoreApplication::applicationName has been set. + + The default value is an empty string. + */ +QString QxtConfirmationMessage::overrideSettingsApplication() const +{ + return qxt_d().overrideApp; +} + +void QxtConfirmationMessage::setOverrideSettingsApplication(const QString& application) +{ + qxt_d().overrideApp = application; +} + +/*! + \property QxtConfirmationMessage::overrideSettingsKey + \brief the override key used for settings + + When no \bold overrideSettingsKey has been set, the key is calculated with + qChecksum() based on title, text and confirmation message. + + The default value is an empty string. + */ +QString QxtConfirmationMessage::overrideSettingsKey() const +{ + return qxt_d().overrideKey; +} + +void QxtConfirmationMessage::setOverrideSettingsKey(const QString& key) +{ + qxt_d().overrideKey = key; +} + +/*! + \property QxtConfirmationMessage::overrideSettingsOrganization + \brief the override organization name used for settings + + QCoreApplication::organizationName is used when no \bold overrideSettingsOrganization + has been set. The organization name falls back to \bold "QxtWidgets" when no + QCoreApplication::organizationName has been set. + + The default value is an empty string. + */ +QString QxtConfirmationMessage::overrideSettingsOrganization() const +{ + return qxt_d().overrideOrg; +} + +void QxtConfirmationMessage::setOverrideSettingsOrganization(const QString& organization) +{ + qxt_d().overrideOrg = organization; +} + +/*! + \property QxtConfirmationMessage::rememberOnReject + \brief whether \bold {"Do not show again."} option is stored even + if the message box is rejected (eg. user presses Cancel). + + The default value is \c false. + */ +bool QxtConfirmationMessage::rememberOnReject() const +{ + return qxt_d().remember; +} + +void QxtConfirmationMessage::setRememberOnReject(bool remember) +{ + qxt_d().remember = remember; +} + +/*! + Returns The format used for storing settings. + + The default value is QSettings::NativeFormat. + */ +QSettings::Format QxtConfirmationMessage::settingsFormat() +{ + return QxtConfirmationMessagePrivate::format; +} + +/*! + Sets the \a format used for storing settings. + */ +void QxtConfirmationMessage::setSettingsFormat(QSettings::Format format) +{ + QxtConfirmationMessagePrivate::format = format; +} + +/*! + Returns The scope used for storing settings. + + The default value is QSettings::UserScope. + */ +QSettings::Scope QxtConfirmationMessage::settingsScope() +{ + return QxtConfirmationMessagePrivate::scope; +} + +/*! + Sets the \a scope used for storing settings. + */ +void QxtConfirmationMessage::setSettingsScope(QSettings::Scope scope) +{ + QxtConfirmationMessagePrivate::scope = scope; +} + +/*! + Returns the path used for storing settings. + + The default value is an empty string. + */ +QString QxtConfirmationMessage::settingsPath() +{ + return QxtConfirmationMessagePrivate::path; +} + +/*! + Sets the \a path used for storing settings. + */ +void QxtConfirmationMessage::setSettingsPath(const QString& path) +{ + QxtConfirmationMessagePrivate::path = path; +} + +/*! + Shows the confirmation message if necessary. The confirmation message is not + shown in case \bold {"Do not show again."} has been checked while the same + confirmation message was earlierly accepted. + + A confirmation message is identified by the combination of title, + QMessageBox::text and optional QMessageBox::informativeText. + + A clicked button with role QDialogButtonBox::AcceptRole or + QDialogButtonBox::YesRole is considered as "accepted". + + \warning This function does not reimplement but shadows QMessageBox::exec(). + + \sa QWidget::windowTitle, QMessageBox::text, QMessageBox::informativeText + */ +int QxtConfirmationMessage::exec() +{ + int res = qxt_d().showAgain(); + if (res == -1) + res = QMessageBox::exec(); + return res; +} + +/*! + \reimp + */ +void QxtConfirmationMessage::done(int result) +{ + QDialogButtonBox* buttons = this->findChild(); + Q_ASSERT(buttons != 0); + + int role = buttons->buttonRole(clickedButton()); + if (qxt_d().confirm->isChecked() && + (qxt_d().remember || role != QDialogButtonBox::RejectRole)) + { + qxt_d().doNotShowAgain(result); + } + QMessageBox::done(result); +} + +/*! + Resets this instance of QxtConfirmationMessage. A reseted confirmation + message is shown again until user checks \bold {"Do not show again."} and + accepts the confirmation message. + */ +void QxtConfirmationMessage::reset() +{ + qxt_d().reset(); +} diff --git a/src/qtgui/widgets/qxtconfirmationmessage.h b/src/qtgui/widgets/qxtconfirmationmessage.h new file mode 100644 index 00000000..bce83e97 --- /dev/null +++ b/src/qtgui/widgets/qxtconfirmationmessage.h @@ -0,0 +1,94 @@ +#ifndef QXTCONFIRMATIONMESSAGE_H +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of the LibQxt project nor the +** names of its contributors may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +** +*****************************************************************************/ + +#define QXTCONFIRMATIONMESSAGE_H + +#include +#include +#include "qxtglobal.h" + +class QxtConfirmationMessagePrivate; + +class QXT_GUI_EXPORT QxtConfirmationMessage : public QMessageBox +{ + Q_OBJECT + QXT_DECLARE_PRIVATE(QxtConfirmationMessage) + Q_PROPERTY(QString confirmationText READ confirmationText WRITE setConfirmationText) + Q_PROPERTY(QString overrideSettingsApplication READ overrideSettingsApplication WRITE setOverrideSettingsApplication) + Q_PROPERTY(QString overrideSettingsKey READ overrideSettingsKey WRITE setOverrideSettingsKey) + Q_PROPERTY(QString overrideSettingsOrganization READ overrideSettingsOrganization WRITE setOverrideSettingsOrganization) + Q_PROPERTY(bool rememberOnReject READ rememberOnReject WRITE setRememberOnReject) + +public: + explicit QxtConfirmationMessage(QWidget* parent = 0); + virtual ~QxtConfirmationMessage(); + + QxtConfirmationMessage(QMessageBox::Icon icon, + const QString& title, const QString& text, const QString& confirmation = QString(), + QMessageBox::StandardButtons buttons = QMessageBox::NoButton, QWidget* parent = 0, + Qt::WindowFlags flags = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint); + + static QMessageBox::StandardButton confirm(QWidget* parent, + const QString& title, const QString& text, const QString& confirmation = QString(), + QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); + + QString confirmationText() const; + void setConfirmationText(const QString& confirmation); + + QString overrideSettingsApplication() const; + void setOverrideSettingsApplication(const QString& application); + + QString overrideSettingsKey() const; + void setOverrideSettingsKey(const QString& key); + + QString overrideSettingsOrganization() const; + void setOverrideSettingsOrganization(const QString& organization); + + bool rememberOnReject() const; + void setRememberOnReject(bool remember); + + static QSettings::Format settingsFormat(); + static void setSettingsFormat(QSettings::Format format); + + static QSettings::Scope settingsScope(); + static void setSettingsScope(QSettings::Scope scope); + + static QString settingsPath(); + static void setSettingsPath(const QString& path); + +public Q_SLOTS: + int exec(); + void reset(); + virtual void done(int result); +}; + +#endif // QXTCONFIRMATIONMESSAGE_H diff --git a/src/qtgui/widgets/qxtglobal.h b/src/qtgui/widgets/qxtglobal.h new file mode 100644 index 00000000..283b330a --- /dev/null +++ b/src/qtgui/widgets/qxtglobal.h @@ -0,0 +1,235 @@ + +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of the LibQxt project nor the +** names of its contributors may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +** +*****************************************************************************/ + +#ifndef QXTGLOBAL_H +#define QXTGLOBAL_H + +#include + +#define QXT_VERSION 0x000700 +#define QXT_VERSION_STR "0.7.0" + +//--------------------------global macros------------------------------ + +#ifndef QXT_NO_MACROS + +#ifndef _countof +#define _countof(x) (sizeof(x)/sizeof(*x)) +#endif + +#endif // QXT_NO_MACROS + +//--------------------------export macros------------------------------ + +#define QXT_STATIC + +#define QXT_DLLEXPORT DO_NOT_USE_THIS_ANYMORE + +#if !defined(QXT_STATIC) && !defined(QXT_DOXYGEN_RUN) +# if defined(BUILD_QXT_CORE) +# define QXT_CORE_EXPORT Q_DECL_EXPORT +# else +# define QXT_CORE_EXPORT Q_DECL_IMPORT +# endif +#else +# define QXT_CORE_EXPORT +#endif // BUILD_QXT_CORE + +#if !defined(QXT_STATIC) && !defined(QXT_DOXYGEN_RUN) +# if defined(BUILD_QXT_GUI) +# define QXT_GUI_EXPORT Q_DECL_EXPORT +# else +# define QXT_GUI_EXPORT Q_DECL_IMPORT +# endif +#else +# define QXT_GUI_EXPORT +#endif // BUILD_QXT_GUI + +#if !defined(QXT_STATIC) && !defined(QXT_DOXYGEN_RUN) +# if defined(BUILD_QXT_NETWORK) +# define QXT_NETWORK_EXPORT Q_DECL_EXPORT +# else +# define QXT_NETWORK_EXPORT Q_DECL_IMPORT +# endif +#else +# define QXT_NETWORK_EXPORT +#endif // BUILD_QXT_NETWORK + +#if !defined(QXT_STATIC) && !defined(QXT_DOXYGEN_RUN) +# if defined(BUILD_QXT_SQL) +# define QXT_SQL_EXPORT Q_DECL_EXPORT +# else +# define QXT_SQL_EXPORT Q_DECL_IMPORT +# endif +#else +# define QXT_SQL_EXPORT +#endif // BUILD_QXT_SQL + +#if !defined(QXT_STATIC) && !defined(QXT_DOXYGEN_RUN) +# if defined(BUILD_QXT_WEB) +# define QXT_WEB_EXPORT Q_DECL_EXPORT +# else +# define QXT_WEB_EXPORT Q_DECL_IMPORT +# endif +#else +# define QXT_WEB_EXPORT +#endif // BUILD_QXT_WEB + +#if !defined(QXT_STATIC) && !defined(QXT_DOXYGEN_RUN) +# if defined(BUILD_QXT_BERKELEY) +# define QXT_BERKELEY_EXPORT Q_DECL_EXPORT +# else +# define QXT_BERKELEY_EXPORT Q_DECL_IMPORT +# endif +#else +# define QXT_BERKELEY_EXPORT +#endif // BUILD_QXT_BERKELEY + +#if !defined(QXT_STATIC) && !defined(QXT_DOXYGEN_RUN) +# if defined(BUILD_QXT_ZEROCONF) +# define QXT_ZEROCONF_EXPORT Q_DECL_EXPORT +# else +# define QXT_ZEROCONF_EXPORT Q_DECL_IMPORT +# endif +#else +# define QXT_ZEROCONF_EXPORT +#endif // QXT_ZEROCONF_EXPORT + +#if defined(BUILD_QXT_CORE) || defined(BUILD_QXT_GUI) || defined(BUILD_QXT_SQL) || defined(BUILD_QXT_NETWORK) || defined(BUILD_QXT_WEB) || defined(BUILD_QXT_BERKELEY) || defined(BUILD_QXT_ZEROCONF) +# define BUILD_QXT +#endif + +QXT_CORE_EXPORT const char* qxtVersion(); + +#ifndef QT_BEGIN_NAMESPACE +#define QT_BEGIN_NAMESPACE +#endif + +#ifndef QT_END_NAMESPACE +#define QT_END_NAMESPACE +#endif + +#ifndef QT_FORWARD_DECLARE_CLASS +#define QT_FORWARD_DECLARE_CLASS(Class) class Class; +#endif + +/**************************************************************************** +** This file is derived from code bearing the following notice: +** The sole author of this file, Adam Higerd, has explicitly disclaimed all +** copyright interest and protection for the content within. This file has +** been placed in the public domain according to United States copyright +** statute and case law. In jurisdictions where this public domain dedication +** is not legally recognized, anyone who receives a copy of this file is +** permitted to use, modify, duplicate, and redistribute this file, in whole +** or in part, with no restrictions or conditions. In these jurisdictions, +** this file shall be copyright (C) 2006-2008 by Adam Higerd. +****************************************************************************/ + +#define QXT_DECLARE_PRIVATE(PUB) friend class PUB##Private; QxtPrivateInterface qxt_d; +#define QXT_DECLARE_PUBLIC(PUB) friend class PUB; +#define QXT_INIT_PRIVATE(PUB) qxt_d.setPublic(this); +#define QXT_D(PUB) PUB##Private& d = qxt_d() +#define QXT_P(PUB) PUB& p = qxt_p() + +template +class QxtPrivate +{ +public: + virtual ~QxtPrivate() + {} + inline void QXT_setPublic(PUB* pub) + { + qxt_p_ptr = pub; + } + +protected: + inline PUB& qxt_p() + { + return *qxt_p_ptr; + } + inline const PUB& qxt_p() const + { + return *qxt_p_ptr; + } + inline PUB* qxt_ptr() + { + return qxt_p_ptr; + } + inline const PUB* qxt_ptr() const + { + return qxt_p_ptr; + } + +private: + PUB* qxt_p_ptr; +}; + +template +class QxtPrivateInterface +{ + friend class QxtPrivate; +public: + QxtPrivateInterface() + { + pvt = new PVT; + } + ~QxtPrivateInterface() + { + delete pvt; + } + + inline void setPublic(PUB* pub) + { + pvt->QXT_setPublic(pub); + } + inline PVT& operator()() + { + return *static_cast(pvt); + } + inline const PVT& operator()() const + { + return *static_cast(pvt); + } + inline PVT * operator->() + { + return static_cast(pvt); + } + inline const PVT * operator->() const + { + return static_cast(pvt); + } +private: + QxtPrivateInterface(const QxtPrivateInterface&) { } + QxtPrivateInterface& operator=(const QxtPrivateInterface&) { } + QxtPrivate* pvt; +}; + +#endif // QXT_GLOBAL diff --git a/src/qtgui/xmltosd.cpp b/src/qtgui/xmltosd.cpp index 5c5c1f19..d2b26849 100644 --- a/src/qtgui/xmltosd.cpp +++ b/src/qtgui/xmltosd.cpp @@ -22,7 +22,7 @@ #include "ssearch_w.h" #include "guiutils.h" -#include "debuglog.h" +#include "log.h" #include "xmltosd.h" #include "smallut.h" #include "recoll.h" @@ -51,7 +51,7 @@ public: } // The object we set up - RefCntr sd; + std::shared_ptr sd; bool isvalid; private: @@ -82,21 +82,20 @@ bool SDHXMLHandler::startElement(const QString & /* namespaceURI */, const QString &qName, const QXmlAttributes &attrs) { - LOGDEB2(("SDHXMLHandler::startElement: name [%s]\n", - (const char *)qName.toUtf8())); + LOGDEB2("SDHXMLHandler::startElement: name [" << qs2utf8s(qName) << "]\n"); if (qName == "SD") { // Advanced search history entries have no type. So we're good // either if type is absent, or if it's searchdata int idx = attrs.index("type"); if (idx >= 0 && attrs.value(idx).compare("searchdata")) { - LOGDEB(("XMLTOSD: bad type\n")) - return false; - } + LOGDEB("XMLTOSD: bad type\n"); + return false; + } resetTemps(); // A new search descriptor. Allocate data structure - sd = RefCntr(new SearchData); - if (sd.isNull()) { - LOGERR(("SDHXMLHandler::startElement: out of memory\n")); + sd = std::shared_ptr(new SearchData); + if (!sd) { + LOGERR("SDHXMLHandler::startElement: out of memory\n"); return false; } } @@ -107,8 +106,7 @@ bool SDHXMLHandler::endElement(const QString & /* namespaceURI */, const QString & /* localName */, const QString &qName) { - LOGDEB2(("SDHXMLHandler::endElement: name [%s]\n", - (const char *)qName.toUtf8())); + LOGDEB2("SDHXMLHandler::endElement: name [" << qs2utf8s(qName) << "]\n"); if (qName == "CLT") { if (currentText == "OR") { @@ -147,7 +145,7 @@ bool SDHXMLHandler::endElement(const QString & /* namespaceURI */, c = new SearchDataClauseDist(SCLT_NEAR, text, slack, field); c->setexclude(exclude); } else { - LOGERR(("Bad clause type [%s]\n", qs2utf8s(whatclause).c_str())); + LOGERR("Bad clause type [" << qs2utf8s(whatclause) << "]\n"); return false; } sd->addClause(c); @@ -208,7 +206,7 @@ bool SDHXMLHandler::endElement(const QString & /* namespaceURI */, } -RefCntr xmlToSearchData(const string& xml) +std::shared_ptr xmlToSearchData(const string& xml) { SDHXMLHandler handler; QXmlSimpleReader reader; @@ -219,9 +217,8 @@ RefCntr xmlToSearchData(const string& xml) xmlInputSource.setData(QString::fromUtf8(xml.c_str())); if (!reader.parse(xmlInputSource) || !handler.isvalid) { - LOGERR(("xmlToSearchData: parse failed for [%s]\n", - xml.c_str())); - return RefCntr(); + LOGERR("xmlToSearchData: parse failed for [" << xml << "]\n"); + return std::shared_ptr(); } return handler.sd; } @@ -270,13 +267,12 @@ bool SSHXMLHandler::startElement(const QString & /* namespaceURI */, const QString &qName, const QXmlAttributes &attrs) { - LOGDEB2(("SSHXMLHandler::startElement: name [%s]\n", - (const char *)qName.toUtf8())); + LOGDEB2("SSHXMLHandler::startElement: name [" << u8s2qs(qName) << "]\n"); if (qName == "SD") { // Simple search saved data has a type='ssearch' attribute. int idx = attrs.index("type"); if (idx < 0 && attrs.value(idx).compare("ssearch")) { - LOGDEB(("XMLTOSSS: bad type\n")); + LOGDEB("XMLTOSSS: bad type\n"); return false; } resetTemps(); @@ -288,8 +284,7 @@ bool SSHXMLHandler::endElement(const QString & /* namespaceURI */, const QString & /* localName */, const QString &qName) { - LOGDEB2(("SSHXMLHandler::endElement: name [%s]\n", - (const char *)qName.toUtf8())); + LOGDEB2("SSHXMLHandler::endElement: name [" << u8s2qs(qName) << "]\n"); currentText = currentText.trimmed(); @@ -309,7 +304,7 @@ bool SSHXMLHandler::endElement(const QString & /* namespaceURI */, } else if (!currentText.compare("AND")) { data.mode = SSearch::SST_ALL; } else { - LOGERR(("BAD SEARCH MODE: [%s]\n", qs2utf8s(currentText).c_str())); + LOGERR("BAD SEARCH MODE: [" << qs2utf8s(currentText) << "]\n"); return false; } } else if (qName == "AS") { @@ -336,10 +331,10 @@ bool xmlToSSearch(const string& xml, SSearchDef& data) xmlInputSource.setData(QString::fromUtf8(xml.c_str())); if (!reader.parse(xmlInputSource) || !handler.isvalid) { - LOGERR(("xmlToSSearch: parse failed for [%s]\n", - xml.c_str())); + LOGERR("xmlToSSearch: parse failed for [" << xml << "]\n"); return false; } data = handler.data; return true; } + diff --git a/src/qtgui/xmltosd.h b/src/qtgui/xmltosd.h index 4f58842f..35d13e55 100644 --- a/src/qtgui/xmltosd.h +++ b/src/qtgui/xmltosd.h @@ -14,6 +14,9 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#ifndef XMLTOSD_H_INCLUDED +#define XMLTOSD_H_INCLUDED +#include "autoconfig.h" /** Parsing XML from saved queries or advanced search history. * @@ -57,12 +60,12 @@ * */ -#include "refcntr.h" +#include #include "searchdata.h" // Parsing XML from advanced search history or saved advanced search to to // SearchData structure: -RefCntr xmlToSearchData(const string& xml); +std::shared_ptr xmlToSearchData(const string& xml); // Parsing XML from saved simple search to ssearch parameters struct SSearchDef { @@ -75,3 +78,4 @@ struct SSearchDef { int mode; }; bool xmlToSSearch(const string& xml, SSearchDef&); +#endif /* XMLTOSD_H_INCLUDED */ diff --git a/src/query/Makefile b/src/query/Makefile deleted file mode 100644 index 303a8a11..00000000 --- a/src/query/Makefile +++ /dev/null @@ -1,49 +0,0 @@ -depth = .. -include $(depth)/mk/sysconf - -PROGS = xadump recollq #trhist qtry qxtry -SRCS = xadump.cpp - -all: wasaparse.tab.cpp depend librecoll $(PROGS) - -wasaparse.tab.cpp : wasaparse.y - bison wasaparse.y - mv -f wasaparse.tab.c wasaparse.tab.cpp - -XADUMP_OBJS= xadump.o -xadump : $(XADUMP_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o xadump $(XADUMP_OBJS) \ - $(depth)/lib/librecoll.a $(LIBICONV) $(LIBXAPIAN) $(LIBSYS) -xadump.o : xadump.cpp - $(CXX) $(ALL_CXXFLAGS) -c xadump.cpp - -RECOLLQ_OBJS= recollq.o -recollq : $(RECOLLQ_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o recollq $(RECOLLQ_OBJS) \ - $(BSTATIC) $(LIBRECOLL) $(LIBXAPIAN) $(LIBICONV) $(BDYNAMIC) \ - $(LIBSYS) -recollq.o : recollq.cpp - $(CXX) $(ALL_CXXFLAGS) -DTEST_RECOLLQ -c recollq.cpp - -HISTORY_OBJS= trhist.o -trhist : $(HISTORY_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o trhist $(HISTORY_OBJS) \ - $(LIBICONV) $(LIBXAPIAN) -trhist.o : history.cpp history.h - $(CXX) $(ALL_CXXFLAGS) -DTEST_HISTORY -c -o trhist.o history.cpp - -WASASTRINGTOQUERY_OBJS= trwasastrtoq.o -trwasastrtoq : $(WASASTRINGTOQUERY_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o trwasastrtoq $(WASASTRINGTOQUERY_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBXAPIAN) -trwasastrtoq.o : wasastringtoquery.cpp wasastringtoquery.h - $(CXX) $(ALL_CXXFLAGS) -DTEST_WASASTRINGTOQUERY -c \ - -o trwasastrtoq.o wasastringtoquery.cpp - -include $(depth)/mk/commontargets - -include alldeps - -distclean:: - -rm -f location.hh position.hh stack.hh \ - wasaparse.tab.c wasaparse.tab.cpp wasaparse.tab.h diff --git a/src/query/docseq.cpp b/src/query/docseq.cpp index 537d3cbb..163926c0 100644 --- a/src/query/docseq.cpp +++ b/src/query/docseq.cpp @@ -14,13 +14,15 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#include "autoconfig.h" + #include "docseq.h" #include "filtseq.h" #include "sortseq.h" -#include "debuglog.h" +#include "log.h" #include "internfile.h" -PTMutexInit DocSequence::o_dblock; +std::mutex DocSequence::o_dblock; string DocSequence::o_sort_trans; string DocSequence::o_filt_trans; @@ -41,10 +43,10 @@ bool DocSequence::getEnclosing(Rcl::Doc& doc, Rcl::Doc& pdoc) { Rcl::Db *db = getDb(); if (db == 0) { - LOGERR(("DocSequence::getEnclosing: no db\n")); + LOGERR("DocSequence::getEnclosing: no db\n" ); return false; } - PTMutexLocker locker(o_dblock); + std::unique_lock locker(o_dblock); string udi; if (!FileInterner::getEnclosingUDI(doc, udi)) return false; @@ -56,41 +58,41 @@ bool DocSequence::getEnclosing(Rcl::Doc& doc, Rcl::Doc& pdoc) // Remove stacked modifying sources (sort, filter) until we get to a real one void DocSource::stripStack() { - if (m_seq.isNull()) + if (!m_seq) return; - while (m_seq->getSourceSeq().isNotNull()) { + while (m_seq->getSourceSeq()) { m_seq = m_seq->getSourceSeq(); } } bool DocSource::buildStack() { - LOGDEB2(("DocSource::buildStack()\n")); + LOGDEB2("DocSource::buildStack()\n" ); stripStack(); - if (m_seq.isNull()) + if (!m_seq) return false; // Filtering must be done before sorting, (which may // truncates the original list) if (m_seq->canFilter()) { if (!m_seq->setFiltSpec(m_fspec)) { - LOGERR(("DocSource::buildStack: setfiltspec failed\n")); + LOGERR("DocSource::buildStack: setfiltspec failed\n" ); } } else { if (m_fspec.isNotNull()) { m_seq = - RefCntr(new DocSeqFiltered(m_config, m_seq, m_fspec)); + std::shared_ptr(new DocSeqFiltered(m_config, m_seq, m_fspec)); } } if (m_seq->canSort()) { if (!m_seq->setSortSpec(m_sspec)) { - LOGERR(("DocSource::buildStack: setsortspec failed\n")); + LOGERR("DocSource::buildStack: setsortspec failed\n" ); } } else { if (m_sspec.isNotNull()) { - m_seq = RefCntr(new DocSeqSorted(m_seq, m_sspec)); + m_seq = std::shared_ptr(new DocSeqSorted(m_seq, m_sspec)); } } return true; @@ -98,7 +100,7 @@ bool DocSource::buildStack() string DocSource::title() { - if (m_seq.isNull()) + if (!m_seq) return string(); string qual; if (m_fspec.isNotNull() && !m_sspec.isNotNull()) @@ -112,7 +114,7 @@ string DocSource::title() bool DocSource::setFiltSpec(const DocSeqFiltSpec &f) { - LOGDEB2(("DocSource::setFiltSpec\n")); + LOGDEB2("DocSource::setFiltSpec\n" ); m_fspec = f; buildStack(); return true; @@ -120,9 +122,10 @@ bool DocSource::setFiltSpec(const DocSeqFiltSpec &f) bool DocSource::setSortSpec(const DocSeqSortSpec &s) { - LOGDEB2(("DocSource::setSortSpec\n")); + LOGDEB2("DocSource::setSortSpec\n" ); m_sspec = s; buildStack(); return true; } + diff --git a/src/query/docseq.h b/src/query/docseq.h index 7257ef08..e787ef3b 100644 --- a/src/query/docseq.h +++ b/src/query/docseq.h @@ -16,15 +16,17 @@ */ #ifndef _DOCSEQ_H_INCLUDED_ #define _DOCSEQ_H_INCLUDED_ + +#include "autoconfig.h" + #include #include #include - +#include +#include #include "rcldoc.h" -#include "refcntr.h" #include "hldata.h" -#include "ptmutex.h" // Need this for the "Snippet" class def. #include "rclquery.h" @@ -153,7 +155,7 @@ class DocSequence { virtual bool canSort() {return false;} virtual bool setFiltSpec(const DocSeqFiltSpec &) {return false;} virtual bool setSortSpec(const DocSeqSortSpec &) {return false;} - virtual RefCntr getSourceSeq() {return RefCntr();} + virtual std::shared_ptr getSourceSeq() {return std::shared_ptr();} static void set_translations(const std::string& sort, const std::string& filt) { @@ -165,7 +167,7 @@ class DocSequence { protected: friend class DocSeqModifier; virtual Rcl::Db *getDb() = 0; - static PTMutexInit o_dblock; + static std::mutex o_dblock; static std::string o_sort_trans; static std::string o_filt_trans; std::string m_reason; @@ -179,59 +181,59 @@ protected: */ class DocSeqModifier : public DocSequence { public: - DocSeqModifier(RefCntr iseq) + DocSeqModifier(std::shared_ptr iseq) : DocSequence(""), m_seq(iseq) {} virtual ~DocSeqModifier() {} virtual bool getAbstract(Rcl::Doc& doc, std::vector& abs) { - if (m_seq.isNull()) + if (!m_seq) return false; return m_seq->getAbstract(doc, abs); } virtual bool getAbstract(Rcl::Doc& doc, std::vector& abs) { - if (m_seq.isNull()) + if (!m_seq) return false; return m_seq->getAbstract(doc, abs); } /** Get duplicates. */ virtual bool docDups(const Rcl::Doc& doc, std::vector& dups) { - if (m_seq.isNull()) + if (!m_seq) return false; return m_seq->docDups(doc, dups); } virtual bool snippetsCapable() { - if (m_seq.isNull()) + if (!m_seq) return false; return m_seq->snippetsCapable(); } virtual std::string getDescription() { - if (m_seq.isNull()) + if (!m_seq) return ""; return m_seq->getDescription(); } virtual void getTerms(HighlightData& hld) { - if (m_seq.isNull()) + if (!m_seq) return; m_seq->getTerms(hld); } virtual bool getEnclosing(Rcl::Doc& doc, Rcl::Doc& pdoc) { - if (m_seq.isNull()) + if (!m_seq) return false; return m_seq->getEnclosing(doc, pdoc); } virtual std::string getReason() { - if (m_seq.isNull()) + if (!m_seq) return string(); return m_seq->getReason(); } @@ -239,7 +241,7 @@ public: { return m_seq->title(); } - virtual RefCntr getSourceSeq() + virtual std::shared_ptr getSourceSeq() { return m_seq; } @@ -247,12 +249,12 @@ public: protected: virtual Rcl::Db *getDb() { - if (m_seq.isNull()) + if (!m_seq) return 0; return m_seq->getDb(); } - RefCntr m_seq; + std::shared_ptr m_seq; }; class RclConfig; @@ -261,7 +263,7 @@ class RclConfig; // sorting and filtering in ways depending on the base seqs capabilities class DocSource : public DocSeqModifier { public: - DocSource(RclConfig *config, RefCntr iseq) + DocSource(RclConfig *config, std::shared_ptr iseq) : DocSeqModifier(iseq), m_config(config) {} virtual bool canFilter() {return true;} @@ -270,13 +272,13 @@ public: virtual bool setSortSpec(const DocSeqSortSpec &); virtual bool getDoc(int num, Rcl::Doc &doc, std::string *sh = 0) { - if (m_seq.isNull()) + if (!m_seq) return false; return m_seq->getDoc(num, doc, sh); } virtual int getResCnt() { - if (m_seq.isNull()) + if (!m_seq) return 0; return m_seq->getResCnt(); } diff --git a/src/query/docseqdb.cpp b/src/query/docseqdb.cpp index 4b7a9dec..4ae37edc 100644 --- a/src/query/docseqdb.cpp +++ b/src/query/docseqdb.cpp @@ -14,19 +14,22 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#include "autoconfig.h" + #include #include #include -using std::list; #include "docseqdb.h" #include "rcldb.h" -#include "debuglog.h" +#include "log.h" #include "wasatorcl.h" -DocSequenceDb::DocSequenceDb(RefCntr q, const string &t, - RefCntr sdata) +using std::list; + +DocSequenceDb::DocSequenceDb(std::shared_ptr q, const string &t, + std::shared_ptr sdata) : DocSequence(t), m_q(q), m_sdata(sdata), m_fsdata(sdata), m_rescnt(-1), m_queryBuildAbstract(true), @@ -50,7 +53,7 @@ string DocSequenceDb::getDescription() bool DocSequenceDb::getDoc(int num, Rcl::Doc &doc, string *sh) { - PTMutexLocker locker(o_dblock); + std::unique_lock locker(o_dblock); if (!setQuery()) return false; if (sh) sh->erase(); @@ -59,7 +62,7 @@ bool DocSequenceDb::getDoc(int num, Rcl::Doc &doc, string *sh) int DocSequenceDb::getResCnt() { - PTMutexLocker locker(o_dblock); + std::unique_lock locker(o_dblock); if (!setQuery()) return false; if (m_rescnt < 0) { @@ -74,8 +77,8 @@ static const string cstr_mre("[...]"); // We ignore most abstract/snippets preferences. bool DocSequenceDb::getAbstract(Rcl::Doc &doc, vector& vpabs) { - LOGDEB(("DocSequenceDb::getAbstract/pair\n")); - PTMutexLocker locker(o_dblock); + LOGDEB("DocSequenceDb::getAbstract/pair\n" ); + std::unique_lock locker(o_dblock); if (!setQuery()) return false; @@ -86,8 +89,7 @@ bool DocSequenceDb::getAbstract(Rcl::Doc &doc, vector& vpabs) ret = m_q->makeDocAbstract(doc, vpabs, maxoccs, m_q->whatDb()->getAbsCtxLen()+ 2); } - LOGDEB(("DocSequenceDb::getAbstract: got ret %d vpabs len %u\n", ret, - vpabs.size())); + LOGDEB("DocSequenceDb::getAbstract: got ret " << (ret) << " vpabs len " << (vpabs.size()) << "\n" ); if (vpabs.empty()) { return true; } @@ -106,7 +108,7 @@ bool DocSequenceDb::getAbstract(Rcl::Doc &doc, vector& vpabs) bool DocSequenceDb::getAbstract(Rcl::Doc &doc, vector& vabs) { - PTMutexLocker locker(o_dblock); + std::unique_lock locker(o_dblock); if (!setQuery()) return false; if (m_q->whatDb() && @@ -120,7 +122,7 @@ bool DocSequenceDb::getAbstract(Rcl::Doc &doc, vector& vabs) int DocSequenceDb::getFirstMatchPage(Rcl::Doc &doc, string& term) { - PTMutexLocker locker(o_dblock); + std::unique_lock locker(o_dblock); if (!setQuery()) return false; if (m_q->whatDb()) { @@ -131,12 +133,12 @@ int DocSequenceDb::getFirstMatchPage(Rcl::Doc &doc, string& term) Rcl::Db *DocSequenceDb::getDb() { - return m_q.isNotNull() ? m_q->whatDb() : 0; + return m_q ? m_q->whatDb() : 0; } list DocSequenceDb::expand(Rcl::Doc &doc) { - PTMutexLocker locker(o_dblock); + std::unique_lock locker(o_dblock); if (!setQuery()) return list(); vector v = m_q->expand(doc); @@ -158,11 +160,11 @@ string DocSequenceDb::title() bool DocSequenceDb::setFiltSpec(const DocSeqFiltSpec &fs) { - LOGDEB(("DocSequenceDb::setFiltSpec\n")); - PTMutexLocker locker(o_dblock); + LOGDEB("DocSequenceDb::setFiltSpec\n" ); + std::unique_lock locker(o_dblock); if (fs.isNotNull()) { // We build a search spec by adding a filtering layer to the base one. - m_fsdata = RefCntr( + m_fsdata = std::shared_ptr( new Rcl::SearchData(Rcl::SCLT_AND, m_sdata->getStemLang())); Rcl::SearchDataClauseSub *cl = new Rcl::SearchDataClauseSub(m_sdata); @@ -175,7 +177,7 @@ bool DocSequenceDb::setFiltSpec(const DocSeqFiltSpec &fs) break; case DocSeqFiltSpec::DSFS_QLANG: { - if (m_q.isNull()) + if (!m_q) break; string reason; @@ -186,7 +188,7 @@ bool DocSequenceDb::setFiltSpec(const DocSeqFiltSpec &fs) if (sd) { Rcl::SearchDataClauseSub *cl1 = new Rcl::SearchDataClauseSub( - RefCntr(sd)); + std::shared_ptr(sd)); m_fsdata->addClause(cl1); } } @@ -206,9 +208,8 @@ bool DocSequenceDb::setFiltSpec(const DocSeqFiltSpec &fs) bool DocSequenceDb::setSortSpec(const DocSeqSortSpec &spec) { - LOGDEB(("DocSequenceDb::setSortSpec: fld [%s] %s\n", - spec.field.c_str(), spec.desc ? "desc" : "asc")); - PTMutexLocker locker(o_dblock); + LOGDEB("DocSequenceDb::setSortSpec: fld [" << (spec.field) << "] " << (spec.desc ? "desc" : "asc") << "\n" ); + std::unique_lock locker(o_dblock); if (spec.isNotNull()) { m_q->setSortBy(spec.field, !spec.desc); m_isSorted = true; @@ -230,8 +231,7 @@ bool DocSequenceDb::setQuery() m_lastSQStatus = m_q->setQuery(m_fsdata); if (!m_lastSQStatus) { m_reason = m_q->getReason(); - LOGERR(("DocSequenceDb::setQuery: rclquery::setQuery failed: %s\n", - m_reason.c_str())); + LOGERR("DocSequenceDb::setQuery: rclquery::setQuery failed: " << (m_reason) << "\n" ); } return m_lastSQStatus; } @@ -239,9 +239,10 @@ bool DocSequenceDb::setQuery() bool DocSequenceDb::docDups(const Rcl::Doc& doc, std::vector& dups) { if (m_q->whatDb()) { - PTMutexLocker locker(o_dblock); + std::unique_lock locker(o_dblock); return m_q->whatDb()->docDups(doc, dups); } else { return false; } } + diff --git a/src/query/docseqdb.h b/src/query/docseqdb.h index a1304ea8..882cc5a6 100644 --- a/src/query/docseqdb.h +++ b/src/query/docseqdb.h @@ -17,7 +17,7 @@ #ifndef _DOCSEQDB_H_INCLUDED_ #define _DOCSEQDB_H_INCLUDED_ #include "docseq.h" -#include "refcntr.h" +#include #include "searchdata.h" #include "rclquery.h" @@ -25,8 +25,8 @@ /** A DocSequence from a Db query */ class DocSequenceDb : public DocSequence { public: - DocSequenceDb(RefCntr q, const string &t, - RefCntr sdata); + DocSequenceDb(std::shared_ptr q, const string &t, + std::shared_ptr sdata); virtual ~DocSequenceDb() {} virtual bool getDoc(int num, Rcl::Doc &doc, string * = 0); virtual int getResCnt(); @@ -60,9 +60,9 @@ class DocSequenceDb : public DocSequence { protected: virtual Rcl::Db *getDb(); private: - RefCntr m_q; - RefCntr m_sdata; - RefCntr m_fsdata; // Filtered + std::shared_ptr m_q; + std::shared_ptr m_sdata; + std::shared_ptr m_fsdata; // Filtered int m_rescnt; bool m_queryBuildAbstract; bool m_queryReplaceAbstract; diff --git a/src/query/docseqhist.cpp b/src/query/docseqhist.cpp index 264b36f0..4a3d40e6 100644 --- a/src/query/docseqhist.cpp +++ b/src/query/docseqhist.cpp @@ -26,7 +26,7 @@ using std::list; #include "rcldb.h" #include "fileudi.h" #include "base64.h" -#include "debuglog.h" +#include "log.h" #include "smallut.h" // Encode document history entry: @@ -34,11 +34,9 @@ using std::list; // The U distinguishes udi-based entries from older fn+ipath ones bool RclDHistoryEntry::encode(string& value) { - char chartime[30]; - sprintf(chartime,"%ld", unixtime); string budi; base64_encode(udi, budi); - value = string("U ") + string(chartime) + " " + budi; + value = string("U ") + lltodecstr(unixtime) + " " + budi; return true; } @@ -79,7 +77,7 @@ bool RclDHistoryEntry::decode(const string &value) // Old style entry found, make an udi, using the fs udi maker make_udi(fn, ipath, udi); } - LOGDEB1(("RclDHistoryEntry::decode: udi [%s]\n", udi.c_str())); + LOGDEB1("RclDHistoryEntry::decode: udi [" << (udi) << "]\n" ); return true; } @@ -91,8 +89,7 @@ bool RclDHistoryEntry::equal(const DynConfEntry& other) bool historyEnterDoc(RclDynConf *dncf, const string& udi) { - LOGDEB1(("historyEnterDoc: [%s] into %s\n", - udi.c_str(), dncf->getFilename().c_str())); + LOGDEB1("historyEnterDoc: [" << (udi) << "] into " << (dncf->getFilename()) << "\n" ); RclDHistoryEntry ne(time(0), udi); RclDHistoryEntry scratch; return dncf->insertNew(docHistSubKey, ne, scratch, 200); @@ -161,5 +158,6 @@ int DocSequenceHistory::getResCnt() { if (m_hlist.empty()) m_hlist = getDocHistory(m_hist); - return m_hlist.size(); + return int(m_hlist.size()); } + diff --git a/src/query/docseqhist.h b/src/query/docseqhist.h index b176c776..2f2f5abc 100644 --- a/src/query/docseqhist.h +++ b/src/query/docseqhist.h @@ -16,6 +16,7 @@ */ #ifndef _DOCSEQHIST_H_INCLUDED_ #define _DOCSEQHIST_H_INCLUDED_ +#include #include "docseq.h" #include "dynconf.h" @@ -28,13 +29,13 @@ namespace Rcl { class RclDHistoryEntry : public DynConfEntry { public: RclDHistoryEntry() : unixtime(0) {} - RclDHistoryEntry(long t, const string& u) + RclDHistoryEntry(time_t t, const string& u) : unixtime(t), udi(u) {} virtual ~RclDHistoryEntry() {} virtual bool decode(const string &value); virtual bool encode(string& value); virtual bool equal(const DynConfEntry& other); - long unixtime; + time_t unixtime; string udi; }; @@ -57,7 +58,7 @@ private: Rcl::Db *m_db; RclDynConf *m_hist; int m_prevnum; - long m_prevtime; + time_t m_prevtime; std::string m_description; // This is just an nls translated 'doc history' std::list m_hlist; std::list::const_iterator m_it; diff --git a/src/query/dynconf.cpp b/src/query/dynconf.cpp index c8527071..17e6338a 100644 --- a/src/query/dynconf.cpp +++ b/src/query/dynconf.cpp @@ -23,7 +23,7 @@ #include "dynconf.h" #include "base64.h" #include "smallut.h" -#include "debuglog.h" +#include "log.h" using namespace std; @@ -44,13 +44,13 @@ bool RclDynConf::insertNew(const string &sk, DynConfEntry &n, DynConfEntry &s, for (it = names.begin(); it != names.end(); it++) { string oval; if (!m_data.get(*it, oval, sk)) { - LOGDEB(("No data for %s\n", (*it).c_str())); + LOGDEB("No data for " << ((*it)) << "\n" ); continue; } s.decode(oval); if (s.equal(n)) { - LOGDEB(("Erasing old entry\n")); + LOGDEB("Erasing old entry\n" ); m_data.erase(*it, sk); changed = true; } @@ -80,9 +80,9 @@ bool RclDynConf::insertNew(const string &sk, DynConfEntry &n, DynConfEntry &s, string value; n.encode(value); - LOGDEB1(("Encoded value [%s] (%d)\n", value.c_str(), value.size())); + LOGDEB1("Encoded value [" << (value) << "] (" << (value.size()) << ")\n" ); if (!m_data.set(string(nname), value, sk)) { - LOGERR(("RclDHistory::insertNew: set failed\n")); + LOGERR("RclDHistory::insertNew: set failed\n" ); return false; } return true; @@ -124,7 +124,8 @@ list RclDynConf::getStringList(const string sk) #include #include "history.h" -#include "debuglog.h" +#include "log.h" + #ifndef NO_NAMESPACES using namespace std; @@ -211,3 +212,4 @@ int main(int argc, char **argv) } #endif + diff --git a/src/query/filtseq.cpp b/src/query/filtseq.cpp index ecb991b0..5b00d8fc 100644 --- a/src/query/filtseq.cpp +++ b/src/query/filtseq.cpp @@ -15,7 +15,7 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include "debuglog.h" +#include "log.h" #include "filtseq.h" #include "rclconfig.h" @@ -23,20 +23,19 @@ using std::string; static bool filter(const DocSeqFiltSpec& fs, const Rcl::Doc *x) { - LOGDEB2((" Filter: ncrits %d\n", fs.crits.size())); + LOGDEB2(" Filter: ncrits " << (fs.crits.size()) << "\n" ); // Compare using each criterion in term. We're doing an or: // 1st ok ends for (unsigned int i = 0; i < fs.crits.size(); i++) { switch (fs.crits[i]) { case DocSeqFiltSpec::DSFS_MIMETYPE: - LOGDEB2((" filter: MIMETYPE: me [%s] doc [%s]\n", - fs.values[i].c_str(), x->mimetype.c_str())); + LOGDEB2(" filter: MIMETYPE: me [" << (fs.values[i]) << "] doc [" << (x->mimetype) << "]\n" ); if (x->mimetype == fs.values[i]) return true; break; case DocSeqFiltSpec::DSFS_QLANG: { - LOGDEB((" filter: QLANG [%s]!!\n", fs.values[i].c_str())); + LOGDEB(" filter: QLANG [" << (fs.values[i]) << "]!!\n" ); } break; case DocSeqFiltSpec::DSFS_PASSALL: @@ -47,7 +46,7 @@ static bool filter(const DocSeqFiltSpec& fs, const Rcl::Doc *x) return false; } -DocSeqFiltered::DocSeqFiltered(RclConfig *conf, RefCntr iseq, +DocSeqFiltered::DocSeqFiltered(RclConfig *conf, std::shared_ptr iseq, DocSeqFiltSpec &filtspec) : DocSeqModifier(iseq), m_config(conf) { @@ -56,7 +55,7 @@ DocSeqFiltered::DocSeqFiltered(RclConfig *conf, RefCntr iseq, bool DocSeqFiltered::setFiltSpec(const DocSeqFiltSpec &filtspec) { - LOGDEB0(("DocSeqFiltered::setFiltSpec\n")); + LOGDEB0("DocSeqFiltered::setFiltSpec\n" ); for (unsigned int i = 0; i < filtspec.crits.size(); i++) { switch (filtspec.crits[i]) { case DocSeqFiltSpec::DSFS_MIMETYPE: @@ -74,7 +73,7 @@ bool DocSeqFiltered::setFiltSpec(const DocSeqFiltSpec &filtspec) m_config->getMimeCatTypes(catg, tps); for (vector::const_iterator it = tps.begin(); it != tps.end(); it++) { - LOGDEB2(("Adding mime: [%s]\n", it->c_str())); + LOGDEB2("Adding mime: [" << (it) << "]\n" ); m_spec.orCrit(DocSeqFiltSpec::DSFS_MIMETYPE, *it); } } @@ -94,7 +93,7 @@ bool DocSeqFiltered::setFiltSpec(const DocSeqFiltSpec &filtspec) bool DocSeqFiltered::getDoc(int idx, Rcl::Doc &doc, string *) { - LOGDEB2(("DocSeqFiltered::getDoc() fetching %d\n", idx)); + LOGDEB2("DocSeqFiltered::getDoc() fetching " << (idx) << "\n" ); if (idx >= (int)m_dbindices.size()) { // Have to fetch docs and filter until we get enough or @@ -106,7 +105,6 @@ bool DocSeqFiltered::getDoc(int idx, Rcl::Doc &doc, string *) // Loop until we get enough docs Rcl::Doc tdoc; - int i = 0; while (idx >= (int)m_dbindices.size()) { if (!m_seq->getDoc(backend_idx, tdoc)) return false; @@ -123,3 +121,4 @@ bool DocSeqFiltered::getDoc(int idx, Rcl::Doc &doc, string *) } return true; } + diff --git a/src/query/filtseq.h b/src/query/filtseq.h index c9ef771c..c9ceab77 100644 --- a/src/query/filtseq.h +++ b/src/query/filtseq.h @@ -16,13 +16,12 @@ */ #ifndef _FILTSEQ_H_INCLUDED_ #define _FILTSEQ_H_INCLUDED_ +#include "autoconfig.h" #include #include -using std::string; -using std::vector; +#include -#include "refcntr.h" #include "docseq.h" class RclConfig; @@ -33,17 +32,17 @@ class RclConfig; */ class DocSeqFiltered : public DocSeqModifier { public: - DocSeqFiltered(RclConfig *conf, RefCntr iseq, + DocSeqFiltered(RclConfig *conf, std::shared_ptr iseq, DocSeqFiltSpec &filtspec); virtual ~DocSeqFiltered() {} virtual bool canFilter() {return true;} virtual bool setFiltSpec(const DocSeqFiltSpec &filtspec); - virtual bool getDoc(int num, Rcl::Doc &doc, string *sh = 0); + virtual bool getDoc(int num, Rcl::Doc &doc, std::string *sh = 0); virtual int getResCnt() {return m_seq->getResCnt();} private: RclConfig *m_config; DocSeqFiltSpec m_spec; - vector m_dbindices; + std::vector m_dbindices; }; #endif /* _FILTSEQ_H_INCLUDED_ */ diff --git a/src/query/location.hh b/src/query/location.hh new file mode 100644 index 00000000..7b708fd4 --- /dev/null +++ b/src/query/location.hh @@ -0,0 +1,187 @@ +// A Bison parser, made by GNU Bison 3.0.2. + +// Locations for Bison parsers in C++ + +// Copyright (C) 2002-2013 Free Software Foundation, Inc. + +// 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 3 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 General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// As a special exception, you may create a larger work that contains +// part or all of the Bison parser skeleton and distribute that work +// under terms of your choice, so long as that work isn't itself a +// parser generator using the skeleton or a modified version thereof +// as a parser skeleton. Alternatively, if you modify or redistribute +// the parser skeleton itself, you may (at your option) remove this +// special exception, which will cause the skeleton and the resulting +// Bison output files to be licensed under the GNU General Public +// License without this special exception. + +// This special exception was added by the Free Software Foundation in +// version 2.2 of Bison. + +/** + ** \file location.hh + ** Define the yy::location class. + */ + +#ifndef YY_YY_LOCATION_HH_INCLUDED +# define YY_YY_LOCATION_HH_INCLUDED + +# include "position.hh" + + +namespace yy { +#line 46 "location.hh" // location.cc:291 + /// Abstract a location. + class location + { + public: + + /// Construct a location from \a b to \a e. + location (const position& b, const position& e) + : begin (b) + , end (e) + { + } + + /// Construct a 0-width location in \a p. + explicit location (const position& p = position ()) + : begin (p) + , end (p) + { + } + + /// Construct a 0-width location in \a f, \a l, \a c. + explicit location (std::string* f, + unsigned int l = 1u, + unsigned int c = 1u) + : begin (f, l, c) + , end (f, l, c) + { + } + + + /// Initialization. + void initialize (std::string* f = YY_NULLPTR, + unsigned int l = 1u, + unsigned int c = 1u) + { + begin.initialize (f, l, c); + end = begin; + } + + /** \name Line and Column related manipulators + ** \{ */ + public: + /// Reset initial location to final location. + void step () + { + begin = end; + } + + /// Extend the current location to the COUNT next columns. + void columns (int count = 1) + { + end += count; + } + + /// Extend the current location to the COUNT next lines. + void lines (int count = 1) + { + end.lines (count); + } + /** \} */ + + + public: + /// Beginning of the located region. + position begin; + /// End of the located region. + position end; + }; + + /// Join two location objects to create a location. + inline location operator+ (location res, const location& end) + { + res.end = end.end; + return res; + } + + /// Change end position in place. + inline location& operator+= (location& res, int width) + { + res.columns (width); + return res; + } + + /// Change end position. + inline location operator+ (location res, int width) + { + return res += width; + } + + /// Change end position in place. + inline location& operator-= (location& res, int width) + { + return res += -width; + } + + /// Change end position. + inline location operator- (const location& begin, int width) + { + return begin + -width; + } + + /// Compare two location objects. + inline bool + operator== (const location& loc1, const location& loc2) + { + return loc1.begin == loc2.begin && loc1.end == loc2.end; + } + + /// Compare two location objects. + inline bool + operator!= (const location& loc1, const location& loc2) + { + return !(loc1 == loc2); + } + + /** \brief Intercept output stream redirection. + ** \param ostr the destination output stream + ** \param loc a reference to the location to redirect + ** + ** Avoid duplicate information. + */ + template + inline std::basic_ostream& + operator<< (std::basic_ostream& ostr, const location& loc) + { + unsigned int end_col = 0 < loc.end.column ? loc.end.column - 1 : 0; + ostr << loc.begin// << "(" << loc.end << ") " +; + if (loc.end.filename + && (!loc.begin.filename + || *loc.begin.filename != *loc.end.filename)) + ostr << '-' << loc.end.filename << ':' << loc.end.line << '.' << end_col; + else if (loc.begin.line < loc.end.line) + ostr << '-' << loc.end.line << '.' << end_col; + else if (loc.begin.column < end_col) + ostr << '-' << end_col; + return ostr; + } + + +} // yy +#line 187 "location.hh" // location.cc:291 +#endif // !YY_YY_LOCATION_HH_INCLUDED diff --git a/src/query/plaintorich.cpp b/src/query/plaintorich.cpp index 9ea08fdc..a1752e46 100644 --- a/src/query/plaintorich.cpp +++ b/src/query/plaintorich.cpp @@ -15,7 +15,7 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ - +#include #include #include #include @@ -31,31 +31,22 @@ using std::set; #include "rcldb.h" #include "rclconfig.h" -#include "debuglog.h" +#include "log.h" #include "textsplit.h" #include "utf8iter.h" #include "smallut.h" +#include "chrono.h" #include "plaintorich.h" #include "cancelcheck.h" #include "unacpp.h" -// For debug printing -static string vecStringToString(const vector& t) -{ - string sterms; - for (vector::const_iterator it = t.begin(); it != t.end(); it++) { - sterms += "[" + *it + "] "; - } - return sterms; -} - struct MatchEntry { // Start/End byte offsets in the document text pair offs; // Index of the search group this comes from: this is to relate a // match to the original user input. - unsigned int grpidx; - MatchEntry(int sta, int sto, unsigned int idx) + size_t grpidx; + MatchEntry(int sta, int sto, size_t idx) : offs(sta, sto), grpidx(idx) { } @@ -95,17 +86,15 @@ class TextSplitPTR : public TextSplit { string dumb = term; if (o_index_stripchars) { if (!unacmaybefold(term, dumb, "UTF-8", UNACOP_UNACFOLD)) { - LOGINFO(("PlainToRich::takeword: unac failed for [%s]\n", - term.c_str())); + LOGINFO("PlainToRich::takeword: unac failed for [" << (term) << "]\n" ); return true; } } - //LOGDEB2(("Input dumbbed term: '%s' %d %d %d\n", dumb.c_str(), - // pos, bts, bte)); + //LOGDEB2("Input dumbbed term: '" << (dumb) << "' " << (// pos) << " " << (bts) << " " << (bte) << "\n" ); // If this word is a search term, remember its byte-offset span. - map::const_iterator it = m_terms.find(dumb); + map::const_iterator it = m_terms.find(dumb); if (it != m_terms.end()) { tboffs.push_back(MatchEntry(bts, bte, (*it).second)); } @@ -115,7 +104,7 @@ class TextSplitPTR : public TextSplit { // Term group (phrase/near) handling m_plists[dumb].push_back(pos); m_gpostobytes[pos] = pair(bts, bte); - //LOGDEB2(("Recorded bpos for %d: %d %d\n", pos, bts, bte)); + //LOGDEB2("Recorded bpos for " << (pos) << ": " << (bts) << " " << (bte) << "\n" ); } // Check for cancellation request @@ -135,7 +124,7 @@ private: int m_wcount; // In: user query terms - map m_terms; + map m_terms; // m_gterms holds all the terms in m_groups, as a set for quick lookup set m_gterms; @@ -178,8 +167,7 @@ static bool do_proximity_test(int window, vector* >& plists, unsigned int i, int min, int max, int *sp, int *ep, int minpos) { - LOGDEB1(("do_prox_test: win %d i %d min %d max %d minpos %d\n", - window, i, min, max, minpos)); + LOGDEB1("do_prox_test: win " << (window) << " i " << (i) << " min " << (min) << " max " << (max) << " minpos " << (minpos) << "\n" ); int tmp = max + 1 - window; if (tmp < minpos) tmp = minpos; @@ -214,10 +202,9 @@ static bool do_proximity_test(int window, vector* >& plists, bool TextSplitPTR::matchGroup(unsigned int grpidx) { const vector& terms = m_hdata.groups[grpidx]; - int window = m_hdata.groups[grpidx].size() + m_hdata.slacks[grpidx]; + int window = int(m_hdata.groups[grpidx].size() + m_hdata.slacks[grpidx]); - LOGDEB1(("TextSplitPTR::matchGroup:d %d: %s\n", window, - vecStringToString(terms).c_str())); + LOGDEB1("TextSplitPTR::matchGroup:d " << (window) << ": " << (vecStringToString(terms)) << "\n" ); // The position lists we are going to work with. We extract them from the // (string->plist) map @@ -233,8 +220,7 @@ bool TextSplitPTR::matchGroup(unsigned int grpidx) it != terms.end(); it++) { map >::iterator pl = m_plists.find(*it); if (pl == m_plists.end()) { - LOGDEB1(("TextSplitPTR::matchGroup: [%s] not found in m_plists\n", - (*it).c_str())); + LOGDEB1("TextSplitPTR::matchGroup: [" << ((*it)) << "] not found in m_plists\n" ); return false; } plists.push_back(&(pl->second)); @@ -243,7 +229,7 @@ bool TextSplitPTR::matchGroup(unsigned int grpidx) // I think this can't actually happen, was useful when we used to // prune the groups, but doesn't hurt. if (plists.size() < 2) { - LOGDEB1(("TextSplitPTR::matchGroup: no actual groups found\n")); + LOGDEB1("TextSplitPTR::matchGroup: no actual groups found\n" ); return false; } // Sort the positions lists so that the shorter is first @@ -254,11 +240,10 @@ bool TextSplitPTR::matchGroup(unsigned int grpidx) it = plistToTerm.find(plists[0]); if (it == plistToTerm.end()) { // SuperWeird - LOGERR(("matchGroup: term for first list not found !?!\n")); + LOGERR("matchGroup: term for first list not found !?!\n" ); return false; } - LOGDEB1(("matchGroup: walking the shortest plist. Term [%s], len %d\n", - it->second.c_str(), plists[0]->size())); + LOGDEB1("matchGroup: walking the shortest plist. Term [" << (it->second) << "], len " << (plists[0]->size()) << "\n" ); } // Minpos is the highest end of a found match. While looking for @@ -270,11 +255,10 @@ bool TextSplitPTR::matchGroup(unsigned int grpidx) for (vector::iterator it = plists[0]->begin(); it != plists[0]->end(); it++) { int pos = *it; - int sta = int(10E9), sto = 0; - LOGDEB2(("MatchGroup: Testing at pos %d\n", pos)); + int sta = INT_MAX, sto = 0; + LOGDEB2("MatchGroup: Testing at pos " << (pos) << "\n" ); if (do_proximity_test(window,plists, 1, pos, pos, &sta, &sto, minpos)) { - LOGDEB1(("TextSplitPTR::matchGroup: MATCH termpos [%d,%d]\n", - sta, sto)); + LOGDEB1("TextSplitPTR::matchGroup: MATCH termpos [" << (sta) << "," << (sto) << "]\n" ); // Maybe extend the window by 1st term position, this was not // done by do_prox.. SETMINMAX(pos, sta, sto); @@ -283,15 +267,14 @@ bool TextSplitPTR::matchGroup(unsigned int grpidx) map >::iterator i1 = m_gpostobytes.find(sta); map >::iterator i2 = m_gpostobytes.find(sto); if (i1 != m_gpostobytes.end() && i2 != m_gpostobytes.end()) { - LOGDEB2(("TextSplitPTR::matchGroup: pushing bpos %d %d\n", - i1->second.first, i2->second.second)); + LOGDEB2("TextSplitPTR::matchGroup: pushing bpos " << (i1->second.first) << " " << (i2->second.second) << "\n" ); tboffs.push_back(MatchEntry(i1->second.first, i2->second.second, grpidx)); } else { - LOGDEB0(("matchGroup: no bpos found for %d or %d\n", sta, sto)); + LOGDEB0("matchGroup: no bpos found for " << (sta) << " or " << (sto) << "\n" ); } } else { - LOGDEB1(("matchGroup: no group match found at this position\n")); + LOGDEB1("matchGroup: no group match found at this position\n" ); } } @@ -342,7 +325,7 @@ bool PlainToRich::plaintorich(const string& in, { Chrono chron; bool ret = true; - LOGDEB1(("plaintorichich: in: [%s]\n", in.c_str())); + LOGDEB1("plaintorichich: in: [" << (in) << "]\n" ); m_hdata = &hdata; // Compute the positions for the query terms. We use the text @@ -352,10 +335,10 @@ bool PlainToRich::plaintorich(const string& in, // Note: the splitter returns the term locations in byte, not // character, offsets. splitter.text_to_words(in); - LOGDEB2(("plaintorich: split done %d mS\n", chron.millis())); + LOGDEB2("plaintorich: split done " << (chron.millis()) << " mS\n" ); // Compute the positions for NEAR and PHRASE groups. splitter.matchGroups(); - LOGDEB2(("plaintorich: group match done %d mS\n", chron.millis())); + LOGDEB2("plaintorich: group match done " << (chron.millis()) << " mS\n" ); out.clear(); out.push_back(""); @@ -368,7 +351,7 @@ bool PlainToRich::plaintorich(const string& in, // a term match when we are actually looking for a group match // (the snippet generator does this...). if (splitter.tboffs.empty()) { - LOGDEB1(("plaintorich: no term matches\n")); + LOGDEB1("plaintorich: no term matches\n" ); ret = false; } @@ -381,7 +364,7 @@ bool PlainToRich::plaintorich(const string& in, #if 0 for (vector >::const_iterator it = splitter.tboffs.begin(); it != splitter.tboffs.end(); it++) { - LOGDEB2(("plaintorich: region: %d %d\n", it->first, it->second)); + LOGDEB2("plaintorich: region: " << (it->first) << " " << (it->second) << "\n" ); } #endif @@ -417,10 +400,10 @@ bool PlainToRich::plaintorich(const string& in, // If we still have terms positions, check (byte) position. If // we are at or after a term match, mark. if (tPosIt != tPosEnd) { - int ibyteidx = chariter.getBpos(); + int ibyteidx = int(chariter.getBpos()); if (ibyteidx == tPosIt->offs.first) { if (!intag && ibyteidx >= (int)headend) { - *olit += startMatch(tPosIt->grpidx); + *olit += startMatch((unsigned int)(tPosIt->grpidx)); } inrcltag = 1; } else if (ibyteidx == tPosIt->offs.second) { @@ -541,6 +524,7 @@ bool PlainToRich::plaintorich(const string& in, fclose(fp); } #endif - LOGDEB2(("plaintorich: done %d mS\n", chron.millis())); + LOGDEB2("plaintorich: done " << (chron.millis()) << " mS\n" ); return ret; } + diff --git a/src/query/position.hh b/src/query/position.hh new file mode 100644 index 00000000..107d8e11 --- /dev/null +++ b/src/query/position.hh @@ -0,0 +1,180 @@ +// A Bison parser, made by GNU Bison 3.0.2. + +// Positions for Bison parsers in C++ + +// Copyright (C) 2002-2013 Free Software Foundation, Inc. + +// 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 3 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 General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// As a special exception, you may create a larger work that contains +// part or all of the Bison parser skeleton and distribute that work +// under terms of your choice, so long as that work isn't itself a +// parser generator using the skeleton or a modified version thereof +// as a parser skeleton. Alternatively, if you modify or redistribute +// the parser skeleton itself, you may (at your option) remove this +// special exception, which will cause the skeleton and the resulting +// Bison output files to be licensed under the GNU General Public +// License without this special exception. + +// This special exception was added by the Free Software Foundation in +// version 2.2 of Bison. + +/** + ** \file position.hh + ** Define the yy::position class. + */ + +#ifndef YY_YY_POSITION_HH_INCLUDED +# define YY_YY_POSITION_HH_INCLUDED + +# include // std::max +# include +# include + +# ifndef YY_NULLPTR +# if defined __cplusplus && 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# endif + + +namespace yy { +#line 56 "position.hh" // location.cc:291 + /// Abstract a position. + class position + { + public: + /// Construct a position. + explicit position (std::string* f = YY_NULLPTR, + unsigned int l = 1u, + unsigned int c = 1u) + : filename (f) + , line (l) + , column (c) + { + } + + + /// Initialization. + void initialize (std::string* fn = YY_NULLPTR, + unsigned int l = 1u, + unsigned int c = 1u) + { + filename = fn; + line = l; + column = c; + } + + /** \name Line and Column related manipulators + ** \{ */ + /// (line related) Advance to the COUNT next lines. + void lines (int count = 1) + { + if (count) + { + column = 1u; + line = add_ (line, count, 1); + } + } + + /// (column related) Advance to the COUNT next columns. + void columns (int count = 1) + { + column = add_ (column, count, 1); + } + /** \} */ + + /// File name to which this position refers. + std::string* filename; + /// Current line number. + unsigned int line; + /// Current column number. + unsigned int column; + + private: + /// Compute max(min, lhs+rhs) (provided min <= lhs). + static unsigned int add_ (unsigned int lhs, int rhs, unsigned int min) + { + return (0 < rhs || -static_cast(rhs) < lhs + ? rhs + lhs + : min); + } + }; + + /// Add and assign a position. + inline position& + operator+= (position& res, int width) + { + res.columns (width); + return res; + } + + /// Add two position objects. + inline position + operator+ (position res, int width) + { + return res += width; + } + + /// Add and assign a position. + inline position& + operator-= (position& res, int width) + { + return res += -width; + } + + /// Add two position objects. + inline position + operator- (position res, int width) + { + return res -= width; + } + + /// Compare two position objects. + inline bool + operator== (const position& pos1, const position& pos2) + { + return (pos1.line == pos2.line + && pos1.column == pos2.column + && (pos1.filename == pos2.filename + || (pos1.filename && pos2.filename + && *pos1.filename == *pos2.filename))); + } + + /// Compare two position objects. + inline bool + operator!= (const position& pos1, const position& pos2) + { + return !(pos1 == pos2); + } + + /** \brief Intercept output stream redirection. + ** \param ostr the destination output stream + ** \param pos a reference to the position to redirect + */ + template + inline std::basic_ostream& + operator<< (std::basic_ostream& ostr, const position& pos) + { + if (pos.filename) + ostr << *pos.filename << ':'; + return ostr << pos.line << '.' << pos.column; + } + + +} // yy +#line 180 "position.hh" // location.cc:291 +#endif // !YY_YY_POSITION_HH_INCLUDED diff --git a/src/query/recollq.cpp b/src/query/recollq.cpp index c68bc968..ac4472df 100644 --- a/src/query/recollq.cpp +++ b/src/query/recollq.cpp @@ -16,34 +16,33 @@ */ // Takes a query and run it, no gui, results to stdout -#ifndef TEST_RECOLLQ #include #include -#include #include #include -#include #include #include #include #include -using namespace std; #include "rcldb.h" #include "rclquery.h" #include "rclconfig.h" #include "pathut.h" #include "rclinit.h" -#include "debuglog.h" +#include "log.h" #include "wasatorcl.h" #include "internfile.h" #include "wipedir.h" #include "transcode.h" #include "textsplit.h" #include "smallut.h" +#include "chrono.h" #include "base64.h" +using namespace std; + bool dump_contents(RclConfig *rclconfig, Rcl::Doc& idoc) { FileInterner interner(idoc, rclconfig, FileInterner::FIF_forPreview); @@ -120,6 +119,7 @@ static char usage [] = " -D : sort descending\n" " -s stemlang : set stemming language to use (must exist in index...)\n" " Use -s \"\" to turn off stem expansion\n" +" -T : use the parameter (Thesaurus) for word expansion" " -i : additional index, several can be given\n" " -e use url encoding (%xx) for urls\n" " -F : output exactly these fields for each result.\n" @@ -149,21 +149,22 @@ static int op_flags; #define OPT_c 0x8 #define OPT_D 0x10 #define OPT_d 0x20 -#define OPT_f 0x40 -#define OPT_i 0x80 -#define OPT_l 0x100 -#define OPT_m 0x200 -#define OPT_n 0x400 -#define OPT_o 0x800 -#define OPT_P 0x1000 -#define OPT_Q 0x2000 -#define OPT_q 0x4000 -#define OPT_S 0x8000 -#define OPT_s 0x10000 -#define OPT_t 0x20000 -#define OPT_e 0x40000 -#define OPT_F 0x80000 -#define OPT_N 0x100000 +#define OPT_e 0x40 +#define OPT_F 0x80 +#define OPT_f 0x100 +#define OPT_i 0x200 +#define OPT_l 0x400 +#define OPT_m 0x800 +#define OPT_N 0x1000 +#define OPT_n 0x2000 +#define OPT_o 0x4000 +#define OPT_P 0x8000 +#define OPT_Q 0x10000 +#define OPT_q 0x20000 +#define OPT_S 0x40000 +#define OPT_s 0x80000 +#define OPT_T 0x100000 +#define OPT_t 0x200000 int recollq(RclConfig **cfp, int argc, char **argv) { @@ -173,7 +174,8 @@ int recollq(RclConfig **cfp, int argc, char **argv) list extra_dbs; string sf; vector fields; - + string syngroupsfn; + int firstres = 0; int maxcount = 2000; thisprog = argv[0]; @@ -236,6 +238,9 @@ int recollq(RclConfig **cfp, int argc, char **argv) stemlang = *(++argv); argc--; goto b1; case 't': op_flags |= OPT_t; break; + case 'T': op_flags |= OPT_T; if (argc < 2) Usage(); + syngroupsfn = *(++argv); + argc--; goto b1; default: Usage(); break; } b1: argc--; argv++; @@ -268,7 +273,13 @@ endopts: } } } - + if (!syngroupsfn.empty()) { + if (!rcldb.setSynGroupsFile(syngroupsfn)) { + cerr << "Can't use synonyms file: " << syngroupsfn << endl; + exit(1); + } + } + if (!rcldb.open(Rcl::Db::DbRO)) { cerr << "Cant open database in " << rclconfig->getDbDir() << " reason: " << rcldb.getReason() << endl; @@ -331,7 +342,7 @@ endopts: return 1; } - RefCntr rq(sd); + std::shared_ptr rq(sd); Rcl::Query query(&rcldb); if (op_flags & OPT_S) { query.setSortBy(sortfield, (op_flags & OPT_D) ? false : true); @@ -417,16 +428,3 @@ endopts: return 0; } -#else // TEST_RECOLLQ The test driver is actually the useful program... -#include - -#include "rclconfig.h" -#include "recollq.h" - -static RclConfig *rclconfig; - -int main(int argc, char **argv) -{ - return(recollq(&rclconfig, argc, argv)); -} -#endif // TEST_RECOLLQ diff --git a/src/query/recollqmain.cpp b/src/query/recollqmain.cpp new file mode 100644 index 00000000..e212ef96 --- /dev/null +++ b/src/query/recollqmain.cpp @@ -0,0 +1,30 @@ +/* Copyright (C) 2006 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +// Takes a query and run it, no gui, results to stdout +#include "autoconfig.h" + +#include + +#include "rclconfig.h" +#include "recollq.h" + +static RclConfig *rclconfig; + +int main(int argc, char **argv) +{ + return(recollq(&rclconfig, argc, argv)); +} diff --git a/src/query/reslistpager.cpp b/src/query/reslistpager.cpp index 236fdadb..e4379926 100644 --- a/src/query/reslistpager.cpp +++ b/src/query/reslistpager.cpp @@ -31,11 +31,13 @@ using std::list; #include "cstr.h" #include "reslistpager.h" -#include "debuglog.h" +#include "log.h" #include "rclconfig.h" #include "smallut.h" +#include "rclutil.h" #include "plaintorich.h" #include "mimehandler.h" +#include "transcode.h" // Default highlighter. No need for locking, this is query-only. static const string cstr_hlfontcolor(""); @@ -65,19 +67,18 @@ ResListPager::ResListPager(int pagesize) void ResListPager::resultPageNext() { - if (m_docSource.isNull()) { - LOGDEB(("ResListPager::resultPageNext: null source\n")); + if (!m_docSource) { + LOGDEB("ResListPager::resultPageNext: null source\n" ); return; } int resCnt = m_docSource->getResCnt(); - LOGDEB(("ResListPager::resultPageNext: rescnt %d, winfirst %d\n", - resCnt, m_winfirst)); + LOGDEB("ResListPager::resultPageNext: rescnt " << (resCnt) << ", winfirst " << (m_winfirst) << "\n" ); if (m_winfirst < 0) { m_winfirst = 0; } else { - m_winfirst += 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 @@ -102,7 +103,7 @@ void ResListPager::resultPageNext() // 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 -= m_respage.size(); + m_winfirst -= int(m_respage.size()); } else { // No results at all (on first page) m_winfirst = -1; @@ -123,14 +124,13 @@ static string maybeEscapeHtml(const string& fld) void ResListPager::resultPageFor(int docnum) { - if (m_docSource.isNull()) { - LOGDEB(("ResListPager::resultPageFor: null source\n")); + if (!m_docSource) { + LOGDEB("ResListPager::resultPageFor: null source\n" ); return; } int resCnt = m_docSource->getResCnt(); - LOGDEB(("ResListPager::resultPageFor(%d): rescnt %d, winfirst %d\n", - docnum, resCnt, m_winfirst)); + LOGDEB("ResListPager::resultPageFor(" << (docnum) << "): rescnt " << (resCnt) << ", winfirst " << (m_winfirst) << "\n" ); m_winfirst = (docnum / m_pagesize) * m_pagesize; // Get the next page of results. @@ -151,18 +151,10 @@ void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc, const HighlightData& hdata, const string& sh) { ostringstream chunk; - int percent; - if (doc.pc == -1) { - percent = 0; - // Document not available, maybe other further, will go on. - doc.meta[Rcl::Doc::keyabs] = string(trans("Unavailable document")); - } else { - percent = doc.pc; - } // Determine icon to display if any string iconurl = iconUrl(config, doc); - + // Printable url: either utf-8 if transcoding succeeds, or url-encoded string url; printableUrl(config->getDefCharset(), doc.url, url); @@ -200,22 +192,24 @@ void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc, int docnumforlinks = m_winfirst + 1 + i; sprintf(numbuf, "%d", docnumforlinks); - // Document date: either doc or file modification time - char datebuf[100]; - datebuf[0] = 0; + // Document date: either doc or file modification times + string datebuf; 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(datebuf, 99, dateFormat().c_str(), tm); + 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 off_t fsize = -1, dsize = -1; if (!doc.dbytes.empty()) - dsize = atoll(doc.dbytes.c_str()); + dsize = static_cast(atoll(doc.dbytes.c_str())); if (!doc.fbytes.empty()) - fsize = atoll(doc.fbytes.c_str()); + fsize = static_cast(atoll(doc.fbytes.c_str())); string sizebuf; if (dsize > 0) { sizebuf = displayableBytes(dsize); @@ -227,7 +221,7 @@ void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc, string richabst; bool needabstract = parFormat().find("%A") != string::npos; - if (needabstract && m_docSource.isNotNull()) { + if (needabstract && m_docSource) { vector vabs; m_docSource->getAbstract(doc, vabs); m_hiliter->set_inputhtml(false); @@ -290,6 +284,9 @@ void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc, else chunk << "

          "; + char xdocidbuf[100]; + sprintf(xdocidbuf, "%lu", doc.xdocid); + // Configurable stuff map subs; subs["A"] = !richabst.empty() ? richabst : ""; @@ -309,7 +306,8 @@ void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc, subs["t"] = maybeEscapeHtml(doc.meta[Rcl::Doc::keytt]); subs["U"] = url; subs["u"] = urlOrLocal; - + subs["x"] = xdocidbuf; + // Let %(xx) access all metadata. HTML-neuter everything: for (map::iterator it = doc.meta.begin(); it != doc.meta.end(); it++) { @@ -327,7 +325,7 @@ void ResListPager::displayDoc(RclConfig *config, int i, Rcl::Doc& doc, // the table approach for 1.15 for now (in guiutils.cpp) // chunk << "
          " << endl; - LOGDEB2(("Chunk: [%s]\n", (const char *)chunk.rdbuf()->str().c_str())); + LOGDEB2("Chunk: [" << ((const char *)chunk.rdbuf()->str()) << "]\n" ); append(chunk.rdbuf()->str(), i, doc); } @@ -343,13 +341,13 @@ bool ResListPager::getDoc(int num, Rcl::Doc& doc) void ResListPager::displayPage(RclConfig *config) { - LOGDEB(("ResListPager::displayPage\n")); - if (m_docSource.isNull()) { - LOGDEB(("ResListPager::displayPage: null source\n")); + LOGDEB("ResListPager::displayPage\n" ); + if (!m_docSource) { + LOGDEB("ResListPager::displayPage: null source\n" ); return; } if (m_winfirst < 0 && !pageEmpty()) { - LOGDEB(("ResListPager::displayPage: sequence error: winfirst < 0\n")); + LOGDEB("ResListPager::displayPage: sequence error: winfirst < 0\n" ); return; } @@ -493,7 +491,7 @@ string ResListPager::iconUrl(RclConfig *config, Rcl::Doc& doc) string apptag; doc.getmeta(Rcl::Doc::keyapptg, &apptag); - return cstr_fileu + config->getMimeIconPath(doc.mimetype, apptag); + return path_pathtofileurl(config->getMimeIconPath(doc.mimetype, apptag)); } bool ResListPager::append(const string& data) @@ -529,3 +527,4 @@ const string &ResListPager::dateFormat() return cstr_format; } + diff --git a/src/query/reslistpager.h b/src/query/reslistpager.h index 6e5e1f09..087e307d 100644 --- a/src/query/reslistpager.h +++ b/src/query/reslistpager.h @@ -17,10 +17,11 @@ #ifndef _reslistpager_h_included_ #define _reslistpager_h_included_ +#include "autoconfig.h" #include +#include -#include "refcntr.h" #include "docseq.h" #include "hldata.h" @@ -39,7 +40,7 @@ public: { m_hiliter = ptr; } - void setDocSource(RefCntr src, int winfirst = -1) + void setDocSource(std::shared_ptr src, int winfirst = -1) { m_pagesize = m_newpagesize; m_winfirst = winfirst; @@ -63,7 +64,7 @@ public: int pageLastDocNum() { if (m_winfirst < 0 || m_respage.size() == 0) return -1; - return m_winfirst + m_respage.size() - 1; + return m_winfirst + int(m_respage.size()) - 1; } virtual int pageSize() const {return m_pagesize;} void pageNext(); @@ -87,8 +88,9 @@ public: const HighlightData& hdata, const string& sh = ""); bool pageEmpty() {return m_respage.size() == 0;} - string queryDescription() {return m_docSource.isNull() ? "" : - m_docSource->getDescription();} + string queryDescription() { + return m_docSource ? m_docSource->getDescription() : ""; + } bool getDoc(int num, Rcl::Doc &doc); @@ -127,7 +129,7 @@ private: int m_winfirst; bool m_hasNext; PlainToRich *m_hiliter; - RefCntr m_docSource; + std::shared_ptr m_docSource; std::vector m_respage; }; diff --git a/src/query/sortseq.cpp b/src/query/sortseq.cpp index 6624338b..97492303 100644 --- a/src/query/sortseq.cpp +++ b/src/query/sortseq.cpp @@ -16,7 +16,7 @@ */ #include -#include "debuglog.h" +#include "log.h" #include "sortseq.h" using std::string; @@ -30,7 +30,7 @@ public: // behaves as operator< int operator()(const Rcl::Doc *x, const Rcl::Doc *y) { - LOGDEB1(("Comparing .. \n")); + LOGDEB1("Comparing .. \n" ); map::const_iterator xit, yit; xit = x->meta.find(ss.field); @@ -43,15 +43,15 @@ public: bool DocSeqSorted::setSortSpec(const DocSeqSortSpec &sortspec) { - LOGDEB(("DocSeqSorted::setSortSpec\n")); + LOGDEB("DocSeqSorted::setSortSpec\n" ); m_spec = sortspec; int count = m_seq->getResCnt(); - LOGDEB(("DocSeqSorted:: count %d\n", count)); + LOGDEB("DocSeqSorted:: count " << (count) << "\n" ); m_docs.resize(count); int i; for (i = 0; i < count; i++) { if (!m_seq->getDoc(i, m_docs[i])) { - LOGERR(("DocSeqSorted: getDoc failed for doc %d\n", i)); + LOGERR("DocSeqSorted: getDoc failed for doc " << (i) << "\n" ); count = i; break; } @@ -68,9 +68,10 @@ bool DocSeqSorted::setSortSpec(const DocSeqSortSpec &sortspec) bool DocSeqSorted::getDoc(int num, Rcl::Doc &doc, string *) { - LOGDEB(("DocSeqSorted::getDoc(%d)\n", num)); + LOGDEB("DocSeqSorted::getDoc(" << (num) << ")\n" ); if (num < 0 || num >= int(m_docsp.size())) return false; doc = *m_docsp[num]; return true; } + diff --git a/src/query/sortseq.h b/src/query/sortseq.h index 96d76143..c8ebeb2b 100644 --- a/src/query/sortseq.h +++ b/src/query/sortseq.h @@ -16,11 +16,12 @@ */ #ifndef _SORTSEQ_H_INCLUDED_ #define _SORTSEQ_H_INCLUDED_ +#include "autoconfig.h" #include #include +#include -#include "refcntr.h" #include "docseq.h" /** @@ -29,7 +30,7 @@ */ class DocSeqSorted : public DocSeqModifier { public: - DocSeqSorted(RefCntr iseq, DocSeqSortSpec &sortspec) + DocSeqSorted(std::shared_ptr iseq, DocSeqSortSpec &sortspec) : DocSeqModifier(iseq) { setSortSpec(sortspec); @@ -38,7 +39,7 @@ class DocSeqSorted : public DocSeqModifier { virtual bool canSort() {return true;} virtual bool setSortSpec(const DocSeqSortSpec &sortspec); virtual bool getDoc(int num, Rcl::Doc &doc, string *sh = 0); - virtual int getResCnt() {return m_docsp.size();} + virtual int getResCnt() {return int(m_docsp.size());} private: DocSeqSortSpec m_spec; std::vector m_docs; diff --git a/src/query/stack.hh b/src/query/stack.hh new file mode 100644 index 00000000..87d8f3ef --- /dev/null +++ b/src/query/stack.hh @@ -0,0 +1,158 @@ +// A Bison parser, made by GNU Bison 3.0.2. + +// Stack handling for Bison parsers in C++ + +// Copyright (C) 2002-2013 Free Software Foundation, Inc. + +// 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 3 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 General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// As a special exception, you may create a larger work that contains +// part or all of the Bison parser skeleton and distribute that work +// under terms of your choice, so long as that work isn't itself a +// parser generator using the skeleton or a modified version thereof +// as a parser skeleton. Alternatively, if you modify or redistribute +// the parser skeleton itself, you may (at your option) remove this +// special exception, which will cause the skeleton and the resulting +// Bison output files to be licensed under the GNU General Public +// License without this special exception. + +// This special exception was added by the Free Software Foundation in +// version 2.2 of Bison. + +/** + ** \file stack.hh + ** Define the yy::stack class. + */ + +#ifndef YY_YY_STACK_HH_INCLUDED +# define YY_YY_STACK_HH_INCLUDED + +# include + + +namespace yy { +#line 46 "stack.hh" // stack.hh:133 + template > + class stack + { + public: + // Hide our reversed order. + typedef typename S::reverse_iterator iterator; + typedef typename S::const_reverse_iterator const_iterator; + + stack () + : seq_ () + { + } + + stack (unsigned int n) + : seq_ (n) + { + } + + inline + T& + operator[] (unsigned int i) + { + return seq_[seq_.size () - 1 - i]; + } + + inline + const T& + operator[] (unsigned int i) const + { + return seq_[seq_.size () - 1 - i]; + } + + /// Steal the contents of \a t. + /// + /// Close to move-semantics. + inline + void + push (T& t) + { + seq_.push_back (T()); + operator[](0).move (t); + } + + inline + void + pop (unsigned int n = 1) + { + for (; n; --n) + seq_.pop_back (); + } + + void + clear () + { + seq_.clear (); + } + + inline + typename S::size_type + size () const + { + return seq_.size (); + } + + inline + const_iterator + begin () const + { + return seq_.rbegin (); + } + + inline + const_iterator + end () const + { + return seq_.rend (); + } + + private: + stack (const stack&); + stack& operator= (const stack&); + /// The wrapped container. + S seq_; + }; + + /// Present a slice of the top of a stack. + template > + class slice + { + public: + slice (const S& stack, unsigned int range) + : stack_ (stack) + , range_ (range) + { + } + + inline + const T& + operator [] (unsigned int i) const + { + return stack_[range_ - i]; + } + + private: + const S& stack_; + unsigned int range_; + }; + + +} // yy +#line 157 "stack.hh" // stack.hh:133 + +#endif // !YY_YY_STACK_HH_INCLUDED diff --git a/src/query/wasaparse.cpp b/src/query/wasaparse.cpp index 2a6b8310..81293f3e 100644 --- a/src/query/wasaparse.cpp +++ b/src/query/wasaparse.cpp @@ -1,235 +1,1536 @@ -/* Copyright (C) 2006 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 - * (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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the - * Free Software Foundation, Inc., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ -#include "autoconfig.h" +// A Bison parser, made by GNU Bison 3.0.2. -#include +// Skeleton implementation for Bison LALR(1) parsers in C++ -#include "wasatorcl.h" -#include "wasaparserdriver.h" -#include "searchdata.h" -#include "debuglog.h" +// Copyright (C) 2002-2013 Free Software Foundation, Inc. + +// 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 3 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 General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// As a special exception, you may create a larger work that contains +// part or all of the Bison parser skeleton and distribute that work +// under terms of your choice, so long as that work isn't itself a +// parser generator using the skeleton or a modified version thereof +// as a parser skeleton. Alternatively, if you modify or redistribute +// the parser skeleton itself, you may (at your option) remove this +// special exception, which will cause the skeleton and the resulting +// Bison output files to be licensed under the GNU General Public +// License without this special exception. + +// This special exception was added by the Free Software Foundation in +// version 2.2 of Bison. + + +// First part of user declarations. +#line 1 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:399 #define YYDEBUG 1 +#include "autoconfig.h" -#include "wasaparse.tab.h" +#include + +#include +#include + +#include "searchdata.h" +#include "wasaparserdriver.h" +#include "wasaparse.hpp" using namespace std; -using namespace Rcl; +// #define LOG_PARSER +#ifdef LOG_PARSER +#define LOGP(X) {cerr << X;} +#else +#define LOGP(X) +#endif -void -yy::parser::error (const location_type& l, const std::string& m) +int yylex(yy::parser::semantic_type *, yy::parser::location_type *, + WasaParserDriver *); +void yyerror(char const *); +static void qualify(Rcl::SearchDataClauseDist *, const string &); + +static void addSubQuery(WasaParserDriver *d, + Rcl::SearchData *sd, Rcl::SearchData *sq) { - d->setreason(m); + if (sd && sq) + sd->addClause( + new Rcl::SearchDataClauseSub(std::shared_ptr(sq))); } -SearchData *wasaStringToRcl(const RclConfig *config, - const std::string& stemlang, - const std::string& query, string &reason, - const std::string& autosuffs) +#line 73 "y.tab.c" // lalr1.cc:399 + +# ifndef YY_NULLPTR +# if defined __cplusplus && 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# endif + + + +// User implementation prologue. + +#line 87 "y.tab.c" // lalr1.cc:407 + + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include // FIXME: INFRINGES ON USER NAME SPACE. +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +#define YYRHSLOC(Rhs, K) ((Rhs)[K].location) +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +# ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (N) \ + { \ + (Current).begin = YYRHSLOC (Rhs, 1).begin; \ + (Current).end = YYRHSLOC (Rhs, N).end; \ + } \ + else \ + { \ + (Current).begin = (Current).end = YYRHSLOC (Rhs, 0).end; \ + } \ + while (/*CONSTCOND*/ false) +# endif + + +// Suppress unused-variable warnings by "using" E. +#define YYUSE(E) ((void) (E)) + +// Enable debugging if requested. +#if YYDEBUG + +// A pseudo ostream that takes yydebug_ into account. +# define YYCDEBUG if (yydebug_) (*yycdebug_) + +# define YY_SYMBOL_PRINT(Title, Symbol) \ + do { \ + if (yydebug_) \ + { \ + *yycdebug_ << Title << ' '; \ + yy_print_ (*yycdebug_, Symbol); \ + *yycdebug_ << std::endl; \ + } \ + } while (false) + +# define YY_REDUCE_PRINT(Rule) \ + do { \ + if (yydebug_) \ + yy_reduce_print_ (Rule); \ + } while (false) + +# define YY_STACK_PRINT() \ + do { \ + if (yydebug_) \ + yystack_print_ (); \ + } while (false) + +#else // !YYDEBUG + +# define YYCDEBUG if (false) std::cerr +# define YY_SYMBOL_PRINT(Title, Symbol) YYUSE(Symbol) +# define YY_REDUCE_PRINT(Rule) static_cast(0) +# define YY_STACK_PRINT() static_cast(0) + +#endif // !YYDEBUG + +#define yyerrok (yyerrstatus_ = 0) +#define yyclearin (yyempty = true) + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab +#define YYRECOVERING() (!!yyerrstatus_) + + +namespace yy { +#line 173 "y.tab.c" // lalr1.cc:474 + + /* Return YYSTR after stripping away unnecessary quotes and + backslashes, so that it's suitable for yyerror. The heuristic is + that double-quoting is unnecessary unless the string contains an + apostrophe, a comma, or backslash (other than backslash-backslash). + YYSTR is taken from yytname. */ + std::string + parser::yytnamerr_ (const char *yystr) + { + if (*yystr == '"') + { + std::string yyr = ""; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + // Fall through. + default: + yyr += *yyp; + break; + + case '"': + return yyr; + } + do_not_strip_quotes: ; + } + + return yystr; + } + + + /// Build a parser object. + parser::parser (WasaParserDriver* d_yyarg) + : +#if YYDEBUG + yydebug_ (false), + yycdebug_ (&std::cerr), +#endif + d (d_yyarg) + {} + + parser::~parser () + {} + + + /*---------------. + | Symbol types. | + `---------------*/ + + inline + parser::syntax_error::syntax_error (const location_type& l, const std::string& m) + : std::runtime_error (m) + , location (l) + {} + + // basic_symbol. + template + inline + parser::basic_symbol::basic_symbol () + : value () + {} + + template + inline + parser::basic_symbol::basic_symbol (const basic_symbol& other) + : Base (other) + , value () + , location (other.location) + { + value = other.value; + } + + + template + inline + parser::basic_symbol::basic_symbol (typename Base::kind_type t, const semantic_type& v, const location_type& l) + : Base (t) + , value (v) + , location (l) + {} + + + /// Constructor for valueless symbols. + template + inline + parser::basic_symbol::basic_symbol (typename Base::kind_type t, const location_type& l) + : Base (t) + , value () + , location (l) + {} + + template + inline + parser::basic_symbol::~basic_symbol () + { + } + + template + inline + void + parser::basic_symbol::move (basic_symbol& s) + { + super_type::move(s); + value = s.value; + location = s.location; + } + + // by_type. + inline + parser::by_type::by_type () + : type (empty) + {} + + inline + parser::by_type::by_type (const by_type& other) + : type (other.type) + {} + + inline + parser::by_type::by_type (token_type t) + : type (yytranslate_ (t)) + {} + + inline + void + parser::by_type::move (by_type& that) + { + type = that.type; + that.type = empty; + } + + inline + int + parser::by_type::type_get () const + { + return type; + } + + + // by_state. + inline + parser::by_state::by_state () + : state (empty) + {} + + inline + parser::by_state::by_state (const by_state& other) + : state (other.state) + {} + + inline + void + parser::by_state::move (by_state& that) + { + state = that.state; + that.state = empty; + } + + inline + parser::by_state::by_state (state_type s) + : state (s) + {} + + inline + parser::symbol_number_type + parser::by_state::type_get () const + { + return state == empty ? 0 : yystos_[state]; + } + + inline + parser::stack_symbol_type::stack_symbol_type () + {} + + + inline + parser::stack_symbol_type::stack_symbol_type (state_type s, symbol_type& that) + : super_type (s, that.location) + { + value = that.value; + // that is emptied. + that.type = empty; + } + + inline + parser::stack_symbol_type& + parser::stack_symbol_type::operator= (const stack_symbol_type& that) + { + state = that.state; + value = that.value; + location = that.location; + return *this; + } + + + template + inline + void + parser::yy_destroy_ (const char* yymsg, basic_symbol& yysym) const + { + if (yymsg) + YY_SYMBOL_PRINT (yymsg, yysym); + + // User destructor. + switch (yysym.type_get ()) + { + case 3: // WORD + +#line 51 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:599 + {delete (yysym.value.str);} +#line 392 "y.tab.c" // lalr1.cc:599 + break; + + case 4: // QUOTED + +#line 51 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:599 + {delete (yysym.value.str);} +#line 399 "y.tab.c" // lalr1.cc:599 + break; + + case 5: // QUALIFIERS + +#line 51 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:599 + {delete (yysym.value.str);} +#line 406 "y.tab.c" // lalr1.cc:599 + break; + + case 22: // complexfieldname + +#line 51 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:599 + {delete (yysym.value.str);} +#line 413 "y.tab.c" // lalr1.cc:599 + break; + + + default: + break; + } + } + +#if YYDEBUG + template + void + parser::yy_print_ (std::ostream& yyo, + const basic_symbol& yysym) const + { + std::ostream& yyoutput = yyo; + YYUSE (yyoutput); + symbol_number_type yytype = yysym.type_get (); + yyo << (yytype < yyntokens_ ? "token" : "nterm") + << ' ' << yytname_[yytype] << " (" + << yysym.location << ": "; + YYUSE (yytype); + yyo << ')'; + } +#endif + + inline + void + parser::yypush_ (const char* m, state_type s, symbol_type& sym) + { + stack_symbol_type t (s, sym); + yypush_ (m, t); + } + + inline + void + parser::yypush_ (const char* m, stack_symbol_type& s) + { + if (m) + YY_SYMBOL_PRINT (m, s); + yystack_.push (s); + } + + inline + void + parser::yypop_ (unsigned int n) + { + yystack_.pop (n); + } + +#if YYDEBUG + std::ostream& + parser::debug_stream () const + { + return *yycdebug_; + } + + void + parser::set_debug_stream (std::ostream& o) + { + yycdebug_ = &o; + } + + + parser::debug_level_type + parser::debug_level () const + { + return yydebug_; + } + + void + parser::set_debug_level (debug_level_type l) + { + yydebug_ = l; + } +#endif // YYDEBUG + + inline parser::state_type + parser::yy_lr_goto_state_ (state_type yystate, int yysym) + { + int yyr = yypgoto_[yysym - yyntokens_] + yystate; + if (0 <= yyr && yyr <= yylast_ && yycheck_[yyr] == yystate) + return yytable_[yyr]; + else + return yydefgoto_[yysym - yyntokens_]; + } + + inline bool + parser::yy_pact_value_is_default_ (int yyvalue) + { + return yyvalue == yypact_ninf_; + } + + inline bool + parser::yy_table_value_is_error_ (int yyvalue) + { + return yyvalue == yytable_ninf_; + } + + int + parser::parse () + { + /// Whether yyla contains a lookahead. + bool yyempty = true; + + // State. + int yyn; + /// Length of the RHS of the rule being reduced. + int yylen = 0; + + // Error handling. + int yynerrs_ = 0; + int yyerrstatus_ = 0; + + /// The lookahead symbol. + symbol_type yyla; + + /// The locations where the error started and ended. + stack_symbol_type yyerror_range[3]; + + /// The return value of parse (). + int yyresult; + + // FIXME: This shoud be completely indented. It is not yet to + // avoid gratuitous conflicts when merging into the master branch. + try + { + YYCDEBUG << "Starting parse" << std::endl; + + + /* Initialize the stack. The initial state will be set in + yynewstate, since the latter expects the semantical and the + location values to have been already stored, initialize these + stacks with a primary value. */ + yystack_.clear (); + yypush_ (YY_NULLPTR, 0, yyla); + + // A new symbol was pushed on the stack. + yynewstate: + YYCDEBUG << "Entering state " << yystack_[0].state << std::endl; + + // Accept? + if (yystack_[0].state == yyfinal_) + goto yyacceptlab; + + goto yybackup; + + // Backup. + yybackup: + + // Try to take a decision without lookahead. + yyn = yypact_[yystack_[0].state]; + if (yy_pact_value_is_default_ (yyn)) + goto yydefault; + + // Read a lookahead token. + if (yyempty) + { + YYCDEBUG << "Reading a token: "; + try + { + yyla.type = yytranslate_ (yylex (&yyla.value, &yyla.location, d)); + } + catch (const syntax_error& yyexc) + { + error (yyexc); + goto yyerrlab1; + } + yyempty = false; + } + YY_SYMBOL_PRINT ("Next token is", yyla); + + /* If the proper action on seeing token YYLA.TYPE is to reduce or + to detect an error, take that action. */ + yyn += yyla.type_get (); + if (yyn < 0 || yylast_ < yyn || yycheck_[yyn] != yyla.type_get ()) + goto yydefault; + + // Reduce or error. + yyn = yytable_[yyn]; + if (yyn <= 0) + { + if (yy_table_value_is_error_ (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + // Discard the token being shifted. + yyempty = true; + + // Count tokens shifted since error; after three, turn off error status. + if (yyerrstatus_) + --yyerrstatus_; + + // Shift the lookahead token. + yypush_ ("Shifting", yyn, yyla); + goto yynewstate; + + /*-----------------------------------------------------------. + | yydefault -- do the default action for the current state. | + `-----------------------------------------------------------*/ + yydefault: + yyn = yydefact_[yystack_[0].state]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + /*-----------------------------. + | yyreduce -- Do a reduction. | + `-----------------------------*/ + yyreduce: + yylen = yyr2_[yyn]; + { + stack_symbol_type yylhs; + yylhs.state = yy_lr_goto_state_(yystack_[yylen].state, yyr1_[yyn]); + /* If YYLEN is nonzero, implement the default value of the + action: '$$ = $1'. Otherwise, use the top of the stack. + + Otherwise, the following line sets YYLHS.VALUE to garbage. + This behavior is undocumented and Bison users should not rely + upon it. */ + if (yylen) + yylhs.value = yystack_[yylen - 1].value; + else + yylhs.value = yystack_[0].value; + + // Compute the default @$. + { + slice slice (yystack_, yylen); + YYLLOC_DEFAULT (yylhs.location, slice, yylen); + } + + // Perform the reduction. + YY_REDUCE_PRINT (yyn); + try + { + switch (yyn) + { + case 2: +#line 72 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + // It's possible that we end up with no query (e.g.: because just a + // date filter was set, no terms). Allocate an empty query so that we + // have something to set the global criteria on (this will yield a + // Xapian search like FILTER xxx + if ((yystack_[0].value.sd) == 0) + d->m_result = new Rcl::SearchData(Rcl::SCLT_AND, d->m_stemlang); + else + d->m_result = (yystack_[0].value.sd); +} +#line 664 "y.tab.c" // lalr1.cc:847 + break; + + case 3: +#line 85 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("q: query query\n"); + Rcl::SearchData *sd = 0; + if ((yystack_[1].value.sd) || (yystack_[0].value.sd)) { + sd = new Rcl::SearchData(Rcl::SCLT_AND, d->m_stemlang); + addSubQuery(d, sd, (yystack_[1].value.sd)); + addSubQuery(d, sd, (yystack_[0].value.sd)); + } + (yylhs.value.sd) = sd; +} +#line 679 "y.tab.c" // lalr1.cc:847 + break; + + case 4: +#line 96 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("q: query AND query\n"); + Rcl::SearchData *sd = 0; + if ((yystack_[2].value.sd) || (yystack_[0].value.sd)) { + sd = new Rcl::SearchData(Rcl::SCLT_AND, d->m_stemlang); + addSubQuery(d, sd, (yystack_[2].value.sd)); + addSubQuery(d, sd, (yystack_[0].value.sd)); + } + (yylhs.value.sd) = sd; +} +#line 694 "y.tab.c" // lalr1.cc:847 + break; + + case 5: +#line 107 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("query: query OR query\n"); + Rcl::SearchData *top = 0; + if ((yystack_[2].value.sd) || (yystack_[0].value.sd)) { + top = new Rcl::SearchData(Rcl::SCLT_OR, d->m_stemlang); + addSubQuery(d, top, (yystack_[2].value.sd)); + addSubQuery(d, top, (yystack_[0].value.sd)); + } + (yylhs.value.sd) = top; +} +#line 709 "y.tab.c" // lalr1.cc:847 + break; + + case 6: +#line 118 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("q: ( query )\n"); + (yylhs.value.sd) = (yystack_[1].value.sd); +} +#line 718 "y.tab.c" // lalr1.cc:847 + break; + + case 7: +#line 124 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("q: fieldexpr\n"); + Rcl::SearchData *sd = new Rcl::SearchData(Rcl::SCLT_AND, d->m_stemlang); + if (d->addClause(sd, (yystack_[0].value.cl))) { + (yylhs.value.sd) = sd; + } else { + delete sd; + (yylhs.value.sd) = 0; + } +} +#line 733 "y.tab.c" // lalr1.cc:847 + break; + + case 8: +#line 137 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("fe: simple fieldexpr: " << (yystack_[0].value.cl)->gettext() << endl); + (yylhs.value.cl) = (yystack_[0].value.cl); +} +#line 742 "y.tab.c" // lalr1.cc:847 + break; + + case 9: +#line 142 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("fe: " << *(yystack_[2].value.str) << " = " << (yystack_[0].value.cl)->gettext() << endl); + (yystack_[0].value.cl)->setfield(*(yystack_[2].value.str)); + (yystack_[0].value.cl)->setrel(Rcl::SearchDataClause::REL_EQUALS); + (yylhs.value.cl) = (yystack_[0].value.cl); + delete (yystack_[2].value.str); +} +#line 754 "y.tab.c" // lalr1.cc:847 + break; + + case 10: +#line 150 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("fe: " << *(yystack_[2].value.str) << " : " << (yystack_[0].value.cl)->gettext() << endl); + (yystack_[0].value.cl)->setfield(*(yystack_[2].value.str)); + (yystack_[0].value.cl)->setrel(Rcl::SearchDataClause::REL_CONTAINS); + (yylhs.value.cl) = (yystack_[0].value.cl); + delete (yystack_[2].value.str); +} +#line 766 "y.tab.c" // lalr1.cc:847 + break; + + case 11: +#line 158 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP(cerr << "fe: " << *(yystack_[2].value.str) << " < " << (yystack_[0].value.cl)->gettext() << endl); + (yystack_[0].value.cl)->setfield(*(yystack_[2].value.str)); + (yystack_[0].value.cl)->setrel(Rcl::SearchDataClause::REL_LT); + (yylhs.value.cl) = (yystack_[0].value.cl); + delete (yystack_[2].value.str); +} +#line 778 "y.tab.c" // lalr1.cc:847 + break; + + case 12: +#line 166 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("fe: " << *(yystack_[2].value.str) << " <= " << (yystack_[0].value.cl)->gettext() << endl); + (yystack_[0].value.cl)->setfield(*(yystack_[2].value.str)); + (yystack_[0].value.cl)->setrel(Rcl::SearchDataClause::REL_LTE); + (yylhs.value.cl) = (yystack_[0].value.cl); + delete (yystack_[2].value.str); +} +#line 790 "y.tab.c" // lalr1.cc:847 + break; + + case 13: +#line 174 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("fe: " << *(yystack_[2].value.str) << " > " << (yystack_[0].value.cl)->gettext() << endl); + (yystack_[0].value.cl)->setfield(*(yystack_[2].value.str)); + (yystack_[0].value.cl)->setrel(Rcl::SearchDataClause::REL_GT); + (yylhs.value.cl) = (yystack_[0].value.cl); + delete (yystack_[2].value.str); +} +#line 802 "y.tab.c" // lalr1.cc:847 + break; + + case 14: +#line 182 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("fe: " << *(yystack_[2].value.str) << " >= " << (yystack_[0].value.cl)->gettext() << endl); + (yystack_[0].value.cl)->setfield(*(yystack_[2].value.str)); + (yystack_[0].value.cl)->setrel(Rcl::SearchDataClause::REL_GTE); + (yylhs.value.cl) = (yystack_[0].value.cl); + delete (yystack_[2].value.str); +} +#line 814 "y.tab.c" // lalr1.cc:847 + break; + + case 15: +#line 190 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("fe: - fieldexpr[" << (yystack_[0].value.cl)->gettext() << "]" << endl); + (yystack_[0].value.cl)->setexclude(true); + (yylhs.value.cl) = (yystack_[0].value.cl); +} +#line 824 "y.tab.c" // lalr1.cc:847 + break; + + case 16: +#line 200 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("cfn: WORD" << endl); + (yylhs.value.str) = (yystack_[0].value.str); +} +#line 833 "y.tab.c" // lalr1.cc:847 + break; + + case 17: +#line 206 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("cfn: complexfieldname ':' WORD" << endl); + (yylhs.value.str) = new string(*(yystack_[2].value.str) + string(":") + *(yystack_[0].value.str)); + delete (yystack_[2].value.str); + delete (yystack_[0].value.str); +} +#line 844 "y.tab.c" // lalr1.cc:847 + break; + + case 18: +#line 215 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("term[" << *(yystack_[0].value.str) << "]" << endl); + (yylhs.value.cl) = new Rcl::SearchDataClauseSimple(Rcl::SCLT_AND, *(yystack_[0].value.str)); + delete (yystack_[0].value.str); +} +#line 854 "y.tab.c" // lalr1.cc:847 + break; + + case 19: +#line 221 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + (yylhs.value.cl) = (yystack_[0].value.cl); +} +#line 862 "y.tab.c" // lalr1.cc:847 + break; + + case 20: +#line 227 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("QUOTED[" << *(yystack_[0].value.str) << "]" << endl); + (yylhs.value.cl) = new Rcl::SearchDataClauseDist(Rcl::SCLT_PHRASE, *(yystack_[0].value.str), 0); + delete (yystack_[0].value.str); +} +#line 872 "y.tab.c" // lalr1.cc:847 + break; + + case 21: +#line 233 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:847 + { + LOGP("QUOTED[" << *(yystack_[1].value.str) << "] QUALIFIERS[" << *(yystack_[0].value.str) << "]" << endl); + Rcl::SearchDataClauseDist *cl = + new Rcl::SearchDataClauseDist(Rcl::SCLT_PHRASE, *(yystack_[1].value.str), 0); + qualify(cl, *(yystack_[0].value.str)); + (yylhs.value.cl) = cl; + delete (yystack_[1].value.str); + delete (yystack_[0].value.str); +} +#line 886 "y.tab.c" // lalr1.cc:847 + break; + + +#line 890 "y.tab.c" // lalr1.cc:847 + default: + break; + } + } + catch (const syntax_error& yyexc) + { + error (yyexc); + YYERROR; + } + YY_SYMBOL_PRINT ("-> $$ =", yylhs); + yypop_ (yylen); + yylen = 0; + YY_STACK_PRINT (); + + // Shift the result of the reduction. + yypush_ (YY_NULLPTR, yylhs); + } + goto yynewstate; + + /*--------------------------------------. + | yyerrlab -- here on detecting error. | + `--------------------------------------*/ + yyerrlab: + // If not already recovering from an error, report this error. + if (!yyerrstatus_) + { + ++yynerrs_; + error (yyla.location, yysyntax_error_ (yystack_[0].state, + yyempty ? yyempty_ : yyla.type_get ())); + } + + + yyerror_range[1].location = yyla.location; + if (yyerrstatus_ == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + // Return failure if at end of input. + if (yyla.type_get () == yyeof_) + YYABORT; + else if (!yyempty) + { + yy_destroy_ ("Error: discarding", yyla); + yyempty = true; + } + } + + // Else will try to reuse lookahead token after shifting the error token. + goto yyerrlab1; + + + /*---------------------------------------------------. + | yyerrorlab -- error raised explicitly by YYERROR. | + `---------------------------------------------------*/ + yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (false) + goto yyerrorlab; + yyerror_range[1].location = yystack_[yylen - 1].location; + /* Do not reclaim the symbols of the rule whose action triggered + this YYERROR. */ + yypop_ (yylen); + yylen = 0; + goto yyerrlab1; + + /*-------------------------------------------------------------. + | yyerrlab1 -- common code for both syntax error and YYERROR. | + `-------------------------------------------------------------*/ + yyerrlab1: + yyerrstatus_ = 3; // Each real token shifted decrements this. + { + stack_symbol_type error_token; + for (;;) + { + yyn = yypact_[yystack_[0].state]; + if (!yy_pact_value_is_default_ (yyn)) + { + yyn += yyterror_; + if (0 <= yyn && yyn <= yylast_ && yycheck_[yyn] == yyterror_) + { + yyn = yytable_[yyn]; + if (0 < yyn) + break; + } + } + + // Pop the current state because it cannot handle the error token. + if (yystack_.size () == 1) + YYABORT; + + yyerror_range[1].location = yystack_[0].location; + yy_destroy_ ("Error: popping", yystack_[0]); + yypop_ (); + YY_STACK_PRINT (); + } + + yyerror_range[2].location = yyla.location; + YYLLOC_DEFAULT (error_token.location, yyerror_range, 2); + + // Shift the error token. + error_token.state = yyn; + yypush_ ("Shifting", error_token); + } + goto yynewstate; + + // Accept. + yyacceptlab: + yyresult = 0; + goto yyreturn; + + // Abort. + yyabortlab: + yyresult = 1; + goto yyreturn; + + yyreturn: + if (!yyempty) + yy_destroy_ ("Cleanup: discarding lookahead", yyla); + + /* Do not reclaim the symbols of the rule whose action triggered + this YYABORT or YYACCEPT. */ + yypop_ (yylen); + while (1 < yystack_.size ()) + { + yy_destroy_ ("Cleanup: popping", yystack_[0]); + yypop_ (); + } + + return yyresult; + } + catch (...) + { + YYCDEBUG << "Exception caught: cleaning lookahead and stack" + << std::endl; + // Do not try to display the values of the reclaimed symbols, + // as their printer might throw an exception. + if (!yyempty) + yy_destroy_ (YY_NULLPTR, yyla); + + while (1 < yystack_.size ()) + { + yy_destroy_ (YY_NULLPTR, yystack_[0]); + yypop_ (); + } + throw; + } + } + + void + parser::error (const syntax_error& yyexc) + { + error (yyexc.location, yyexc.what()); + } + + // Generate an error message. + std::string + parser::yysyntax_error_ (state_type yystate, symbol_number_type yytoken) const + { + std::string yyres; + // Number of reported tokens (one for the "unexpected", one per + // "expected"). + size_t yycount = 0; + // Its maximum. + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + // Arguments of yyformat. + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + + /* There are many possibilities here to consider: + - If this state is a consistent state with a default action, then + the only way this function was invoked is if the default action + is an error action. In that case, don't check for expected + tokens because there are none. + - The only way there can be no lookahead present (in yytoken) is + if this state is a consistent state with a default action. + Thus, detecting the absence of a lookahead is sufficient to + determine that there is no unexpected or expected token to + report. In that case, just report a simple "syntax error". + - Don't assume there isn't a lookahead just because this state is + a consistent state with a default action. There might have + been a previous inconsistent state, consistent state with a + non-default action, or user semantic action that manipulated + yyla. (However, yyla is currently not documented for users.) + - Of course, the expected token list depends on states to have + correct lookahead information, and it depends on the parser not + to perform extra reductions after fetching a lookahead from the + scanner and before detecting a syntax error. Thus, state + merging (from LALR or IELR) and default reductions corrupt the + expected token list. However, the list is correct for + canonical LR with one exception: it will still contain any + token that will not be accepted due to an error action in a + later state. + */ + if (yytoken != yyempty_) + { + yyarg[yycount++] = yytname_[yytoken]; + int yyn = yypact_[yystate]; + if (!yy_pact_value_is_default_ (yyn)) + { + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. In other words, skip the first -YYN actions for + this state because they are default actions. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + // Stay within bounds of both yycheck and yytname. + int yychecklim = yylast_ - yyn + 1; + int yyxend = yychecklim < yyntokens_ ? yychecklim : yyntokens_; + for (int yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck_[yyx + yyn] == yyx && yyx != yyterror_ + && !yy_table_value_is_error_ (yytable_[yyx + yyn])) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + break; + } + else + yyarg[yycount++] = yytname_[yyx]; + } + } + } + + char const* yyformat = YY_NULLPTR; + switch (yycount) + { +#define YYCASE_(N, S) \ + case N: \ + yyformat = S; \ + break + YYCASE_(0, YY_("syntax error")); + YYCASE_(1, YY_("syntax error, unexpected %s")); + YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); + YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); + YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); + YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); +#undef YYCASE_ + } + + // Argument number. + size_t yyi = 0; + for (char const* yyp = yyformat; *yyp; ++yyp) + if (yyp[0] == '%' && yyp[1] == 's' && yyi < yycount) + { + yyres += yytnamerr_ (yyarg[yyi++]); + ++yyp; + } + else + yyres += *yyp; + return yyres; + } + + + const signed char parser::yypact_ninf_ = -3; + + const signed char parser::yytable_ninf_ = -18; + + const signed char + parser::yypact_[] = + { + 24, 25, 3, 24, 26, 6, 16, -3, 31, -3, + -3, -3, 1, -3, -3, 24, 24, 4, -2, 9, + -2, -2, -2, -2, -3, 4, -3, -3, -3, 37, + -3, -3, -3, -3, -3 + }; + + const unsigned char + parser::yydefact_[] = + { + 0, 18, 20, 0, 0, 0, 2, 7, 0, 8, + 19, 21, 0, 15, 1, 0, 0, 3, 0, 0, + 0, 0, 0, 0, 6, 4, 5, 18, 9, 18, + 10, 12, 11, 14, 13 + }; + + const signed char + parser::yypgoto_[] = + { + -3, -3, 0, 13, -3, 36, -3 + }; + + const signed char + parser::yydefgoto_[] = + { + -1, 5, 17, 7, 8, 9, 10 + }; + + const signed char + parser::yytable_[] = + { + 6, 27, 2, 12, 1, 2, 14, 15, 11, 3, + 4, 16, 29, 2, 16, 25, 26, 13, 24, 1, + 2, 0, 15, 0, 3, 4, 16, 1, 2, 1, + 2, 0, 3, 4, 0, 4, -16, -16, -16, -16, + -16, -16, 18, 19, 20, 21, 22, 23, -17, -17, + -17, -17, -17, -17, 28, 30, 31, 32, 33, 34 + }; + + const signed char + parser::yycheck_[] = + { + 0, 3, 4, 3, 3, 4, 0, 6, 5, 8, + 9, 10, 3, 4, 10, 15, 16, 4, 17, 3, + 4, -1, 6, -1, 8, 9, 10, 3, 4, 3, + 4, -1, 8, 9, -1, 9, 11, 12, 13, 14, + 15, 16, 11, 12, 13, 14, 15, 16, 11, 12, + 13, 14, 15, 16, 18, 19, 20, 21, 22, 23 + }; + + const unsigned char + parser::yystos_[] = + { + 0, 3, 4, 8, 9, 19, 20, 21, 22, 23, + 24, 5, 20, 21, 0, 6, 10, 20, 11, 12, + 13, 14, 15, 16, 17, 20, 20, 3, 23, 3, + 23, 23, 23, 23, 23 + }; + + const unsigned char + parser::yyr1_[] = + { + 0, 18, 19, 20, 20, 20, 20, 20, 21, 21, + 21, 21, 21, 21, 21, 21, 22, 22, 23, 23, + 24, 24 + }; + + const unsigned char + parser::yyr2_[] = + { + 0, 2, 1, 2, 3, 3, 3, 1, 1, 3, + 3, 3, 3, 3, 3, 2, 1, 3, 1, 1, + 1, 2 + }; + + + + // YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + // First, the terminals, then, starting at \a yyntokens_, nonterminals. + const char* + const parser::yytname_[] = + { + "$end", "error", "$undefined", "WORD", "QUOTED", "QUALIFIERS", "AND", + "UCONCAT", "'('", "'-'", "OR", "EQUALS", "CONTAINS", "SMALLEREQ", + "SMALLER", "GREATEREQ", "GREATER", "')'", "$accept", "topquery", "query", + "fieldexpr", "complexfieldname", "term", "qualquote", YY_NULLPTR + }; + +#if YYDEBUG + const unsigned char + parser::yyrline_[] = + { + 0, 71, 71, 84, 95, 106, 117, 123, 136, 141, + 149, 157, 165, 173, 181, 189, 199, 205, 214, 220, + 226, 232 + }; + + // Print the state stack on the debug stream. + void + parser::yystack_print_ () + { + *yycdebug_ << "Stack now"; + for (stack_type::const_iterator + i = yystack_.begin (), + i_end = yystack_.end (); + i != i_end; ++i) + *yycdebug_ << ' ' << i->state; + *yycdebug_ << std::endl; + } + + // Report on the debug stream that the rule \a yyrule is going to be reduced. + void + parser::yy_reduce_print_ (int yyrule) + { + unsigned int yylno = yyrline_[yyrule]; + int yynrhs = yyr2_[yyrule]; + // Print the symbols being reduced, and their result. + *yycdebug_ << "Reducing stack by rule " << yyrule - 1 + << " (line " << yylno << "):" << std::endl; + // The symbols being reduced. + for (int yyi = 0; yyi < yynrhs; yyi++) + YY_SYMBOL_PRINT (" $" << yyi + 1 << " =", + yystack_[(yynrhs) - (yyi + 1)]); + } +#endif // YYDEBUG + + // Symbol number corresponding to token number t. + inline + parser::token_number_type + parser::yytranslate_ (int t) + { + static + const token_number_type + translate_table[] = + { + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 8, 17, 2, 2, 2, 9, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 10, 11, 12, 13, 14, 15, 16 + }; + const unsigned int user_token_number_max_ = 269; + const token_number_type undef_token_ = 2; + + if (static_cast(t) <= yyeof_) + return yyeof_; + else if (static_cast (t) <= user_token_number_max_) + return translate_table[t]; + else + return undef_token_; + } + + +} // yy +#line 1327 "y.tab.c" // lalr1.cc:1155 +#line 244 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:1156 + + +#include + +// Look for int at index, skip and return new index found? value. +static unsigned int qualGetInt(const string& q, unsigned int cur, int *pval) { - WasaParserDriver d(config, stemlang, autosuffs); - SearchData *sd = d.parse(query); - if (!sd) - reason = d.getreason(); - return sd; + unsigned int ncur = cur; + if (cur < q.size() - 1) { + char *endptr; + int val = strtol(&q[cur + 1], &endptr, 10); + if (endptr != &q[cur + 1]) { + ncur += endptr - &q[cur + 1]; + *pval = val; + } + } + return ncur; } -SearchData *WasaParserDriver::parse(const std::string& in) +static void qualify(Rcl::SearchDataClauseDist *cl, const string& quals) { - m_input = in; - m_index = 0; - delete m_result; - m_result = 0; - m_returns = stack(); + // cerr << "qualify(" << cl << ", " << quals << ")" << endl; + for (unsigned int i = 0; i < quals.length(); i++) { + //fprintf(stderr, "qual char %c\n", quals[i]); + switch (quals[i]) { + case 'b': + cl->setWeight(10.0); + break; + case 'c': break; + case 'C': + cl->addModifier(Rcl::SearchDataClause::SDCM_CASESENS); + break; + case 'd': break; + case 'D': + cl->addModifier(Rcl::SearchDataClause::SDCM_DIACSENS); + break; + case 'e': + cl->addModifier(Rcl::SearchDataClause::SDCM_CASESENS); + cl->addModifier(Rcl::SearchDataClause::SDCM_DIACSENS); + cl->addModifier(Rcl::SearchDataClause::SDCM_NOSTEMMING); + break; + case 'l': + cl->addModifier(Rcl::SearchDataClause::SDCM_NOSTEMMING); + break; + case 'L': break; + case 'o': + { + int slack = 10; + i = qualGetInt(quals, i, &slack); + cl->setslack(slack); + //cerr << "set slack " << cl->getslack() << " done" << endl; + } + break; + case 'p': + cl->setTp(Rcl::SCLT_NEAR); + if (cl->getslack() == 0) { + cl->setslack(10); + //cerr << "set slack " << cl->getslack() << " done" << endl; + } + break; + case 's': + cl->addModifier(Rcl::SearchDataClause::SDCM_NOSYNS); + break; + case 'S': + break; + case '.':case '0':case '1':case '2':case '3':case '4': + case '5':case '6':case '7':case '8':case '9': + { + int n = 0; + float factor = 1.0; + if (sscanf(&(quals[i]), "%f %n", &factor, &n)) { + if (factor != 1.0) { + cl->setWeight(factor); + } + } + if (n > 0) + i += n - 1; + } + default: + break; + } + } +} - yy::parser parser(this); - parser.set_debug_level(0); - if (parser.parse() != 0) { - delete m_result; - m_result = 0; +// specialstartchars are special only at the beginning of a token +// (e.g. doctor-who is a term, not 2 terms separated by '-') +static const string specialstartchars("-"); +// specialinchars are special everywhere except inside a quoted string +static const string specialinchars(":=<>()"); + +// Called with the first dquote already read +static int parseString(WasaParserDriver *d, yy::parser::semantic_type *yylval) +{ + string* value = new string(); + d->qualifiers().clear(); + int c; + while ((c = d->GETCHAR())) { + switch (c) { + case '\\': + /* Escape: get next char */ + c = d->GETCHAR(); + if (c == 0) { + value->push_back(c); + goto out; + } + value->push_back(c); + break; + case '"': + /* End of string. Look for qualifiers */ + while ((c = d->GETCHAR()) && (isalnum(c) || c == '.')) + d->qualifiers().push_back(c); + d->UNGETCHAR(c); + goto out; + default: + value->push_back(c); + } + } +out: + //cerr << "GOT QUOTED ["<qualifiers() << "]" << endl; + yylval->str = value; + return yy::parser::token::QUOTED; +} + + +int yylex(yy::parser::semantic_type *yylval, yy::parser::location_type *, + WasaParserDriver *d) +{ + if (!d->qualifiers().empty()) { + yylval->str = new string(); + yylval->str->swap(d->qualifiers()); + return yy::parser::token::QUALIFIERS; } - return m_result; -} + int c; -int WasaParserDriver::GETCHAR() -{ - if (!m_returns.empty()) { - int c = m_returns.top(); - m_returns.pop(); + /* Skip white space. */ + while ((c = d->GETCHAR()) && isspace(c)) + continue; + + if (c == 0) + return 0; + + if (specialstartchars.find_first_of(c) != string::npos) { + //cerr << "yylex: return " << c << endl; return c; } - if (m_index < m_input.size()) - return m_input[m_index++]; - return 0; -} -void WasaParserDriver::UNGETCHAR(int c) -{ - m_returns.push(c); -} -// Add clause to query, handling special pseudo-clauses for size/date -// etc. (mostly determined on field name). -bool WasaParserDriver::addClause(SearchData *sd, - SearchDataClauseSimple* cl) -{ - if (cl->getfield().empty()) { - // Simple clause with empty field spec. - // Possibly change terms found in the "autosuffs" list into "ext" - // field queries - if (!m_autosuffs.empty()) { - vector asfv; - if (stringToStrings(m_autosuffs, asfv)) { - if (find_if(asfv.begin(), asfv.end(), - StringIcmpPred(cl->gettext())) != asfv.end()) { - cl->setfield("ext"); - cl->addModifier(SearchDataClause::SDCM_NOSTEMMING); - } - } - } - return sd->addClause(cl); - } - - - const string& fld = cl->getfield(); - - // MIME types and categories - if (!stringicmp("mime", fld) ||!stringicmp("format", fld)) { - if (cl->getexclude()) { - sd->remFiletype(cl->gettext()); + // field-term relations + switch (c) { + case '=': return yy::parser::token::EQUALS; + case ':': return yy::parser::token::CONTAINS; + case '<': { + int c1 = d->GETCHAR(); + if (c1 == '=') { + return yy::parser::token::SMALLEREQ; } else { - sd->addFiletype(cl->gettext()); + d->UNGETCHAR(c1); + return yy::parser::token::SMALLER; } - delete cl; - return true; - } - - if (!stringicmp("rclcat", fld) || !stringicmp("type", fld)) { - vector mtypes; - if (m_config && m_config->getMimeCatTypes(cl->gettext(), mtypes)) { - for (vector::iterator mit = mtypes.begin(); - mit != mtypes.end(); mit++) { - if (cl->getexclude()) { - sd->remFiletype(*mit); - } else { - sd->addFiletype(*mit); - } - } - } - delete cl; - return true; } - - // Handle "date" spec - if (!stringicmp("date", fld)) { - DateInterval di; - if (!parsedateinterval(cl->gettext(), &di)) { - LOGERR(("Bad date interval format: %s\n", - cl->gettext().c_str())); - m_reason = "Bad date interval format"; - delete cl; - return false; - } - LOGDEB(("addClause:: date span: %d-%d-%d/%d-%d-%d\n", - di.y1,di.m1,di.d1, di.y2,di.m2,di.d2)); - sd->setDateSpan(&di); - delete cl; - return true; - } - - // Handle "size" spec - if (!stringicmp("size", fld)) { - char *cp; - size_t size = strtoll(cl->gettext().c_str(), &cp, 10); - if (*cp != 0) { - switch (*cp) { - case 'k': case 'K': size *= 1E3;break; - case 'm': case 'M': size *= 1E6;break; - case 'g': case 'G': size *= 1E9;break; - case 't': case 'T': size *= 1E12;break; - default: - m_reason = string("Bad multiplier suffix: ") + *cp; - delete cl; - return false; - } - } - - SearchDataClause::Relation rel = cl->getrel(); - - delete cl; - - switch (rel) { - case SearchDataClause::REL_EQUALS: - sd->setMaxSize(size); - sd->setMinSize(size); - break; - case SearchDataClause::REL_LT: - case SearchDataClause::REL_LTE: - sd->setMaxSize(size); - break; - case SearchDataClause::REL_GT: - case SearchDataClause::REL_GTE: - sd->setMinSize(size); - break; - default: - m_reason = "Bad relation operator with size query. Use > < or ="; - return false; - } - return true; - } - - if (!stringicmp("dir", fld)) { - // dir filtering special case - SearchDataClausePath *nclause = - new SearchDataClausePath(cl->gettext(), cl->getexclude()); - delete cl; - return sd->addClause(nclause); - } - - if (cl->getTp() == SCLT_OR || cl->getTp() == SCLT_AND) { - // If this is a normal clause and the term has commas or - // slashes inside, take it as a list, turn the slashes/commas - // to spaces, leave unquoted. Otherwise, this would end up as - // a phrase query. This is a handy way to enter multiple terms - // to be searched inside a field. We interpret ',' as AND, and - // '/' as OR. No mixes allowed and ',' wins. - SClType tp = SCLT_FILENAME;// impossible value - string ns = neutchars(cl->gettext(), ","); - if (ns.compare(cl->gettext())) { - // had ',' - tp = SCLT_AND; + case '>': { + int c1 = d->GETCHAR(); + if (c1 == '=') { + return yy::parser::token::GREATEREQ; } else { - ns = neutchars(cl->gettext(), "/"); - if (ns.compare(cl->gettext())) { - // had not ',' but has '/' - tp = SCLT_OR; - } - } - - if (tp != SCLT_FILENAME) { - SearchDataClauseSimple *ncl = - new SearchDataClauseSimple(tp, ns, fld); - delete cl; - return sd->addClause(ncl); + d->UNGETCHAR(c1); + return yy::parser::token::GREATER; } } - return sd->addClause(cl); -} + case '(': case ')': + return c; + } + + if (c == '"') + return parseString(d, yylval); + d->UNGETCHAR(c); + + // Other chars start a term or field name or reserved word + string* word = new string(); + while ((c = d->GETCHAR())) { + if (isspace(c)) { + //cerr << "Word broken by whitespace" << endl; + break; + } else if (specialinchars.find_first_of(c) != string::npos) { + //cerr << "Word broken by special char" << endl; + d->UNGETCHAR(c); + break; + } else if (c == 0) { + //cerr << "Word broken by EOF" << endl; + break; + } else { + word->push_back(c); + } + } + + if (!word->compare("AND") || !word->compare("&&")) { + delete word; + return yy::parser::token::AND; + } else if (!word->compare("OR") || !word->compare("||")) { + delete word; + return yy::parser::token::OR; + } + +// cerr << "Got word [" << word << "]" << endl; + yylval->str = word; + return yy::parser::token::WORD; +} diff --git a/src/query/wasaparse.hpp b/src/query/wasaparse.hpp new file mode 100644 index 00000000..186bc4f5 --- /dev/null +++ b/src/query/wasaparse.hpp @@ -0,0 +1,476 @@ +// A Bison parser, made by GNU Bison 3.0.2. + +// Skeleton interface for Bison LALR(1) parsers in C++ + +// Copyright (C) 2002-2013 Free Software Foundation, Inc. + +// 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 3 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 General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// As a special exception, you may create a larger work that contains +// part or all of the Bison parser skeleton and distribute that work +// under terms of your choice, so long as that work isn't itself a +// parser generator using the skeleton or a modified version thereof +// as a parser skeleton. Alternatively, if you modify or redistribute +// the parser skeleton itself, you may (at your option) remove this +// special exception, which will cause the skeleton and the resulting +// Bison output files to be licensed under the GNU General Public +// License without this special exception. + +// This special exception was added by the Free Software Foundation in +// version 2.2 of Bison. + +/** + ** \file y.tab.h + ** Define the yy::parser class. + */ + +// C++ LALR(1) parser skeleton written by Akim Demaille. + +#ifndef YY_YY_Y_TAB_H_INCLUDED +# define YY_YY_Y_TAB_H_INCLUDED + + +# include +# include +# include +# include +# include "stack.hh" +# include "location.hh" + + +#ifndef YY_ATTRIBUTE +# if (defined __GNUC__ \ + && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__))) \ + || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C +# define YY_ATTRIBUTE(Spec) __attribute__(Spec) +# else +# define YY_ATTRIBUTE(Spec) /* empty */ +# endif +#endif + +#ifndef YY_ATTRIBUTE_PURE +# define YY_ATTRIBUTE_PURE YY_ATTRIBUTE ((__pure__)) +#endif + +#ifndef YY_ATTRIBUTE_UNUSED +# define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__)) +#endif + +#if !defined _Noreturn \ + && (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112) +# if defined _MSC_VER && 1200 <= _MSC_VER +# define _Noreturn __declspec (noreturn) +# else +# define _Noreturn YY_ATTRIBUTE ((__noreturn__)) +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(E) ((void) (E)) +#else +# define YYUSE(E) /* empty */ +#endif + +#if defined __GNUC__ && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ +/* Suppress an incorrect diagnostic about yylval being uninitialized. */ +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\ + _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ + _Pragma ("GCC diagnostic pop") +#else +# define YY_INITIAL_VALUE(Value) Value +#endif +#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_END +#endif +#ifndef YY_INITIAL_VALUE +# define YY_INITIAL_VALUE(Value) /* Nothing. */ +#endif + +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + + +namespace yy { +#line 113 "y.tab.h" // lalr1.cc:372 + + + + + + /// A Bison parser. + class parser + { + public: +#ifndef YYSTYPE + /// Symbol semantic values. + union semantic_type + { + #line 46 "/y/home/dockes/projets/fulltext/recoll/src/query/wasaparse.ypp" // lalr1.cc:372 + + std::string *str; + Rcl::SearchDataClauseSimple *cl; + Rcl::SearchData *sd; + +#line 133 "y.tab.h" // lalr1.cc:372 + }; +#else + typedef YYSTYPE semantic_type; +#endif + /// Symbol locations. + typedef location location_type; + + /// Syntax errors thrown from user actions. + struct syntax_error : std::runtime_error + { + syntax_error (const location_type& l, const std::string& m); + location_type location; + }; + + /// Tokens. + struct token + { + enum yytokentype + { + WORD = 258, + QUOTED = 259, + QUALIFIERS = 260, + AND = 261, + UCONCAT = 262, + OR = 263, + EQUALS = 264, + CONTAINS = 265, + SMALLEREQ = 266, + SMALLER = 267, + GREATEREQ = 268, + GREATER = 269 + }; + }; + + /// (External) token type, as returned by yylex. + typedef token::yytokentype token_type; + + /// Internal symbol number. + typedef int symbol_number_type; + + /// Internal symbol number for tokens (subsumed by symbol_number_type). + typedef unsigned char token_number_type; + + /// A complete symbol. + /// + /// Expects its Base type to provide access to the symbol type + /// via type_get(). + /// + /// Provide access to semantic value and location. + template + struct basic_symbol : Base + { + /// Alias to Base. + typedef Base super_type; + + /// Default constructor. + basic_symbol (); + + /// Copy constructor. + basic_symbol (const basic_symbol& other); + + /// Constructor for valueless symbols. + basic_symbol (typename Base::kind_type t, + const location_type& l); + + /// Constructor for symbols with semantic value. + basic_symbol (typename Base::kind_type t, + const semantic_type& v, + const location_type& l); + + ~basic_symbol (); + + /// Destructive move, \a s is emptied into this. + void move (basic_symbol& s); + + /// The semantic value. + semantic_type value; + + /// The location. + location_type location; + + private: + /// Assignment operator. + basic_symbol& operator= (const basic_symbol& other); + }; + + /// Type access provider for token (enum) based symbols. + struct by_type + { + /// Default constructor. + by_type (); + + /// Copy constructor. + by_type (const by_type& other); + + /// The symbol type as needed by the constructor. + typedef token_type kind_type; + + /// Constructor from (external) token numbers. + by_type (kind_type t); + + /// Steal the symbol type from \a that. + void move (by_type& that); + + /// The (internal) type number (corresponding to \a type). + /// -1 when this symbol is empty. + symbol_number_type type_get () const; + + /// The token. + token_type token () const; + + enum { empty = 0 }; + + /// The symbol type. + /// -1 when this symbol is empty. + token_number_type type; + }; + + /// "External" symbols: returned by the scanner. + typedef basic_symbol symbol_type; + + + /// Build a parser object. + parser (WasaParserDriver* d_yyarg); + virtual ~parser (); + + /// Parse. + /// \returns 0 iff parsing succeeded. + virtual int parse (); + +#if YYDEBUG + /// The current debugging stream. + std::ostream& debug_stream () const YY_ATTRIBUTE_PURE; + /// Set the current debugging stream. + void set_debug_stream (std::ostream &); + + /// Type for debugging levels. + typedef int debug_level_type; + /// The current debugging level. + debug_level_type debug_level () const YY_ATTRIBUTE_PURE; + /// Set the current debugging level. + void set_debug_level (debug_level_type l); +#endif + + /// Report a syntax error. + /// \param loc where the syntax error is found. + /// \param msg a description of the syntax error. + virtual void error (const location_type& loc, const std::string& msg); + + /// Report a syntax error. + void error (const syntax_error& err); + + private: + /// This class is not copyable. + parser (const parser&); + parser& operator= (const parser&); + + /// State numbers. + typedef int state_type; + + /// Generate an error message. + /// \param yystate the state where the error occurred. + /// \param yytoken the lookahead token type, or yyempty_. + virtual std::string yysyntax_error_ (state_type yystate, + symbol_number_type yytoken) const; + + /// Compute post-reduction state. + /// \param yystate the current state + /// \param yysym the nonterminal to push on the stack + state_type yy_lr_goto_state_ (state_type yystate, int yysym); + + /// Whether the given \c yypact_ value indicates a defaulted state. + /// \param yyvalue the value to check + static bool yy_pact_value_is_default_ (int yyvalue); + + /// Whether the given \c yytable_ value indicates a syntax error. + /// \param yyvalue the value to check + static bool yy_table_value_is_error_ (int yyvalue); + + static const signed char yypact_ninf_; + static const signed char yytable_ninf_; + + /// Convert a scanner token number \a t to a symbol number. + static token_number_type yytranslate_ (int t); + + // Tables. + // YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + // STATE-NUM. + static const signed char yypact_[]; + + // YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. + // Performed when YYTABLE does not specify something else to do. Zero + // means the default is an error. + static const unsigned char yydefact_[]; + + // YYPGOTO[NTERM-NUM]. + static const signed char yypgoto_[]; + + // YYDEFGOTO[NTERM-NUM]. + static const signed char yydefgoto_[]; + + // YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If + // positive, shift that token. If negative, reduce the rule whose + // number is the opposite. If YYTABLE_NINF, syntax error. + static const signed char yytable_[]; + + static const signed char yycheck_[]; + + // YYSTOS[STATE-NUM] -- The (internal number of the) accessing + // symbol of state STATE-NUM. + static const unsigned char yystos_[]; + + // YYR1[YYN] -- Symbol number of symbol that rule YYN derives. + static const unsigned char yyr1_[]; + + // YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. + static const unsigned char yyr2_[]; + + + /// Convert the symbol name \a n to a form suitable for a diagnostic. + static std::string yytnamerr_ (const char *n); + + + /// For a symbol, its name in clear. + static const char* const yytname_[]; +#if YYDEBUG + // YYRLINE[YYN] -- Source line where rule number YYN was defined. + static const unsigned char yyrline_[]; + /// Report on the debug stream that the rule \a r is going to be reduced. + virtual void yy_reduce_print_ (int r); + /// Print the state stack on the debug stream. + virtual void yystack_print_ (); + + // Debugging. + int yydebug_; + std::ostream* yycdebug_; + + /// \brief Display a symbol type, value and location. + /// \param yyo The output stream. + /// \param yysym The symbol. + template + void yy_print_ (std::ostream& yyo, const basic_symbol& yysym) const; +#endif + + /// \brief Reclaim the memory associated to a symbol. + /// \param yymsg Why this token is reclaimed. + /// If null, print nothing. + /// \param yysym The symbol. + template + void yy_destroy_ (const char* yymsg, basic_symbol& yysym) const; + + private: + /// Type access provider for state based symbols. + struct by_state + { + /// Default constructor. + by_state (); + + /// The symbol type as needed by the constructor. + typedef state_type kind_type; + + /// Constructor. + by_state (kind_type s); + + /// Copy constructor. + by_state (const by_state& other); + + /// Steal the symbol type from \a that. + void move (by_state& that); + + /// The (internal) type number (corresponding to \a state). + /// "empty" when empty. + symbol_number_type type_get () const; + + enum { empty = 0 }; + + /// The state. + state_type state; + }; + + /// "Internal" symbol: element of the stack. + struct stack_symbol_type : basic_symbol + { + /// Superclass. + typedef basic_symbol super_type; + /// Construct an empty symbol. + stack_symbol_type (); + /// Steal the contents from \a sym to build this. + stack_symbol_type (state_type s, symbol_type& sym); + /// Assignment, needed by push_back. + stack_symbol_type& operator= (const stack_symbol_type& that); + }; + + /// Stack type. + typedef stack stack_type; + + /// The stack. + stack_type yystack_; + + /// Push a new state on the stack. + /// \param m a debug message to display + /// if null, no trace is output. + /// \param s the symbol + /// \warning the contents of \a s.value is stolen. + void yypush_ (const char* m, stack_symbol_type& s); + + /// Push a new look ahead token on the state on the stack. + /// \param m a debug message to display + /// if null, no trace is output. + /// \param s the state + /// \param sym the symbol (for its value and location). + /// \warning the contents of \a s.value is stolen. + void yypush_ (const char* m, state_type s, symbol_type& sym); + + /// Pop \a n symbols the three stacks. + void yypop_ (unsigned int n = 1); + + // Constants. + enum + { + yyeof_ = 0, + yylast_ = 59, ///< Last index in yytable_. + yynnts_ = 7, ///< Number of nonterminal symbols. + yyempty_ = -2, + yyfinal_ = 14, ///< Termination state number. + yyterror_ = 1, + yyerrcode_ = 256, + yyntokens_ = 18 ///< Number of tokens. + }; + + + // User arguments. + WasaParserDriver* d; + }; + + + +} // yy +#line 472 "y.tab.h" // lalr1.cc:372 + + + + +#endif // !YY_YY_Y_TAB_H_INCLUDED diff --git a/src/query/wasaparse.y b/src/query/wasaparse.ypp similarity index 76% rename from src/query/wasaparse.y rename to src/query/wasaparse.ypp index e37f7e4e..3f373cf9 100644 --- a/src/query/wasaparse.y +++ b/src/query/wasaparse.ypp @@ -1,5 +1,6 @@ %{ #define YYDEBUG 1 +#include "autoconfig.h" #include @@ -8,19 +9,28 @@ #include "searchdata.h" #include "wasaparserdriver.h" -#include "wasaparse.tab.h" +#include "wasaparse.hpp" using namespace std; +// #define LOG_PARSER +#ifdef LOG_PARSER +#define LOGP(X) {cerr << X;} +#else +#define LOGP(X) +#endif + int yylex(yy::parser::semantic_type *, yy::parser::location_type *, - WasaParserDriver *); + WasaParserDriver *); void yyerror(char const *); static void qualify(Rcl::SearchDataClauseDist *, const string &); static void addSubQuery(WasaParserDriver *d, Rcl::SearchData *sd, Rcl::SearchData *sq) { - sd->addClause(new Rcl::SearchDataClauseSub(RefCntr(sq))); + if (sd && sq) + sd->addClause( + new Rcl::SearchDataClauseSub(std::shared_ptr(sq))); } %} @@ -46,8 +56,8 @@ static void addSubQuery(WasaParserDriver *d, %type query %type complexfieldname - /* Non operator tokens need precedence because of the possibility of - concatenation which needs to have lower prec than OR */ +/* Non operator tokens need precedence because of the possibility of + concatenation which needs to have lower prec than OR */ %left WORD %left QUOTED %left QUALIFIERS @@ -60,59 +70,77 @@ static void addSubQuery(WasaParserDriver *d, topquery: query { - d->m_result = $1; + // It's possible that we end up with no query (e.g.: because just a + // date filter was set, no terms). Allocate an empty query so that we + // have something to set the global criteria on (this will yield a + // Xapian search like FILTER xxx + if ($1 == 0) + d->m_result = new Rcl::SearchData(Rcl::SCLT_AND, d->m_stemlang); + else + d->m_result = $1; } query: query query %prec UCONCAT { - //cerr << "q: query query" << endl; - Rcl::SearchData *sd = new Rcl::SearchData(Rcl::SCLT_AND, d->m_stemlang); - addSubQuery(d, sd, $1); - addSubQuery(d, sd, $2); + LOGP("q: query query\n"); + Rcl::SearchData *sd = 0; + if ($1 || $2) { + sd = new Rcl::SearchData(Rcl::SCLT_AND, d->m_stemlang); + addSubQuery(d, sd, $1); + addSubQuery(d, sd, $2); + } $$ = sd; } | query AND query { - //cerr << "q: query AND query" << endl; - Rcl::SearchData *sd = new Rcl::SearchData(Rcl::SCLT_AND, d->m_stemlang); - addSubQuery(d, sd, $1); - addSubQuery(d, sd, $3); + LOGP("q: query AND query\n"); + Rcl::SearchData *sd = 0; + if ($1 || $3) { + sd = new Rcl::SearchData(Rcl::SCLT_AND, d->m_stemlang); + addSubQuery(d, sd, $1); + addSubQuery(d, sd, $3); + } $$ = sd; } | query OR query { - //cerr << "q: query OR query" << endl; - Rcl::SearchData *top = new Rcl::SearchData(Rcl::SCLT_AND, d->m_stemlang); - Rcl::SearchData *sd = new Rcl::SearchData(Rcl::SCLT_OR, d->m_stemlang); - addSubQuery(d, sd, $1); - addSubQuery(d, sd, $3); - addSubQuery(d, top, sd); + LOGP("query: query OR query\n"); + Rcl::SearchData *top = 0; + if ($1 || $3) { + top = new Rcl::SearchData(Rcl::SCLT_OR, d->m_stemlang); + addSubQuery(d, top, $1); + addSubQuery(d, top, $3); + } $$ = top; } | '(' query ')' { - //cerr << "q: ( query )" << endl; + LOGP("q: ( query )\n"); $$ = $2; } | fieldexpr %prec UCONCAT { - //cerr << "q: fieldexpr" << endl; + LOGP("q: fieldexpr\n"); Rcl::SearchData *sd = new Rcl::SearchData(Rcl::SCLT_AND, d->m_stemlang); - d->addClause(sd, $1); - $$ = sd; + if (d->addClause(sd, $1)) { + $$ = sd; + } else { + delete sd; + $$ = 0; + } } ; fieldexpr: term { - // cerr << "fe: simple fieldexpr: " << $1->gettext() << endl; + LOGP("fe: simple fieldexpr: " << $1->gettext() << endl); $$ = $1; } | complexfieldname EQUALS term { - // cerr << "fe: " << *$1 << " = " << $3->gettext() << endl; + LOGP("fe: " << *$1 << " = " << $3->gettext() << endl); $3->setfield(*$1); $3->setrel(Rcl::SearchDataClause::REL_EQUALS); $$ = $3; @@ -120,7 +148,7 @@ fieldexpr: term } | complexfieldname CONTAINS term { - // cerr << "fe: " << *$1 << " : " << $3->gettext() << endl; + LOGP("fe: " << *$1 << " : " << $3->gettext() << endl); $3->setfield(*$1); $3->setrel(Rcl::SearchDataClause::REL_CONTAINS); $$ = $3; @@ -128,7 +156,7 @@ fieldexpr: term } | complexfieldname SMALLER term { - // cerr << "fe: " << *$1 << " < " << $3->gettext() << endl; + LOGP(cerr << "fe: " << *$1 << " < " << $3->gettext() << endl); $3->setfield(*$1); $3->setrel(Rcl::SearchDataClause::REL_LT); $$ = $3; @@ -136,7 +164,7 @@ fieldexpr: term } | complexfieldname SMALLEREQ term { - // cerr << "fe: " << *$1 << " <= " << $3->gettext() << endl; + LOGP("fe: " << *$1 << " <= " << $3->gettext() << endl); $3->setfield(*$1); $3->setrel(Rcl::SearchDataClause::REL_LTE); $$ = $3; @@ -144,7 +172,7 @@ fieldexpr: term } | complexfieldname GREATER term { - // cerr << "fe: " << *$1 << " > " << $3->gettext() << endl; + LOGP("fe: " << *$1 << " > " << $3->gettext() << endl); $3->setfield(*$1); $3->setrel(Rcl::SearchDataClause::REL_GT); $$ = $3; @@ -152,7 +180,7 @@ fieldexpr: term } | complexfieldname GREATEREQ term { - // cerr << "fe: " << *$1 << " >= " << $3->gettext() << endl; + LOGP("fe: " << *$1 << " >= " << $3->gettext() << endl); $3->setfield(*$1); $3->setrel(Rcl::SearchDataClause::REL_GTE); $$ = $3; @@ -160,7 +188,7 @@ fieldexpr: term } | '-' fieldexpr { - // cerr << "fe: - fieldexpr[" << $2->gettext() << "]" << endl; + LOGP("fe: - fieldexpr[" << $2->gettext() << "]" << endl); $2->setexclude(true); $$ = $2; } @@ -170,13 +198,13 @@ fieldexpr: term complexfieldname: WORD { - // cerr << "cfn: WORD" << endl; + LOGP("cfn: WORD" << endl); $$ = $1; } | complexfieldname CONTAINS WORD { - // cerr << "cfn: complexfieldname ':' WORD" << endl; + LOGP("cfn: complexfieldname ':' WORD" << endl); $$ = new string(*$1 + string(":") + *$3); delete $1; delete $3; @@ -185,7 +213,7 @@ complexfieldname CONTAINS WORD term: WORD { - //cerr << "term[" << *$1 << "]" << endl; + LOGP("term[" << *$1 << "]" << endl); $$ = new Rcl::SearchDataClauseSimple(Rcl::SCLT_AND, *$1); delete $1; } @@ -197,13 +225,13 @@ WORD qualquote: QUOTED { - // cerr << "QUOTED[" << *$1 << "]" << endl; + LOGP("QUOTED[" << *$1 << "]" << endl); $$ = new Rcl::SearchDataClauseDist(Rcl::SCLT_PHRASE, *$1, 0); delete $1; } | QUOTED QUALIFIERS { - // cerr << "QUOTED[" << *$1 << "] QUALIFIERS[" << *$2 << "]" << endl; + LOGP("QUOTED[" << *$1 << "] QUALIFIERS[" << *$2 << "]" << endl); Rcl::SearchDataClauseDist *cl = new Rcl::SearchDataClauseDist(Rcl::SCLT_PHRASE, *$1, 0); qualify(cl, *$2); @@ -273,6 +301,11 @@ static void qualify(Rcl::SearchDataClauseDist *cl, const string& quals) //cerr << "set slack " << cl->getslack() << " done" << endl; } break; + case 's': + cl->addModifier(Rcl::SearchDataClause::SDCM_NOSYNS); + break; + case 'S': + break; case '.':case '0':case '1':case '2':case '3':case '4': case '5':case '6':case '7':case '8':case '9': { @@ -318,8 +351,9 @@ static int parseString(WasaParserDriver *d, yy::parser::semantic_type *yylval) break; case '"': /* End of string. Look for qualifiers */ - while ((c = d->GETCHAR()) && !isspace(c)) + while ((c = d->GETCHAR()) && (isalnum(c) || c == '.')) d->qualifiers().push_back(c); + d->UNGETCHAR(c); goto out; default: value->push_back(c); diff --git a/src/query/wasaparseaux.cpp b/src/query/wasaparseaux.cpp new file mode 100644 index 00000000..85a9d19f --- /dev/null +++ b/src/query/wasaparseaux.cpp @@ -0,0 +1,269 @@ +/* Copyright (C) 2006 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include "autoconfig.h" + +#include + +#include "wasatorcl.h" +#include "wasaparserdriver.h" +#include "searchdata.h" +#include "log.h" + +#define YYDEBUG 1 + +// bison-generated file +#include "wasaparse.hpp" + +using namespace std; +using namespace Rcl; + + +void +yy::parser::error (const location_type& l, const std::string& m) +{ + d->setreason(m); +} + + +SearchData *wasaStringToRcl(const RclConfig *config, + const std::string& stemlang, + const std::string& query, string &reason, + const std::string& autosuffs) +{ + WasaParserDriver d(config, stemlang, autosuffs); + SearchData *sd = d.parse(query); + if (!sd) + reason = d.getreason(); + return sd; +} + +WasaParserDriver::WasaParserDriver(const RclConfig *c, const std::string sl, + const std::string& as) + : m_stemlang(sl), m_autosuffs(as), m_config(c), + m_index(0), m_result(0), m_haveDates(false), + m_maxSize((size_t)-1), m_minSize((size_t)-1) +{ + +} + +WasaParserDriver::~WasaParserDriver() +{ +} + +SearchData *WasaParserDriver::parse(const std::string& in) +{ + m_input = in; + m_index = 0; + delete m_result; + m_result = 0; + m_returns = stack(); + + yy::parser parser(this); + parser.set_debug_level(0); + + if (parser.parse() != 0) { + delete m_result; + m_result = 0; + } + + if (m_result == 0) + return m_result; + + // Set the top level filters (types, dates, size) + for (vector::const_iterator it = m_filetypes.begin(); + it != m_filetypes.end(); it++) { + m_result->addFiletype(*it); + } + for (vector::const_iterator it = m_nfiletypes.begin(); + it != m_nfiletypes.end(); it++) { + m_result->remFiletype(*it); + } + if (m_haveDates) { + m_result->setDateSpan(&m_dates); + } + if (m_minSize != (size_t)-1) { + m_result->setMinSize(m_minSize); + } + if (m_maxSize != (size_t)-1) { + m_result->setMaxSize(m_maxSize); + } + //if (m_result) m_result->dump(cout); + return m_result; +} + +int WasaParserDriver::GETCHAR() +{ + if (!m_returns.empty()) { + int c = m_returns.top(); + m_returns.pop(); + return c; + } + if (m_index < m_input.size()) + return m_input[m_index++]; + return 0; +} +void WasaParserDriver::UNGETCHAR(int c) +{ + m_returns.push(c); +} + +// Add clause to query, handling special pseudo-clauses for size/date +// etc. (mostly determined on field name). +bool WasaParserDriver::addClause(SearchData *sd, + SearchDataClauseSimple* cl) +{ + if (cl->getfield().empty()) { + // Simple clause with empty field spec. + // Possibly change terms found in the "autosuffs" list into "ext" + // field queries + if (!m_autosuffs.empty()) { + vector asfv; + if (stringToStrings(m_autosuffs, asfv)) { + if (find_if(asfv.begin(), asfv.end(), + StringIcmpPred(cl->gettext())) != asfv.end()) { + cl->setfield("ext"); + cl->addModifier(SearchDataClause::SDCM_NOSTEMMING); + } + } + } + return sd->addClause(cl); + } + + const string& ofld = cl->getfield(); + string fld = stringtolower(ofld); + + // MIME types and categories + if (!fld.compare("mime") || !fld.compare("format")) { + if (cl->getexclude()) { + m_nfiletypes.push_back(cl->gettext()); + } else { + m_filetypes.push_back(cl->gettext()); + } + delete cl; + return false; + } + + if (!fld.compare("rclcat") || !fld.compare("type")) { + vector mtypes; + if (m_config && m_config->getMimeCatTypes(cl->gettext(), mtypes)) { + for (vector::iterator mit = mtypes.begin(); + mit != mtypes.end(); mit++) { + if (cl->getexclude()) { + m_nfiletypes.push_back(*mit); + } else { + m_filetypes.push_back(*mit); + } + } + } + delete cl; + return false; + } + + // Handle "date" spec + if (!fld.compare("date")) { + DateInterval di; + if (!parsedateinterval(cl->gettext(), &di)) { + LOGERR("Bad date interval format: " << (cl->gettext()) << "\n" ); + m_reason = "Bad date interval format"; + delete cl; + return false; + } + LOGDEB("addClause:: date span: " << (di.y1) << "-" << (di.m1) << "-" << (di.d1) << "/" << (di.y2) << "-" << (di.m2) << "-" << (di.d2) << "\n" ); + m_haveDates = true; + m_dates = di; + delete cl; + return false; + } + + // Handle "size" spec + if (!fld.compare("size")) { + char *cp; + size_t size = strtoll(cl->gettext().c_str(), &cp, 10); + if (*cp != 0) { + switch (*cp) { + case 'k': case 'K': size *= 1000;break; + case 'm': case 'M': size *= 1000*1000;break; + case 'g': case 'G': size *= 1000*1000*1000;break; + case 't': case 'T': size *= size_t(1000)*1000*1000*1000;break; + default: + m_reason = string("Bad multiplier suffix: ") + *cp; + delete cl; + return false; + } + } + + SearchDataClause::Relation rel = cl->getrel(); + + delete cl; + + switch (rel) { + case SearchDataClause::REL_EQUALS: + m_maxSize = m_minSize = size; + break; + case SearchDataClause::REL_LT: + case SearchDataClause::REL_LTE: + m_maxSize = size; + break; + case SearchDataClause::REL_GT: + case SearchDataClause::REL_GTE: + m_minSize = size; + break; + default: + m_reason = "Bad relation operator with size query. Use > < or ="; + return false; + } + return false; + } + + if (!fld.compare("dir")) { + // dir filtering special case + SearchDataClausePath *nclause = + new SearchDataClausePath(cl->gettext(), cl->getexclude()); + delete cl; + return sd->addClause(nclause); + } + + if (cl->getTp() == SCLT_OR || cl->getTp() == SCLT_AND) { + // If this is a normal clause and the term has commas or + // slashes inside, take it as a list, turn the slashes/commas + // to spaces, leave unquoted. Otherwise, this would end up as + // a phrase query. This is a handy way to enter multiple terms + // to be searched inside a field. We interpret ',' as AND, and + // '/' as OR. No mixes allowed and ',' wins. + SClType tp = SCLT_FILENAME;// impossible value + string ns = neutchars(cl->gettext(), ","); + if (ns.compare(cl->gettext())) { + // had ',' + tp = SCLT_AND; + } else { + ns = neutchars(cl->gettext(), "/"); + if (ns.compare(cl->gettext())) { + // had not ',' but has '/' + tp = SCLT_OR; + } + } + + if (tp != SCLT_FILENAME) { + SearchDataClauseSimple *ncl = + new SearchDataClauseSimple(tp, ns, ofld); + delete cl; + return sd->addClause(ncl); + } + } + return sd->addClause(cl); +} + diff --git a/src/query/wasaparserdriver.h b/src/query/wasaparserdriver.h index da6fe6dd..8c5a35b9 100644 --- a/src/query/wasaparserdriver.h +++ b/src/query/wasaparserdriver.h @@ -19,6 +19,9 @@ #include #include +#include + +#include "smallut.h" class WasaParserDriver; namespace Rcl { @@ -35,10 +38,9 @@ class WasaParserDriver { public: WasaParserDriver(const RclConfig *c, const std::string sl, - const std::string& as) - : m_stemlang(sl), m_autosuffs(as), m_config(c), - m_index(0), m_result(0) {} - + const std::string& as); + ~WasaParserDriver(); + Rcl::SearchData *parse(const std::string&); bool addClause(Rcl::SearchData *sd, Rcl::SearchDataClauseSimple* cl); @@ -62,11 +64,23 @@ private: std::string m_autosuffs; const RclConfig *m_config; + // input string. std::string m_input; + // Current position in m_input unsigned int m_index; + // Characters pushed-back, ready for next getchar. std::stack m_returns; + // Result, set by parser. Rcl::SearchData *m_result; + // Storage for top level filters + std::vector m_filetypes; + std::vector m_nfiletypes; + bool m_haveDates; + DateInterval m_dates; // Restrict to date interval + size_t m_maxSize; + size_t m_minSize; + std::string m_reason; // Let the quoted string reader store qualifiers in there, simpler diff --git a/src/query/xadump.cpp b/src/query/xadump.cpp index 6fad27ae..5f2dd877 100644 --- a/src/query/xadump.cpp +++ b/src/query/xadump.cpp @@ -45,6 +45,7 @@ static string usage = " -i docid -X : delete document docid\n" " -i docid -b : 'rebuild' document from term positions\n" " -i docid -T : term list for doc docid\n" + " -i docid -r : reconstructed text for docid\n" " -t term -E : term existence test\n" " -t term -F : retrieve term frequency data for given term\n" " -t term -P : retrieve postings for term\n" @@ -83,6 +84,7 @@ static int op_flags; #define OPT_t 0x4000 #define OPT_x 0x8000 #define OPT_l 0x10000 +#define OPT_r 0x20000 // Compute an exploded version of string, inserting a space between each char. // (no character combining possible) @@ -127,6 +129,27 @@ inline bool has_prefix(const string& trm) } } + +void wholedoc(Xapian::Database* db, int docid) +{ + vector buf; + Xapian::TermIterator term; + for (term = db->termlist_begin(docid); + term != db->termlist_end(docid); term++) { + Xapian::PositionIterator pos; + for (pos = db->positionlist_begin(docid, *term); + pos != db->positionlist_end(docid, *term); pos++) { + if (buf.size() < *pos) + buf.resize(2*((*pos)+1)); + buf[(*pos)] = *term; + } + } + for (vector::iterator it = buf.begin(); it != buf.end(); it++) { + if (!it->empty()) + cout << *it << " "; + } +} + int main(int argc, char **argv) { string dbdir = path_cat(path_home(), ".recoll/xapiandb"); @@ -165,6 +188,7 @@ int main(int argc, char **argv) case 'n': op_flags |= OPT_n; break; case 'P': op_flags |= OPT_P; break; case 'q': op_flags |= OPT_q; break; + case 'r': op_flags |= OPT_r; break; case 's': op_flags |= OPT_s; break; case 'T': op_flags |= OPT_T; break; case 't': op_flags |= OPT_t; if (argc < 2) Usage(); @@ -259,6 +283,8 @@ int main(int argc, char **argv) Xapian::Document doc = db->get_document(docid); string data = doc.get_data(); cout << data << endl; + } else if (op_flags & OPT_r) { + wholedoc(db, docid); } else if (op_flags & OPT_X) { Xapian::Document doc = db->get_document(docid); string data = doc.get_data(); diff --git a/src/rcldb/Makefile b/src/rcldb/Makefile index bec225fa..20ab6b30 100644 --- a/src/rcldb/Makefile +++ b/src/rcldb/Makefile @@ -1,15 +1,10 @@ -depth = .. -include $(depth)/mk/sysconf - -# Only test executables get build in here PROGS = synfamily stoplist -all: librecoll $(PROGS) +all: $(PROGS) STOPLIST_OBJS= trstoplist.o stoplist : $(STOPLIST_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o stoplist $(STOPLIST_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) $(ALL_CXXFLAGS) -o stoplist $(STOPLIST_OBJS) $(LIBRECOLL) trstoplist.o : stoplist.cpp $(CXX) $(ALL_CXXFLAGS) -DTEST_STOPLIST -c -o trstoplist.o \ stoplist.cpp @@ -17,9 +12,10 @@ trstoplist.o : stoplist.cpp SYNFAMILY_OBJS= trsynfamily.o synfamily : $(SYNFAMILY_OBJS) $(CXX) $(ALL_CXXFLAGS) -o synfamily $(SYNFAMILY_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBXAPIAN) $(LIBSYS) + $(LIBRECOLL) -lxapian + trsynfamily.o : synfamily.cpp $(CXX) $(ALL_CXXFLAGS) -DTEST_SYNFAMILY -c -o trsynfamily.o \ synfamily.cpp -include $(depth)/mk/commontargets +include ../utils/utmkdefs.mk diff --git a/src/rcldb/daterange.cpp b/src/rcldb/daterange.cpp index ab747587..38a21cd5 100644 --- a/src/rcldb/daterange.cpp +++ b/src/rcldb/daterange.cpp @@ -29,9 +29,9 @@ #include using namespace std; -#include "xapian.h" +#include -#include "debuglog.h" +#include "log.h" #include "rclconfig.h" namespace Rcl { @@ -124,3 +124,4 @@ Xapian::Query date_range_filter(int y1, int m1, int d1, int y2, int m2, int d2) } + diff --git a/src/rcldb/expansiondbs.cpp b/src/rcldb/expansiondbs.cpp index f8d65ab8..a5b9bd9b 100644 --- a/src/rcldb/expansiondbs.cpp +++ b/src/rcldb/expansiondbs.cpp @@ -18,10 +18,12 @@ #include "autoconfig.h" -#include "debuglog.h" +#include + +#include "log.h" #include "utf8iter.h" #include "smallut.h" -#include "refcntr.h" +#include "chrono.h" #include "textsplit.h" #include "xmacros.h" #include "rcldb.h" @@ -39,8 +41,7 @@ namespace Rcl { bool createExpansionDbs(Xapian::WritableDatabase& wdb, const vector& langs) { - LOGDEB(("StemDb::createExpansionDbs: languages: %s\n", - stringsToString(langs).c_str())); + LOGDEB("StemDb::createExpansionDbs: languages: " << (stringsToString(langs)) << "\n" ); Chrono cron; // Erase and recreate all the expansion groups @@ -52,40 +53,40 @@ bool createExpansionDbs(Xapian::WritableDatabase& wdb, return true; } - // Stem dbs - vector stemdbs; - // Note: tried to make this to work with stack-allocated objects, couldn't. - // Looks like a bug in copy constructors somewhere, can't guess where - vector > stemmers; - for (unsigned int i = 0; i < langs.size(); i++) { - stemmers.push_back(RefCntr - (new SynTermTransStem(langs[i]))); - stemdbs.push_back( - XapWritableComputableSynFamMember(wdb, synFamStem, langs[i], - stemmers.back().getptr())); - stemdbs.back().recreate(); - } - - // Unaccented stem dbs - vector unacstemdbs; - // We can reuse the same stemmer pointers, the objects are stateless. - if (!o_index_stripchars) { - for (unsigned int i = 0; i < langs.size(); i++) { - unacstemdbs.push_back( - XapWritableComputableSynFamMember(wdb, synFamStemUnac, langs[i], - stemmers.back().getptr())); - unacstemdbs.back().recreate(); - } - } - SynTermTransUnac transunac(UNACOP_UNACFOLD); - XapWritableComputableSynFamMember - diacasedb(wdb, synFamDiCa, "all", &transunac); - if (!o_index_stripchars) - diacasedb.recreate(); - // Walk the list of all terms, and stem/unac each. string ermsg; try { + // Stem dbs + vector stemdbs; + // Note: tried to make this to work with stack-allocated objects, couldn't. + // Looks like a bug in copy constructors somewhere, can't guess where + vector > stemmers; + for (unsigned int i = 0; i < langs.size(); i++) { + stemmers.push_back(std::shared_ptr + (new SynTermTransStem(langs[i]))); + stemdbs.push_back( + XapWritableComputableSynFamMember(wdb, synFamStem, langs[i], + stemmers.back().get())); + stemdbs.back().recreate(); + } + + // Unaccented stem dbs + vector unacstemdbs; + // We can reuse the same stemmer pointers, the objects are stateless. + if (!o_index_stripchars) { + for (unsigned int i = 0; i < langs.size(); i++) { + unacstemdbs.push_back( + XapWritableComputableSynFamMember(wdb, synFamStemUnac, langs[i], + stemmers.back().get())); + unacstemdbs.back().recreate(); + } + } + SynTermTransUnac transunac(UNACOP_UNACFOLD); + XapWritableComputableSynFamMember + diacasedb(wdb, synFamDiCa, "all", &transunac); + if (!o_index_stripchars) + diacasedb.recreate(); + Xapian::TermIterator it = wdb.allterms_begin(); // We'd want to skip to the first non-prefixed term, but this is a bit // complicated, so we just jump over most of the prefixed term and then @@ -97,8 +98,10 @@ bool createExpansionDbs(Xapian::WritableDatabase& wdb, // Detect and skip CJK terms. Utf8Iter utfit(*it); + if (utfit.eof()) // Empty term?? Seems to happen. + continue; if (TextSplit::isCJK(*utfit)) { - // LOGDEB(("stemskipped: Skipping CJK\n")); + // LOGDEB("stemskipped: Skipping CJK\n" ); continue; } @@ -115,7 +118,7 @@ bool createExpansionDbs(Xapian::WritableDatabase& wdb, // Dont' apply stemming to terms which don't look like // natural language words. if (!Db::isSpellingCandidate(*it)) { - LOGDEB1(("createExpansionDbs: skipped: [%s]\n", (*it).c_str())); + LOGDEB1("createExpansionDbs: skipped: [" << ((*it)) << "]\n" ); continue; } @@ -141,12 +144,13 @@ bool createExpansionDbs(Xapian::WritableDatabase& wdb, } } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("Db::createStemDb: map build failed: %s\n", ermsg.c_str())); + LOGERR("Db::createStemDb: map build failed: " << (ermsg) << "\n" ); return false; } - LOGDEB(("StemDb::createExpansionDbs: done: %.2f S\n", cron.secs())); + LOGDEB("StemDb::createExpansionDbs: done: " << (cron.secs()) << " S\n" ); return true; } } + diff --git a/src/rcldb/expansiondbs.h b/src/rcldb/expansiondbs.h index 6ed0bea9..ae4a4710 100644 --- a/src/rcldb/expansiondbs.h +++ b/src/rcldb/expansiondbs.h @@ -43,12 +43,19 @@ public: : m_op(op) { } + virtual std::string name() { + std::string nm("Unac: "); + if (m_op & UNACOP_UNAC) + nm += "UNAC "; + if (m_op & UNACOP_FOLD) + nm += "FOLD "; + return nm; + } virtual std::string operator()(const std::string& in) { string out; unacmaybefold(in, out, "UTF-8", m_op); - LOGDEB2(("SynTermTransUnac(%d): in [%s] out [%s]\n", int(m_op), - in.c_str(), out.c_str())); + LOGDEB2("SynTermTransUnac(" << (int(m_op)) << "): in [" << (in) << "] out [" << (out) << "]\n" ); return out; } UnacOp m_op; @@ -60,3 +67,4 @@ extern bool createExpansionDbs(Xapian::WritableDatabase& wdb, } #endif /* _EXPANSIONDBS_H_INCLUDED_ */ + diff --git a/src/rcldb/rclabstract.cpp b/src/rcldb/rclabstract.cpp index 98b2cb3d..457281db 100644 --- a/src/rcldb/rclabstract.cpp +++ b/src/rcldb/rclabstract.cpp @@ -19,11 +19,8 @@ #include #include -#include "unordered_defs.h" -using namespace std; - -#include "debuglog.h" +#include "log.h" #include "rcldb.h" #include "rcldb_p.h" #include "rclquery.h" @@ -32,6 +29,9 @@ using namespace std; #include "searchdata.h" #include "utf8iter.h" #include "hldata.h" +#include "chrono.h" + +using namespace std; namespace Rcl { // This is used as a marker inside the abstract frag lists, but @@ -50,7 +50,7 @@ static void listList(const string& what, const vector&l) for (vector::const_iterator it = l.begin(); it != l.end(); it++) { a = a + *it + " "; } - LOGDEB(("%s: %s\n", what.c_str(), a.c_str())); + LOGDEB("" << (what) << ": " << (a) << "\n" ); } #else #define LOGABS LOGDEB2 @@ -82,8 +82,8 @@ static void noPrefixList(const vector& in, vector& out) bool Query::Native::getMatchTerms(unsigned long xdocid, vector& terms) { if (!xenquire) { - LOGERR(("Query::getMatchTerms: no query opened\n")); - return -1; + LOGERR("Query::getMatchTerms: no query opened\n" ); + return false; } terms.clear(); @@ -95,7 +95,7 @@ bool Query::Native::getMatchTerms(unsigned long xdocid, vector& terms) xenquire->get_matching_terms_end(id)), m_q->m_db->m_ndb->xrdb, m_q->m_reason); if (!m_q->m_reason.empty()) { - LOGERR(("getMatchTerms: xapian error: %s\n", m_q->m_reason.c_str())); + LOGERR("getMatchTerms: xapian error: " << (m_q->m_reason) << "\n" ); return false; } noPrefixList(iterms, terms); @@ -127,8 +127,7 @@ void Query::Native::setDbWideQTermsFreqs() for (vector::const_iterator qit = qterms.begin(); qit != qterms.end(); qit++) { termfreqs[*qit] = xrdb.get_termfreq(*qit) / doccnt; - LOGABS(("setDbWideQTermFreqs: [%s] db freq %.1e\n", qit->c_str(), - termfreqs[*qit])); + LOGABS("setDbWideQTermFreqs: [" << (qit) << "] db freq " << (termfreqs[*qit]) << "\n" ); } } @@ -147,7 +146,7 @@ double Query::Native::qualityTerms(Xapian::docid docid, const vector& terms, multimap >& byQ) { - LOGABS(("qualityTerms\n")); + LOGABS("qualityTerms\n" ); setDbWideQTermsFreqs(); map termQcoefs; @@ -158,7 +157,7 @@ double Query::Native::qualityTerms(Xapian::docid docid, if (doclen == 0) doclen = 1; HighlightData hld; - if (!m_q->m_sd.isNull()) { + if (m_q->m_sd) { m_q->m_sd->getTerms(hld); } @@ -166,7 +165,7 @@ double Query::Native::qualityTerms(Xapian::docid docid, { string deb; hld.toString(deb); - LOGABS(("qualityTerms: hld: %s\n", deb.c_str())); + LOGABS("qualityTerms: hld: " << (deb) << "\n" ); } #endif @@ -174,12 +173,11 @@ double Query::Native::qualityTerms(Xapian::docid docid, map > byRoot; for (vector::const_iterator qit = terms.begin(); qit != terms.end(); qit++) { - bool found = false; map::const_iterator eit = hld.terms.find(*qit); if (eit != hld.terms.end()) { byRoot[eit->second].push_back(*qit); } else { - LOGDEB0(("qualityTerms: [%s] not found in hld\n", (*qit).c_str())); + LOGDEB0("qualityTerms: [" << ((*qit)) << "] not found in hld\n" ); byRoot[*qit].push_back(*qit); } } @@ -196,7 +194,7 @@ double Query::Native::qualityTerms(Xapian::docid docid, } byRootstr.append("\n"); } - LOGABS(("\nqualityTerms: uterms to terms: %s\n", byRootstr.c_str())); + LOGABS("\nqualityTerms: uterms to terms: " << (byRootstr) << "\n" ); } #endif @@ -244,10 +242,10 @@ double Query::Native::qualityTerms(Xapian::docid docid, #ifdef DEBUGABSTRACT for (multimap >::reverse_iterator mit= byQ.rbegin(); mit != byQ.rend(); mit++) { - LOGABS(("qualityTerms: group\n")); + LOGABS("qualityTerms: group\n" ); for (vector::const_iterator qit = mit->second.begin(); qit != mit->second.end(); qit++) { - LOGABS(("%.1e->[%s]\n", mit->first, qit->c_str())); + LOGABS("" << (mit->first) << "->[" << (qit) << "]\n" ); } } #endif @@ -257,8 +255,9 @@ double Query::Native::qualityTerms(Xapian::docid docid, // Return page number for first match of "significant" term. int Query::Native::getFirstMatchPage(Xapian::docid docid, string& term) { + LOGDEB("Query::Native::getFirstMatchPage\n"); if (!m_q|| !m_q->m_db || !m_q->m_db->m_ndb || !m_q->m_db->m_ndb->m_isopen) { - LOGERR(("Query::getFirstMatchPage: no db\n")); + LOGERR("Query::getFirstMatchPage: no db\n" ); return -1; } Rcl::Db::Native *ndb(m_q->m_db->m_ndb); @@ -268,7 +267,7 @@ int Query::Native::getFirstMatchPage(Xapian::docid docid, string& term) getMatchTerms(docid, terms); if (terms.empty()) { - LOGDEB(("getFirstMatchPage: empty match term list (field match?)\n")); + LOGDEB("getFirstMatchPage: empty match term list (field match?)\n" ); return -1; } @@ -281,7 +280,7 @@ int Query::Native::getFirstMatchPage(Xapian::docid docid, string& term) // We try to use a page which matches the "best" term. Get a sorted list multimap > byQ; - double totalweight = qualityTerms(docid, terms, byQ); + qualityTerms(docid, terms, byQ); for (multimap >::reverse_iterator mit = byQ.rbegin(); mit != byQ.rend(); mit++) { @@ -317,14 +316,13 @@ int Query::Native::makeAbstract(Xapian::docid docid, int imaxoccs, int ictxwords) { Chrono chron; - LOGABS(("makeAbstract: docid %ld imaxoccs %d ictxwords %d\n", - long(docid), imaxoccs, ictxwords)); + LOGABS("makeAbstract: docid " << (long(docid)) << " imaxoccs " << (imaxoccs) << " ictxwords " << (ictxwords) << "\n" ); // The (unprefixed) terms matched by this document vector matchedTerms; getMatchTerms(docid, matchedTerms); if (matchedTerms.empty()) { - LOGDEB(("makeAbstract::Empty term list\n")); + LOGDEB("makeAbstract::Empty term list\n" ); return ABSRES_ERROR; } @@ -341,10 +339,10 @@ int Query::Native::makeAbstract(Xapian::docid docid, // aggregated by the qualityTerms() routine. multimap > byQ; double totalweight = qualityTerms(docid, matchedTerms, byQ); - LOGABS(("makeAbstract:%d: computed Qcoefs.\n", chron.ms())); + LOGABS("makeAbstract:" << (chron.ms()) << ": computed Qcoefs.\n" ); // This can't happen, but would crash us if (totalweight == 0.0) { - LOGERR(("makeAbstract: totalweight == 0.0 !\n")); + LOGERR("makeAbstract: totalweight == 0.0 !\n" ); return ABSRES_ERROR; } @@ -361,7 +359,7 @@ int Query::Native::makeAbstract(Xapian::docid docid, map sparseDoc; // Also remember apart the search term positions so that we can list // them with their snippets. - STD_UNORDERED_SET searchTermPositions; + std::unordered_set searchTermPositions; // Remember max position. Used to stop walking positions lists while // populating the adjacent slots. @@ -378,8 +376,7 @@ int Query::Native::makeAbstract(Xapian::docid docid, const unsigned int maxtotaloccs = imaxoccs > 0 ? imaxoccs : m_q->m_db->getAbsLen() /(7 * (m_q->m_db->getAbsCtxLen() + 1)); int ctxwords = ictxwords == -1 ? m_q->m_db->getAbsCtxLen() : ictxwords; - LOGABS(("makeAbstract:%d: mxttloccs %d ctxwords %d\n", - chron.ms(), maxtotaloccs, ctxwords)); + LOGABS("makeAbstract:" << (chron.ms()) << ": mxttloccs " << (maxtotaloccs) << " ctxwords " << (ctxwords) << "\n" ); int ret = ABSRES_OK; @@ -387,7 +384,7 @@ int Query::Native::makeAbstract(Xapian::docid docid, for (multimap >::reverse_iterator mit = byQ.rbegin(); mit != byQ.rend(); mit++) { unsigned int maxgrpoccs; - float q; + double q; if (byQ.size() == 1) { maxgrpoccs = maxtotaloccs; q = 1.0; @@ -407,8 +404,7 @@ int Query::Native::makeAbstract(Xapian::docid docid, string qterm = *qit; - LOGABS(("makeAbstract: [%s] %d max grp occs (coef %.2f)\n", - qterm.c_str(), maxgrpoccs, q)); + LOGABS("makeAbstract: [" << (qterm) << "] " << (maxgrpoccs) << " max grp occs (coef " << (q) << ")\n" ); // The match term may span several words int qtrmwrdcnt = @@ -427,8 +423,7 @@ int Query::Native::makeAbstract(Xapian::docid docid, int ipos = *pos; if (ipos < int(baseTextPosition)) // Not in text body continue; - LOGABS(("makeAbstract: [%s] at pos %d grpoccs %d maxgrpoccs" - " %d\n", qterm.c_str(), ipos, grpoccs, maxgrpoccs)); + LOGABS("makeAbstract: [" << (qterm) << "] at pos " << (ipos) << " grpoccs " << (grpoccs) << " maxgrpoccs " << (maxgrpoccs) << "\n" ); totaloccs++; grpoccs++; @@ -468,13 +463,13 @@ int Query::Native::makeAbstract(Xapian::docid docid, // Group done ? if (grpoccs >= maxgrpoccs) { ret |= ABSRES_TRUNC; - LOGABS(("Db::makeAbstract: max group occs cutoff\n")); + LOGABS("Db::makeAbstract: max group occs cutoff\n" ); break; } // Global done ? if (totaloccs >= maxtotaloccs) { ret |= ABSRES_TRUNC; - LOGABS(("Db::makeAbstract: max occurrences cutoff\n")); + LOGABS("Db::makeAbstract: max occurrences cutoff\n" ); break; } } @@ -484,19 +479,18 @@ int Query::Native::makeAbstract(Xapian::docid docid, if (totaloccs >= maxtotaloccs) { ret |= ABSRES_TRUNC; - LOGABS(("Db::makeAbstract: max1 occurrences cutoff\n")); + LOGABS("Db::makeAbstract: max1 occurrences cutoff\n" ); break; } } } maxpos += ctxwords + 1; - LOGABS(("makeAbstract:%d:chosen number of positions %d\n", - chron.millis(), totaloccs)); + LOGABS("makeAbstract:" << (chron.millis()) << ":chosen number of positions " << (totaloccs) << "\n" ); // This can happen if there are term occurences in the keywords // etc. but not elsewhere ? if (totaloccs == 0) { - LOGDEB(("makeAbstract: no occurrences\n")); + LOGDEB("makeAbstract: no occurrences\n" ); return ABSRES_OK; } @@ -515,8 +509,7 @@ int Query::Native::makeAbstract(Xapian::docid docid, continue; if (m_q->m_snipMaxPosWalk > 0 && cutoff-- < 0) { ret |= ABSRES_TERMMISS; - LOGDEB0(("makeAbstract: max term count cutoff %d\n", - m_q->m_snipMaxPosWalk)); + LOGDEB0("makeAbstract: max term count cutoff " << (m_q->m_snipMaxPosWalk) << "\n" ); break; } @@ -526,8 +519,7 @@ int Query::Native::makeAbstract(Xapian::docid docid, pos != xrdb.positionlist_end(docid, *term); pos++) { if (m_q->m_snipMaxPosWalk > 0 && cutoff-- < 0) { ret |= ABSRES_TERMMISS; - LOGDEB0(("makeAbstract: max term count cutoff %d\n", - m_q->m_snipMaxPosWalk)); + LOGDEB0("makeAbstract: max term count cutoff " << (m_q->m_snipMaxPosWalk) << "\n" ); break; } // If we are beyond the max possible position, stop @@ -541,8 +533,7 @@ int Query::Native::makeAbstract(Xapian::docid docid, // at the same position, we want to keep only the // first one (ie: dockes and dockes@wanadoo.fr) if (vit->second.empty()) { - LOGDEB2(("makeAbstract: populating: [%s] at %d\n", - (*term).c_str(), *pos)); + LOGDEB2("makeAbstract: populating: [" << ((*term)) << "] at " << (*pos) << "\n" ); sparseDoc[*pos] = *term; } } @@ -559,11 +550,11 @@ int Query::Native::makeAbstract(Xapian::docid docid, it++, ipos++) { if (it->empty()) { if (!epty) - LOGDEB(("makeAbstract:vec[%d]: [%s]\n", ipos, it->c_str())); + LOGDEB("makeAbstract:vec[" << (ipos) << "]: [" << (it) << "]\n" ); epty=true; } else { epty = false; - LOGDEB(("makeAbstract:vec[%d]: [%s]\n", ipos, it->c_str())); + LOGDEB("makeAbstract:vec[" << (ipos) << "]: [" << (it) << "]\n" ); } } #endif @@ -571,8 +562,7 @@ int Query::Native::makeAbstract(Xapian::docid docid, vector vpbreaks; ndb->getPagePositions(docid, vpbreaks); - LOGABS(("makeAbstract:%d: extracting. Got %u pages\n", chron.millis(), - vpbreaks.size())); + LOGABS("makeAbstract:" << (chron.millis()) << ": extracting. Got " << (vpbreaks.size()) << " pages\n" ); // Finally build the abstract by walking the map (in order of position) vabs.clear(); string chunk; @@ -581,9 +571,9 @@ int Query::Native::makeAbstract(Xapian::docid docid, string term; for (map::const_iterator it = sparseDoc.begin(); it != sparseDoc.end(); it++) { - LOGDEB2(("Abtract:output %u -> [%s]\n", it->first, it->second.c_str())); + LOGDEB2("Abtract:output " << (it->first) << " -> [" << (it->second) << "]\n" ); if (!occupiedmarker.compare(it->second)) { - LOGDEB(("Abstract: qtrm position not filled ??\n")); + LOGDEB("Abstract: qtrm position not filled ??\n" ); continue; } if (chunk.empty() && !vpbreaks.empty()) { @@ -613,9 +603,12 @@ int Query::Native::makeAbstract(Xapian::docid docid, if (!chunk.empty()) vabs.push_back(Snippet(page, chunk).setTerm(term)); - LOGDEB2(("makeAbtract: done in %d mS\n", chron.millis())); + LOGDEB2("makeAbtract: done in " << (chron.millis()) << " mS\n" ); return ret; } } + + + diff --git a/src/rcldb/rcldb.cpp b/src/rcldb/rcldb.cpp index 31754635..bffe9510 100644 --- a/src/rcldb/rcldb.cpp +++ b/src/rcldb/rcldb.cpp @@ -18,7 +18,7 @@ #include #include -#include +#include "safeunistd.h" #include #include @@ -26,13 +26,14 @@ #include #include #include +#include using namespace std; #include "xapian.h" #include "rclconfig.h" -#include "debuglog.h" +#include "log.h" #include "rcldb.h" #include "rcldb_p.h" #include "stemdb.h" @@ -41,7 +42,9 @@ using namespace std; #include "unacpp.h" #include "conftree.h" #include "pathut.h" +#include "rclutil.h" #include "smallut.h" +#include "chrono.h" #include "utf8iter.h" #include "searchdata.h" #include "rclquery.h" @@ -49,10 +52,11 @@ using namespace std; #include "md5ut.h" #include "rclversion.h" #include "cancelcheck.h" -#include "ptmutex.h" #include "termproc.h" #include "expansiondbs.h" #include "rclinit.h" +#include "internfile.h" +#include "utf8fn.h" // Recoll index format version is stored in user metadata. When this change, // we can't open the db and will have to reindex. @@ -71,8 +75,8 @@ static const string xapday_prefix = "D"; static const string xapmonth_prefix = "M"; static const string xapyear_prefix = "Y"; const string pathelt_prefix = "XP"; -const string udi_prefix("Q"); -const string parent_prefix("F"); +static const string udi_prefix("Q"); +static const string parent_prefix("F"); // Special terms to mark begin/end of field (for anchored searches), and // page breaks @@ -123,26 +127,42 @@ static inline string make_parentterm(const string& udi) return pterm; } +static void utf8truncate(string& s, int maxlen) +{ + if (s.size() <= string::size_type(maxlen)) { + return; + } + Utf8Iter iter(s); + string::size_type pos = 0; + while (iter++ != string::npos) + if (iter.getBpos() < string::size_type(maxlen)) { + pos = iter.getBpos(); + } + + s.erase(pos); +} + Db::Native::Native(Db *db) : m_rcldb(db), m_isopen(false), m_iswritable(false), m_noversionwrite(false) #ifdef IDX_THREADS , m_wqueue("DbUpd", m_rcldb->m_config->getThrConf(RclConfig::ThrDbWrite).first), - m_loglevel(4), m_totalworkns(0LL), m_havewriteq(false) #endif // IDX_THREADS { - LOGDEB1(("Native::Native: me %p\n", this)); + LOGDEB1("Native::Native: me " << (this) << "\n" ); } Db::Native::~Native() { - LOGDEB1(("Native::~Native: me %p\n", this)); + LOGDEB1("Native::~Native: me " << (this) << "\n" ); #ifdef IDX_THREADS if (m_havewriteq) { void *status = m_wqueue.setTerminateAndWait(); - LOGDEB2(("Native::~Native: worker status %ld\n", long(status))); + if (status) { + LOGDEB1("Native::~Native: worker status " << status << "\n"); + } } #endif // IDX_THREADS } @@ -153,7 +173,6 @@ void *DbUpdWorker(void* vdbp) recoll_threadinit(); Db::Native *ndbp = (Db::Native *)vdbp; WorkQueue *tqp = &(ndbp->m_wqueue); - DebugLog::getdbl()->setloglevel(ndbp->m_loglevel); DbUpdTask *tsk = 0; for (;;) { @@ -165,24 +184,24 @@ void *DbUpdWorker(void* vdbp) bool status = false; switch (tsk->op) { case DbUpdTask::AddOrUpdate: - LOGDEB(("DbUpdWorker: got add/update task, ql %d\n", int(qsz))); + LOGDEB("DbUpdWorker: got add/update task, ql " << (int(qsz)) << "\n" ); status = ndbp->addOrUpdateWrite(tsk->udi, tsk->uniterm, tsk->doc, tsk->txtlen); break; case DbUpdTask::Delete: - LOGDEB(("DbUpdWorker: got delete task, ql %d\n", int(qsz))); + LOGDEB("DbUpdWorker: got delete task, ql " << (int(qsz)) << "\n" ); status = ndbp->purgeFileWrite(false, tsk->udi, tsk->uniterm); break; case DbUpdTask::PurgeOrphans: - LOGDEB(("DbUpdWorker: got orphans purge task, ql %d\n", int(qsz))); + LOGDEB("DbUpdWorker: got orphans purge task, ql " << (int(qsz)) << "\n" ); status = ndbp->purgeFileWrite(true, tsk->udi, tsk->uniterm); break; default: - LOGERR(("DbUpdWorker: unknown op %d !!\n", tsk->op)); + LOGERR("DbUpdWorker: unknown op " << (tsk->op) << " !!\n" ); break; } if (!status) { - LOGERR(("DbUpdWorker: xxWrite failed\n")); + LOGERR("DbUpdWorker: xxWrite failed\n" ); tqp->workerExit(); delete tsk; return (void*)0; @@ -193,25 +212,22 @@ void *DbUpdWorker(void* vdbp) void Db::Native::maybeStartThreads() { - m_loglevel = DebugLog::getdbl()->getlevel(); - m_havewriteq = false; const RclConfig *cnf = m_rcldb->m_config; int writeqlen = cnf->getThrConf(RclConfig::ThrDbWrite).first; int writethreads = cnf->getThrConf(RclConfig::ThrDbWrite).second; if (writethreads > 1) { - LOGINFO(("RclDb: write threads count was forced down to 1\n")); + LOGINFO("RclDb: write threads count was forced down to 1\n" ); writethreads = 1; } if (writeqlen >= 0 && writethreads > 0) { if (!m_wqueue.start(writethreads, DbUpdWorker, this)) { - LOGERR(("Db::Db: Worker start failed\n")); + LOGERR("Db::Db: Worker start failed\n" ); return; } m_havewriteq = true; } - LOGDEB(("RclDb:: threads: haveWriteQ %d, wqlen %d wqts %d\n", - m_havewriteq, writeqlen, writethreads)); + LOGDEB("RclDb:: threads: haveWriteQ " << (m_havewriteq) << ", wqlen " << (writeqlen) << " wqts " << (writethreads) << "\n" ); } #endif // IDX_THREADS @@ -222,7 +238,7 @@ void Db::Native::maybeStartThreads() bool Db::Native::subDocs(const string &udi, int idxi, vector& docids) { - LOGDEB2(("subDocs: [%s]\n", uniterm.c_str())); + LOGDEB2("subDocs: [" << (uniterm) << "]\n" ); string pterm = make_parentterm(udi); vector candidates; XAPTRY(docids.clear(); @@ -230,7 +246,7 @@ bool Db::Native::subDocs(const string &udi, int idxi, xrdb.postlist_end(pterm)), xrdb, m_rcldb->m_reason); if (!m_rcldb->m_reason.empty()) { - LOGERR(("Rcl::Db::subDocs: %s\n", m_rcldb->m_reason.c_str())); + LOGERR("Rcl::Db::subDocs: " << (m_rcldb->m_reason) << "\n" ); return false; } else { for (unsigned int i = 0; i < candidates.size(); i++) { @@ -238,7 +254,7 @@ bool Db::Native::subDocs(const string &udi, int idxi, docids.push_back(candidates[i]); } } - LOGDEB0(("Db::Native::subDocs: returning %d ids\n", docids.size())); + LOGDEB0("Db::Native::subDocs: returning " << (docids.size()) << " ids\n" ); return true; } } @@ -250,7 +266,7 @@ bool Db::Native::xdocToUdi(Xapian::Document& xdoc, string &udi) xit.skip_to(wrap_prefix(udi_prefix)), xrdb, m_rcldb->m_reason); if (!m_rcldb->m_reason.empty()) { - LOGERR(("xdocToUdi: xapian error: %s\n", m_rcldb->m_reason.c_str())); + LOGERR("xdocToUdi: xapian error: " << (m_rcldb->m_reason) << "\n" ); return false; } if (xit != xdoc.termlist_end()) { @@ -268,30 +284,27 @@ bool Db::Native::xdocToUdi(Xapian::Document& xdoc, string &udi) // posting, but we have to do it ourselves bool Db::Native::clearDocTermIfWdf0(Xapian::Document& xdoc, const string& term) { - LOGDEB1(("Db::clearDocTermIfWdf0: [%s]\n", term.c_str())); + LOGDEB1("Db::clearDocTermIfWdf0: [" << (term) << "]\n" ); // Find the term Xapian::TermIterator xit; XAPTRY(xit = xdoc.termlist_begin(); xit.skip_to(term);, xrdb, m_rcldb->m_reason); if (!m_rcldb->m_reason.empty()) { - LOGERR(("Db::clearDocTerm...: [%s] skip failed: %s\n", - term.c_str(), m_rcldb->m_reason.c_str())); + LOGERR("Db::clearDocTerm...: [" << (term) << "] skip failed: " << (m_rcldb->m_reason) << "\n" ); return false; } if (xit == xdoc.termlist_end() || term.compare(*xit)) { - LOGDEB0(("Db::clearDocTermIFWdf0: term [%s] not found. xit: [%s]\n", - term.c_str(), xit == xdoc.termlist_end() ? "EOL":(*xit).c_str())); + LOGDEB0("Db::clearDocTermIFWdf0: term [" << (term) << "] not found. xit: [" << (xit == xdoc.termlist_end() ? "EOL":(*xit)) << "]\n" ); return false; } // Clear the term if its frequency is 0 if (xit.get_wdf() == 0) { - LOGDEB1(("Db::clearDocTermIfWdf0: clearing [%s]\n", term.c_str())); + LOGDEB1("Db::clearDocTermIfWdf0: clearing [" << (term) << "]\n" ); XAPTRY(xdoc.remove_term(term), xwdb, m_rcldb->m_reason); if (!m_rcldb->m_reason.empty()) { - LOGDEB0(("Db::clearDocTermIfWdf0: failed [%s]: %s\n", - term.c_str(), m_rcldb->m_reason.c_str())); + LOGDEB0("Db::clearDocTermIfWdf0: failed [" << (term) << "]: " << (m_rcldb->m_reason) << "\n" ); } } return true; @@ -312,8 +325,7 @@ struct DocPosting { bool Db::Native::clearField(Xapian::Document& xdoc, const string& pfx, Xapian::termcount wdfdec) { - LOGDEB1(("Db::clearField: clearing prefix [%s] for docid %u\n", - pfx.c_str(), unsigned(xdoc.get_docid()))); + LOGDEB1("Db::clearField: clearing prefix [" << (pfx) << "] for docid " << (unsigned(xdoc.get_docid())) << "\n" ); vector eraselist; @@ -327,7 +339,7 @@ bool Db::Native::clearField(Xapian::Document& xdoc, const string& pfx, xit.skip_to(wrapd); while (xit != xdoc.termlist_end() && !(*xit).compare(0, wrapd.size(), wrapd)) { - LOGDEB1(("Db::clearfield: erasing for [%s]\n", (*xit).c_str())); + LOGDEB1("Db::clearfield: erasing for [" << ((*xit)) << "]\n" ); Xapian::PositionIterator posit; for (posit = xit.positionlist_begin(); posit != xit.positionlist_end(); posit++) { @@ -344,23 +356,20 @@ bool Db::Native::clearField(Xapian::Document& xdoc, const string& pfx, break; } if (!m_rcldb->m_reason.empty()) { - LOGERR(("Db::clearField: failed building erase list: %s\n", - m_rcldb->m_reason.c_str())); + LOGERR("Db::clearField: failed building erase list: " << (m_rcldb->m_reason) << "\n" ); return false; } // Now remove the found positions, and the terms if the wdf is 0 for (vector::const_iterator it = eraselist.begin(); it != eraselist.end(); it++) { - LOGDEB1(("Db::clearField: remove posting: [%s] pos [%d]\n", - it->term.c_str(), int(it->pos))); + LOGDEB1("Db::clearField: remove posting: [" << (it->term) << "] pos [" << (int(it->pos)) << "]\n" ); XAPTRY(xdoc.remove_posting(it->term, it->pos, wdfdec);, xwdb,m_rcldb->m_reason); if (!m_rcldb->m_reason.empty()) { // Not that this normally fails for non-prefixed XXST and // ND, don't make a fuss - LOGDEB1(("Db::clearFiedl: remove_posting failed for [%s],%d: %s\n", - it->term.c_str(),int(it->pos), m_rcldb->m_reason.c_str())); + LOGDEB1("Db::clearFiedl: remove_posting failed for [" << (it->term) << "]," << (int(it->pos)) << ": " << (m_rcldb->m_reason) << "\n" ); } clearDocTermIfWdf0(xdoc, it->term); } @@ -370,7 +379,7 @@ bool Db::Native::clearField(Xapian::Document& xdoc, const string& pfx, // Check if doc given by udi is indexed by term bool Db::Native::hasTerm(const string& udi, int idxi, const string& term) { - LOGDEB2(("Native::hasTerm: udi [%s] term [%s]\n",udi.c_str(),term.c_str())); + LOGDEB2("Native::hasTerm: udi [" << (udi) << "] term [" << (term) << "]\n" ); Xapian::Document xdoc; if (getDoc(udi, idxi, xdoc)) { Xapian::TermIterator xit; @@ -378,7 +387,7 @@ bool Db::Native::hasTerm(const string& udi, int idxi, const string& term) xit.skip_to(term);, xrdb, m_rcldb->m_reason); if (!m_rcldb->m_reason.empty()) { - LOGERR(("Rcl::Native::hasTerm: %s\n", m_rcldb->m_reason.c_str())); + LOGERR("Rcl::Native::hasTerm: " << (m_rcldb->m_reason) << "\n" ); return false; } if (xit != xdoc.termlist_end() && !term.compare(*xit)) { @@ -412,8 +421,7 @@ Xapian::docid Db::Native::getDoc(const string& udi, int idxi, } XCATCHERROR(m_rcldb->m_reason); break; } - LOGERR(("Db::Native::getDoc: Xapian error: %s\n", - m_rcldb->m_reason.c_str())); + LOGERR("Db::Native::getDoc: Xapian error: " << (m_rcldb->m_reason) << "\n" ); return 0; } @@ -421,7 +429,7 @@ Xapian::docid Db::Native::getDoc(const string& udi, int idxi, bool Db::Native::dbDataToRclDoc(Xapian::docid docid, std::string &data, Doc &doc) { - LOGDEB2(("Db::dbDataToRclDoc: data:\n%s\n", data.c_str())); + LOGDEB2("Db::dbDataToRclDoc: data:\n" << (data) << "\n" ); ConfSimple parms(data); if (!parms.ok()) return false; @@ -433,7 +441,7 @@ bool Db::Native::dbDataToRclDoc(Xapian::docid docid, std::string &data, string dbdir = m_rcldb->m_basedir; doc.idxi = 0; if (!m_rcldb->m_extraDbs.empty()) { - unsigned int idxi = whatDbIdx(docid); + int idxi = int(whatDbIdx(docid)); // idxi is in [0, extraDbs.size()]. 0 is for the main index, // idxi-1 indexes into the additional dbs array. @@ -492,7 +500,7 @@ bool Db::Native::hasPages(Xapian::docid docid) }, xrdb, ermsg); if (!ermsg.empty()) { - LOGERR(("Db::Native::hasPages: xapian error: %s\n", ermsg.c_str())); + LOGERR("Db::Native::hasPages: xapian error: " << (ermsg) << "\n" ); } return false; } @@ -529,15 +537,13 @@ bool Db::Native::getPagePositions(Xapian::docid docid, vector& vpos) pos != xrdb.positionlist_end(docid, qterm); pos++) { int ipos = *pos; if (ipos < int(baseTextPosition)) { - LOGDEB(("getPagePositions: got page position %d not in body\n", - ipos)); + LOGDEB("getPagePositions: got page position " << (ipos) << " not in body\n" ); // Not in text body. Strange... continue; } map::iterator it = mbreaksmap.find(ipos); if (it != mbreaksmap.end()) { - LOGDEB1(("getPagePositions: found multibreak at %d incr %d\n", - ipos, it->second)); + LOGDEB1("getPagePositions: found multibreak at " << (ipos) << " incr " << (it->second) << "\n" ); for (int i = 0 ; i < it->second; i++) vpos.push_back(ipos); } @@ -549,14 +555,13 @@ bool Db::Native::getPagePositions(Xapian::docid docid, vector& vpos) return true; } -int Db::Native::getPageNumberForPosition(const vector& pbreaks, - unsigned int pos) +int Db::Native::getPageNumberForPosition(const vector& pbreaks, int pos) { - if (pos < baseTextPosition) // Not in text body + if (pos < int(baseTextPosition)) // Not in text body return -1; vector::const_iterator it = upper_bound(pbreaks.begin(), pbreaks.end(), pos); - return it - pbreaks.begin() + 1; + return int(it - pbreaks.begin() + 1); } // Note: we're passed a Xapian::Document* because Xapian @@ -568,9 +573,9 @@ bool Db::Native::addOrUpdateWrite(const string& udi, const string& uniterm, { #ifdef IDX_THREADS Chrono chron; - PTMutexLocker lock(m_mutex); + std::unique_lock lock(m_mutex); #endif - RefCntr doc_cleaner(newdocument_ptr); + std::shared_ptr doc_cleaner(newdocument_ptr); // Check file system full every mbyte of indexed text. It's a bit wasteful // to do this after having prepared the document, but it needs to be in @@ -578,12 +583,12 @@ bool Db::Native::addOrUpdateWrite(const string& udi, const string& uniterm, if (m_rcldb->m_maxFsOccupPc > 0 && (m_rcldb->m_occFirstCheck || (m_rcldb->m_curtxtsz - m_rcldb->m_occtxtsz) / MB >= 1)) { - LOGDEB(("Db::add: checking file system usage\n")); + LOGDEB("Db::add: checking file system usage\n" ); int pc; m_rcldb->m_occFirstCheck = 0; if (fsocc(m_rcldb->m_basedir, &pc) && pc >= m_rcldb->m_maxFsOccupPc) { - LOGERR(("Db::add: stop indexing: file system " - "%d%% full > max %d%%\n", pc, m_rcldb->m_maxFsOccupPc)); + LOGERR("Db::add: stop indexing: file system " << pc << " %" << + " full > max " << m_rcldb->m_maxFsOccupPc << " %" << "\n"); return false; } m_rcldb->m_occtxtsz = m_rcldb->m_curtxtsz; @@ -601,23 +606,22 @@ bool Db::Native::addOrUpdateWrite(const string& udi, const string& uniterm, // by needUpdate(), so the subdocs existence flags are only set // here. m_rcldb->updated[did] = true; - LOGINFO(("Db::add: docid %d updated [%s]\n", did, fnc)); + LOGINFO("Db::add: docid " << (did) << " updated [" << (fnc) << "]\n" ); } else { - LOGINFO(("Db::add: docid %d added [%s]\n", did, fnc)); + LOGINFO("Db::add: docid " << (did) << " added [" << (fnc) << "]\n" ); } } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("Db::add: replace_document failed: %s\n", ermsg.c_str())); + LOGERR("Db::add: replace_document failed: " << (ermsg) << "\n" ); ermsg.erase(); // FIXME: is this ever actually needed? try { xwdb.add_document(*newdocument_ptr); - LOGDEB(("Db::add: %s added (failed re-seek for duplicate)\n", - fnc)); + LOGDEB("Db::add: " << (fnc) << " added (failed re-seek for duplicate)\n" ); } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("Db::add: add_document failed: %s\n", ermsg.c_str())); + LOGERR("Db::add: add_document failed: " << (ermsg) << "\n" ); return false; } } @@ -638,7 +642,7 @@ bool Db::Native::purgeFileWrite(bool orphansOnly, const string& udi, // be called by a single thread) to protect about multiple acces // to xrdb from subDocs() which is also called from needupdate() // (called from outside the write thread ! - PTMutexLocker lock(m_mutex); + std::unique_lock lock(m_mutex); #endif // IDX_THREADS string ermsg; @@ -656,16 +660,16 @@ bool Db::Native::purgeFileWrite(bool orphansOnly, const string& udi, Xapian::Document doc = xwdb.get_document(*docid); sig = doc.get_value(VALUE_SIG); if (sig.empty()) { - LOGINFO(("purgeFileWrite: got empty sig\n")); + LOGINFO("purgeFileWrite: got empty sig\n" ); return false; } } else { - LOGDEB(("purgeFile: delete docid %d\n", *docid)); + LOGDEB("purgeFile: delete docid " << (*docid) << "\n" ); xwdb.delete_document(*docid); } vector docids; subDocs(udi, 0, docids); - LOGDEB(("purgeFile: subdocs cnt %d\n", docids.size())); + LOGDEB("purgeFile: subdocs cnt " << (docids.size()) << "\n" ); for (vector::iterator it = docids.begin(); it != docids.end(); it++) { if (m_rcldb->m_flushMb > 0) { @@ -677,20 +681,20 @@ bool Db::Native::purgeFileWrite(bool orphansOnly, const string& udi, Xapian::Document doc = xwdb.get_document(*it); subdocsig = doc.get_value(VALUE_SIG); if (subdocsig.empty()) { - LOGINFO(("purgeFileWrite: got empty sig for subdoc??\n")); + LOGINFO("purgeFileWrite: got empty sig for subdoc??\n" ); continue; } } if (!orphansOnly || sig != subdocsig) { - LOGDEB(("Db::purgeFile: delete subdoc %d\n", *it)); + LOGDEB("Db::purgeFile: delete subdoc " << (*it) << "\n" ); xwdb.delete_document(*it); } } return true; } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("Db::purgeFileWrite: %s\n", ermsg.c_str())); + LOGERR("Db::purgeFileWrite: " << (ermsg) << "\n" ); } return false; } @@ -727,11 +731,10 @@ Db::Db(const RclConfig *cfp) Db::~Db() { - LOGDEB2(("Db::~Db\n")); + LOGDEB2("Db::~Db\n" ); if (m_ndb == 0) return; - LOGDEB(("Db::~Db: isopen %d m_iswritable %d\n", m_ndb->m_isopen, - m_ndb->m_iswritable)); + LOGDEB("Db::~Db: isopen " << (m_ndb->m_isopen) << " m_iswritable " << (m_ndb->m_iswritable) << "\n" ); i_close(true); delete m_config; } @@ -752,8 +755,7 @@ bool Db::open(OpenMode mode, OpenError *error) m_reason = "Null configuration or Xapian Db"; return false; } - LOGDEB(("Db::open: m_isopen %d m_iswritable %d mode %d\n", m_ndb->m_isopen, - m_ndb->m_iswritable, mode)); + LOGDEB("Db::open: m_isopen " << (m_ndb->m_isopen) << " m_iswritable " << (m_ndb->m_iswritable) << " mode " << (mode) << "\n" ); if (m_ndb->m_isopen) { // We used to return an error here but I see no reason to @@ -762,6 +764,7 @@ bool Db::open(OpenMode mode, OpenError *error) } if (!m_config->getStopfile().empty()) m_stops.setFile(m_config->getStopfile()); + string dir = m_config->getDbDir(); string ermsg; try { @@ -790,9 +793,8 @@ bool Db::open(OpenMode mode, OpenError *error) // trigger other Xapian issues, so the query db is now // a clone of the update one. m_ndb->xrdb = m_ndb->xwdb; - LOGDEB(("Db::open: lastdocid: %d\n", - m_ndb->xwdb.get_lastdocid())); - LOGDEB2(("Db::open: resetting updated\n")); + LOGDEB("Db::open: lastdocid: " << (m_ndb->xwdb.get_lastdocid()) << "\n" ); + LOGDEB2("Db::open: resetting updated\n" ); updated.resize(m_ndb->xwdb.get_lastdocid() + 1); for (unsigned int i = 0; i < updated.size(); i++) updated[i] = false; @@ -806,7 +808,7 @@ bool Db::open(OpenMode mode, OpenError *error) it != m_extraDbs.end(); it++) { if (error) *error = DbOpenExtraDb; - LOGDEB(("Db::Open: adding query db [%s]\n", it->c_str())); + LOGDEB("Db::Open: adding query db [" << &(*it) << "]\n" ); // An error here used to be non-fatal (1.13 and older) // but I can't see why m_ndb->xrdb.add_database(Xapian::Database(*it)); @@ -822,8 +824,7 @@ bool Db::open(OpenMode mode, OpenError *error) string version = m_ndb->xrdb.get_metadata(cstr_RCL_IDX_VERSION_KEY); if (version.compare(cstr_RCL_IDX_VERSION)) { m_ndb->m_noversionwrite = true; - LOGERR(("Rcl::Db::open: file index [%s], software [%s]\n", - version.c_str(), cstr_RCL_IDX_VERSION.c_str())); + LOGERR("Rcl::Db::open: file index [" << (version) << "], software [" << (cstr_RCL_IDX_VERSION) << "]\n" ); throw Xapian::DatabaseError("Recoll index version mismatch", "", ""); } @@ -837,23 +838,21 @@ bool Db::open(OpenMode mode, OpenError *error) } XCATCHERROR(ermsg); m_reason = ermsg; - LOGERR(("Db::open: exception while opening [%s]: %s\n", - dir.c_str(), ermsg.c_str())); + LOGERR("Db::open: exception while opening [" << (dir) << "]: " << (ermsg) << "\n" ); return false; } // Note: xapian has no close call, we delete and recreate the db bool Db::close() { - LOGDEB1(("Db::close()\n")); + LOGDEB1("Db::close()\n" ); return i_close(false); } bool Db::i_close(bool final) { if (m_ndb == 0) return false; - LOGDEB(("Db::i_close(%d): m_isopen %d m_iswritable %d\n", final, - m_ndb->m_isopen, m_ndb->m_iswritable)); + LOGDEB("Db::i_close(" << (final) << "): m_isopen " << (m_ndb->m_isopen) << " m_iswritable " << (m_ndb->m_iswritable) << "\n" ); if (m_ndb->m_isopen == false && !final) return true; @@ -867,11 +866,11 @@ bool Db::i_close(bool final) if (!m_ndb->m_noversionwrite) m_ndb->xwdb.set_metadata(cstr_RCL_IDX_VERSION_KEY, cstr_RCL_IDX_VERSION); - LOGDEB(("Rcl::Db:close: xapian will close. May take some time\n")); + LOGDEB("Rcl::Db:close: xapian will close. May take some time\n" ); } deleteZ(m_ndb); if (w) - LOGDEB(("Rcl::Db:close() xapian close done.\n")); + LOGDEB("Rcl::Db:close() xapian close done.\n" ); if (final) { return true; } @@ -879,10 +878,10 @@ bool Db::i_close(bool final) if (m_ndb) { return true; } - LOGERR(("Rcl::Db::close(): cant recreate db object\n")); + LOGERR("Rcl::Db::close(): cant recreate db object\n" ); return false; } XCATCHERROR(ermsg); - LOGERR(("Db:close: exception while deleting db: %s\n", ermsg.c_str())); + LOGERR("Db:close: exception while deleting db: " << (ermsg) << "\n" ); return false; } @@ -890,7 +889,7 @@ bool Db::i_close(bool final) bool Db::adjustdbs() { if (m_mode != DbRO) { - LOGERR(("Db::adjustdbs: mode not RO\n")); + LOGERR("Db::adjustdbs: mode not RO\n" ); return false; } if (m_ndb && m_ndb->m_isopen) { @@ -912,7 +911,7 @@ int Db::docCnt() XAPTRY(res = m_ndb->xrdb.get_doccount(), m_ndb->xrdb, m_reason); if (!m_reason.empty()) { - LOGERR(("Db::docCnt: got error: %s\n", m_reason.c_str())); + LOGERR("Db::docCnt: got error: " << (m_reason) << "\n" ); return -1; } return res; @@ -927,19 +926,19 @@ int Db::termDocCnt(const string& _term) string term = _term; if (o_index_stripchars) if (!unacmaybefold(_term, term, "UTF-8", UNACOP_UNACFOLD)) { - LOGINFO(("Db::termDocCnt: unac failed for [%s]\n", _term.c_str())); + LOGINFO("Db::termDocCnt: unac failed for [" << (_term) << "]\n" ); return 0; } if (m_stops.isStop(term)) { - LOGDEB1(("Db::termDocCnt [%s] in stop list\n", term.c_str())); + LOGDEB1("Db::termDocCnt [" << (term) << "] in stop list\n" ); return 0; } XAPTRY(res = m_ndb->xrdb.get_termfreq(term), m_ndb->xrdb, m_reason); if (!m_reason.empty()) { - LOGERR(("Db::termDocCnt: got error: %s\n", m_reason.c_str())); + LOGERR("Db::termDocCnt: got error: " << (m_reason) << "\n" ); return -1; } return res; @@ -948,8 +947,7 @@ int Db::termDocCnt(const string& _term) bool Db::addQueryDb(const string &_dir) { string dir = _dir; - LOGDEB0(("Db::addQueryDb: ndb %p iswritable %d db [%s]\n", m_ndb, - (m_ndb)?m_ndb->m_iswritable:0, dir.c_str())); + LOGDEB0("Db::addQueryDb: ndb " << (m_ndb) << " iswritable " << ((m_ndb)?m_ndb->m_iswritable:0) << " db [" << (dir) << "]\n" ); if (!m_ndb) return false; if (m_ndb->m_iswritable) @@ -989,8 +987,8 @@ size_t Db::whatDbIdx(const Doc& doc) size_t Db::Native::whatDbIdx(Xapian::docid id) { - LOGDEB1(("Db::whatDbIdx: xdocid %lu, %u extraDbs\n", - (unsigned long)id, m_extraDbs.size())); + LOGDEB1("Db::whatDbIdx: xdocid " << ((unsigned long)id) << ", " << + (m_rcldb->m_extraDbs.size()) << " extraDbs\n" ); if (id == 0) return (size_t)-1; if (m_rcldb->m_extraDbs.size() == 0) @@ -1002,7 +1000,7 @@ bool Db::testDbDir(const string &dir, bool *stripped_p) { string aerr; bool mstripped = true; - LOGDEB(("Db::testDbDir: [%s]\n", dir.c_str())); + LOGDEB("Db::testDbDir: [" << (dir) << "]\n" ); try { Xapian::Database db(dir); // If we have terms with a leading ':' it's an @@ -1014,8 +1012,7 @@ bool Db::testDbDir(const string &dir, bool *stripped_p) mstripped = false; } XCATCHERROR(aerr); if (!aerr.empty()) { - LOGERR(("Db::Open: error while trying to open database " - "from [%s]: %s\n", dir.c_str(), aerr.c_str())); + LOGERR("Db::Open: error while trying to open database from [" << (dir) << "]: " << (aerr) << "\n" ); return false; } if (stripped_p) @@ -1066,7 +1063,6 @@ class TextSplitDb : public TextSplitP { // Reimplement text_to_words to insert the begin and end anchor terms. virtual bool text_to_words(const string &in) { - bool ret = false; string ermsg; try { @@ -1075,12 +1071,12 @@ class TextSplitDb : public TextSplitP { ++basepos; } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("Db: xapian add_posting error %s\n", ermsg.c_str())); + LOGERR("Db: xapian add_posting error " << (ermsg) << "\n" ); goto out; } if (!TextSplitP::text_to_words(in)) { - LOGDEB(("TextSplitDb: TextSplit::text_to_words failed\n")); + LOGDEB("TextSplitDb: TextSplit::text_to_words failed\n" ); goto out; } @@ -1091,12 +1087,10 @@ class TextSplitDb : public TextSplitP { ++basepos; } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("Db: xapian add_posting error %s\n", ermsg.c_str())); + LOGERR("Db: xapian add_posting error " << (ermsg) << "\n" ); goto out; } - ret = true; - out: basepos += curpos + 100; return true; @@ -1133,7 +1127,7 @@ public: string ermsg; try { // Index without prefix, using the field-specific weighting - LOGDEB1(("Emitting term at %d : [%s]\n", pos, term.c_str())); + LOGDEB1("Emitting term at " << pos << " : [" << term << "]\n" ); if (!m_ts->ft.pfxonly) m_ts->doc.add_posting(term, pos, m_ts->ft.wdfinc); @@ -1149,30 +1143,27 @@ public: } return true; } XCATCHERROR(ermsg); - LOGERR(("Db: xapian add_posting error %s\n", ermsg.c_str())); + LOGERR("Db: xapian add_posting error " << (ermsg) << "\n" ); return false; } void newpage(int pos) { pos += m_ts->basepos; if (pos < int(baseTextPosition)) { - LOGDEB(("newpage: not in body\n", pos)); + LOGDEB("newpage: not in body: " << (pos) << "\n" ); return; } m_ts->doc.add_posting(m_ts->ft.pfx + page_break_term, pos); if (pos == m_lastpagepos) { m_pageincr++; - LOGDEB2(("newpage: same pos, pageincr %d lastpagepos %d\n", - m_pageincr, m_lastpagepos)); + LOGDEB2("newpage: same pos, pageincr " << (m_pageincr) << " lastpagepos " << (m_lastpagepos) << "\n" ); } else { - LOGDEB2(("newpage: pos change, pageincr %d lastpagepos %d\n", - m_pageincr, m_lastpagepos)); + LOGDEB2("newpage: pos change, pageincr " << (m_pageincr) << " lastpagepos " << (m_lastpagepos) << "\n" ); if (m_pageincr > 0) { // Remember the multiple page break at this position unsigned int relpos = m_lastpagepos - baseTextPosition; - LOGDEB2(("Remembering multiple page break. Relpos %u cnt %d\n", - relpos, m_pageincr)); + LOGDEB2("Remembering multiple page break. Relpos " << (relpos) << " cnt " << (m_pageincr) << "\n" ); m_pageincrvec.push_back(pair(relpos, m_pageincr)); } m_pageincr = 0; @@ -1184,8 +1175,7 @@ public: { if (m_pageincr > 0) { unsigned int relpos = m_lastpagepos - baseTextPosition; - LOGDEB2(("Remembering multiple page break. Position %u cnt %d\n", - relpos, m_pageincr)); + LOGDEB2("Remembering multiple page break. Position " << (relpos) << " cnt " << (m_pageincr) << "\n" ); m_pageincrvec.push_back(pair(relpos, m_pageincr)); m_pageincr = 0; } @@ -1212,7 +1202,7 @@ string Db::getSpellingSuggestion(const string& word) if (o_index_stripchars) if (!unacmaybefold(word, term, "UTF-8", UNACOP_UNACFOLD)) { - LOGINFO(("Db::getSpelling: unac failed for [%s]\n", word.c_str())); + LOGINFO("Db::getSpelling: unac failed for [" << (word) << "]\n" ); return string(); } @@ -1225,8 +1215,7 @@ string Db::getSpellingSuggestion(const string& word) // Let our user set the parameters for abstract processing void Db::setAbstractParams(int idxtrunc, int syntlen, int syntctxlen) { - LOGDEB1(("Db::setAbstractParams: trunc %d syntlen %d ctxlen %d\n", - idxtrunc, syntlen, syntctxlen)); + LOGDEB1("Db::setAbstractParams: trunc " << (idxtrunc) << " syntlen " << (syntlen) << " ctxlen " << (syntctxlen) << "\n" ); if (idxtrunc > 0) m_idxAbsTruncLen = idxtrunc; if (syntlen > 0) @@ -1235,6 +1224,11 @@ void Db::setAbstractParams(int idxtrunc, int syntlen, int syntctxlen) m_synthAbsWordCtxLen = syntctxlen; } +bool Db::setSynGroupsFile(const string& fn) +{ + return m_syngroups.setfile(fn); +} + static const string cstr_nc("\n\r\x0c\\"); #define RECORD_APPEND(R, NM, VAL) {R += NM + "=" + VAL + "\n";} @@ -1244,8 +1238,7 @@ static const string cstr_nc("\n\r\x0c\\"); // metadata), and update database bool Db::addOrUpdate(const string &udi, const string &parent_udi, Doc &doc) { - LOGDEB(("Db::add: udi [%s] parent [%s]\n", - udi.c_str(), parent_udi.c_str())); + LOGDEB("Db::add: udi [" << (udi) << "] parent [" << (parent_udi) << "]\n" ); if (m_ndb == 0) return false; @@ -1302,7 +1295,15 @@ bool Db::addOrUpdate(const string &udi, const string &parent_udi, Doc &doc) // Split and index the path from the url for path-based filtering { - string path = url_gpath(doc.url); + string path = url_gpathS(doc.url); + +#ifdef _WIN32 + // Windows file names are case-insensitive, so we + // translate to UTF-8 and lowercase + string upath = compute_utf8fn(m_config, path, false); + unacmaybefold(upath, path, "UTF-8", UNACOP_FOLD); +#endif + vector vpath; stringToTokens(path, vpath, "/"); // If vpath is not /, the last elt is the file/dir name, not a @@ -1321,6 +1322,7 @@ bool Db::addOrUpdate(const string &udi, const string &parent_udi, Doc &doc) newdocument.add_posting(wrap_prefix(pathelt_prefix) + *it, splitter.basepos + splitter.curpos++); } + splitter.basepos += splitter.curpos + 100; } // Index textual metadata. These are all indexed as text with @@ -1336,17 +1338,13 @@ bool Db::addOrUpdate(const string &udi, const string &parent_udi, Doc &doc) // We don't test for an empty prefix here. Some fields are part // of the internal conf with an empty prefix (ie: abstract). if (!fieldToTraits(meta_it->first, &ftp)) { - LOGDEB0(("Db::add: no prefix for field [%s], no indexing\n", - meta_it->first.c_str())); + LOGDEB0("Db::add: no prefix for field [" << (meta_it->first) << "], no indexing\n" ); continue; } - LOGDEB0(("Db::add: field [%s] pfx [%s] inc %d: [%s]\n", - meta_it->first.c_str(), ftp->pfx.c_str(), ftp->wdfinc, - meta_it->second.c_str())); + LOGDEB0("Db::add: field [" << (meta_it->first) << "] pfx [" << (ftp->pfx) << "] inc " << (ftp->wdfinc) << ": [" << (meta_it->second) << "]\n" ); splitter.setTraits(*ftp); if (!splitter.text_to_words(meta_it->second)) - LOGDEB(("Db::addOrUpdate: split failed for %s\n", - meta_it->first.c_str())); + LOGDEB("Db::addOrUpdate: split failed for " << (meta_it->first) << "\n" ); } } @@ -1357,13 +1355,13 @@ bool Db::addOrUpdate(const string &udi, const string &parent_udi, Doc &doc) splitter.basepos = baseTextPosition; // Split and index body text - LOGDEB2(("Db::add: split body: [%s]\n", doc.text.c_str())); + LOGDEB2("Db::add: split body: [" << (doc.text) << "]\n" ); #ifdef TEXTSPLIT_STATS splitter.resetStats(); #endif if (!splitter.text_to_words(doc.text)) - LOGDEB(("Db::addOrUpdate: split failed for main text\n")); + LOGDEB("Db::addOrUpdate: split failed for main text\n" ); #ifdef TEXTSPLIT_STATS // Reject bad data. unrecognized base64 text is characterized by @@ -1372,10 +1370,7 @@ bool Db::addOrUpdate(const string &udi, const string &parent_udi, Doc &doc) TextSplit::Stats::Values v = splitter.getStats(); // v.avglen > 15 && v.sigma > 12 if (v.count > 200 && (v.avglen > 10 && v.sigma / v.avglen > 0.8)) { - LOGINFO(("RclDb::addOrUpdate: rejecting doc for bad stats " - "count %d avglen %.4f sigma %.4f url [%s] ipath [%s] text %s\n", - v.count, v.avglen, v.sigma, doc.url.c_str(), - doc.ipath.c_str(), doc.text.c_str())); + LOGINFO("RclDb::addOrUpdate: rejecting doc for bad stats count " << (v.count) << " avglen " << (v.avglen) << " sigma " << (v.sigma) << " url [" << (doc.url) << "] ipath [" << (doc.ipath) << "] text " << (doc.text) << "\n" ); delete newdocument_ptr; return true; } @@ -1416,10 +1411,11 @@ bool Db::addOrUpdate(const string &udi, const string &parent_udi, Doc &doc) time_t mtime = atoll(doc.dmtime.empty() ? doc.fmtime.c_str() : doc.dmtime.c_str()); struct tm tmb; - localtime_r(&mtime, &tmb); - char buf[9]; - snprintf(buf, 9, "%04d%02d%02d", - tmb.tm_year+1900, tmb.tm_mon + 1, tmb.tm_mday); + localtime_r(&mtime, &tmb); + char buf[9]; + snprintf(buf, 9, "%04d%02d%02d", + tmb.tm_year+1900, tmb.tm_mon + 1, tmb.tm_mday); + // Date (YYYYMMDD) newdocument.add_boolean_term(wrap_prefix(xapday_prefix) + string(buf)); // Month (YYYYMM) @@ -1496,13 +1492,11 @@ bool Db::addOrUpdate(const string &udi, const string &parent_udi, Doc &doc) // document. This is then not indexed, but part of the doc data so // that we can return it to a query without having to decode the // original file. - bool syntabs = false; // Note that the map accesses by operator[] create empty entries if they // don't exist yet. string& absref = doc.meta[Doc::keyabs]; trimstring(absref, " \t\r\n"); if (absref.empty()) { - syntabs = true; if (!doc.text.empty()) absref = cstr_syntAbs + neutchars(truncate_to_word(doc.text, m_idxAbsTruncLen), @@ -1573,7 +1567,7 @@ bool Db::addOrUpdate(const string &udi, const string &parent_udi, Doc &doc) newdocument.add_boolean_term(wrap_prefix("XM") + *md5); } - LOGDEB0(("Rcl::Db::add: new doc record:\n%s\n", record.c_str())); + LOGDEB0("Rcl::Db::add: new doc record:\n" << (record) << "\n" ); newdocument.set_data(record); } #ifdef IDX_THREADS @@ -1581,7 +1575,7 @@ bool Db::addOrUpdate(const string &udi, const string &parent_udi, Doc &doc) DbUpdTask *tp = new DbUpdTask(DbUpdTask::AddOrUpdate, udi, uniterm, newdocument_ptr, doc.text.length()); if (!m_ndb->m_wqueue.put(tp)) { - LOGERR(("Db::addOrUpdate:Cant queue task\n")); + LOGERR("Db::addOrUpdate:Cant queue task\n" ); delete newdocument_ptr; return false; } else { @@ -1597,20 +1591,20 @@ bool Db::addOrUpdate(const string &udi, const string &parent_udi, Doc &doc) bool Db::Native::docToXdocXattrOnly(TextSplitDb *splitter, const string &udi, Doc &doc, Xapian::Document& xdoc) { - LOGDEB0(("Db::docToXdocXattrOnly\n")); + LOGDEB0("Db::docToXdocXattrOnly\n" ); #ifdef IDX_THREADS - PTMutexLocker lock(m_mutex); + std::unique_lock lock(m_mutex); #endif // Read existing document and its data record if (getDoc(udi, 0, xdoc) == 0) { - LOGERR(("docToXdocXattrOnly: existing doc not found\n")); + LOGERR("docToXdocXattrOnly: existing doc not found\n" ); return false; } string data; XAPTRY(data = xdoc.get_data(), xrdb, m_rcldb->m_reason); if (!m_rcldb->m_reason.empty()) { - LOGERR(("Db::xattrOnly: got error: %s\n", m_rcldb->m_reason.c_str())); + LOGERR("Db::xattrOnly: got error: " << (m_rcldb->m_reason) << "\n" ); return false; } @@ -1619,26 +1613,22 @@ bool Db::Native::docToXdocXattrOnly(TextSplitDb *splitter, const string &udi, for (meta_it = doc.meta.begin(); meta_it != doc.meta.end(); meta_it++) { const FieldTraits *ftp; if (!m_rcldb->fieldToTraits(meta_it->first, &ftp) || ftp->pfx.empty()) { - LOGDEB0(("Db::xattrOnly: no prefix for field [%s], skipped\n", - meta_it->first.c_str())); + LOGDEB0("Db::xattrOnly: no prefix for field [" << (meta_it->first) << "], skipped\n" ); continue; } // Clear the previous terms for the field clearField(xdoc, ftp->pfx, ftp->wdfinc); - LOGDEB0(("Db::xattrOnly: field [%s] pfx [%s] inc %d: [%s]\n", - meta_it->first.c_str(), ftp->pfx.c_str(), ftp->wdfinc, - meta_it->second.c_str())); + LOGDEB0("Db::xattrOnly: field [" << (meta_it->first) << "] pfx [" << (ftp->pfx) << "] inc " << (ftp->wdfinc) << ": [" << (meta_it->second) << "]\n" ); splitter->setTraits(*ftp); if (!splitter->text_to_words(meta_it->second)) - LOGDEB(("Db::xattrOnly: split failed for %s\n", - meta_it->first.c_str())); + LOGDEB("Db::xattrOnly: split failed for " << (meta_it->first) << "\n" ); } xdoc.add_value(VALUE_SIG, doc.sig); // Parse current data record into a dict for ease of processing ConfSimple datadic(data); if (!datadic.ok()) { - LOGERR(("db::docToXdocXattrOnly: failed turning data rec to dict\n")); + LOGERR("db::docToXdocXattrOnly: failed turning data rec to dict\n" ); return false; } @@ -1680,14 +1670,13 @@ void Db::waitUpdIdle() // We flush here just for correct measurement of the thread work time string ermsg; try { - m_ndb->xwdb.flush(); + m_ndb->xwdb.commit(); } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("Db::waitUpdIdle: flush() failed: %s\n", ermsg.c_str())); + LOGERR("Db::waitUpdIdle: flush() failed: " << (ermsg) << "\n" ); } m_ndb->m_totalworkns += chron.nanos(); - LOGINFO(("Db::waitUpdIdle: total xapian work %lld mS\n", - m_ndb->m_totalworkns/1000000)); + LOGINFO("Db::waitUpdIdle: total xapian work " << (lltodecstr(m_ndb->m_totalworkns/1000000)) << " mS\n" ); } } #endif @@ -1698,8 +1687,7 @@ bool Db::maybeflush(off_t moretext) if (m_flushMb > 0) { m_curtxtsz += moretext; if ((m_curtxtsz - m_flushtxtsz) / MB >= m_flushMb) { - LOGDEB(("Db::add/delete: txt size >= %d Mb, flushing\n", - m_flushMb)); + LOGDEB("Db::add/delete: txt size >= " << (m_flushMb) << " Mb, flushing\n" ); return doFlush(); } } @@ -1709,15 +1697,15 @@ bool Db::maybeflush(off_t moretext) bool Db::doFlush() { if (!m_ndb) { - LOGERR(("Db::doFLush: no ndb??\n")); + LOGERR("Db::doFLush: no ndb??\n" ); return false; } string ermsg; try { - m_ndb->xwdb.flush(); + m_ndb->xwdb.commit(); } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("Db::doFlush: flush() failed: %s\n", ermsg.c_str())); + LOGERR("Db::doFlush: flush() failed: " << (ermsg) << "\n" ); return false; } m_flushtxtsz = m_curtxtsz; @@ -1729,11 +1717,11 @@ void Db::setExistingFlags(const string& udi, unsigned int docid) if (m_mode == DbRO) return; if (docid == (unsigned int)-1) { - LOGERR(("Db::setExistingFlags: called with bogus docid !!\n")); + LOGERR("Db::setExistingFlags: called with bogus docid !!\n" ); return; } #ifdef IDX_THREADS - PTMutexLocker lock(m_ndb->m_mutex); + std::unique_lock lock(m_ndb->m_mutex); #endif i_setExistingFlags(udi, docid); } @@ -1742,10 +1730,7 @@ void Db::i_setExistingFlags(const string& udi, unsigned int docid) { // Set the up to date flag for the document and its subdocs if (docid >= updated.size()) { - LOGERR(("needUpdate: existing docid beyond " - "updated.size(). Udi [%s], docid %u, " - "updated.size() %u\n", udi.c_str(), - unsigned(docid), (unsigned)updated.size())); + LOGERR("needUpdate: existing docid beyond updated.size(). Udi [" << (udi) << "], docid " << (unsigned(docid)) << ", updated.size() " << ((unsigned)updated.size()) << "\n" ); return; } else { updated[docid] = true; @@ -1754,13 +1739,13 @@ void Db::i_setExistingFlags(const string& udi, unsigned int docid) // Set the existence flag for all the subdocs (if any) vector docids; if (!m_ndb->subDocs(udi, 0, docids)) { - LOGERR(("Rcl::Db::needUpdate: can't get subdocs\n")); + LOGERR("Rcl::Db::needUpdate: can't get subdocs\n" ); return; } for (vector::iterator it = docids.begin(); it != docids.end(); it++) { if (*it < updated.size()) { - LOGDEB2(("Db::needUpdate: docid %d set\n", *it)); + LOGDEB2("Db::needUpdate: docid " << (*it) << " set\n" ); updated[*it] = true; } } @@ -1796,26 +1781,25 @@ bool Db::needUpdate(const string &udi, const string& sig, // thread which also updates the existence map, and even multiple // accesses to the readonly Xapian::Database are not allowed // anyway - PTMutexLocker lock(m_ndb->m_mutex); + std::unique_lock lock(m_ndb->m_mutex); #endif // Try to find the document indexed by the uniterm. Xapian::PostingIterator docid; XAPTRY(docid = m_ndb->xrdb.postlist_begin(uniterm), m_ndb->xrdb, m_reason); if (!m_reason.empty()) { - LOGERR(("Db::needUpdate: xapian::postlist_begin failed: %s\n", - m_reason.c_str())); + LOGERR("Db::needUpdate: xapian::postlist_begin failed: " << (m_reason) << "\n" ); return false; } if (docid == m_ndb->xrdb.postlist_end(uniterm)) { // No document exists with this path: we do need update - LOGDEB(("Db::needUpdate:yes (new): [%s]\n", uniterm.c_str())); + LOGDEB("Db::needUpdate:yes (new): [" << (uniterm) << "]\n" ); return true; } Xapian::Document xdoc; XAPTRY(xdoc = m_ndb->xrdb.get_document(*docid), m_ndb->xrdb, m_reason); if (!m_reason.empty()) { - LOGERR(("Db::needUpdate: get_document error: %s\n", m_reason.c_str())); + LOGERR("Db::needUpdate: get_document error: " << (m_reason) << "\n" ); return true; } @@ -1827,11 +1811,10 @@ bool Db::needUpdate(const string &udi, const string& sig, string osig; XAPTRY(osig = xdoc.get_value(VALUE_SIG), m_ndb->xrdb, m_reason); if (!m_reason.empty()) { - LOGERR(("Db::needUpdate: get_value error: %s\n", m_reason.c_str())); + LOGERR("Db::needUpdate: get_value error: " << (m_reason) << "\n" ); return true; } - LOGDEB2(("Db::needUpdate: oldsig [%s] new [%s]\n", - osig.c_str(), sig.c_str())); + LOGDEB2("Db::needUpdate: oldsig [" << (osig) << "] new [" << (sig) << "]\n" ); if (osigp) { *osigp = osig; @@ -1839,15 +1822,14 @@ bool Db::needUpdate(const string &udi, const string& sig, // Compare new/old sig if (sig != osig) { - LOGDEB(("Db::needUpdate:yes: olsig [%s] new [%s] [%s]\n", - osig.c_str(), sig.c_str(), uniterm.c_str())); + LOGDEB("Db::needUpdate:yes: olsig [" << (osig) << "] new [" << (sig) << "] [" << (uniterm) << "]\n" ); // Db is not up to date. Let's index the file return true; } // Up to date. Set the existance flags in the map for the doc and // its subdocs. - LOGDEB(("Db::needUpdate:no: [%s]\n", uniterm.c_str())); + LOGDEB("Db::needUpdate:no: [" << (uniterm) << "]\n" ); i_setExistingFlags(udi, *docid); return false; } @@ -1855,7 +1837,7 @@ bool Db::needUpdate(const string &udi, const string& sig, // Return existing stem db languages vector Db::getStemLangs() { - LOGDEB(("Db::getStemLang\n")); + LOGDEB("Db::getStemLang\n" ); vector langs; if (m_ndb == 0 || m_ndb->m_isopen == false) return langs; @@ -1869,7 +1851,7 @@ vector Db::getStemLangs() */ bool Db::deleteStemDb(const string& lang) { - LOGDEB(("Db::deleteStemDb(%s)\n", lang.c_str())); + LOGDEB("Db::deleteStemDb(" << (lang) << ")\n" ); if (m_ndb == 0 || m_ndb->m_isopen == false || !m_ndb->m_iswritable) return false; XapWritableSynFamily db(m_ndb->xwdb, synFamStem); @@ -1884,9 +1866,9 @@ bool Db::deleteStemDb(const string& lang) */ bool Db::createStemDbs(const vector& langs) { - LOGDEB(("Db::createStemDbs\n")); + LOGDEB("Db::createStemDbs\n" ); if (m_ndb == 0 || m_ndb->m_isopen == false || !m_ndb->m_iswritable) { - LOGERR(("createStemDb: db not open or not writable\n")); + LOGERR("createStemDb: db not open or not writable\n" ); return false; } @@ -1901,11 +1883,10 @@ bool Db::createStemDbs(const vector& langs) */ bool Db::purge() { - LOGDEB(("Db::purge\n")); + LOGDEB("Db::purge\n" ); if (m_ndb == 0) return false; - LOGDEB(("Db::purge: m_isopen %d m_iswritable %d\n", m_ndb->m_isopen, - m_ndb->m_iswritable)); + LOGDEB("Db::purge: m_isopen " << (m_ndb->m_isopen) << " m_iswritable " << (m_ndb->m_iswritable) << "\n" ); if (m_ndb->m_isopen == false || m_ndb->m_iswritable == false) return false; @@ -1916,7 +1897,7 @@ bool Db::purge() // else we need to lock out other top level threads. This is just // a precaution as they should have been waited for by the top // level actor at this point - PTMutexLocker lock(m_ndb->m_mutex, m_ndb->m_havewriteq); + std::unique_lock lock(m_ndb->m_mutex); #endif // IDX_THREADS // For xapian versions up to 1.0.1, deleting a non-existant @@ -1926,9 +1907,9 @@ bool Db::purge() // that any added document would go to the index. Kept here // because it doesn't really hurt. try { - m_ndb->xwdb.flush(); + m_ndb->xwdb.commit(); } catch (...) { - LOGERR(("Db::purge: 1st flush failed\n")); + LOGERR("Db::purge: 1st flush failed\n" ); } @@ -1941,7 +1922,7 @@ bool Db::purge() try { CancelCheck::instance().checkCancel(); } catch(CancelExcept) { - LOGINFO(("Db::purge: partially cancelled\n")); + LOGINFO("Db::purge: partially cancelled\n" ); break; } } @@ -1958,22 +1939,22 @@ bool Db::purge() maybeflush(trms * 5); } m_ndb->xwdb.delete_document(docid); - LOGDEB(("Db::purge: deleted document #%d\n", docid)); + LOGDEB("Db::purge: deleted document #" << (docid) << "\n" ); } catch (const Xapian::DocNotFoundError &) { - LOGDEB0(("Db::purge: document #%d not found\n", docid)); + LOGDEB0("Db::purge: document #" << (docid) << " not found\n" ); } catch (const Xapian::Error &e) { - LOGERR(("Db::purge: document #%d: %s\n", docid, e.get_msg().c_str())); + LOGERR("Db::purge: document #" << (docid) << ": " << (e.get_msg()) << "\n" ); } catch (...) { - LOGERR(("Db::purge: document #%d: unknown error\n", docid)); + LOGERR("Db::purge: document #" << (docid) << ": unknown error\n" ); } purgecount++; } } try { - m_ndb->xwdb.flush(); + m_ndb->xwdb.commit(); } catch (...) { - LOGERR(("Db::purge: 2nd flush failed\n")); + LOGERR("Db::purge: 2nd flush failed\n" ); } return true; } @@ -1983,7 +1964,7 @@ bool Db::docExists(const string& uniterm) { #ifdef IDX_THREADS // Need to protect read db against multiaccess. - PTMutexLocker lock(m_ndb->m_mutex); + std::unique_lock lock(m_ndb->m_mutex); #endif string ermsg; @@ -1996,7 +1977,7 @@ bool Db::docExists(const string& uniterm) } } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("Db::docExists(%s) %s\n", uniterm.c_str(), ermsg.c_str())); + LOGERR("Db::docExists(" << (uniterm) << ") " << (ermsg) << "\n" ); } return false; } @@ -2004,7 +1985,7 @@ bool Db::docExists(const string& uniterm) /* Delete document(s) for given unique identifier (doc and descendents) */ bool Db::purgeFile(const string &udi, bool *existed) { - LOGDEB(("Db:purgeFile: [%s]\n", udi.c_str())); + LOGDEB("Db:purgeFile: [" << (udi) << "]\n" ); if (m_ndb == 0 || !m_ndb->m_iswritable) return false; @@ -2020,7 +2001,7 @@ bool Db::purgeFile(const string &udi, bool *existed) DbUpdTask *tp = new DbUpdTask(DbUpdTask::Delete, udi, uniterm, 0, (size_t)-1); if (!m_ndb->m_wqueue.put(tp)) { - LOGERR(("Db::purgeFile:Cant queue task\n")); + LOGERR("Db::purgeFile:Cant queue task\n" ); return false; } else { return true; @@ -2036,7 +2017,7 @@ bool Db::purgeFile(const string &udi, bool *existed) will be done */ bool Db::purgeOrphans(const string &udi) { - LOGDEB(("Db:purgeOrphans: [%s]\n", udi.c_str())); + LOGDEB("Db:purgeOrphans: [" << (udi) << "]\n" ); if (m_ndb == 0 || !m_ndb->m_iswritable) return false; @@ -2047,7 +2028,7 @@ bool Db::purgeOrphans(const string &udi) DbUpdTask *tp = new DbUpdTask(DbUpdTask::PurgeOrphans, udi, uniterm, 0, (size_t)-1); if (!m_ndb->m_wqueue.put(tp)) { - LOGERR(("Db::purgeFile:Cant queue task\n")); + LOGERR("Db::purgeFile:Cant queue task\n" ); return false; } else { return true; @@ -2080,7 +2061,7 @@ bool Db::dbStats(DbStats& res) // existence should be tested by looking at doc.pc bool Db::getDoc(const string &udi, const Doc& idxdoc, Doc &doc) { - LOGDEB(("Db:getDoc: [%s]\n", udi.c_str())); + LOGDEB("Db:getDoc: [" << (udi) << "]\n" ); if (m_ndb == 0) return false; @@ -2101,7 +2082,7 @@ bool Db::getDoc(const string &udi, const Doc& idxdoc, Doc &doc) // other ok docs further) but indicate the error with // pc = -1 doc.pc = -1; - LOGINFO(("Db:getDoc: no such doc in index: [%s]\n", udi.c_str())); + LOGINFO("Db:getDoc: no such doc in index: [" << (udi) << "]\n" ); return true; } } @@ -2112,18 +2093,26 @@ bool Db::hasSubDocs(const Doc &idoc) return false; string inudi; if (!idoc.getmeta(Doc::keyudi, &inudi) || inudi.empty()) { - LOGERR(("Db::hasSubDocs: no input udi or empty\n")); + LOGERR("Db::hasSubDocs: no input udi or empty\n" ); return false; } + LOGDEB1("Db::hasSubDocs: idxi " << (idoc.idxi) << " inudi [" << (inudi) << "]\n" ); + + // Not sure why we perform both the subDocs() call and the test on + // has_children. The former will return docs if the input is a + // file-level document, but the latter should be true both in this + // case and if the input is already a subdoc, so the first test + // should be redundant. Does not hurt much in any case, to be + // checked one day. vector docids; if (!m_ndb->subDocs(inudi, idoc.idxi, docids)) { - LOGDEB(("Db:getSubDocs: lower level subdocs failed\n")); + LOGDEB("Db::hasSubDocs: lower level subdocs failed\n" ); return false; } if (!docids.empty()) return true; - // Check if doc has an has_children term + // Check if doc has an "has_children" term if (m_ndb->hasTerm(inudi, idoc.idxi, has_children_term)) return true; return false; @@ -2138,12 +2127,13 @@ bool Db::getSubDocs(const Doc &idoc, vector& subdocs) string inudi; if (!idoc.getmeta(Doc::keyudi, &inudi) || inudi.empty()) { - LOGERR(("Db::getSubDocs: no input udi or empty\n")); + LOGERR("Db::getSubDocs: no input udi or empty\n" ); return false; } string rootudi; string ipath = idoc.ipath; + LOGDEB0("Db::getSubDocs: idxi " << (idoc.idxi) << " inudi [" << (inudi) << "] ipath [" << (ipath) << "]\n" ); if (ipath.empty()) { // File-level doc. Use it as root rootudi = inudi; @@ -2151,7 +2141,7 @@ bool Db::getSubDocs(const Doc &idoc, vector& subdocs) // See if we have a parent term Xapian::Document xdoc; if (!m_ndb->getDoc(inudi, idoc.idxi, xdoc)) { - LOGERR(("Db::getSubDocs: can't get Xapian document\n")); + LOGERR("Db::getSubDocs: can't get Xapian document\n" ); return false; } Xapian::TermIterator xit; @@ -2159,22 +2149,22 @@ bool Db::getSubDocs(const Doc &idoc, vector& subdocs) xit.skip_to(wrap_prefix(parent_prefix)), m_ndb->xrdb, m_reason); if (!m_reason.empty()) { - LOGERR(("Db::getSubDocs: xapian error: %s\n", m_reason.c_str())); + LOGERR("Db::getSubDocs: xapian error: " << (m_reason) << "\n" ); return false; } if (xit == xdoc.termlist_end()) { - LOGERR(("Db::getSubDocs: parent term not found\n")); + LOGERR("Db::getSubDocs: parent term not found\n" ); return false; } rootudi = strip_prefix(*xit); } - LOGDEB(("Db::getSubDocs: root: [%s]\n", rootudi.c_str())); + LOGDEB("Db::getSubDocs: root: [" << (rootudi) << "]\n" ); // Retrieve all subdoc xapian ids for the root vector docids; if (!m_ndb->subDocs(rootudi, idoc.idxi, docids)) { - LOGDEB(("Db:getSubDocs: lower level subdocs failed\n")); + LOGDEB("Db::getSubDocs: lower level subdocs failed\n" ); return false; } @@ -2192,11 +2182,13 @@ bool Db::getSubDocs(const Doc &idoc, vector& subdocs) doc.meta[Doc::keyrr] = "100%"; doc.pc = 100; if (!m_ndb->dbDataToRclDoc(*it, data, doc)) { - LOGERR(("Db::getSubDocs: doc conversion error\n")); + LOGERR("Db::getSubDocs: doc conversion error\n" ); return false; } - if (ipath.empty() || doc.ipath.find(ipath) == 0) - subdocs.push_back(doc); + if (ipath.empty() || + FileInterner::ipathContains(ipath, doc.ipath)) { + subdocs.push_back(doc); + } } return true; } catch (const Xapian::DatabaseModifiedError &e) { @@ -2207,8 +2199,9 @@ bool Db::getSubDocs(const Doc &idoc, vector& subdocs) break; } - LOGERR(("Db::getSubDocs: Xapian error: %s\n", m_reason.c_str())); + LOGERR("Db::getSubDocs: Xapian error: " << (m_reason) << "\n" ); return false; } } // End namespace Rcl + diff --git a/src/rcldb/rcldb.h b/src/rcldb/rcldb.h index ab1ab978..e8e4b577 100644 --- a/src/rcldb/rcldb.h +++ b/src/rcldb/rcldb.h @@ -21,14 +21,15 @@ #include #include +#include #include "cstr.h" -#include "refcntr.h" #include "rcldoc.h" #include "stoplist.h" #include "rclconfig.h" #include "utf8iter.h" #include "textsplit.h" +#include "syngroups.h" using std::string; using std::vector; @@ -340,8 +341,9 @@ class Db { * Stem expansion is performed if lang is not empty * * @param typ_sens defines the kind of expansion: none, wildcard, - * regexp or stemming. "none" will still expand case and - * diacritics depending on the casesens and diacsens flags. + * regexp or stemming. "none" may still expand case, + * diacritics and synonyms, depending on the casesens, diacsens and + * synexp flags. * @param lang sets the stemming language(s). Can be a space-separated list * @param term is the term to expand * @param result is the main output @@ -353,14 +355,14 @@ class Db { * in the TermMatchResult header */ enum MatchType {ET_NONE=0, ET_WILD=1, ET_REGEXP=2, ET_STEM=3, - ET_DIACSENS=8, ET_CASESENS=16}; + ET_DIACSENS=8, ET_CASESENS=16, ET_SYNEXP=32}; int matchTypeTp(int tp) { return tp & 7; } bool termMatch(int typ_sens, const string &lang, const string &term, - TermMatchResult& result, int max = -1, - const string& field = cstr_null); + TermMatchResult& result, int max = -1, + const string& field = "", vector *multiwords = 0); bool dbStats(DbStats& stats); /** Return min and max years for doc mod times in db */ bool maxYearSpan(int *minyear, int *maxyear); @@ -454,6 +456,9 @@ class Db { } bool doFlush(); + // Use empty fn for no synonyms + bool setSynGroupsFile(const std::string& fn); + /* This has to be public for access by embedded Query::Native */ Native *m_ndb; private: @@ -475,11 +480,21 @@ private: // First fs occup check ? int m_occFirstCheck; + // Synonym groups. There is no strict reason that this has to be + // an Rcl::Db member, as it is only used when building each It + // could be a SearchData member, or even a parameter to + // Query::setQuery(). Otoh, building the syngroups structure from + // a file may be expensive and it's unlikely to change with every + // query, so it makes sense to cache it, and Rcl::Db is not a bad + // place for this. + SynGroups m_syngroups; + /*************** * Parameters cached out of the configuration files. Logically const * after init */ // Stop terms: those don't get indexed. StopList m_stops; + // Truncation length for stored meta fields int m_idxMetaStoredLen; // This is how long an abstract we keep or build from beginning of @@ -533,8 +548,6 @@ private: string version_string(); extern const string pathelt_prefix; -extern const string udi_prefix; -extern const string parent_prefix; extern const string mimetype_prefix; extern const string unsplitFilenameFieldName; extern string start_of_field_term; diff --git a/src/rcldb/rcldb_p.h b/src/rcldb/rcldb_p.h index bc7c1d02..f895d172 100644 --- a/src/rcldb/rcldb_p.h +++ b/src/rcldb/rcldb_p.h @@ -21,15 +21,14 @@ #include "autoconfig.h" #include +#include #include #ifdef IDX_THREADS #include "workqueue.h" #endif // IDX_THREADS -#include "debuglog.h" #include "xmacros.h" -#include "ptmutex.h" namespace Rcl { @@ -82,8 +81,7 @@ class Db::Native { bool m_noversionwrite; //Set if open failed because of version mismatch! #ifdef IDX_THREADS WorkQueue m_wqueue; - int m_loglevel; - PTMutexInit m_mutex; + std::mutex m_mutex; long long m_totalworkns; bool m_havewriteq; void maybeStartThreads(); @@ -120,7 +118,7 @@ class Db::Native { const string& uniterm); bool getPagePositions(Xapian::docid docid, vector& vpos); - int getPageNumberForPosition(const vector& pbreaks, unsigned int pos); + int getPageNumberForPosition(const vector& pbreaks, int pos); bool dbDataToRclDoc(Xapian::docid docid, std::string &data, Doc &doc); diff --git a/src/rcldb/rcldoc.cpp b/src/rcldb/rcldoc.cpp index 2dcaa05f..09700d07 100644 --- a/src/rcldb/rcldoc.cpp +++ b/src/rcldb/rcldoc.cpp @@ -14,9 +14,11 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#include "autoconfig.h" #include "rcldoc.h" -#include "debuglog.h" +#include "log.h" +#include "rclutil.h" namespace Rcl { const string Doc::keyabs("abstract"); @@ -48,27 +50,53 @@ namespace Rcl { void Doc::dump(bool dotext) const { - LOGDEB(("Rcl::Doc::dump: url: [%s]\n", url.c_str())); - LOGDEB(("Rcl::Doc::dump: idxurl: [%s]\n", idxurl.c_str())); - LOGDEB(("Rcl::Doc::dump: ipath: [%s]\n", ipath.c_str())); - LOGDEB(("Rcl::Doc::dump: mimetype: [%s]\n", mimetype.c_str())); - LOGDEB(("Rcl::Doc::dump: fmtime: [%s]\n", fmtime.c_str())); - LOGDEB(("Rcl::Doc::dump: dmtime: [%s]\n", dmtime.c_str())); - LOGDEB(("Rcl::Doc::dump: origcharset: [%s]\n", origcharset.c_str())); - LOGDEB(("Rcl::Doc::dump: syntabs: [%d]\n", syntabs)); - LOGDEB(("Rcl::Doc::dump: pcbytes: [%s]\n", pcbytes.c_str())); - LOGDEB(("Rcl::Doc::dump: fbytes: [%s]\n", fbytes.c_str())); - LOGDEB(("Rcl::Doc::dump: dbytes: [%s]\n", dbytes.c_str())); - LOGDEB(("Rcl::Doc::dump: sig: [%s]\n", sig.c_str())); - LOGDEB(("Rcl::Doc::dump: pc: [%d]\n", pc)); - LOGDEB(("Rcl::Doc::dump: xdocid: [%lu]\n", (unsigned long)xdocid)); + LOGDEB("Rcl::Doc::dump: url: [" << (url) << "]\n" ); + LOGDEB("Rcl::Doc::dump: idxurl: [" << (idxurl) << "]\n" ); + LOGDEB("Rcl::Doc::dump: ipath: [" << (ipath) << "]\n" ); + LOGDEB("Rcl::Doc::dump: mimetype: [" << (mimetype) << "]\n" ); + LOGDEB("Rcl::Doc::dump: fmtime: [" << (fmtime) << "]\n" ); + LOGDEB("Rcl::Doc::dump: dmtime: [" << (dmtime) << "]\n" ); + LOGDEB("Rcl::Doc::dump: origcharset: [" << (origcharset) << "]\n" ); + LOGDEB("Rcl::Doc::dump: syntabs: [" << (syntabs) << "]\n" ); + LOGDEB("Rcl::Doc::dump: pcbytes: [" << (pcbytes) << "]\n" ); + LOGDEB("Rcl::Doc::dump: fbytes: [" << (fbytes) << "]\n" ); + LOGDEB("Rcl::Doc::dump: dbytes: [" << (dbytes) << "]\n" ); + LOGDEB("Rcl::Doc::dump: sig: [" << (sig) << "]\n" ); + LOGDEB("Rcl::Doc::dump: pc: [" << (pc) << "]\n" ); + LOGDEB("Rcl::Doc::dump: xdocid: [" << ((unsigned long)xdocid) << "]\n" ); for (map::const_iterator it = meta.begin(); it != meta.end(); it++) { - LOGDEB(("Rcl::Doc::dump: meta[%s]: [%s]\n", - (*it).first.c_str(), (*it).second.c_str())); + LOGDEB("Rcl::Doc::dump: meta[" << ((*it).first) << "]: [" << ((*it).second) << "]\n" ); } if (dotext) - LOGDEB(("Rcl::Doc::dump: text: \n[%s]\n", text.c_str())); + LOGDEB("Rcl::Doc::dump: text: \n[" << (text) << "]\n" ); + } + + // Copy ensuring no shared string data, for threading issues. + void Doc::copyto(Doc *d) const + { + d->url.assign(url.begin(), url.end()); + d->idxurl.assign(idxurl.begin(), idxurl.end()); + d->idxi = idxi; + d->ipath.assign(ipath.begin(), ipath.end()); + d->mimetype.assign(mimetype.begin(), mimetype.end()); + d->fmtime.assign(fmtime.begin(), fmtime.end()); + d->dmtime.assign(dmtime.begin(), dmtime.end()); + d->origcharset.assign(origcharset.begin(), origcharset.end()); + map_ss_cp_noshr(meta, &d->meta); + d->syntabs = syntabs; + d->pcbytes.assign(pcbytes.begin(), pcbytes.end()); + d->fbytes.assign(fbytes.begin(), fbytes.end()); + d->dbytes.assign(dbytes.begin(), dbytes.end()); + d->sig.assign(sig.begin(), sig.end()); + d->text.assign(text.begin(), text.end()); + d->pc = pc; + d->xdocid = xdocid; + d->idxi = idxi; + d->haspages = haspages; + d->haschildren = haschildren; + d->onlyxattr = onlyxattr; } } + diff --git a/src/rcldb/rcldoc.h b/src/rcldb/rcldoc.h index 120d9ae8..08a08dd7 100644 --- a/src/rcldb/rcldoc.h +++ b/src/rcldb/rcldoc.h @@ -163,33 +163,11 @@ class Doc { onlyxattr = false; } // Copy ensuring no shared string data, for threading issues. - void copyto(Doc *d) const { - d->url.assign(url.begin(), url.end()); - d->idxurl.assign(idxurl.begin(), idxurl.end()); - d->idxi = idxi; - d->ipath.assign(ipath.begin(), ipath.end()); - d->mimetype.assign(mimetype.begin(), mimetype.end()); - d->fmtime.assign(fmtime.begin(), fmtime.end()); - d->dmtime.assign(dmtime.begin(), dmtime.end()); - d->origcharset.assign(origcharset.begin(), origcharset.end()); - map_ss_cp_noshr(meta, &d->meta); - d->syntabs = syntabs; - d->pcbytes.assign(pcbytes.begin(), pcbytes.end()); - d->fbytes.assign(fbytes.begin(), fbytes.end()); - d->dbytes.assign(dbytes.begin(), dbytes.end()); - d->sig.assign(sig.begin(), sig.end()); - d->text.assign(text.begin(), text.end()); - d->pc = pc; - d->xdocid = xdocid; - d->idxi = idxi; - d->haspages = haspages; - d->haschildren = haschildren; - d->onlyxattr = onlyxattr; - } + void copyto(Doc *d) const; + Doc() : idxi(0), syntabs(false), pc(0), xdocid(0), - haspages(false), haschildren(false), onlyxattr(false) - { + haspages(false), haschildren(false), onlyxattr(false) { } /** Get value for named field. If value pointer is 0, just test existence */ bool getmeta(const string& nm, string *value = 0) const diff --git a/src/rcldb/rcldups.cpp b/src/rcldb/rcldups.cpp index ddebce97..8519fce7 100644 --- a/src/rcldb/rcldups.cpp +++ b/src/rcldb/rcldups.cpp @@ -24,7 +24,7 @@ using namespace std; #include -#include "debuglog.h" +#include "log.h" #include "rcldb.h" #include "rcldb_p.h" #include "xmacros.h" @@ -39,11 +39,11 @@ namespace Rcl { bool Db::docDups(const Doc& idoc, vector& odocs) { if (m_ndb == 0) { - LOGERR(("Db::docDups: no db\n")); + LOGERR("Db::docDups: no db\n" ); return false; } if (idoc.xdocid == 0) { - LOGERR(("Db::docDups: null xdocid in input doc\n")); + LOGERR("Db::docDups: null xdocid in input doc\n" ); return false; } // Get the xapian doc @@ -51,7 +51,7 @@ bool Db::docDups(const Doc& idoc, vector& odocs) XAPTRY(xdoc = m_ndb->xrdb.get_document(Xapian::docid(idoc.xdocid)), m_ndb->xrdb, m_reason); if (!m_reason.empty()) { - LOGERR(("Db::docDups: xapian error: %s\n", m_reason.c_str())); + LOGERR("Db::docDups: xapian error: " << (m_reason) << "\n" ); return false; } @@ -59,18 +59,18 @@ bool Db::docDups(const Doc& idoc, vector& odocs) string digest; XAPTRY(digest = xdoc.get_value(VALUE_MD5), m_ndb->xrdb, m_reason); if (!m_reason.empty()) { - LOGERR(("Db::docDups: xapian error: %s\n", m_reason.c_str())); + LOGERR("Db::docDups: xapian error: " << (m_reason) << "\n" ); return false; } if (digest.empty()) { - LOGDEB(("Db::docDups: doc has no md5\n")); + LOGDEB("Db::docDups: doc has no md5\n" ); return false; } string md5; MD5HexPrint(digest, md5); SearchData *sdp = new SearchData(); - RefCntr sd(sdp); + std::shared_ptr sd(sdp); SearchDataClauseSimple *sdc = new SearchDataClauseSimple(SCLT_AND, md5, "rclmd5"); sdc->addModifier(SearchDataClause::SDCM_CASESENS); @@ -79,14 +79,14 @@ bool Db::docDups(const Doc& idoc, vector& odocs) Query query(this); query.setCollapseDuplicates(0); if (!query.setQuery(sd)) { - LOGERR(("Db::docDups: setQuery failed\n")); + LOGERR("Db::docDups: setQuery failed\n" ); return false; } int cnt = query.getResCnt(); for (int i = 0; i < cnt; i++) { Doc doc; if (!query.getDoc(i, doc)) { - LOGERR(("Db::docDups: getDoc failed at %d (cnt %d)\n", i, cnt)); + LOGERR("Db::docDups: getDoc failed at " << (i) << " (cnt " << (cnt) << ")\n" ); return false; } odocs.push_back(doc); @@ -98,18 +98,19 @@ bool Db::docDups(const Doc& idoc, vector& odocs) { vector dups; bool ret; - LOGDEB(("DOCDUPS\n")); + LOGDEB("DOCDUPS\n" ); ret = m_db->docDups(doc, dups); if (!ret) { - LOGDEB(("docDups failed\n")); + LOGDEB("docDups failed\n" ); } else if (dups.size() == 1) { - LOGDEB(("No dups\n")); + LOGDEB("No dups\n" ); } else { for (unsigned int i = 0; i < dups.size(); i++) { - LOGDEB(("Dup: %s\n", dups[i].url.c_str())); + LOGDEB("Dup: " << (dups[i].url) << "\n" ); } } } #endif } + diff --git a/src/rcldb/rclquery.cpp b/src/rcldb/rclquery.cpp index fdf811c7..13c20a8a 100644 --- a/src/rcldb/rclquery.cpp +++ b/src/rcldb/rclquery.cpp @@ -14,6 +14,7 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#include "autoconfig.h" #include #include @@ -21,22 +22,24 @@ #include #include -using namespace std; #include "xapian.h" #include "cstr.h" #include "rclconfig.h" -#include "debuglog.h" +#include "log.h" #include "rcldb.h" #include "rcldb_p.h" #include "rclquery.h" #include "rclquery_p.h" #include "conftree.h" #include "smallut.h" +#include "chrono.h" #include "searchdata.h" #include "unacpp.h" +using namespace std; + namespace Rcl { // This is used as a marker inside the abstract frag lists, but // normally doesn't remain in final output (which is built with a @@ -129,7 +132,7 @@ public: sortterm = sortterm.substr(i1, sortterm.size()-i1); } - LOGDEB2(("QSorter: [%s] -> [%s]\n", term.c_str(), sortterm.c_str())); + LOGDEB2("QSorter: [" << (term) << "] -> [" << (sortterm) << "]\n" ); return sortterm; } @@ -163,20 +166,19 @@ void Query::setSortBy(const string& fld, bool ascending) { m_sortField = m_db->getConf()->fieldQCanon(fld); m_sortAscending = ascending; } - LOGDEB0(("RclQuery::setSortBy: [%s] %s\n", m_sortField.c_str(), - m_sortAscending ? "ascending" : "descending")); + LOGDEB0("RclQuery::setSortBy: [" << (m_sortField) << "] " << (m_sortAscending ? "ascending" : "descending") << "\n" ); } //#define ISNULL(X) (X).isNull() #define ISNULL(X) !(X) // Prepare query out of user search data -bool Query::setQuery(RefCntr sdata) +bool Query::setQuery(std::shared_ptr sdata) { - LOGDEB(("Query::setQuery:\n")); + LOGDEB("Query::setQuery:\n" ); if (!m_db || ISNULL(m_nq)) { - LOGERR(("Query::setQuery: not initialised!\n")); + LOGERR("Query::setQuery: not initialised!\n" ); return false; } m_resCnt = -1; @@ -230,7 +232,7 @@ bool Query::setQuery(RefCntr sdata) } if (!m_reason.empty()) { - LOGDEB(("Query::SetQuery: xapian error %s\n", m_reason.c_str())); + LOGDEB("Query::SetQuery: xapian error " << (m_reason) << "\n" ); return false; } @@ -239,7 +241,7 @@ bool Query::setQuery(RefCntr sdata) sdata->setDescription(d); m_sd = sdata; - LOGDEB(("Query::SetQuery: Q: %s\n", sdata->getDescription().c_str())); + LOGDEB("Query::SetQuery: Q: " << (sdata->getDescription()) << "\n" ); return true; } @@ -258,7 +260,7 @@ bool Query::getQueryTerms(vector& terms) } } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("getQueryTerms: xapian error: %s\n", ermsg.c_str())); + LOGERR("getQueryTerms: xapian error: " << (ermsg) << "\n" ); return false; } return true; @@ -268,17 +270,16 @@ int Query::makeDocAbstract(const Doc &doc, vector& abstract, int maxoccs, int ctxwords) { - LOGDEB(("makeDocAbstract: maxoccs %d ctxwords %d\n", maxoccs, ctxwords)); + LOGDEB("makeDocAbstract: maxoccs " << (maxoccs) << " ctxwords " << (ctxwords) << "\n" ); if (!m_db || !m_db->m_ndb || !m_db->m_ndb->m_isopen || !m_nq) { - LOGERR(("Query::makeDocAbstract: no db or no nq\n")); + LOGERR("Query::makeDocAbstract: no db or no nq\n" ); return ABSRES_ERROR; } int ret = ABSRES_ERROR; XAPTRY(ret = m_nq->makeAbstract(doc.xdocid, abstract, maxoccs, ctxwords), m_db->m_ndb->xrdb, m_reason); if (!m_reason.empty()) { - LOGDEB(("makeDocAbstract: makeAbstract error, reason: %s\n", - m_reason.c_str())); + LOGDEB("makeDocAbstract: makeAbstract error, reason: " << (m_reason) << "\n" ); return ABSRES_ERROR; } return ret; @@ -318,9 +319,9 @@ bool Query::makeDocAbstract(const Doc &doc, string& abstract) int Query::getFirstMatchPage(const Doc &doc, string& term) { - LOGDEB1(("Db::getFirstMatchPage\n"));; + LOGDEB1("Db::getFirstMatchPage\n" );; if (!m_nq) { - LOGERR(("Query::getFirstMatchPage: no nq\n")); + LOGERR("Query::getFirstMatchPage: no nq\n" ); return false; } int pagenum = -1; @@ -338,7 +339,7 @@ static const int qquantum = 50; int Query::getResCnt() { if (ISNULL(m_nq) || !m_nq->xenquire) { - LOGERR(("Query::getResCnt: no query opened\n")); + LOGERR("Query::getResCnt: no query opened\n" ); return -1; } if (m_resCnt >= 0) @@ -353,9 +354,9 @@ int Query::getResCnt() m_resCnt = m_nq->xmset.get_matches_lower_bound(), m_db->m_ndb->xrdb, m_reason); - LOGDEB(("Query::getResCnt: %d %d mS\n", m_resCnt, chron.millis())); + LOGDEB("Query::getResCnt: " << (m_resCnt) << " " << (chron.millis()) << " mS\n" ); if (!m_reason.empty()) - LOGERR(("xenquire->get_mset: exception: %s\n", m_reason.c_str())); + LOGERR("xenquire->get_mset: exception: " << (m_reason) << "\n" ); } else { m_resCnt = m_nq->xmset.get_matches_lower_bound(); } @@ -372,9 +373,9 @@ int Query::getResCnt() // on subsequent calls is probably only due to disk caching. bool Query::getDoc(int xapi, Doc &doc) { - LOGDEB1(("Query::getDoc: xapian enquire index %d\n", xapi)); + LOGDEB1("Query::getDoc: xapian enquire index " << (xapi) << "\n" ); if (ISNULL(m_nq) || !m_nq->xenquire) { - LOGERR(("Query::getDoc: no query opened\n")); + LOGERR("Query::getDoc: no query opened\n" ); return false; } @@ -382,28 +383,24 @@ bool Query::getDoc(int xapi, Doc &doc) int last = first + m_nq->xmset.size() -1; if (!(xapi >= first && xapi <= last)) { - LOGDEB(("Fetching for first %d, count %d\n", xapi, qquantum)); + LOGDEB("Fetching for first " << (xapi) << ", count " << (qquantum) << "\n" ); XAPTRY(m_nq->xmset = m_nq->xenquire->get_mset(xapi, qquantum, (const Xapian::RSet *)0), m_db->m_ndb->xrdb, m_reason); if (!m_reason.empty()) { - LOGERR(("enquire->get_mset: exception: %s\n", m_reason.c_str())); + LOGERR("enquire->get_mset: exception: " << (m_reason) << "\n" ); return false; } if (m_nq->xmset.empty()) { - LOGDEB(("enquire->get_mset: got empty result\n")); + LOGDEB("enquire->get_mset: got empty result\n" ); return false; } first = m_nq->xmset.get_firstitem(); last = first + m_nq->xmset.size() -1; } - LOGDEB1(("Query::getDoc: Qry [%s] win [%d-%d] Estimated results: %d", - m_nq->query.get_description().c_str(), - first, last, m_nq->xmset.get_matches_lower_bound())); - Xapian::Document xdoc; Xapian::docid docid = 0; int pc = 0; @@ -421,8 +418,7 @@ bool Query::getDoc(int xapi, Doc &doc) m_reason.erase(); Chrono chron; m_db->m_ndb->xdocToUdi(xdoc, udi); - LOGDEB2(("Query::getDoc: %d ms for udi [%s], collapse count %d\n", - chron.millis(), udi.c_str(), collapsecount)); + LOGDEB2("Query::getDoc: " << (chron.millis()) << " ms for udi [" << (udi) << "], collapse count " << (collapsecount) << "\n" ); break; } catch (Xapian::DatabaseModifiedError &error) { // retry or end of loop @@ -433,7 +429,7 @@ bool Query::getDoc(int xapi, Doc &doc) break; } if (!m_reason.empty()) { - LOGERR(("Query::getDoc: %s\n", m_reason.c_str())); + LOGERR("Query::getDoc: " << (m_reason) << "\n" ); return false; } doc.meta[Rcl::Doc::keyudi] = udi; @@ -458,10 +454,10 @@ bool Query::getDoc(int xapi, Doc &doc) vector Query::expand(const Doc &doc) { - LOGDEB(("Rcl::Query::expand()\n")); + LOGDEB("Rcl::Query::expand()\n" ); vector res; if (ISNULL(m_nq) || !m_nq->xenquire) { - LOGERR(("Query::expand: no query opened\n")); + LOGERR("Query::expand: no query opened\n" ); return res; } @@ -471,11 +467,11 @@ vector Query::expand(const Doc &doc) rset.add_document(Xapian::docid(doc.xdocid)); // We don't exclude the original query terms. Xapian::ESet eset = m_nq->xenquire->get_eset(20, rset, false); - LOGDEB(("ESet terms:\n")); + LOGDEB("ESet terms:\n" ); // We filter out the special terms for (Xapian::ESetIterator it = eset.begin(); it != eset.end(); it++) { - LOGDEB((" [%s]\n", (*it).c_str())); + LOGDEB(" [" << ((*it)) << "]\n" ); if ((*it).empty() || has_prefix(*it)) continue; res.push_back(*it); @@ -493,7 +489,7 @@ vector Query::expand(const Doc &doc) } if (!m_reason.empty()) { - LOGERR(("Query::expand: xapian error %s\n", m_reason.c_str())); + LOGERR("Query::expand: xapian error " << (m_reason) << "\n" ); res.clear(); } @@ -501,3 +497,4 @@ vector Query::expand(const Doc &doc) } } + diff --git a/src/rcldb/rclquery.h b/src/rcldb/rclquery.h index 438d9e1c..2bb6c43a 100644 --- a/src/rcldb/rclquery.h +++ b/src/rcldb/rclquery.h @@ -19,7 +19,7 @@ #include #include -#include "refcntr.h" +#include #include "searchdata.h" #ifndef NO_NAMESPACES @@ -92,7 +92,7 @@ class Query { * be called repeatedly on the same object which gets reinitialized each * time. */ - bool setQuery(RefCntr q); + bool setQuery(std::shared_ptr q); /** Get results count for current query */ int getResCnt(); @@ -117,7 +117,7 @@ class Query { int getFirstMatchPage(const Doc &doc, std::string& term); /** Retrieve a reference to the searchData we are using */ - RefCntr getSD() + std::shared_ptr getSD() { return m_sd; } @@ -143,7 +143,7 @@ private: bool m_sortAscending; bool m_collapseDuplicates; int m_resCnt; - RefCntr m_sd; + std::shared_ptr m_sd; int m_snipMaxPosWalk; /* Copyconst and assignement private and forbidden */ diff --git a/src/rcldb/rclterms.cpp b/src/rcldb/rclterms.cpp index f9246131..d83fe2cc 100644 --- a/src/rcldb/rclterms.cpp +++ b/src/rcldb/rclterms.cpp @@ -21,13 +21,15 @@ #include "autoconfig.h" #include -using namespace std; -#include "debuglog.h" +#include "log.h" #include "rcldb.h" #include "rcldb_p.h" #include "stemdb.h" #include "expansiondbs.h" +#include "strmatcher.h" + +using namespace std; namespace Rcl { @@ -41,13 +43,13 @@ bool Db::filenameWildExp(const string& fnexp, vector& names, int max) // get here currently anyway), and has no wildcards, we add * at // each end: match any substring if (pattern[0] == '"' && pattern[pattern.size()-1] == '"') { - pattern = pattern.substr(1, pattern.size() -2); + pattern = pattern.substr(1, pattern.size() -2); } else if (pattern.find_first_of(cstr_minwilds) == string::npos && - !unaciscapital(pattern)) { - pattern = "*" + pattern + "*"; + !unaciscapital(pattern)) { + pattern = "*" + pattern + "*"; } // else let it be - LOGDEB(("Rcl::Db::filenameWildExp: pattern: [%s]\n", pattern.c_str())); + LOGDEB("Rcl::Db::filenameWildExp: pattern: [" << (pattern) << "]\n" ); // We inconditionnally lowercase and strip the pattern, as is done // during indexing. This seems to be the only sane possible @@ -55,21 +57,21 @@ bool Db::filenameWildExp(const string& fnexp, vector& names, int max) // stripping conditionally on indexstripchars. string pat1; if (unacmaybefold(pattern, pat1, "UTF-8", UNACOP_UNACFOLD)) { - pattern.swap(pat1); + pattern.swap(pat1); } TermMatchResult result; if (!idxTermMatch(ET_WILD, string(), pattern, result, max, - unsplitFilenameFieldName)) - return false; + unsplitFilenameFieldName)) + return false; for (vector::const_iterator it = result.entries.begin(); - it != result.entries.end(); it++) - names.push_back(it->term); + it != result.entries.end(); it++) + names.push_back(it->term); if (names.empty()) { - // Build an impossible query: we know its impossible because we - // control the prefixes! - names.push_back(wrap_prefix("XNONE") + "NoMatchingTerms"); + // Build an impossible query: we know its impossible because we + // control the prefixes! + names.push_back(wrap_prefix("XNONE") + "NoMatchingTerms"); } return true; } @@ -77,16 +79,16 @@ bool Db::filenameWildExp(const string& fnexp, vector& names, int max) // Walk the Y terms and return min/max bool Db::maxYearSpan(int *minyear, int *maxyear) { - LOGDEB(("Rcl::Db:maxYearSpan\n")); + LOGDEB("Rcl::Db:maxYearSpan\n" ); *minyear = 1000000; *maxyear = -1000000; TermMatchResult result; if (!idxTermMatch(ET_WILD, string(), "*", result, -1, "xapyear")) { - LOGINFO(("Rcl::Db:maxYearSpan: termMatch failed\n")); - return false; + LOGINFO("Rcl::Db:maxYearSpan: termMatch failed\n" ); + return false; } for (vector::const_iterator it = result.entries.begin(); - it != result.entries.end(); it++) { + it != result.entries.end(); it++) { if (!it->term.empty()) { int year = atoi(strip_prefix(it->term).c_str()); if (year < *minyear) @@ -102,11 +104,11 @@ bool Db::getAllDbMimeTypes(std::vector& exp) { Rcl::TermMatchResult res; if (!idxTermMatch(Rcl::Db::ET_WILD, "", "*", res, -1, "mtype")) { - return false; + return false; } for (vector::const_iterator rit = res.entries.begin(); - rit != res.entries.end(); rit++) { - exp.push_back(Rcl::strip_prefix(rit->term)); + rit != res.entries.end(); rit++) { + exp.push_back(Rcl::strip_prefix(rit->term)); } return true; } @@ -114,34 +116,22 @@ bool Db::getAllDbMimeTypes(std::vector& exp) class TermMatchCmpByWcf { public: int operator()(const TermMatchEntry& l, const TermMatchEntry& r) { - return r.wcf - l.wcf < 0; + return r.wcf - l.wcf < 0; } }; class TermMatchCmpByTerm { public: int operator()(const TermMatchEntry& l, const TermMatchEntry& r) { - return l.term.compare(r.term) > 0; + return l.term.compare(r.term) > 0; } }; class TermMatchTermEqual { public: int operator()(const TermMatchEntry& l, const TermMatchEntry& r) { - return !l.term.compare(r.term); + return !l.term.compare(r.term); } }; -/** Add prefix to all strings in list. - * @param prefix already wrapped prefix - */ -static void addPrefix(vector& terms, const string& prefix) -{ - if (prefix.empty()) - return; - for (vector::iterator it = terms.begin(); - it != terms.end(); it++) - it->term.insert(0, prefix); -} - static const char *tmtptostr(int typ) { switch (typ) { @@ -164,21 +154,18 @@ static const char *tmtptostr(int typ) // using the main index terms (filtering, retrieving stats, expansion // in some cases). bool Db::termMatch(int typ_sens, const string &lang, const string &_term, - TermMatchResult& res, int max, const string& field) + TermMatchResult& res, int max, const string& field, + vector* multiwords) { int matchtyp = matchTypeTp(typ_sens); if (!m_ndb || !m_ndb->m_isopen) - return false; + return false; Xapian::Database xrdb = m_ndb->xrdb; bool diac_sensitive = (typ_sens & ET_DIACSENS) != 0; bool case_sensitive = (typ_sens & ET_CASESENS) != 0; - LOGDEB0(("Db::TermMatch: typ %s diacsens %d casesens %d lang [%s] term [%s]" - " max %d field [%s] stripped %d init res.size %u\n", - tmtptostr(matchtyp), diac_sensitive, case_sensitive, lang.c_str(), - _term.c_str(), max, field.c_str(), o_index_stripchars, - res.entries.size())); + LOGDEB0("Db::TermMatch: typ " << (tmtptostr(matchtyp)) << " diacsens " << (diac_sensitive) << " casesens " << (case_sensitive) << " lang [" << (lang) << "] term [" << (_term) << "] max " << (max) << " field [" << (field) << "] stripped " << (o_index_stripchars) << " init res.size " << (res.entries.size()) << "\n" ); // If index is stripped, no case or diac expansion can be needed: // for the processing inside this routine, everything looks like @@ -186,11 +173,11 @@ bool Db::termMatch(int typ_sens, const string &lang, const string &_term, // Also, convert input to lowercase and strip its accents. string term = _term; if (o_index_stripchars) { - diac_sensitive = case_sensitive = true; - if (!unacmaybefold(_term, term, "UTF-8", UNACOP_UNACFOLD)) { - LOGERR(("Db::termMatch: unac failed for [%s]\n", _term.c_str())); - return false; - } + diac_sensitive = case_sensitive = true; + if (!unacmaybefold(_term, term, "UTF-8", UNACOP_UNACFOLD)) { + LOGERR("Db::termMatch: unac failed for [" << (_term) << "]\n" ); + return false; + } } // The case/diac expansion db @@ -198,111 +185,146 @@ bool Db::termMatch(int typ_sens, const string &lang, const string &_term, XapComputableSynFamMember synac(xrdb, synFamDiCa, "all", &unacfoldtrans); if (matchtyp == ET_WILD || matchtyp == ET_REGEXP) { - RefCntr matcher; - if (matchtyp == ET_WILD) { - matcher = RefCntr(new StrWildMatcher(term)); - } else { - matcher = RefCntr(new StrRegexpMatcher(term)); - } - if (!diac_sensitive || !case_sensitive) { - // Perform case/diac expansion on the exp as appropriate and - // expand the result. - vector exp; - if (diac_sensitive) { - // Expand for diacritics and case, filtering for same diacritics - SynTermTransUnac foldtrans(UNACOP_FOLD); - synac.synKeyExpand(matcher.getptr(), exp, &foldtrans); - } else if (case_sensitive) { - // Expand for diacritics and case, filtering for same case - SynTermTransUnac unactrans(UNACOP_UNAC); - synac.synKeyExpand(matcher.getptr(), exp, &unactrans); - } else { - // Expand for diacritics and case, no filtering - synac.synKeyExpand(matcher.getptr(), exp); - } - // Retrieve additional info and filter against the index itself - for (vector::const_iterator it = exp.begin(); - it != exp.end(); it++) { - idxTermMatch(ET_NONE, "", *it, res, max, field); - } - // And also expand the original expression against the - // main index: for the common case where the expression - // had no case/diac expansion (no entry in the exp db if - // the original term is lowercase and without accents). - idxTermMatch(typ_sens, lang, term, res, max, field); - } else { - idxTermMatch(typ_sens, lang, term, res, max, field); - } + std::shared_ptr matcher; + if (matchtyp == ET_WILD) { + matcher = std::shared_ptr(new StrWildMatcher(term)); + } else { + matcher = std::shared_ptr(new StrRegexpMatcher(term)); + } + if (!diac_sensitive || !case_sensitive) { + // Perform case/diac expansion on the exp as appropriate and + // expand the result. + vector exp; + if (diac_sensitive) { + // Expand for diacritics and case, filtering for same diacritics + SynTermTransUnac foldtrans(UNACOP_FOLD); + synac.synKeyExpand(matcher.get(), exp, &foldtrans); + } else if (case_sensitive) { + // Expand for diacritics and case, filtering for same case + SynTermTransUnac unactrans(UNACOP_UNAC); + synac.synKeyExpand(matcher.get(), exp, &unactrans); + } else { + // Expand for diacritics and case, no filtering + synac.synKeyExpand(matcher.get(), exp); + } + // Retrieve additional info and filter against the index itself + for (vector::const_iterator it = exp.begin(); + it != exp.end(); it++) { + idxTermMatch(ET_NONE, "", *it, res, max, field); + } + // And also expand the original expression against the + // main index: for the common case where the expression + // had no case/diac expansion (no entry in the exp db if + // the original term is lowercase and without accents). + idxTermMatch(typ_sens, lang, term, res, max, field); + } else { + idxTermMatch(typ_sens, lang, term, res, max, field); + } } else { - // Expansion is STEM or NONE (which may still need case/diac exp) + // Expansion is STEM or NONE (which may still need synonyms + // and case/diac exp) - vector lexp; - if (diac_sensitive && case_sensitive) { - // No case/diac expansion - lexp.push_back(term); - } else if (diac_sensitive) { - // Expand for accents and case, filtering for same accents, - SynTermTransUnac foldtrans(UNACOP_FOLD); - synac.synExpand(term, lexp, &foldtrans); - } else if (case_sensitive) { - // Expand for accents and case, filtering for same case - SynTermTransUnac unactrans(UNACOP_UNAC); - synac.synExpand(term, lexp, &unactrans); - } else { - // We are neither accent- nor case- sensitive and may need stem - // expansion or not. Expand for accents and case - synac.synExpand(term, lexp); - } + vector lexp; + if (diac_sensitive && case_sensitive) { + // No case/diac expansion + lexp.push_back(term); + } else if (diac_sensitive) { + // Expand for accents and case, filtering for same accents, + SynTermTransUnac foldtrans(UNACOP_FOLD); + synac.synExpand(term, lexp, &foldtrans); + } else if (case_sensitive) { + // Expand for accents and case, filtering for same case + SynTermTransUnac unactrans(UNACOP_UNAC); + synac.synExpand(term, lexp, &unactrans); + } else { + // We are neither accent- nor case- sensitive and may need stem + // expansion or not. Expand for accents and case + synac.synExpand(term, lexp); + } - if (matchTypeTp(typ_sens) == ET_STEM) { - // Need stem expansion. Lowercase the result of accent and case - // expansion for input to stemdb. - for (unsigned int i = 0; i < lexp.size(); i++) { - string lower; - unacmaybefold(lexp[i], lower, "UTF-8", UNACOP_FOLD); - lexp[i] = lower; - } - sort(lexp.begin(), lexp.end()); - lexp.erase(unique(lexp.begin(), lexp.end()), lexp.end()); - StemDb sdb(xrdb); - vector exp1; - for (vector::const_iterator it = lexp.begin(); - it != lexp.end(); it++) { - sdb.stemExpand(lang, *it, exp1); - } - LOGDEB(("ExpTerm: stem exp-> %s\n", stringsToString(exp1).c_str())); + if (matchtyp == ET_STEM || (typ_sens & ET_SYNEXP)) { + // Note: if any of the above conds is true, we are insensitive to + // diacs and case (enforced in searchdatatox:termexpand + // Need stem expansion. Lowercase the result of accent and case + // expansion for input to stemdb. + for (unsigned int i = 0; i < lexp.size(); i++) { + string lower; + unacmaybefold(lexp[i], lower, "UTF-8", UNACOP_FOLD); + lexp[i] = lower; + } + sort(lexp.begin(), lexp.end()); + lexp.erase(unique(lexp.begin(), lexp.end()), lexp.end()); - // Expand the resulting list for case (all stemdb content - // is lowercase) - lexp.clear(); - for (vector::const_iterator it = exp1.begin(); - it != exp1.end(); it++) { - synac.synExpand(*it, lexp); - } - sort(lexp.begin(), lexp.end()); - lexp.erase(unique(lexp.begin(), lexp.end()), lexp.end()); - } + if (matchtyp == ET_STEM) { + StemDb sdb(xrdb); + vector exp1; + for (vector::const_iterator it = lexp.begin(); + it != lexp.end(); it++) { + sdb.stemExpand(lang, *it, exp1); + } + exp1.swap(lexp); + sort(lexp.begin(), lexp.end()); + lexp.erase(unique(lexp.begin(), lexp.end()), lexp.end()); + LOGDEB("ExpTerm: stemexp: " << (stringsToString(lexp)) << "\n" ); + } - // Filter the result and get the stats, possibly add prefixes. - LOGDEB(("ExpandTerm:TM: lexp: %s\n", stringsToString(lexp).c_str())); - for (vector::const_iterator it = lexp.begin(); - it != lexp.end(); it++) { - idxTermMatch(Rcl::Db::ET_WILD, "", *it, res, max, field); - } + if (m_syngroups.ok() && (typ_sens & ET_SYNEXP)) { + LOGDEB("ExpTerm: got syngroups\n" ); + vector exp1(lexp); + for (vector::const_iterator it = lexp.begin(); + it != lexp.end(); it++) { + vector sg = m_syngroups.getgroup(*it); + if (!sg.empty()) { + LOGDEB("ExpTerm: syns: " << *it << " -> " << (stringsToString(sg)) << "\n" ); + for (vector::const_iterator it1 = sg.begin(); + it1 != sg.end(); it1++) { + if (it1->find_first_of(" ") != string::npos) { + if (multiwords) { + multiwords->push_back(*it1); + } + } else { + exp1.push_back(*it1); + } + } + } + } + lexp.swap(exp1); + sort(lexp.begin(), lexp.end()); + lexp.erase(unique(lexp.begin(), lexp.end()), lexp.end()); + } + + // Expand the resulting list for case (all stemdb content + // is lowercase) + vector exp1; + for (vector::const_iterator it = lexp.begin(); + it != lexp.end(); it++) { + synac.synExpand(*it, exp1); + } + exp1.swap(lexp); + sort(lexp.begin(), lexp.end()); + lexp.erase(unique(lexp.begin(), lexp.end()), lexp.end()); + } + + // Filter the result and get the stats, possibly add prefixes. + LOGDEB("ExpandTerm:TM: lexp: " << (stringsToString(lexp)) << "\n" ); + for (vector::const_iterator it = lexp.begin(); + it != lexp.end(); it++) { + idxTermMatch(Rcl::Db::ET_WILD, "", *it, res, max, field); + } } TermMatchCmpByTerm tcmp; sort(res.entries.begin(), res.entries.end(), tcmp); TermMatchTermEqual teq; vector::iterator uit = - unique(res.entries.begin(), res.entries.end(), teq); + unique(res.entries.begin(), res.entries.end(), teq); res.entries.resize(uit - res.entries.begin()); TermMatchCmpByWcf wcmp; sort(res.entries.begin(), res.entries.end(), wcmp); if (max > 0) { - // Would need a small max and big stem expansion... - res.entries.resize(MIN(res.entries.size(), (unsigned int)max)); + // Would need a small max and big stem expansion... + res.entries.resize(MIN(res.entries.size(), (unsigned int)max)); } return true; } @@ -310,114 +332,111 @@ bool Db::termMatch(int typ_sens, const string &lang, const string &_term, // Second phase of wildcard/regexp term expansion after case/diac // expansion: expand against main index terms bool Db::idxTermMatch(int typ_sens, const string &lang, const string &root, - TermMatchResult& res, int max, const string& field) + TermMatchResult& res, int max, const string& field) { int typ = matchTypeTp(typ_sens); - LOGDEB1(("Db::idxTermMatch: typ %s lang [%s] term [%s] " - "max %d field [%s] init res.size %u\n", - tmtptostr(typ), lang.c_str(), root.c_str(), - max, field.c_str(), res.entries.size())); + LOGDEB1("Db::idxTermMatch: typ " << (tmtptostr(typ)) << " lang [" << (lang) << "] term [" << (root) << "] max " << (max) << " field [" << (field) << "] init res.size " << (res.entries.size()) << "\n" ); if (typ == ET_STEM) { - LOGFATAL(("RCLDB: internal error: idxTermMatch called with ET_STEM\n")); - abort(); + LOGFATAL("RCLDB: internal error: idxTermMatch called with ET_STEM\n" ); + abort(); } Xapian::Database xdb = m_ndb->xrdb; string prefix; if (!field.empty()) { - const FieldTraits *ftp = 0; - if (!fieldToTraits(field, &ftp, true) || ftp->pfx.empty()) { - LOGDEB(("Db::termMatch: field is not indexed (no prefix): [%s]\n", - field.c_str())); + const FieldTraits *ftp = 0; + if (!fieldToTraits(field, &ftp, true) || ftp->pfx.empty()) { + LOGDEB("Db::termMatch: field is not indexed (no prefix): [" << (field) << "]\n" ); } else { - prefix = wrap_prefix(ftp->pfx); - } + prefix = wrap_prefix(ftp->pfx); + } } res.prefix = prefix; - RefCntr matcher; + std::shared_ptr matcher; if (typ == ET_REGEXP) { - matcher = RefCntr(new StrRegexpMatcher(root)); - if (!matcher->ok()) { - LOGERR(("termMatch: regcomp failed: %s\n", - matcher->getreason().c_str())) - return false; - } + matcher = std::shared_ptr(new StrRegexpMatcher(root)); + if (!matcher->ok()) { + LOGERR("termMatch: regcomp failed: " << (matcher->getreason())); + return false; + } } else if (typ == ET_WILD) { - matcher = RefCntr(new StrWildMatcher(root)); + matcher = std::shared_ptr(new StrWildMatcher(root)); } // Find the initial section before any special char string::size_type es = string::npos; - if (matcher.isNotNull()) { - es = matcher->baseprefixlen(); + if (matcher) { + es = matcher->baseprefixlen(); } // Initial section: the part of the prefix+expr before the // first wildcard character. We only scan the part of the // index where this matches string is; - switch (es) { - case string::npos: is = prefix + root; break; - case 0: is = prefix; break; - default: is = prefix + root.substr(0, es); break; + if (es == string::npos) { + is = prefix + root; + } else if (es == 0) { + is = prefix; + } else { + is = prefix + root.substr(0, es); } - LOGDEB2(("termMatch: initsec: [%s]\n", is.c_str())); + LOGDEB2("termMatch: initsec: [" << (is) << "]\n" ); for (int tries = 0; tries < 2; tries++) { - try { - Xapian::TermIterator it = xdb.allterms_begin(); - if (!is.empty()) - it.skip_to(is.c_str()); - for (int rcnt = 0; it != xdb.allterms_end(); it++) { - // If we're beyond the terms matching the initial - // section, end - if (!is.empty() && (*it).find(is) != 0) - break; + try { + Xapian::TermIterator it = xdb.allterms_begin(); + if (!is.empty()) + it.skip_to(is.c_str()); + for (int rcnt = 0; it != xdb.allterms_end(); it++) { + // If we're beyond the terms matching the initial + // section, end + if (!is.empty() && (*it).find(is) != 0) + break; - // Else try to match the term. The matcher content - // is without prefix, so we remove this if any. We - // just checked that the index term did begin with - // the prefix. - string term; - if (!prefix.empty()) { - term = (*it).substr(prefix.length()); - } else { - if (has_prefix(*it)) { - continue; - } - term = *it; - } + // Else try to match the term. The matcher content + // is without prefix, so we remove this if any. We + // just checked that the index term did begin with + // the prefix. + string term; + if (!prefix.empty()) { + term = (*it).substr(prefix.length()); + } else { + if (has_prefix(*it)) { + continue; + } + term = *it; + } - if (matcher.isNotNull() && !matcher->match(term)) - continue; + if (matcher && !matcher->match(term)) + continue; - res.entries.push_back( - TermMatchEntry(*it, xdb.get_collection_freq(*it), - it.get_termfreq())); + res.entries.push_back( + TermMatchEntry(*it, xdb.get_collection_freq(*it), + it.get_termfreq())); - // The problem with truncating here is that this is done - // alphabetically and we may not keep the most frequent - // terms. OTOH, not doing it may stall the program if - // we are walking the whole term list. We compromise - // by cutting at 2*max - if (max > 0 && ++rcnt >= 2*max) - break; - } - m_reason.erase(); - break; - } catch (const Xapian::DatabaseModifiedError &e) { - m_reason = e.get_msg(); - xdb.reopen(); - continue; - } XCATCHERROR(m_reason); - break; + // The problem with truncating here is that this is done + // alphabetically and we may not keep the most frequent + // terms. OTOH, not doing it may stall the program if + // we are walking the whole term list. We compromise + // by cutting at 2*max + if (max > 0 && ++rcnt >= 2*max) + break; + } + m_reason.erase(); + break; + } catch (const Xapian::DatabaseModifiedError &e) { + m_reason = e.get_msg(); + xdb.reopen(); + continue; + } XCATCHERROR(m_reason); + break; } if (!m_reason.empty()) { - LOGERR(("termMatch: %s\n", m_reason.c_str())); - return false; + LOGERR("termMatch: " << (m_reason) << "\n" ); + return false; } return true; @@ -432,62 +451,61 @@ public: TermIter *Db::termWalkOpen() { if (!m_ndb || !m_ndb->m_isopen) - return 0; + return 0; TermIter *tit = new TermIter; if (tit) { - tit->db = m_ndb->xrdb; + tit->db = m_ndb->xrdb; XAPTRY(tit->it = tit->db.allterms_begin(), tit->db, m_reason); - if (!m_reason.empty()) { - LOGERR(("Db::termWalkOpen: xapian error: %s\n", m_reason.c_str())); - return 0; - } + if (!m_reason.empty()) { + LOGERR("Db::termWalkOpen: xapian error: " << (m_reason) << "\n" ); + return 0; + } } return tit; } bool Db::termWalkNext(TermIter *tit, string &term) { XAPTRY( - if (tit && tit->it != tit->db.allterms_end()) { - term = *(tit->it)++; - return true; - } + if (tit && tit->it != tit->db.allterms_end()) { + term = *(tit->it)++; + return true; + } , tit->db, m_reason); if (!m_reason.empty()) { - LOGERR(("Db::termWalkOpen: xapian error: %s\n", m_reason.c_str())); + LOGERR("Db::termWalkOpen: xapian error: " << (m_reason) << "\n" ); } return false; } void Db::termWalkClose(TermIter *tit) { try { - delete tit; + delete tit; } catch (...) {} } bool Db::termExists(const string& word) { if (!m_ndb || !m_ndb->m_isopen) - return 0; + return 0; XAPTRY(if (!m_ndb->xrdb.term_exists(word)) return false, m_ndb->xrdb, m_reason); if (!m_reason.empty()) { - LOGERR(("Db::termWalkOpen: xapian error: %s\n", m_reason.c_str())); - return false; + LOGERR("Db::termWalkOpen: xapian error: " << (m_reason) << "\n" ); + return false; } return true; } bool Db::stemDiffers(const string& lang, const string& word, - const string& base) + const string& base) { Xapian::Stem stemmer(lang); if (!stemmer(word).compare(stemmer(base))) { - LOGDEB2(("Rcl::Db::stemDiffers: same for %s and %s\n", - word.c_str(), base.c_str())); - return false; + LOGDEB2("Rcl::Db::stemDiffers: same for " << (word) << " and " << (base) << "\n" ); + return false; } return true; } diff --git a/src/rcldb/searchdata.cpp b/src/rcldb/searchdata.cpp index 056d200f..8520d925 100644 --- a/src/rcldb/searchdata.cpp +++ b/src/rcldb/searchdata.cpp @@ -34,7 +34,7 @@ using namespace std; #include "rcldb.h" #include "rcldb_p.h" #include "searchdata.h" -#include "debuglog.h" +#include "log.h" #include "smallut.h" #include "textsplit.h" #include "unacpp.h" @@ -68,7 +68,7 @@ void SearchData::commoninit() SearchData::~SearchData() { - LOGDEB0(("SearchData::~SearchData\n")); + LOGDEB0("SearchData::~SearchData\n" ); for (qlist_it_t it = m_query.begin(); it != m_query.end(); it++) delete *it; } @@ -79,44 +79,44 @@ SearchData::~SearchData() // We remove very common terms from the query to avoid performance issues. bool SearchData::maybeAddAutoPhrase(Rcl::Db& db, double freqThreshold) { - LOGDEB0(("SearchData::maybeAddAutoPhrase()\n")); + LOGDEB0("SearchData::maybeAddAutoPhrase()\n" ); // cerr << "BEFORE SIMPLIFY\n"; dump(cerr); simplify(); // cerr << "AFTER SIMPLIFY\n"; dump(cerr); if (!m_query.size()) { - LOGDEB2(("SearchData::maybeAddAutoPhrase: empty query\n")); + LOGDEB2("SearchData::maybeAddAutoPhrase: empty query\n" ); return false; } string field; vector words; - // Walk the clause list. If we find any non simple clause or different - // field names, bail out. + // Walk the clause list. If this is not an AND list, we find any + // non simple clause or different field names, bail out. for (qlist_it_t it = m_query.begin(); it != m_query.end(); it++) { SClType tp = (*it)->m_tp; - if (tp != SCLT_AND && tp != SCLT_OR) { - LOGDEB2(("SearchData::maybeAddAutoPhrase: wrong tp %d\n", tp)); + if (tp != SCLT_AND) { + LOGDEB2("SearchData::maybeAddAutoPhrase: wrong tp " << (tp) << "\n" ); return false; } SearchDataClauseSimple *clp = dynamic_cast(*it); if (clp == 0) { - LOGDEB2(("SearchData::maybeAddAutoPhrase: dyncast failed\n")); + LOGDEB2("SearchData::maybeAddAutoPhrase: dyncast failed\n" ); return false; } if (it == m_query.begin()) { field = clp->getfield(); } else { if (clp->getfield().compare(field)) { - LOGDEB2(("SearchData::maybeAddAutoPhrase: diff. fields\n")); + LOGDEB2("SearchData::maybeAddAutoPhrase: diff. fields\n" ); return false; } } // If there are wildcards or quotes in there, bail out if (clp->gettext().find_first_of("\"*[?") != string::npos) { - LOGDEB2(("SearchData::maybeAddAutoPhrase: wildcards\n")); + LOGDEB2("SearchData::maybeAddAutoPhrase: wildcards\n" ); return false; } @@ -145,8 +145,8 @@ bool SearchData::maybeAddAutoPhrase(Rcl::Db& db, double freqThreshold) swords.append(1, ' '); swords += *it; } else { - LOGDEB0(("SearchData::Autophrase: [%s] too frequent (%.2f %%)\n", - it->c_str(), 100 * freq)); + LOGDEB0("SearchData::Autophrase: [" << *it << "] too frequent (" + << (100 * freq) << " %" << ")\n" ); slack++; } } @@ -154,7 +154,7 @@ bool SearchData::maybeAddAutoPhrase(Rcl::Db& db, double freqThreshold) // We can't make a phrase with a single word :) int nwords = TextSplit::countWords(swords); if (nwords <= 1) { - LOGDEB2(("SearchData::maybeAddAutoPhrase: ended with 1 word\n")); + LOGDEB2("SearchData::maybeAddAutoPhrase: ended with 1 word\n" ); return false; } @@ -162,7 +162,7 @@ bool SearchData::maybeAddAutoPhrase(Rcl::Db& db, double freqThreshold) // an actual user-entered phrase slack += 1 + nwords / 3; - m_autophrase = RefCntr( + m_autophrase = std::shared_ptr( new SearchDataClauseDist(SCLT_PHRASE, swords, slack, field)); return true; } @@ -171,7 +171,7 @@ bool SearchData::maybeAddAutoPhrase(Rcl::Db& db, double freqThreshold) bool SearchData::addClause(SearchDataClause* cl) { if (m_tp == SCLT_OR && cl->getexclude()) { - LOGERR(("SearchData::addClause: cant add EXCL to OR list\n")); + LOGERR("SearchData::addClause: cant add EXCL to OR list\n" ); m_reason = "No Negative (AND_NOT) clauses allowed in OR queries"; return false; } @@ -181,7 +181,8 @@ bool SearchData::addClause(SearchDataClause* cl) return true; } -// Am I a file name only search ? This is to turn off term highlighting +// Am I a file name only search ? This is to turn off term highlighting. +// There can't be a subclause in a filename search: no possible need to recurse bool SearchData::fileNameOnly() { for (qlist_it_t it = m_query.begin(); it != m_query.end(); it++) @@ -190,6 +191,7 @@ bool SearchData::fileNameOnly() return true; } +// The query language creates a lot of subqueries. See if we can merge them. void SearchData::simplify() { for (unsigned int i = 0; i < m_query.size(); i++) { @@ -208,14 +210,35 @@ void SearchData::simplify() clsubp->getSub()->simplify(); // If this subquery has special attributes, it's not a - // candidate for collapsing + // candidate for collapsing, except if it has no clauses, because + // then, we just pick the attributes. if (!clsubp->getSub()->m_filetypes.empty() || !clsubp->getSub()->m_nfiletypes.empty() || clsubp->getSub()->m_haveDates || clsubp->getSub()->m_maxSize != size_t(-1) || clsubp->getSub()->m_minSize != size_t(-1) || - clsubp->getSub()->m_haveWildCards) - continue; + clsubp->getSub()->m_haveWildCards) { + if (!clsubp->getSub()->m_query.empty()) + continue; + m_filetypes.insert(m_filetypes.end(), + clsubp->getSub()->m_filetypes.begin(), + clsubp->getSub()->m_filetypes.end()); + m_nfiletypes.insert(m_nfiletypes.end(), + clsubp->getSub()->m_nfiletypes.begin(), + clsubp->getSub()->m_nfiletypes.end()); + if (clsubp->getSub()->m_haveDates && !m_haveDates) { + m_dates = clsubp->getSub()->m_dates; + } + if (m_maxSize == size_t(-1)) + m_maxSize = clsubp->getSub()->m_maxSize; + if (m_minSize == size_t(-1)) + m_minSize = clsubp->getSub()->m_minSize; + m_haveWildCards = m_haveWildCards || + clsubp->getSub()->m_haveWildCards; + // And then let the clauses processing go on, there are + // none anyway, we will just delete the subquery. + } + bool allsametp = true; for (qlist_it_t it1 = clsubp->getSub()->m_query.begin(); @@ -240,7 +263,7 @@ void SearchData::simplify() j < i + clsubp->getSub()->m_query.size(); j++) { m_query[j]->setParent(this); } - i += clsubp->getSub()->m_query.size() - 1; + i += int(clsubp->getSub()->m_query.size()) - 1; // We don't want the clauses to be deleted when the parent is, as we // know own them. @@ -249,39 +272,48 @@ void SearchData::simplify() } } -bool SearchData::singleSimple() -{ - if (m_query.size() != 1 || !m_filetypes.empty() || !m_nfiletypes.empty() || - m_haveDates || m_maxSize != size_t(-1) || m_minSize != size_t(-1) || - m_haveWildCards) - return false; - SearchDataClause *clp = *m_query.begin(); - if (clp->getTp() != SCLT_AND && clp->getTp() != SCLT_OR) { - return false; - } - return true; -} - -// Extract all term data +// Extract terms and groups for highlighting void SearchData::getTerms(HighlightData &hld) const { - for (qlist_cit_t it = m_query.begin(); it != m_query.end(); it++) - (*it)->getTerms(hld); + for (qlist_cit_t it = m_query.begin(); it != m_query.end(); it++) { + if (!((*it)->getmodifiers() & SearchDataClause::SDCM_NOTERMS) && + !(*it)->getexclude()) { + (*it)->getTerms(hld); + } + } return; } +static const char * tpToString(SClType t) +{ + switch (t) { + case SCLT_AND: return "AND"; + case SCLT_OR: return "OR"; + case SCLT_FILENAME: return "FILENAME"; + case SCLT_PHRASE: return "PHRASE"; + case SCLT_NEAR: return "NEAR"; + case SCLT_PATH: return "PATH"; + case SCLT_SUB: return "SUB"; + default: return "UNKNOWN"; + } +} + +static string dumptabs; + void SearchData::dump(ostream& o) const { - o << "SearchData: " << " qs " << int(m_query.size()) << + o << dumptabs << + "SearchData: " << tpToString(m_tp) << " qs " << int(m_query.size()) << " ft " << m_filetypes.size() << " nft " << m_nfiletypes.size() << " hd " << m_haveDates << " maxs " << int(m_maxSize) << " mins " << int(m_minSize) << " wc " << m_haveWildCards << "\n"; for (std::vector::const_iterator it = m_query.begin(); it != m_query.end(); it++) { + o << dumptabs; (*it)->dump(o); o << "\n"; } - o << "\n"; +// o << dumptabs << "\n"; } void SearchDataClause::dump(ostream& o) const @@ -291,7 +323,7 @@ void SearchDataClause::dump(ostream& o) const void SearchDataClauseSimple::dump(ostream& o) const { - o << "ClauseSimple: "; + o << "ClauseSimple: " << tpToString(m_tp) << " "; if (m_exclude) o << "- "; o << "[" ; @@ -319,9 +351,9 @@ void SearchDataClausePath::dump(ostream& o) const void SearchDataClauseDist::dump(ostream& o) const { if (m_tp == SCLT_NEAR) - o << "ClauseDist: NEAR: "; + o << "ClauseDist: NEAR "; else - o << "ClauseDist: PHRA: "; + o << "ClauseDist: PHRA "; if (m_exclude) o << " - "; @@ -334,8 +366,11 @@ void SearchDataClauseDist::dump(ostream& o) const void SearchDataClauseSub::dump(ostream& o) const { o << "ClauseSub {\n"; - m_sub.getconstptr()->dump(o); - o << "}"; + dumptabs += '\t'; + m_sub->dump(o); + dumptabs.erase(dumptabs.size()- 1); + o << dumptabs << "}"; } } // Namespace Rcl + diff --git a/src/rcldb/searchdata.h b/src/rcldb/searchdata.h index 4cdc3758..cbabdb44 100644 --- a/src/rcldb/searchdata.h +++ b/src/rcldb/searchdata.h @@ -29,7 +29,7 @@ #include #include "rcldb.h" -#include "refcntr.h" +#include #include "smallut.h" #include "cstr.h" #include "hldata.h" @@ -96,9 +96,6 @@ public: /** Is there anything but a file name search in here ? */ bool fileNameOnly(); - /** Are we a simple query with one clause? */ - bool singleSimple(); - /** Do we have wildcards anywhere apart from filename searches ? */ bool haveWildCards() {return m_haveWildCards;} @@ -153,6 +150,10 @@ public: m_tp = tp; } + SClType getTp() { + return m_tp; + } + void setMaxExpand(int max) { m_softmaxexpand = max; @@ -177,7 +178,7 @@ private: std::vector m_nfiletypes; // Autophrase if set. Can't be part of the normal chain because // it uses OP_AND_MAYBE - RefCntr m_autophrase; + std::shared_ptr m_autophrase; // Special stuff produced by input which looks like a clause but means // something else (date and size specs) @@ -227,8 +228,11 @@ private: class SearchDataClause { public: - enum Modifier {SDCM_NONE=0, SDCM_NOSTEMMING=1, SDCM_ANCHORSTART=2, - SDCM_ANCHOREND=4, SDCM_CASESENS=8, SDCM_DIACSENS=16}; + enum Modifier {SDCM_NONE=0, SDCM_NOSTEMMING=0x1, SDCM_ANCHORSTART=0x2, + SDCM_ANCHOREND=0x4, SDCM_CASESENS=0x8, SDCM_DIACSENS=0x10, + SDCM_NOTERMS=0x20, // Don't include terms for highlighting + SDCM_NOSYNS = 0x40, // Don't perform synonym expansion + }; enum Relation {REL_CONTAINS, REL_EQUALS, REL_LT, REL_LTE, REL_GT, REL_GTE}; SearchDataClause(SClType tp) @@ -270,7 +274,7 @@ public: { return m_parentSearch ? m_parentSearch->getMaxExp() : 10000; } - int getMaxCl() + size_t getMaxCl() { return m_parentSearch ? m_parentSearch->getMaxCl() : 100000; } @@ -278,13 +282,12 @@ public: { return m_parentSearch ? m_parentSearch->getSoftMaxExp() : -1; } - virtual void setModifiers(Modifier mod) - { - m_modifiers = mod; - } virtual void addModifier(Modifier mod) { - m_modifiers = Modifier(m_modifiers | mod); + m_modifiers = m_modifiers | mod; + } + virtual unsigned int getmodifiers() { + return m_modifiers; } virtual void setWeight(float w) { @@ -312,7 +315,7 @@ protected: SClType m_tp; SearchData *m_parentSearch; bool m_haveWildCards; - Modifier m_modifiers; + unsigned int m_modifiers; float m_weight; bool m_exclude; Relation m_rel; @@ -377,14 +380,15 @@ protected: std::string m_field; // Field specification if any HighlightData m_hldata; // Current count of Xapian clauses, to check against expansion limit - int m_curcl; + size_t m_curcl; bool processUserString(Rcl::Db &db, const string &iq, std::string &ermsg, void* pq, int slack = 0, bool useNear = false); bool expandTerm(Rcl::Db &db, std::string& ermsg, int mods, const std::string& term, std::vector& exp, - std::string& sterm, const std::string& prefix); + std::string& sterm, const std::string& prefix, + std::vector* multiwords = 0); // After splitting entry on whitespace: process non-phrase element void processSimpleSpan(Rcl::Db &db, string& ermsg, const string& span, int mods, void *pq); @@ -489,7 +493,7 @@ private: /** Subquery */ class SearchDataClauseSub : public SearchDataClause { public: - SearchDataClauseSub(RefCntr sub) + SearchDataClauseSub(std::shared_ptr sub) : SearchDataClause(SCLT_SUB), m_sub(sub) { } @@ -503,15 +507,15 @@ public: virtual void getTerms(HighlightData& hldata) const { - m_sub.getconstptr()->getTerms(hldata); + m_sub.get()->getTerms(hldata); } - virtual RefCntr getSub() { + virtual std::shared_ptr getSub() { return m_sub; } virtual void dump(ostream& o) const; protected: - RefCntr m_sub; + std::shared_ptr m_sub; }; } // Namespace Rcl diff --git a/src/rcldb/searchdatatox.cpp b/src/rcldb/searchdatatox.cpp index 0dff6c8b..40cfef2c 100644 --- a/src/rcldb/searchdatatox.cpp +++ b/src/rcldb/searchdatatox.cpp @@ -25,6 +25,7 @@ #include #include #include +#include using namespace std; #include "xapian.h" @@ -33,7 +34,7 @@ using namespace std; #include "rcldb.h" #include "rcldb_p.h" #include "searchdata.h" -#include "debuglog.h" +#include "log.h" #include "smallut.h" #include "textsplit.h" #include "unacpp.h" @@ -53,14 +54,15 @@ typedef vector::iterator qlist_it_t; static const int original_term_wqf_booster = 10; -// Expand categories and mime type wild card exps Categories are -// expanded against the configuration, mimetypes against the index -// (for wildcards). +// Expand doc categories and mime type wild card expressions +// +// Categories are expanded against the configuration, mimetypes +// against the index. bool SearchData::expandFileTypes(Db &db, vector& tps) { const RclConfig *cfg = db.getConf(); if (!cfg) { - LOGFATAL(("Db::expandFileTypes: null configuration!!\n")); + LOGFATAL("Db::expandFileTypes: null configuration!!\n" ); return false; } vector exptps; @@ -101,6 +103,8 @@ static const char *maxXapClauseCaseDiacMsg = "wildcards ?" ; + +// Walk the clauses list, translate each and add to top Xapian Query bool SearchData::clausesToQuery(Rcl::Db &db, SClType tp, vector& query, string& reason, void *d) @@ -109,13 +113,12 @@ bool SearchData::clausesToQuery(Rcl::Db &db, SClType tp, for (qlist_it_t it = query.begin(); it != query.end(); it++) { Xapian::Query nq; if (!(*it)->toNativeQuery(db, &nq)) { - LOGERR(("SearchData::clausesToQuery: toNativeQuery failed: %s\n", - (*it)->getReason().c_str())); + LOGERR("SearchData::clausesToQuery: toNativeQuery failed: " << ((*it)->getReason()) << "\n" ); reason += (*it)->getReason() + " "; return false; } if (nq.empty()) { - LOGDEB(("SearchData::clausesToQuery: skipping empty clause\n")); + LOGDEB("SearchData::clausesToQuery: skipping empty clause\n" ); continue; } // If this structure is an AND list, must use AND_NOT for excl clauses. @@ -140,7 +143,7 @@ bool SearchData::clausesToQuery(Rcl::Db &db, SClType tp, xq = Xapian::Query(op, xq, nq); } if (int(xq.get_length()) >= getMaxCl()) { - LOGERR(("%s\n", maxXapClauseMsg)); + LOGERR("" << (maxXapClauseMsg) << "\n" ); m_reason += maxXapClauseMsg; if (!o_index_stripchars) m_reason += maxXapClauseCaseDiacMsg; @@ -148,7 +151,7 @@ bool SearchData::clausesToQuery(Rcl::Db &db, SClType tp, } } - LOGDEB0(("SearchData::clausesToQuery: got %d clauses\n", xq.get_length())); + LOGDEB0("SearchData::clausesToQuery: got " << (xq.get_length()) << " clauses\n" ); if (xq.empty()) xq = Xapian::Query::MatchAll; @@ -159,18 +162,19 @@ bool SearchData::clausesToQuery(Rcl::Db &db, SClType tp, bool SearchData::toNativeQuery(Rcl::Db &db, void *d) { - LOGDEB(("SearchData::toNativeQuery: stemlang [%s]\n", m_stemlang.c_str())); + LOGDEB("SearchData::toNativeQuery: stemlang [" << (m_stemlang) << "]\n" ); m_reason.erase(); db.getConf()->getConfParam("maxTermExpand", &m_maxexp); db.getConf()->getConfParam("maxXapianClauses", &m_maxcl); + db.getConf()->getConfParam("autocasesens", &m_autocasesens); + db.getConf()->getConfParam("autodiacsens", &m_autodiacsens); // Walk the clause list translating each in turn and building the // Xapian query tree Xapian::Query xq; if (!clausesToQuery(db, m_tp, m_query, m_reason, &xq)) { - LOGERR(("SearchData::toNativeQuery: clausesToQuery failed. reason: %s\n", - m_reason.c_str())); + LOGERR("SearchData::toNativeQuery: clausesToQuery failed. reason: " << (m_reason) << "\n" ); return false; } @@ -179,7 +183,7 @@ bool SearchData::toNativeQuery(Rcl::Db &db, void *d) if (m_dates.y1 == 0 || m_dates.y2 == 0) { int minyear = 1970, maxyear = 2100; if (!db.maxYearSpan(&minyear, &maxyear)) { - LOGERR(("Can't retrieve index min/max dates\n")); + LOGERR("Can't retrieve index min/max dates\n" ); //whatever, go on. } @@ -194,18 +198,16 @@ bool SearchData::toNativeQuery(Rcl::Db &db, void *d) m_dates.d2 = 31; } } - LOGDEB(("Db::toNativeQuery: date interval: %d-%d-%d/%d-%d-%d\n", - m_dates.y1, m_dates.m1, m_dates.d1, - m_dates.y2, m_dates.m2, m_dates.d2)); + LOGDEB("Db::toNativeQuery: date interval: " << (m_dates.y1) << "-" << (m_dates.m1) << "-" << (m_dates.d1) << "/" << (m_dates.y2) << "-" << (m_dates.m2) << "-" << (m_dates.d2) << "\n" ); Xapian::Query dq = date_range_filter(m_dates.y1, m_dates.m1, m_dates.d1, m_dates.y2, m_dates.m2, m_dates.d2); if (dq.empty()) { - LOGINFO(("Db::toNativeQuery: date filter is empty\n")); + LOGINFO("Db::toNativeQuery: date filter is empty\n" ); } // If no probabilistic query is provided then promote the daterange // filter to be THE query instead of filtering an empty query. if (xq.empty()) { - LOGINFO(("Db::toNativeQuery: proba query is empty\n")); + LOGINFO("Db::toNativeQuery: proba query is empty\n" ); xq = dq; } else { xq = Xapian::Query(Xapian::Query::OP_FILTER, xq, dq); @@ -215,9 +217,8 @@ bool SearchData::toNativeQuery(Rcl::Db &db, void *d) if (m_minSize != size_t(-1) || m_maxSize != size_t(-1)) { Xapian::Query sq; - char min[50], max[50]; - sprintf(min, "%lld", (long long)m_minSize); - sprintf(max, "%lld", (long long)m_maxSize); + string min = lltodecstr(m_minSize); + string max = lltodecstr(m_maxSize); if (m_minSize == size_t(-1)) { string value(max); leftzeropad(value, 12); @@ -238,7 +239,7 @@ bool SearchData::toNativeQuery(Rcl::Db &db, void *d) // If no probabilistic query is provided then promote the // filter to be THE query instead of filtering an empty query. if (xq.empty()) { - LOGINFO(("Db::toNativeQuery: proba query is empty\n")); + LOGINFO("Db::toNativeQuery: proba query is empty\n" ); xq = sq; } else { xq = Xapian::Query(Xapian::Query::OP_FILTER, xq, sq); @@ -246,7 +247,7 @@ bool SearchData::toNativeQuery(Rcl::Db &db, void *d) } // Add the autophrase if any - if (m_autophrase.isNotNull()) { + if (m_autophrase) { Xapian::Query apq; if (m_autophrase->toNativeQuery(db, &apq)) { xq = xq.empty() ? apq : @@ -262,7 +263,7 @@ bool SearchData::toNativeQuery(Rcl::Db &db, void *d) for (vector::iterator it = m_filetypes.begin(); it != m_filetypes.end(); it++) { string term = wrap_prefix(mimetype_prefix) + *it; - LOGDEB0(("Adding file type term: [%s]\n", term.c_str())); + LOGDEB0("Adding file type term: [" << (term) << "]\n" ); tq = tq.empty() ? Xapian::Query(term) : Xapian::Query(Xapian::Query::OP_OR, tq, Xapian::Query(term)); } @@ -277,7 +278,7 @@ bool SearchData::toNativeQuery(Rcl::Db &db, void *d) for (vector::iterator it = m_nfiletypes.begin(); it != m_nfiletypes.end(); it++) { string term = wrap_prefix(mimetype_prefix) + *it; - LOGDEB0(("Adding negative file type term: [%s]\n", term.c_str())); + LOGDEB0("Adding negative file type term: [" << (term) << "]\n" ); tq = tq.empty() ? Xapian::Query(term) : Xapian::Query(Xapian::Query::OP_OR, tq, Xapian::Query(term)); } @@ -332,8 +333,7 @@ public: if (m_lastpos < pos) m_lastpos = pos; bool noexpand = be ? m_ts->nostemexp() : true; - LOGDEB1(("TermProcQ::takeword: pushing [%s] pos %d noexp %d\n", - term.c_str(), pos, noexpand)); + LOGDEB1("TermProcQ::takeword: pushing [" << (term) << "] pos " << (pos) << " noexp " << (noexpand) << "\n" ); if (m_terms[pos].size() < term.size()) { m_terms[pos] = term; m_nste[pos] = noexpand; @@ -375,17 +375,6 @@ private: }; -#if 1 -static void listVector(const string& what, const vector&l) -{ - string a; - for (vector::const_iterator it = l.begin(); it != l.end(); it++) { - a = a + *it + " "; - } - LOGDEB0(("%s: %s\n", what.c_str(), a.c_str())); -} -#endif - /** Expand term into term list, using appropriate mode: stem, wildcards, * diacritics... * @@ -396,15 +385,18 @@ static void listVector(const string& what, const vector&l) * @param prefix field prefix in index. We could recompute it, but the caller * has it already. Used in the simple case where there is nothing to expand, * and we just return the prefixed term (else Db::termMatch deals with it). + * @param multiwords it may happen that synonym processing results in multi-word + * expansions which should be processed as phrases. */ bool SearchDataClauseSimple::expandTerm(Rcl::Db &db, string& ermsg, int mods, const string& term, vector& oexp, string &sterm, - const string& prefix) + const string& prefix, + vector* multiwords + ) { - LOGDEB0(("expandTerm: mods 0x%x fld [%s] trm [%s] lang [%s]\n", - mods, m_field.c_str(), term.c_str(), getStemLang().c_str())); + LOGDEB0("expandTerm: mods 0x" << (mods) << " fld [" << (m_field) << "] trm [" << (term) << "] lang [" << (getStemLang()) << "]\n" ); sterm.clear(); oexp.clear(); if (term.empty()) @@ -428,17 +420,16 @@ bool SearchDataClauseSimple::expandTerm(Rcl::Db &db, // No stem expansion if there are wildcards or if prevented by caller bool nostemexp = (mods & SDCM_NOSTEMMING) != 0; if (haswild || getStemLang().empty()) { - LOGDEB2(("expandTerm: found wildcards or stemlang empty: no exp\n")); + LOGDEB2("expandTerm: found wildcards or stemlang empty: no exp\n" ); nostemexp = true; } - // noexpansion can be modified further down by possible case/diac expansion - bool noexpansion = nostemexp && !haswild; - - int termmatchsens = 0; - bool diac_sensitive = (mods & SDCM_DIACSENS) != 0; bool case_sensitive = (mods & SDCM_CASESENS) != 0; + bool synonyms = (mods & SDCM_NOSYNS) == 0; + + // noexpansion can be modified further down by possible case/diac expansion + bool noexpansion = nostemexp && !haswild && !synonyms; if (o_index_stripchars) { diac_sensitive = case_sensitive = false; @@ -451,7 +442,7 @@ bool SearchDataClauseSimple::expandTerm(Rcl::Db &db, // performed (conversion+comparison) will automatically ignore // accented characters which are actually a separate letter if (getAutoDiac() && unachasaccents(term)) { - LOGDEB0(("expandTerm: term has accents -> diac-sensitive\n")); + LOGDEB0("expandTerm: term has accents -> diac-sensitive\n" ); diac_sensitive = true; } @@ -462,37 +453,42 @@ bool SearchDataClauseSimple::expandTerm(Rcl::Db &db, Utf8Iter it(term); it++; if (getAutoCase() && unachasuppercase(term.substr(it.getBpos()))) { - LOGDEB0(("expandTerm: term has uppercase -> case-sensitive\n")); + LOGDEB0("expandTerm: term has uppercase -> case-sensitive\n" ); case_sensitive = true; } // If we are sensitive to case or diacritics turn stemming off if (diac_sensitive || case_sensitive) { - LOGDEB0(("expandTerm: diac or case sens set -> stemexpand off\n")); + LOGDEB0("expandTerm: diac or case sens set -> stemexpand and synonyms off\n" ); nostemexp = true; + synonyms = false; } if (!case_sensitive || !diac_sensitive) noexpansion = false; } - if (case_sensitive) - termmatchsens |= Db::ET_CASESENS; - if (diac_sensitive) - termmatchsens |= Db::ET_DIACSENS; if (noexpansion) { oexp.push_back(prefix + term); m_hldata.terms[term] = term; - LOGDEB(("ExpandTerm: noexpansion: final: %s\n", stringsToString(oexp).c_str())); + LOGDEB("ExpandTerm: noexpansion: final: " << (stringsToString(oexp)) << "\n" ); return true; } + int termmatchsens = 0; + if (case_sensitive) + termmatchsens |= Db::ET_CASESENS; + if (diac_sensitive) + termmatchsens |= Db::ET_DIACSENS; + if (synonyms) + termmatchsens |= Db::ET_SYNEXP; + Db::MatchType mtyp = haswild ? Db::ET_WILD : nostemexp ? Db::ET_NONE : Db::ET_STEM; TermMatchResult res; - if (!db.termMatch(mtyp | termmatchsens, getStemLang(), term, res, maxexpand, - m_field)) { + if (!db.termMatch(mtyp | termmatchsens, getStemLang(), + term, res, maxexpand, m_field, multiwords)) { // Let it go through } @@ -517,7 +513,7 @@ bool SearchDataClauseSimple::expandTerm(Rcl::Db &db, it != oexp.end(); it++) { m_hldata.terms[strip_prefix(*it)] = term; } - LOGDEB(("ExpandTerm: final: %s\n", stringsToString(oexp).c_str())); + LOGDEB("ExpandTerm: final: " << (stringsToString(oexp)) << "\n" ); return true; } @@ -555,23 +551,33 @@ void multiply_groups(vector >::const_iterator vvit, } } -void SearchDataClauseSimple::processSimpleSpan(Rcl::Db &db, string& ermsg, - const string& span, - int mods, void * pq) +static void prefix_vector(vector& v, const string& prefix) +{ + for (vector::iterator it = v.begin(); it != v.end(); it++) { + *it = prefix + *it; + } +} + +void SearchDataClauseSimple:: +processSimpleSpan(Rcl::Db &db, string& ermsg, + const string& span, + int mods, void * pq) { vector& pqueries(*(vector*)pq); - LOGDEB0(("StringToXapianQ::processSimpleSpan: [%s] mods 0x%x\n", - span.c_str(), (unsigned int)mods)); + LOGDEB0("StringToXapianQ::processSimpleSpan: [" << (span) << "] mods 0x" << ((unsigned int)mods) << "\n" ); vector exp; string sterm; // dumb version of user term string prefix; const FieldTraits *ftp; if (!m_field.empty() && db.fieldToTraits(m_field, &ftp, true)) { + if (ftp->noterms) + addModifier(SDCM_NOTERMS); // Don't add terms to highlight data prefix = wrap_prefix(ftp->pfx); } - if (!expandTerm(db, ermsg, mods, span, exp, sterm, prefix)) + vector multiwords; + if (!expandTerm(db, ermsg, mods, span, exp, sterm, prefix, &multiwords)) return; // Set up the highlight data. No prefix should go in there @@ -601,6 +607,23 @@ void SearchDataClauseSimple::processSimpleSpan(Rcl::Db &db, string& ermsg, Xapian::Query(prefix+sterm, original_term_wqf_booster)); } + + // Push phrases for the multi-word expansions + for (vector::const_iterator mwp = multiwords.begin(); + mwp != multiwords.end(); mwp++) { + vector phr; + // We just do a basic split to keep things a bit simpler here + // (no textsplit). This means though that no punctuation is + // allowed in multi-word synonyms. + stringToTokens(*mwp, phr); + if (!prefix.empty()) + prefix_vector(phr, prefix); + xq = Xapian::Query(Xapian::Query::OP_OR, xq, + Xapian::Query(Xapian::Query::OP_PHRASE, + phr.begin(), phr.end())); + m_curcl++; + } + pqueries.push_back(xq); } @@ -637,7 +660,7 @@ void SearchDataClauseSimple::processPhraseOrNear(Rcl::Db &db, string& ermsg, vector::const_iterator nxit = splitData->nostemexps().begin(); for (vector::const_iterator it = splitData->terms().begin(); it != splitData->terms().end(); it++, nxit++) { - LOGDEB0(("ProcessPhrase: processing [%s]\n", it->c_str())); + LOGDEB0("ProcessPhrase: processing [" << *it << "]\n" ); // Adjust when we do stem expansion. Not if disabled by // caller, not inside phrases, and some versions of xapian // will accept only one OR clause inside NEAR. @@ -653,8 +676,7 @@ void SearchDataClauseSimple::processPhraseOrNear(Rcl::Db &db, string& ermsg, vector exp; if (!expandTerm(db, ermsg, lmods, *it, exp, sterm, prefix)) return; - LOGDEB0(("ProcessPhraseOrNear: exp size %d\n", exp.size())); - listVector("", exp); + LOGDEB0("ProcessPhraseOrNear: exp size " << (exp.size()) << ", exp: " << (stringsToString(exp)) << "\n" ); // groups is used for highlighting, we don't want prefixes in there. vector noprefs; for (vector::const_iterator it = exp.begin(); @@ -680,8 +702,7 @@ void SearchDataClauseSimple::processPhraseOrNear(Rcl::Db &db, string& ermsg, // Generate an appropriate PHRASE/NEAR query with adjusted slack // For phrases, give a relevance boost like we do for original terms - LOGDEB2(("PHRASE/NEAR: alltermcount %d lastpos %d\n", - splitData->alltermcount(), splitData->lastpos())); + LOGDEB2("PHRASE/NEAR: alltermcount " << (splitData->alltermcount()) << " lastpos " << (splitData->lastpos()) << "\n" ); Xapian::Query xq(op, orqueries.begin(), orqueries.end(), splitData->lastpos() + 1 + slack); if (op == Xapian::Query::OP_PHRASE) @@ -751,9 +772,7 @@ bool SearchDataClauseSimple::processUserString(Rcl::Db &db, const string &iq, vector &pqueries(*(vector*)pq); int mods = m_modifiers; - LOGDEB(("StringToXapianQ:pUS:: qstr [%s] fld [%s] mods 0x%x " - "slack %d near %d\n", - iq.c_str(), m_field.c_str(), mods, slack, useNear)); + LOGDEB("StringToXapianQ:pUS:: qstr [" << (iq) << "] fld [" << (m_field) << "] mods 0x" << (mods) << " slack " << (slack) << " near " << (useNear) << "\n" ); ermsg.erase(); m_curcl = 0; const StopList stops = db.getStopList(); @@ -773,7 +792,7 @@ bool SearchDataClauseSimple::processUserString(Rcl::Db &db, const string &iq, try { for (vector::iterator it = phrases.begin(); it != phrases.end(); it++) { - LOGDEB0(("strToXapianQ: phrase/word: [%s]\n", it->c_str())); + LOGDEB0("strToXapianQ: phrase/word: [" << *it << "]\n" ); // Anchoring modifiers int amods = stringToMods(*it); int terminc = amods != 0 ? 1 : 0; @@ -809,9 +828,9 @@ bool SearchDataClauseSimple::processUserString(Rcl::Db &db, const string &iq, tpq.setTSQ(&splitter); splitter.text_to_words(*it); - slack += tpq.lastpos() - tpq.terms().size() + 1; + slack += tpq.lastpos() - int(tpq.terms().size()) + 1; - LOGDEB0(("strToXapianQ: termcount: %d\n", tpq.terms().size())); + LOGDEB0("strToXapianQ: termcount: " << (tpq.terms().size()) << "\n" ); switch (tpq.terms().size() + terminc) { case 0: continue;// ?? @@ -846,7 +865,7 @@ bool SearchDataClauseSimple::processUserString(Rcl::Db &db, const string &iq, ermsg = "Caught unknown exception"; } if (!ermsg.empty()) { - LOGERR(("stringToXapianQueries: %s\n", ermsg.c_str())); + LOGERR("stringToXapianQueries: " << (ermsg) << "\n" ); return false; } return true; @@ -855,9 +874,7 @@ bool SearchDataClauseSimple::processUserString(Rcl::Db &db, const string &iq, // Translate a simple OR or AND search clause. bool SearchDataClauseSimple::toNativeQuery(Rcl::Db &db, void *p) { - LOGDEB(("SearchDataClauseSimple::toNativeQuery: fld [%s] val [%s] " - "stemlang [%s]\n", m_field.c_str(), m_text.c_str(), - getStemLang().c_str())); + LOGDEB("SearchDataClauseSimple::toNativeQuery: fld [" << (m_field) << "] val [" << (m_text) << "] stemlang [" << (getStemLang()) << "]\n" ); Xapian::Query *qp = (Xapian::Query *)p; *qp = Xapian::Query(); @@ -867,7 +884,7 @@ bool SearchDataClauseSimple::toNativeQuery(Rcl::Db &db, void *p) case SCLT_AND: op = Xapian::Query::OP_AND; break; case SCLT_OR: op = Xapian::Query::OP_OR; break; default: - LOGERR(("SearchDataClauseSimple: bad m_tp %d\n", m_tp)); + LOGERR("SearchDataClauseSimple: bad m_tp " << (m_tp) << "\n" ); m_reason = "Internal error"; return false; } @@ -876,7 +893,7 @@ bool SearchDataClauseSimple::toNativeQuery(Rcl::Db &db, void *p) if (!processUserString(db, m_text, m_reason, &pqueries)) return false; if (pqueries.empty()) { - LOGERR(("SearchDataClauseSimple: resolved to null query\n")); + LOGERR("SearchDataClauseSimple: resolved to null query\n" ); m_reason = string("Resolved to null query. Term too long ? : [" + m_text + string("]")); return false; @@ -920,25 +937,34 @@ bool SearchDataClauseFilename::toNativeQuery(Rcl::Db &db, void *p) // Translate a dir: path filtering clause. See comments in .h bool SearchDataClausePath::toNativeQuery(Rcl::Db &db, void *p) { - LOGDEB(("SearchDataClausePath::toNativeQuery: [%s]\n", m_text.c_str())); + LOGDEB("SearchDataClausePath::toNativeQuery: [" << m_text << "]\n"); Xapian::Query *qp = (Xapian::Query *)p; *qp = Xapian::Query(); - if (m_text.empty()) { - LOGERR(("SearchDataClausePath: empty path??\n")); + string ltext; +#ifdef _WIN32 + // Windows file names are case-insensitive, so we lowercase (same + // as when indexing) + unacmaybefold(m_text, ltext, "UTF-8", UNACOP_FOLD); +#else + ltext = m_text; +#endif + + if (ltext.empty()) { + LOGERR("SearchDataClausePath: empty path??\n" ); m_reason = "Empty path ?"; return false; } vector orqueries; - if (m_text[0] == '/') + if (path_isabsolute(ltext)) orqueries.push_back(Xapian::Query(wrap_prefix(pathelt_prefix))); else - m_text = path_tildexpand(m_text); + ltext = path_tildexpand(ltext); vector vpath; - stringToTokens(m_text, vpath, "/"); + stringToTokens(ltext, vpath, "/"); for (vector::const_iterator pit = vpath.begin(); pit != vpath.end(); pit++){ @@ -950,8 +976,8 @@ bool SearchDataClausePath::toNativeQuery(Rcl::Db &db, void *p) *pit, exp, sterm, wrap_prefix(pathelt_prefix))) { return false; } - LOGDEB0(("SDataPath::toNative: exp size %d\n", exp.size())); - listVector("", exp); + LOGDEB0("SDataPath::toNative: exp size " << exp.size() << ". Exp: " << + stringsToString(exp) << "\n"); if (exp.size() == 1) orqueries.push_back(Xapian::Query(exp[0])); else @@ -974,7 +1000,7 @@ bool SearchDataClausePath::toNativeQuery(Rcl::Db &db, void *p) // Translate NEAR or PHRASE clause. bool SearchDataClauseDist::toNativeQuery(Rcl::Db &db, void *p) { - LOGDEB(("SearchDataClauseDist::toNativeQuery\n")); + LOGDEB("SearchDataClauseDist::toNativeQuery\n" ); Xapian::Query *qp = (Xapian::Query *)p; *qp = Xapian::Query(); @@ -993,7 +1019,7 @@ bool SearchDataClauseDist::toNativeQuery(Rcl::Db &db, void *p) if (!processUserString(db, s, m_reason, &pqueries, m_slack, useNear)) return false; if (pqueries.empty()) { - LOGERR(("SearchDataClauseDist: resolved to null query\n")); + LOGERR("SearchDataClauseDist: resolved to null query\n" ); m_reason = string("Resolved to null query. Term too long ? : [" + m_text + string("]")); return false; @@ -1007,3 +1033,4 @@ bool SearchDataClauseDist::toNativeQuery(Rcl::Db &db, void *p) } } // Namespace Rcl + diff --git a/src/rcldb/searchdataxml.cpp b/src/rcldb/searchdataxml.cpp index 72b3b9e5..409d319e 100644 --- a/src/rcldb/searchdataxml.cpp +++ b/src/rcldb/searchdataxml.cpp @@ -25,12 +25,13 @@ #include #include #include -using namespace std; #include "searchdata.h" -#include "debuglog.h" +#include "log.h" #include "base64.h" +using namespace std; + namespace Rcl { static string tpToString(SClType tp) @@ -48,7 +49,7 @@ static string tpToString(SClType tp) string SearchData::asXML() { - LOGDEB(("SearchData::asXML\n")); + LOGDEB("SearchData::asXML\n" ); ostringstream os; // Searchdata @@ -64,7 +65,7 @@ string SearchData::asXML() for (unsigned int i = 0; i < m_query.size(); i++) { SearchDataClause *c = m_query[i]; if (c->getTp() == SCLT_SUB) { - LOGERR(("SearchData::asXML: can't do subclauses !\n")); + LOGERR("SearchData::asXML: can't do subclauses !\n" ); continue; } if (c->getTp() == SCLT_PATH) { @@ -159,3 +160,4 @@ string SearchData::asXML() } + diff --git a/src/rcldb/stemdb.cpp b/src/rcldb/stemdb.cpp index 1a5d40fe..f2611dc9 100644 --- a/src/rcldb/stemdb.cpp +++ b/src/rcldb/stemdb.cpp @@ -22,17 +22,18 @@ #include "autoconfig.h" -#include +#include "safeunistd.h" #include #include #include +#include using namespace std; #include #include "stemdb.h" -#include "debuglog.h" +#include "log.h" #include "smallut.h" #include "synfamily.h" #include "unacpp.h" @@ -84,10 +85,10 @@ bool StemDb::stemExpand(const std::string& langs, const std::string& _term, sort(result.begin(), result.end()); vector::iterator uit = unique(result.begin(), result.end()); result.resize(uit - result.begin()); - LOGDEB1(("stemExpand:%s: %s -> %s\n", langs.c_str(), term.c_str(), - stringsToString(result).c_str())); + LOGDEB1("stemExpand:" << (langs) << ": " << (term) << " -> " << (stringsToString(result)) << "\n" ); return true; } } + diff --git a/src/rcldb/stemdb.h b/src/rcldb/stemdb.h index 43153605..c84a4ba5 100644 --- a/src/rcldb/stemdb.h +++ b/src/rcldb/stemdb.h @@ -73,8 +73,7 @@ public: virtual std::string operator()(const std::string& in) { string out = m_stemmer(in); - LOGDEB2(("SynTermTransStem(%s): in [%s] out [%s]\n", m_lang.c_str(), - in.c_str(), out.c_str())); + LOGDEB2("SynTermTransStem(" << (m_lang) << "): in [" << (in) << "] out [" << (out) << "]\n" ); return out; } Xapian::Stem m_stemmer; @@ -102,3 +101,4 @@ public: } #endif /* _STEMDB_H_INCLUDED_ */ + diff --git a/src/rcldb/stoplist.cpp b/src/rcldb/stoplist.cpp index c0c1d177..6e2ed0ec 100644 --- a/src/rcldb/stoplist.cpp +++ b/src/rcldb/stoplist.cpp @@ -16,7 +16,7 @@ */ #ifndef TEST_STOPLIST -#include "debuglog.h" +#include "log.h" #include "readfile.h" #include "unacpp.h" #include "smallut.h" @@ -32,8 +32,7 @@ bool StopList::setFile(const string &filename) m_stops.clear(); string stoptext, reason; if (!file_to_string(filename, stoptext, &reason)) { - LOGDEB0(("StopList::StopList: file_to_string(%s) failed: %s\n", - filename.c_str(), reason.c_str())); + LOGDEB0("StopList::StopList: file_to_string(" << (filename) << ") failed: " << (reason) << "\n" ); return false; } set stops; @@ -116,3 +115,4 @@ int main(int argc, char **argv) } #endif // TEST_STOPLIST + diff --git a/src/rcldb/synfamily.cpp b/src/rcldb/synfamily.cpp index 8d0192ff..ec752615 100644 --- a/src/rcldb/synfamily.cpp +++ b/src/rcldb/synfamily.cpp @@ -20,13 +20,13 @@ #include #include +#include -#include "debuglog.h" +#include "log.h" #include "cstr.h" #include "xmacros.h" #include "synfamily.h" #include "smallut.h" -#include "refcntr.h" using namespace std; @@ -39,7 +39,7 @@ bool XapWritableSynFamily::createMember(const string& membername) m_wdb.add_synonym(memberskey(), membername); } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("XapSynFamily::createMember: error: %s\n", ermsg.c_str())); + LOGERR("XapSynFamily::createMember: error: " << (ermsg) << "\n" ); return false; } return true; @@ -68,7 +68,7 @@ bool XapSynFamily::getMembers(vector& members) } } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("XapSynFamily::getMembers: xapian error %s\n", ermsg.c_str())); + LOGERR("XapSynFamily::getMembers: xapian error " << (ermsg) << "\n" ); return false; } return true; @@ -90,7 +90,7 @@ bool XapSynFamily::listMap(const string& membername) } } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("XapSynFamily::listMap: xapian error %s\n", ermsg.c_str())); + LOGERR("XapSynFamily::listMap: xapian error " << (ermsg) << "\n" ); return false; } vectormembers; @@ -107,21 +107,19 @@ bool XapSynFamily::listMap(const string& membername) bool XapSynFamily::synExpand(const string& member, const string& term, vector& result) { - LOGDEB(("XapSynFamily::synExpand:(%s) %s for %s\n", - m_prefix1.c_str(), term.c_str(), member.c_str())); + LOGDEB("XapSynFamily::synExpand:(" << (m_prefix1) << ") " << (term) << " for " << (member) << "\n" ); string key = entryprefix(member) + term; string ermsg; try { for (Xapian::TermIterator xit = m_rdb.synonyms_begin(key); xit != m_rdb.synonyms_end(key); xit++) { - LOGDEB2((" Pushing %s\n", (*xit).c_str())); + LOGDEB2(" Pushing " << ((*xit)) << "\n" ); result.push_back(*xit); } } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("synFamily::synExpand: error for member [%s] term [%s]\n", - member.c_str(), term.c_str())); + LOGERR("synFamily::synExpand: error for member [" << (member) << "] term [" << (term) << "]\n" ); result.push_back(term); return false; } @@ -144,40 +142,37 @@ bool XapComputableSynFamMember::synExpand(const string& term, string key = m_prefix + root; - LOGDEB(("XapCompSynFamMbr::synExpand([%s]): term [%s] root [%s] \n", - m_prefix.c_str(), term.c_str(), root.c_str())); + LOGDEB("XapCompSynFamMbr::synExpand([" << (m_prefix) << "]): term [" << (term) << "] root [" << (root) << "] m_trans: " << (m_trans->name()) << " filter: " << (filtertrans ? filtertrans->name() : "none") << "\n" ); string ermsg; try { for (Xapian::TermIterator xit = m_family.getdb().synonyms_begin(key); xit != m_family.getdb().synonyms_end(key); xit++) { if (!filtertrans || (*filtertrans)(*xit) == filter_root) { - LOGDEB2((" Pushing %s\n", (*xit).c_str())); + LOGDEB2(" Pushing " << ((*xit)) << "\n" ); result.push_back(*xit); } } } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("XapSynDb::synExpand: error for term [%s] (key %s)\n", - term.c_str(), key.c_str())); + LOGERR("XapSynDb::synExpand: error for term [" << (term) << "] (key " << (key) << ")\n" ); result.push_back(term); return false; } // If the input term and root are not in the list, add them if (find(result.begin(), result.end(), term) == result.end()) { - LOGDEB2((" Pushing %s\n", term.c_str())); + LOGDEB2(" Pushing " << (term) << "\n" ); result.push_back(term); } if (root != term && find(result.begin(), result.end(), root) == result.end()) { if (!filtertrans || (*filtertrans)(root) == filter_root) { - LOGDEB2((" Pushing %s\n", root.c_str())); + LOGDEB2(" Pushing " << (root) << "\n" ); result.push_back(root); } } - LOGDEB(("XapCompSynFamMbr::synExpand([%s]): term [%s] -> [%s]\n", - m_prefix.c_str(), term.c_str(), stringsToString(result).c_str())); + LOGDEB("XapCompSynFamMbr::synExpand([" << (m_prefix) << "]): term [" << (term) << "] -> [" << (stringsToString(result)) << "]\n" ); return true; } @@ -185,12 +180,12 @@ bool XapComputableSynFamMember::synKeyExpand(StrMatcher* inexp, vector& result, SynTermTrans *filtertrans) { - LOGDEB(("XapCompSynFam::synKeyExpand: [%s]\n", inexp->exp().c_str())); + LOGDEB("XapCompSynFam::synKeyExpand: [" << (inexp->exp()) << "]\n" ); // If set, compute filtering term (e.g.: only case-folded) - RefCntr filter_exp; + std::shared_ptr filter_exp; if (filtertrans) { - filter_exp = RefCntr(inexp->clone()); + filter_exp = std::shared_ptr(inexp->clone()); filter_exp->setExp((*filtertrans)(inexp->exp())); } @@ -201,14 +196,13 @@ bool XapComputableSynFamMember::synKeyExpand(StrMatcher* inexp, string::size_type es = inexp->baseprefixlen(); string is = inexp->exp().substr(0, es); string::size_type preflen = m_prefix.size(); - LOGDEB2(("XapCompSynFam::synKeyExpand: init section: [%s]\n", is.c_str())); + LOGDEB2("XapCompSynFam::synKeyExpand: init section: [" << (is) << "]\n" ); string ermsg; try { for (Xapian::TermIterator xit = m_family.getdb().synonym_keys_begin(is); xit != m_family.getdb().synonym_keys_end(is); xit++) { - LOGDEB2((" Checking1 [%s] against [%s]\n", (*xit).c_str(), - inexp->exp().c_str())); + LOGDEB2(" Checking1 [" << ((*xit)) << "] against [" << (inexp->exp()) << "]\n" ); if (!inexp->match(*xit)) continue; @@ -217,38 +211,34 @@ bool XapComputableSynFamMember::synKeyExpand(StrMatcher* inexp, m_family.getdb().synonyms_begin(*xit); xit1 != m_family.getdb().synonyms_end(*xit); xit1++) { string term = *xit1; - if (filter_exp.isNotNull()) { + if (filter_exp) { string term1 = (*filtertrans)(term); - LOGDEB2((" Testing [%s] against [%s]\n", - term1.c_str(), filter_exp->exp().c_str())); + LOGDEB2(" Testing [" << (term1) << "] against [" << (filter_exp->exp()) << "]\n" ); if (!filter_exp->match(term1)) { continue; } } - LOGDEB2(("XapCompSynFam::keyWildExpand: [%s]\n", - (*xit1).c_str())); + LOGDEB2("XapCompSynFam::keyWildExpand: [" << ((*xit1)) << "]\n" ); result.push_back(*xit1); } // Same with key itself string term = (*xit).substr(preflen); - if (filter_exp.isNotNull()) { + if (filter_exp) { string term1 = (*filtertrans)(term); - LOGDEB2((" Testing [%s] against [%s]\n", - term1.c_str(), filter_exp->exp().c_str())); + LOGDEB2(" Testing [" << (term1) << "] against [" << (filter_exp->exp()) << "]\n" ); if (!filter_exp->match(term1)) { continue; } } - LOGDEB2(("XapCompSynFam::keyWildExpand: [%s]\n", term.c_str())); + LOGDEB2("XapCompSynFam::keyWildExpand: [" << (term) << "]\n" ); result.push_back(term); } } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("XapCompSynFam::synKeyExpand: xapian: [%s]\n", ermsg.c_str())); + LOGERR("XapCompSynFam::synKeyExpand: xapian: [" << (ermsg) << "]\n" ); return false; } - LOGDEB1(("XapCompSynFam::synKeyExpand: final: [%s]\n", - stringsToString(result).c_str())); + LOGDEB1("XapCompSynFam::synKeyExpand: final: [" << (stringsToString(result)) << "]\n" ); return true; } @@ -398,3 +388,4 @@ int main(int argc, char **argv) } #endif // TEST_SYNFAMILY + diff --git a/src/rcldb/synfamily.h b/src/rcldb/synfamily.h index 219eb0ee..01ade5df 100644 --- a/src/rcldb/synfamily.h +++ b/src/rcldb/synfamily.h @@ -37,7 +37,7 @@ #include -#include "debuglog.h" +#include "log.h" #include "xmacros.h" #include "strmatcher.h" @@ -118,6 +118,7 @@ protected: class SynTermTrans { public: virtual std::string operator()(const std::string&) = 0; + virtual std::string name() { return "SynTermTrans: unknown";} }; /** A member (set of root-synonyms associations) of a SynFamily for @@ -167,10 +168,9 @@ public: virtual bool addSynonym(const std::string& term) { - LOGDEB2(("addSynonym:me %p term [%s] m_trans %p\n", this, - term.c_str(), m_trans)); + LOGDEB2("addSynonym:me " << (this) << " term [" << (term) << "] m_trans " << (m_trans) << "\n" ); std::string transformed = (*m_trans)(term); - LOGDEB2(("addSynonym: transformed [%s]\n", transformed.c_str())); + LOGDEB2("addSynonym: transformed [" << (transformed) << "]\n" ); if (transformed == term) return true; @@ -179,8 +179,7 @@ public: m_family.getdb().add_synonym(m_prefix + transformed, term); } XCATCHERROR(ermsg); if (!ermsg.empty()) { - LOGERR(("XapWritableComputableSynFamMember::addSynonym: " - "xapian error %s\n", ermsg.c_str())); + LOGERR("XapWritableComputableSynFamMember::addSynonym: xapian error " << (ermsg) << "\n" ); return false; } return true; @@ -223,3 +222,4 @@ static const std::string synFamDiCa("DCa"); } // end namespace Rcl #endif /* _SYNFAMILY_H_INCLUDED_ */ + diff --git a/src/rcldb/termproc.h b/src/rcldb/termproc.h index eb7cd4a8..dc224b1d 100644 --- a/src/rcldb/termproc.h +++ b/src/rcldb/termproc.h @@ -14,13 +14,15 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ - - #ifndef _TERMPROC_H_INCLUDED_ #define _TERMPROC_H_INCLUDED_ +#include +#include + #include "textsplit.h" #include "stoplist.h" +#include "smallut.h" namespace Rcl { @@ -127,27 +129,55 @@ public: m_totalterms++; string otrm; if (!unacmaybefold(itrm, otrm, "UTF-8", UNACOP_UNACFOLD)) { - LOGDEB(("splitter::takeword: unac [%s] failed\n", itrm.c_str())); + LOGDEB("splitter::takeword: unac [" << (itrm) << "] failed\n" ); m_unacerrors++; // We don't generate a fatal error because of a bad term, // but one has to put the limit somewhere if (m_unacerrors > 500 && (double(m_totalterms) / double(m_unacerrors)) < 2.0) { // More than 1 error for every other term - LOGERR(("splitter::takeword: too many unac errors %d/%d\n", - m_unacerrors, m_totalterms)); + LOGERR("splitter::takeword: too many unac errors " << (m_unacerrors) << "/" << (m_totalterms) << "\n" ); return false; } return true; } - // It may happen in some weird cases that the output from unac is - // empty (if the word actually consisted entirely of diacritics ...) - // The consequence is that a phrase search won't work without addional - // slack. - if (otrm.empty()) + + if (otrm.empty()) { + // It may happen in some weird cases that the output from + // unac is empty (if the word actually consisted entirely + // of diacritics ...) The consequence is that a phrase + // search won't work without addional slack. return true; - else - return TermProc::takeword(otrm, pos, bs, be); + } + + // It may also occur that unac introduces spaces in the string + // (when removing isolated accents, may happen for Greek + // for example). This is a pathological situation. We + // index all the resulting terms at the same pos because + // the surrounding code is not designed to handle a pos + // change in here. This means that phrase searches and + // snippets will be wrong, but at least searching for the + // terms will work. + bool hasspace = false; + for (string::const_iterator it = otrm.begin();it < otrm.end();it++) { + if (*it == ' ') { + hasspace=true; + break; + } + } + if (hasspace) { + std::vector terms; + stringToTokens(otrm, terms, " ", true); + for (std::vector::const_iterator it = terms.begin(); + it < terms.end(); it++) { + if (!TermProc::takeword(*it, pos, bs, be)) { + return false; + } + } + return true; + } else { + return TermProc::takeword(otrm, pos, bs, be); + } } virtual bool flush() @@ -196,8 +226,7 @@ public: virtual bool takeword(const string& term, int pos, int bs, int be) { - LOGDEB1(("TermProcCom::takeword: pos %d %d %d [%s]\n", - pos, bs, be, term.c_str())); + LOGDEB1("TermProcCom::takeword: pos " << (pos) << " " << (bs) << " " << (be) << " [" << (term) << "]\n" ); bool isstop = m_stops.isStop(term); bool twogramemit = false; @@ -269,3 +298,4 @@ private: } // End namespace Rcl #endif /* _TERMPROC_H_INCLUDED_ */ + diff --git a/src/recollinstall.in b/src/recollinstall.in deleted file mode 100755 index d155b410..00000000 --- a/src/recollinstall.in +++ /dev/null @@ -1,166 +0,0 @@ -#!/bin/sh -# Install recoll files. - -fatal() -{ - echo $* - exit 1 -} -usage() -{ - fatal 'Usage: recollinstall [], ie: recollinstall /usr/local' -} - -if test $# -eq 0 ; then - PREFIX=@prefix@ -elif test $# -eq 1 ; then - PREFIX=$1 -else - usage -fi -QTGUI=@QTGUI@ -# The .qm files are in qtgui/i18n even if qt4 is used -I18N=qtgui/i18n - -RCLLIBVERSION=@RCLLIBVERSION@ - -test -n "$bindir" || bindir=${PREFIX}/bin -test -n "$libdir" || libdir=${PREFIX}/lib -test -n "$datadir" || datadir=${PREFIX}/share -if test -z "$mandir" ; then - if test -d ${PREFIX}/man ; then - mandir=${PREFIX}/man - else - mandir=${datadir}/man - fi -fi - -REALPREFIX=$PREFIX -ROOTFORPYTHON="" -if test -n "$DESTDIR" ; then - PREFIX=$DESTDIR/$PREFIX - bindir=$DESTDIR/$bindir - libdir=$DESTDIR/$libdir - datadir=$DESTDIR/$datadir - mandir=$DESTDIR/$mandir - ROOTFORPYTHON="--root=${DESTDIR}" -fi -if test -f /etc/debian_version ; then - OPTSFORPYTHON=--install-layout=deb -fi - -echo "Installing to $PREFIX" - -# Note: solaris 'install' does not understand -v -INSTALL=${INSTALL:="install -c"} -STRIP=${STRIP:=strip} - -sys=`uname` -@NOQTMAKE@if test "$sys" = Darwin ; then -@NOQTMAKE@ RECOLLPROG=${QTGUI}/recoll.app/Contents/MacOS/recoll -@NOQTMAKE@else -@NOQTMAKE@ RECOLLPROG=${QTGUI}/recoll -@NOQTMAKE@fi - -TESTPROG=index/recollindex -test -x $TESTPROG || fatal "$TESTPROG does not exist." \ - " You need to build first (type 'make')." - -for d in \ - ${bindir} \ - ${libdir} \ - ${libdir}/recoll \ - ${mandir}/man1 \ - ${mandir}/man5 \ - ${datadir}/icons \ - ${datadir}/recoll/doc \ - ${datadir}/recoll/examples \ - ${datadir}/recoll/filters \ - ${datadir}/recoll/images \ - ${datadir}/recoll/translations \ - ${datadir}/icons/hicolor/48x48/apps \ - ${datadir}/pixmaps -do - test -d $d || mkdir -p $d || exit 1 -done - -@NOQTMAKE@test -d ${datadir}/applications \ -@NOQTMAKE@ || mkdir -p ${datadir}/applications || exit 1 -@NOQTMAKE@test -d ${datadir}/appdata \ -@NOQTMAKE@ || mkdir -p ${datadir}/appdata || exit 1 - -# Use the xdg utilies to install the desktop file and icon? Couldn't find -# out how to get this to work sanely. So keep the old way -#PATH=$PATH:desktop/xdg-utils-1.0.1/scripts -#export PATH -#xdg-desktop-menu install desktop/recoll-searchgui.desktop -#xdg-icon-resource install --size 48 desktop/recoll.png -@NOQTMAKE@${INSTALL} -m 0444 desktop/recoll.appdata.xml ${datadir}/appdata -@NOQTMAKE@${INSTALL} -m 0444 desktop/recoll-searchgui.desktop ${datadir}/applications -@NOQTMAKE@${INSTALL} -m 0444 desktop/recoll.png ${datadir}/icons/hicolor/48x48/apps -@NOQTMAKE@${INSTALL} -m 0444 desktop/recoll.png ${datadir}/pixmaps/ - -if test -f doc/user/usermanual.html ; then - ${INSTALL} -m 0444 doc/user/usermanual.html doc/user/docbook-xsl.css \ - ${datadir}/recoll/doc -else - echo "User manual not found -> not installed" -fi - -@NOQTMAKE@${INSTALL} -m 0444 doc/man/recoll.1 ${mandir}/man1/ -# Install the recollq man page in all cases, it's useful for recoll -t -${INSTALL} -m 0444 doc/man/recollq.1 ${mandir}/man1/ -${INSTALL} -m 0444 doc/man/recollindex.1 ${mandir}/man1/ -${INSTALL} -m 0444 doc/man/recoll.conf.5 ${mandir}/man5/ - -@NOQTMAKE@${INSTALL} -m 0755 ${RECOLLPROG} ${bindir} || exit 1 -@NOQTMAKE@${STRIP} ${bindir}/recoll -@NOCMDLINE@${INSTALL} -m 0755 query/recollq ${bindir} || exit 1 -@NOCMDLINE@${STRIP} ${bindir}/recollq -${INSTALL} -m 0755 index/recollindex ${bindir} || exit 1 -${STRIP} ${bindir}/recollindex - -@NODYNLIB@${INSTALL} -m 0644 lib/librecoll.so.${RCLLIBVERSION} \ -@NODYNLIB@ ${libdir}/recoll/ || exit 1 - -${INSTALL} -m 0755 filters/rcl* ${datadir}/recoll/filters/ || exit 1 -${INSTALL} -m 0555 filters/ppt-dump.py ${datadir}/recoll/filters/ || exit 1 -${INSTALL} -m 0555 filters/xls-dump.py ${datadir}/recoll/filters/ || exit 1 -${INSTALL} -m 0555 filters/xlsxmltocsv.py ${datadir}/recoll/filters/ || exit 1 - -${INSTALL} -m 0444 filters/msodump.zip ${datadir}/recoll/filters/ || exit 1 -for f in rclexecm.py rcllatinstops.zip;do - chmod 644 ${datadir}/recoll/filters/$f -done - -${INSTALL} -m 0755 desktop/xdg-utils-1.0.1/scripts/xdg-open \ - ${datadir}/recoll/filters/ || exit 1 -${INSTALL} -m 0755 desktop/hotrecoll.py \ - ${datadir}/recoll/filters/ || exit 1 -${INSTALL} -m 0644 python/recoll/recoll/rclconfig.py \ - ${datadir}/recoll/filters/ || exit 1 - -${INSTALL} -m 0444 \ - desktop/recollindex.desktop \ - sampleconf/fragbuts.xml \ - sampleconf/mimeconf \ - sampleconf/mimeview \ - sampleconf/recoll.conf \ - sampleconf/mimemap \ - sampleconf/fields \ - sampleconf/recoll.qss \ - ${datadir}/recoll/examples/ || exit 1 -${INSTALL} -m 0755 index/rclmon.sh ${datadir}/recoll/examples/ || exit 1 - - -${INSTALL} -m 0444 qtgui/mtpics/*.png ${datadir}/recoll/images || exit 1 - -${INSTALL} -m 0444 ${I18N}/recoll*.qm ${datadir}/recoll/translations || exit 1 -# Install the simplified chinese file as just chinese until I can understand -# if it's possible to have both. zh_CN doesn't seem to work -${INSTALL} -m 0444 ${I18N}/recoll_zh_CN.qm \ - ${datadir}/recoll/translations/recoll_zh.qm || exit 1 - - -@NOPYTHON@(cd python/recoll;python setup.py install \ -@NOPYTHON@ --prefix=${REALPREFIX} ${ROOTFORPYTHON} ${OPTSFORPYTHON}) diff --git a/src/sampleconf/fields b/src/sampleconf/fields index ace96c23..4250fb2f 100644 --- a/src/sampleconf/fields +++ b/src/sampleconf/fields @@ -43,12 +43,12 @@ keywords= K xapyearmon = M title = S ; wdfinc = 10 mtype = T -ext = XE +ext = XE; noterms = 1 rclmd5 = XM -dir = XP +dir = XP ; noterms = 1 abstract = XS -filename = XSFN -containerfilename = XCFN ; pfxonly = 1 +filename = XSFN ; noterms = 1 +containerfilename = XCFN ; pfxonly = 1 ; noterms = 1 rclUnsplitFN = XSFS xapyear = Y recipient = XTO @@ -58,7 +58,7 @@ recipient = XTO # by default. # Some values are internally reserved by recoll: # XE (file ext), XP (for path elements), XSFN, XSFS, XXST, XXND, XXPG -rclbes = XB +rclbes = XB ; noterms = 1 # Using XX was not a good idea. # # I hereby commit to not using XY for Recoll: @@ -116,8 +116,8 @@ url = dc:identifier xesam:url # query time: there is no risk to pick up a random field from a document # (e.g. an HTML meta field) and index it. [queryaliases] -#filename = fn -#containerfilename = cfn +filename = fn +containerfilename = cfn [xattrtofields] ###################### diff --git a/src/sampleconf/mimeconf b/src/sampleconf/mimeconf index 29695b72..951e2d96 100644 --- a/src/sampleconf/mimeconf +++ b/src/sampleconf/mimeconf @@ -51,7 +51,7 @@ application/javascript = internal text/plain # - with unrtf: rtf files disguising as doc files. # The default is now again to use rcldoc. Use raw antiword if speed is more # important for you than catching all data, -application/msword = exec rcldoc +application/msword = execm rcldoc.py #application/msword = exec antiword -t -i 1 -m UTF-8;mimetype=text/plain # You can also use wvware directly but it's much slower. # application/msword = exec wvWare --charset=utf-8 --nographics @@ -59,41 +59,41 @@ application/msword = exec rcldoc # Also Handle the mime type returned by "file -i" for a suffix-less word # file. This could probably just as well be an excel file, but we have to # chose one. -application/vnd.ms-office = exec rcldoc +application/vnd.ms-office = execm rcldoc.py application/ogg = execm rclaudio -application/pdf = exec rclpdf -# application/pdf = execm rclmpdf +application/pdf = execm rclpdf.py application/postscript = exec pstotext;charset=iso-8859-1;mimetype=text/plain -application/vnd.ms-excel = exec rclxls -application/vnd.ms-powerpoint = exec rclppt -application/vnd.oasis.opendocument.text = exec rclsoff -application/vnd.oasis.opendocument.text-template = exec rclsoff -application/vnd.oasis.opendocument.presentation = exec rclsoff -application/vnd.oasis.opendocument.spreadsheet = exec rclsoff -application/vnd.oasis.opendocument.graphics = exec rclsoff +application/sql = internal text/plain +application/vnd.ms-excel = execm rclxls.py +application/vnd.ms-powerpoint = execm rclppt.py +application/vnd.oasis.opendocument.text = execm rclsoff.py +application/vnd.oasis.opendocument.text-template = execm rclsoff.py +application/vnd.oasis.opendocument.presentation = execm rclsoff.py +application/vnd.oasis.opendocument.spreadsheet = execm rclsoff.py +application/vnd.oasis.opendocument.graphics = execm rclsoff.py application/vnd.openxmlformats-officedocument.wordprocessingml.document = \ - exec rclopxml + execm rclopxml.py application/vnd.openxmlformats-officedocument.wordprocessingml.template = \ - exec rclopxml + execm rclopxml.py application/vnd.openxmlformats-officedocument.presentationml.template = \ - exec rclopxml + execm rclopxml.py application/vnd.openxmlformats-officedocument.presentationml.presentation = \ - exec rclopxml + execm rclopxml.py application/vnd.openxmlformats-officedocument.spreadsheetml.sheet = \ - exec rclopxml + execm rclopxml.py application/vnd.openxmlformats-officedocument.spreadsheetml.template =\ - exec rclopxml -application/vnd.sun.xml.calc = exec rclsoff -application/vnd.sun.xml.calc.template = exec rclsoff -application/vnd.sun.xml.draw = exec rclsoff -application/vnd.sun.xml.draw.template = exec rclsoff -application/vnd.sun.xml.impress = exec rclsoff -application/vnd.sun.xml.impress.template = exec rclsoff -application/vnd.sun.xml.math = exec rclsoff -application/vnd.sun.xml.writer = exec rclsoff -application/vnd.sun.xml.writer.global = exec rclsoff -application/vnd.sun.xml.writer.template = exec rclsoff + execm rclopxml.py +application/vnd.sun.xml.calc = execm rclsoff.py +application/vnd.sun.xml.calc.template = execm rclsoff.py +application/vnd.sun.xml.draw = execm rclsoff.py +application/vnd.sun.xml.draw.template = execm rclsoff.py +application/vnd.sun.xml.impress = execm rclsoff.py +application/vnd.sun.xml.impress.template = execm rclsoff.py +application/vnd.sun.xml.math = execm rclsoff.py +application/vnd.sun.xml.writer = execm rclsoff.py +application/vnd.sun.xml.writer.global = execm rclsoff.py +application/vnd.sun.xml.writer.template = execm rclsoff.py application/vnd.wordperfect = exec wpd2html;mimetype=text/html application/x-abiword = exec rclabw application/x-awk = internal text/plain @@ -101,7 +101,7 @@ application/x-chm = execm rclchm application/x-dia-diagram = execm rcldia;mimetype=text/plain application/x-dvi = exec rcldvi application/x-flac = execm rclaudio -application/x-gnote = exec rclxml +application/x-gnote = execm rclxml.py application/x-gnuinfo = execm rclinfo application/x-gnumeric = exec rclgnm application/x-kword = exec rclkwd @@ -129,11 +129,12 @@ image/jp2 = execm rclimg image/jpeg = execm rclimg image/png = execm rclimg image/tiff = execm rclimg -image/vnd.djvu = exec rcldjvu -image/svg+xml = exec rclsvg +image/vnd.djvu = execm rcldjvu.py +image/svg+xml = execm rclsvg.py image/x-xcf = execm rclimg inode/symlink = internal -inode/x-empty = exec rclnull +application/x-zerosize = internal +inode/x-empty = internal application/x-zerosize message/rfc822 = internal text/calendar = execm rclics;mimetype=text/plain text/html = internal @@ -142,6 +143,9 @@ text/rtf = exec unrtf --nopict --html;mimetype=text/html text/x-c = internal text/x-c++ = internal text/x-c+ = internal +text/x-csharp = internal text/plain +text/css = internal text/plain +application/javascript = internal text/plain text/x-csv = internal text/plain text/x-fictionbook = exec rclfb2 text/x-gaim-log = exec rclgaim @@ -149,6 +153,7 @@ text/x-html-sidux-man = exec rclsiduxman text/x-html-aptosid-man = exec rclaptosidman text/x-chm-html = internal text/html text/x-ini = internal text/plain +text/x-java = internal text/plain text/x-mail = internal text/x-man = exec rclman text/x-perl = internal text/plain @@ -156,14 +161,18 @@ text/x-purple-log = exec rclpurple text/x-purple-html-log = internal text/html text/x-python = exec rclpython text/x-shellscript = internal text/plain +text/x-srt = internal text/plain text/x-tex = exec rcltex -application/xml = exec rclxml -text/xml = exec rclxml -# Using these instead of the two above would index all parameter and tag -# names, attribute values etc, instead of just the text content. -#application/xml = internal text/plain -#text/xml = internal text/plain + +# Generic XML is best indexed as text, else it generates too many errors +# All parameter and tag names, attribute values etc, are indexed as +# text. rclxml.py tries to just index the text content. +#application/xml = execm rclxml.py +#text/xml = execm rclxml.py +application/xml = internal text/plain +text/xml = internal text/plain + ## ############################################# # Icons to be used in the result list if required by gui config @@ -174,6 +183,7 @@ application/msword = wordprocessing application/ogg = sownd application/pdf = pdf application/postscript = postscript +application/sql = source application/vnd.ms-excel = spreadsheet application/vnd.ms-powerpoint = presentation application/vnd.oasis.opendocument.presentation = presentation @@ -252,6 +262,7 @@ text/x-fictionbook = document text/x-html-aptosid-man = aptosid-book text/x-html-sidux-man = sidux-book text/x-ini = txt +text/x-java = source text/x-mail = message text/x-man = document text/x-perl = source @@ -283,6 +294,7 @@ text = \ application/msword \ application/pdf \ application/postscript \ + application/sql \ application/vnd.oasis.opendocument.text \ application/vnd.openxmlformats-officedocument.wordprocessingml.document \ application/vnd.openxmlformats-officedocument.wordprocessingml.template \ @@ -320,6 +332,7 @@ text = \ text/x-html-aptosid-man \ text/x-html-sidux-man \ text/x-ini \ + text/x-java \ text/x-man \ text/x-perl \ text/x-python \ diff --git a/src/sampleconf/mimemap b/src/sampleconf/mimemap index d100237f..50fb4d21 100644 --- a/src/sampleconf/mimemap +++ b/src/sampleconf/mimemap @@ -3,7 +3,7 @@ .txt = text/plain .text = text/plain -.srt = text/plain +.srt = text/x-srt .ini = text/x-ini .csv = text/x-csv @@ -18,11 +18,16 @@ .cc = text/x-c .cxx = text/x-c .hxx = text/x-c +.cs = text/x-csharp +.css = text/css +.java = text/x-java +.js = application/javascript .f = text/x-fortran .py = text/x-python .awk = application/x-awk .pl = application/x-perl .sh = application/x-shellscript +.sql = application/sql .tcl = text/x-tcl .xml = text/xml @@ -39,7 +44,7 @@ # .eml is used as an extension by several mail apps for a single message # saved in raw MIME format. Mainly used here to get Thunderbird to open an -# extracted message. +# extracted message. Also used by Windows Live Mail .eml = message/rfc822 .pdf = application/pdf @@ -147,6 +152,9 @@ .mkv = video/x-matroska .ogv = video/ogg +.flv = video/x-flv +.mp4 = video/mp4 +.ts = video/MP2T .png = image/png .jp2 = image/jp2 diff --git a/src/sampleconf/mimeview b/src/sampleconf/mimeview index b3a50dee..2049f4b3 100644 --- a/src/sampleconf/mimeview +++ b/src/sampleconf/mimeview @@ -112,9 +112,10 @@ image/x-ms-bmp = gwenview %f image/x-xpmi = gwenview %f # Opening mail messages not always works. -# - Thunderbird will only open a single-message file if it has an .emf +# - Thunderbird will only open a single-message file if it has an .eml # extension # - "sylpheed %f" seems to work ok as of version 3.3 +# - claws-mail: no can do apparently # - "kmail --view %u" works message/rfc822 = thunderbird -file %f text/x-mail = thunderbird -file %f @@ -134,6 +135,7 @@ application/x-perl = emacsclient --no-wait %f text/x-perl = emacsclient --no-wait %f application/x-shellscript = emacsclient --no-wait %f text/x-shellscript = emacsclient --no-wait %f +text/x-srt = emacsclient --no-wait %f # Or firefox -remote "openFile(%u)" text/html = firefox %u diff --git a/src/sampleconf/recoll.conf b/src/sampleconf/recoll.conf new file mode 100644 index 00000000..4a418093 --- /dev/null +++ b/src/sampleconf/recoll.conf @@ -0,0 +1,739 @@ +# Recoll main configuration file, recoll.conf + +# The XML tags in the comments are used to help produce the documentation +# from the sample/reference file, and not at all at run time, where +# comments are just comments. Edit at will. + +# This typically lives in $prefix/share/recoll/examples and provides +# default values. You can override selected parameters by adding assigments +# to ~/.recoll/recoll.conf (or $RECOLL_CONFDIR/recoll.conf) +# +# Most of the important values in this file can be set from the GUI +# configuration menus, which may be an easier approach than direct editing. + +# Parameters affecting what documents we +# index + +# Space-separated list of files or +# directories to recursively index.Default to ~ (indexes +# $HOME). You can use symbolic links in the list, they will be followed, +# independantly of the value of the followLinks variable. +topdirs = ~ + +# +# +# Files and directories which should be ignored. +# White space separated list of wildcard patterns (simple ones, not paths, +# must contain no / ), which will be tested against file and directory +# names. The list in the default configuration does not exclude hidden +# directories (names beginning with a dot), which means that it may index +# quite a few things that you do not want. On the other hand, email user +# agents like Thunderbird usually store messages in hidden directories, and +# you probably want this indexed. One possible solution is to have ".*" in +# "skippedNames", and add things like "~/.thunderbird" "~/.evolution" to +# "topdirs". Not even the file names are indexed for patterns in this +# list, see the "noContentSuffixes" variable for an alternative approach +# which indexes the file names. Can be redefined for any +# subtree. +skippedNames = #* bin CVS Cache cache* .cache caughtspam tmp \ + .thumbnails .svn \ + *~ .beagle .git .hg .bzr loop.ps .xsession-errors \ + .recoll* xapiandb recollrc recoll.conf + +# +# +# List of name endings (not necessarily dot-separated suffixes) for +# which we don't try MIME type identification, and don't uncompress or +# index content.Only the names will be indexed. This +# complements the now obsoleted recoll_noindex list from the mimemap file, +# which will go away in a future release (the move from mimemap to +# recoll.conf allows editing the list through the GUI). This is different +# from skippedNames because these are name ending matches only (not +# wildcard patterns), and the file name itself gets indexed normally. This +# can be redefined for subdirectories. +noContentSuffixes = .md5 .map \ + .o .lib .dll .a .sys .exe .com \ + .mpp .mpt .vsd \ + .img .img.gz .img.bz2 .img.xz .image .image.gz .image.bz2 .image.xz \ + .dat .bak .rdf .log.gz .log .db .msf .pid \ + ,v ~ # + +# +# +# Paths we should not go into.Space-separated list of +# wildcard expressions for filesystem paths. Can contain files and +# directories. The database and configuration directories will +# automatically be added. The expressions are matched using 'fnmatch(3)' +# with the FNM_PATHNAME flag set by default. This means that '/' characters +# must be matched explicitely. You can set 'skippedPathsFnmPathname' to 0 +# to disable the use of FNM_PATHNAME (meaning that '/*/dir3' will match +# '/dir1/dir2/dir3'). The default value contains the usual mount point for +# removable media to remind you that it is a bad idea to have Recoll work +# on these (esp. with the monitor: media gets indexed on mount, all data +# gets erased on unmount). Explicitely adding '/media/xxx' to the topdirs +# will override this. +skippedPaths = /media + +# Set to 0 to +# override use of FNM_PATHNAME for matching skipped +# paths. +#skippedPathsFnmPathname = 1 + +# +# +# skippedPaths equivalent specific to +# real time indexing.This enables having parts of the tree +# which are initially indexed but not monitored. If daemSkippedPaths is +# not set, the daemon uses skippedPaths. +#daemSkippedPaths = + + +# +# +# Space-separated list of wildcard expressions for names that should +# be ignored inside zip archives.This is used directly by +# the zip handler, and has a function similar to skippedNames, but works +# independantly. Can be redefined for subdirectories. Supported by recoll +# 1.20 and newer. See +# https://bitbucket.org/medoc/recoll/wiki/Filtering%20out%20Zip%20archive%20members +# +#zipSkippedNames = + +# Follow symbolic links during +# indexing.The default is to ignore symbolic links to avoid +# multiple indexing of linked files. No effort is made to avoid duplication +# when this option is set to true. This option can be set individually for +# each of the 'topdirs' members by using sections. It can not be changed +# below the 'topdirs' level. Links in the 'topdirs' list itself are always +# followed. +#followLinks = 0 + +# Restrictive list of +# indexed mime types.Normally not set (in which case all +# supported types are indexed). If it is set, +# only the types from the list will have their contents indexed. The names +# will be indexed anyway if indexallfilenames is set (default). MIME +# type names should be taken from the mimemap file. Can be redefined for +# subtrees. +#indexedmimetypes = + +# List of excluded MIME +# types.Lets you exclude some types from indexing. Can be +# redefined for subtrees. +#excludedmimetypes = + +# Size limit for compressed +# files.We need to decompress these in a +# temporary directory for identification, which can be wasteful in some +# cases. Limit the waste. Negative means no limit. 0 results in no +# processing of any compressed file. Default 50 MB. +compressedfilemaxkbs = 50000 + +# Size limit for text +# files.Mostly for skipping monster +# logs. Default 20 MB. +textfilemaxmbs = 20 + +# Index the file names of +# unprocessed filesIndex the names of files the contents of +# which we don't index because of an excluded or unsupported MIME +# type. +indexallfilenames = 1 + +# Use a system command +# for file MIME type guessing as a final step in file type +# identificationThis is generally useful, but will usually +# cause the indexing of many bogus 'text' files. See 'systemfilecommand' +# for the command used. +usesystemfilecommand = 1 + +# Command used to guess +# MIME types if the internal methods failsThis should be a +# "file -i" workalike. The file path will be added as a last parameter to +# the command line. 'xdg-mime' works better than the traditional 'file' +# command, and is now the configured default (with a hard-coded fallback to +# 'file') +systemfilecommand = xdg-mime query filetype + +# Decide if we process the +# Web queue.The queue is a directory where the Recoll Web +# browser plugins create the copies of visited pages. +processwebqueue = 0 + +# Page size for text +# files.If this is set, text/plain files will be divided +# into documents of approximately this size. Will reduce memory usage at +# index time and help with loading data in the preview window at query +# time. Particularly useful with very big files, such as application or +# system logs. Also see textfilemaxmbs and +# compressedfilemaxkbs. +textfilepagekbs = 1000 + +# Size limit for archive +# members.This is passed to the filters in the environment +# as RECOLL_FILTER_MAXMEMBERKB. +membermaxkbs = 50000 + + + +# Parameters affecting how we generate +# terms + +# Changing some of these parameters will imply a full +# reindex. Also, when using multiple indexes, it may not make sense +# to search indexes that don't share the values for these parameters, +# because they usually affect both search and index operations. + + +# Decide if we store +# character case and diacritics in the index.If we do, +# searches sensitive to case and diacritics can be performed, but the index +# will be bigger, and some marginal weirdness may sometimes occur. The +# default is a stripped index. When using multiple indexes for a search, +# this parameter must be defined identically for all. Changing the value +# implies an index reset. +indexStripChars = 1 + +# Decides if terms will be +# generated for numbers.For example "123", "1.5e6", +# 192.168.1.4, would not be indexed if nonumbers is set ("value123" would +# still be). Numbers are often quite interesting to search for, and this +# should probably not be set except for special situations, ie, scientific +# documents with huge amounts of numbers in them, where setting nonumbers +# will reduce the index size. This can only be set for a whole index, not +# for a subtree. +#nonumbers = 0 + +# Determines if we index +# 'coworker' also when the input is 'co-worker'.This is new +# in version 1.22, and on by default. Setting the variable to off allows +# restoring the previous behaviour. +#dehyphenate = 1 + +# Decides if specific East Asian +# (Chinese Korean Japanese) characters/word splitting is turned +# off.This will save a small amount of CPU if you have no CJK +# documents. If your document base does include such text but you are not +# interested in searching it, setting nocjk may be a +# significant time and space saver. +#nocjk = 0 + +# This lets you adjust the size of +# n-grams used for indexing CJK text.The default value of 2 is +# probably appropriate in most cases. A value of 3 would allow more precision +# and efficiency on longer words, but the index will be approximately twice +# as large. +#cjkngramlen = 2 + +# +# +# Languages for which to create stemming expansion +# data.Stemmer names can be found by executing 'recollindex +# -l', or this can also be set from a list in the GUI. +indexstemminglanguages = english + +# Default character +# set.This is used for files which do not contain a +# character set definition (e.g.: text/plain). Values found inside files, +# e.g. a 'charset' tag in HTML documents, will override it. If this is not +# set, the default character set is the one defined by the NLS environment +# ($LC_ALL, $LC_CTYPE, $LANG), or ultimately iso-8859-1 (cp-1252 in fact). +# If for some reason you want a general default which does not match your +# LANG and is not 8859-1, use this variable. This can be redefined for any +# sub-directory. +#defaultcharset = iso-8859-1 + +# A list of characters, +# encoded in UTF-8, which should be handled specially +# when converting text to unaccented lowercase.For +# example, in Swedish, the letter a with diaeresis has full alphabet +# citizenship and should not be turned into an a. +# Each element in the space-separated list has the special character as +# first element and the translation following. The handling of both the +# lowercase and upper-case versions of a character should be specified, as +# appartenance to the list will turn-off both standard accent and case +# processing. The value is global and affects both indexing and querying. +# Examples: +# Swedish: +# unac_except_trans = ää Ää öö Öö üü Üü ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl åå Ã…Ã¥ +# . German: +# unac_except_trans = ää Ää öö Öö üü Üü ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl +# In French, you probably want to decompose oe and ae and nobody would type +# a German ß +# unac_except_trans = ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl +# . The default for all until someone protests follows. These decompositions +# are not performed by unac, but it is unlikely that someone would type the +# composed forms in a search. +# unac_except_trans = ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl +unac_except_trans = ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl + +# Overrides the default +# character set for email messages which don't specify +# one.This is mainly useful for readpst (libpst) dumps, +# which are utf-8 but do not say so. +#maildefcharset= + +# Set fields on all files +# (usually of a specific fs area).Syntax is the usual: +# name = value ; attr1 = val1 ; [...] +# value is empty so this needs an initial semi-colon. This is useful, e.g., +# for setting the rclaptg field for application selection inside +# mimeview. +#[/some/app/directory] +#localfields = ; rclaptg = someapp; otherfield = somevalue + +# Use mtime instead of +# ctime to test if a file has been modified.The time is used +# in addition to the size, which is always used. +# Setting this can reduce re-indexing on systems where extended attributes +# are used (by some other application), but not indexed, because changing +# extended attributes only affects ctime. +# Notes: +# - This may prevent detection of change in some marginal file rename cases +# (the target would need to have the same size and mtime). +# - You should probably also set noxattrfields to 1 in this case, except if +# you still prefer to perform xattr indexing, for example if the local +# file update pattern makes it of value (as in general, there is a risk +# for pure extended attributes updates without file modification to go +# undetected). Perform a full index reset after changing this. +# +testmodifusemtime = 0 + +# Disable extended attributes +# conversion to metadata fields.This probably needs to be +# set if testmodifusemtime is set. +noxattrfields = 0 + +# Define commands to +# gather external metadata, e.g. tmsu tags. +# There can be several entries, separated by semi-colons, each defining +# which field name the data goes into and the command to use. Don't forget the +# initial semi-colon. All the field names must be different. You can use +# aliases in the "field" file if necessary. +# As a not too pretty hack conceded to convenience, any field name +# beginning with "rclmulti" will be taken as an indication that the command +# returns multiple field values inside a text blob formatted as a recoll +# configuration file ("fieldname = fieldvalue" lines). The rclmultixx name +# will be ignored, and field names and values will be parsed from the data. +# Example: metadatacmds = ; tags = tmsu tags %f; rclmulti1 = cmdOutputsConf %f +# +#[/some/area/of/the/fs] +#metadatacmds = ; tags = tmsu tags %f; rclmulti1 = cmdOutputsConf %f + + + + +# Parameters affecting where and how we store +# things + +# +# +# Top directory for Recoll data.Recoll data +# directories are normally located relative to the configuration directory +# (e.g. ~/.recoll/xapiandb, ~/.recoll/mboxcache). If 'cachedir' is set, the +# directories are stored under the specified value instead (e.g. if +# cachedir is ~/.cache/recoll, the default dbdir would be +# ~/.cache/recoll/xapiandb). This affects dbdir, webcachedir, +# mboxcachedir, aspellDicDir, which can still be individually specified to +# override cachedir. Note that if you have multiple configurations, each +# must have a different cachedir, there is no automatic computation of a +# subpath under cachedir. +#cachedir = ~/.cache/recoll + +# Maximum file system occupation +# over which we stop indexing.The value is a percentage, +# corresponding to what the "Capacity" df output column shows. The default +# value is 0, meaning no checking. +maxfsoccuppc = 0 + +# Xapian database directory +# location.This will be created on first indexing. If the +# value is not an absolute path, it will be interpreted as relative to +# cachedir if set, or the configuration directory (-c argument or +# $RECOLL_CONFDIR). If nothing is specified, the default is then +# ~/.recoll/xapiandb/ +dbdir = xapiandb + +# +# +# Name of the scratch file where the indexer process updates its +# status.Default: idxstatus.txt inside the configuration +# directory. +#idxstatusfile = idxstatus.txt + +# +# +# Directory location for storing mbox message offsets cache +# files.This is normally 'mboxcache' under cachedir if set, +# or else under the configuration directory, but it may be useful to share +# a directory between different configurations. +#mboxcachedir = mboxcache + +# +# +# Minimum mbox file size over which we cache the offsets. +# There is really no sense in caching offsets for small files. The +# default is 5 MB. +#mboxcacheminmbs = 5 + +# +# +# Directory where we store the archived web pages. +# This is only used by the web history indexing code +# Default: cachedir/webcache if cachedir is set, else +# $RECOLL_CONFDIR/webcache +webcachedir = webcache + +# +# Maximum size in MB of the Web archive. +# This is only used by the web history indexing code. +# Default: 40 MB. +# Reducing the size will not physically truncate the file. +webcachemaxmbs = 40 + +# +# +# The path to the Web indexing queue.This is +# hard-coded in the plugin as ~/.recollweb/ToIndex so there should be no +# need or possibility to change it. +#webqueuedir = ~/.recollweb/ToIndex + +# +# +# Aspell dictionary storage directory location. The +# aspell dictionary (aspdict.(lang).rws) is normally stored in the +# directory specified by cachedir if set, or under the configuration +# directory. +#aspellDicDir = + +# +# +# Directory location for executable input handlers.If +# RECOLL_FILTERSDIR is set in the environment, we use it instead. Defaults +# to $prefix/share/recoll/filters. Can be redefined for +# subdirectories. +#filtersdir = /path/to/my/filters + +# +# +# Directory location for icons.The only reason to +# change this would be if you want to change the icons displayed in the +# result list. Defaults to $prefix/share/recoll/images +#iconsdir = /path/to/my/icons + +# Parameters affecting indexing performance and +# resource usage + +# +# +# Threshold (megabytes of new data) where we flush from memory to +# disk index. Setting this allows some control over memory +# usage by the indexer process. A value of 0 means no explicit flushing, +# which lets Xapian perform its own thing, meaning flushing every +# $XAPIAN_FLUSH_THRESHOLD documents created, modified or deleted: as memory +# usage depends on average document size, not only document count, the +# Xapian approach is is not very useful, and you should let Recoll manage +# the flushes. The program compiled value is 0. The configured default +# value (from this file) is 10 MB, and will be too low in many cases (it is +# chosen to conserve memory). If you are looking +# for maximum speed, you may want to experiment with values between 20 and +# 200. In my experience, values beyond this are always counterproductive. If +# you find otherwise, please drop me a note. +idxflushmb = 10 + +# +# +# Maximum external filter execution time in +# seconds.Default 1200 (20mn). Set to 0 for no limit. This +# is mainly to avoid infinite loops in postscript files +# (loop.ps) +filtermaxseconds = 1200 + +# +# +# Maximum virtual memory space for filter processes +# (setrlimit(RLIMIT_AS)), in megabytes. Note that this +# includes any mapped libs (there is no reliable Linux way to limit the +# data space only), so we need to be a bit generous here. Anything over +# 2000 will be ignored on 32 bits machines. +filtermaxmbytes = 2000 + +# +# +# Stage input queues configuration. There are three +# internal queues in the indexing pipeline stages (file data extraction, +# terms generation, index update). This parameter defines the queue depths +# for each stage (three integer values). If a value of -1 is given for a +# given stage, no queue is used, and the thread will go on performing the +# next stage. In practise, deep queues have not been shown to increase +# performance. Default: a value of 0 for the first queue tells Recoll to +# perform autoconfiguration based on the detected number of CPUs (no need +# for the two other values in this case). Use thrQSizes = -1 -1 -1 to +# disable multithreading entirely. +thrQSizes = 0 + +# +# +# Number of threads used for each indexing stage. The +# three stages are: file data extraction, terms generation, index +# update). The use of the counts is also controlled by some special values +# in thrQSizes: if the first queue depth is 0, all counts are ignored +# (autoconfigured); if a value of -1 is used for a queue depth, the +# corresponding thread count is ignored. It makes no sense to use a value +# other than 1 for the last stage because updating the Xapian index is +# necessarily single-threaded (and protected by a mutex). +#thrTCounts = 4 2 1 + + +# Miscellaneous parameters + +# +# +# Log file verbosity 1-6. A value of 2 will print +# only errors and warnings. 3 will print information like document updates, +# 4 is quite verbose and 6 very verbose. +loglevel = 3 + +# +# +# Log file destination. Use 'stderr' (default) to write to the +# console. +logfilename = stderr + +# +# +# Override loglevel for the indexer. +#idxloglevel = 3 + +# +# +# Override logfilename for the indexer. +#idxlogfilename = stderr + +# +# +# Override loglevel for the indexer in real time +# mode.The default is to use the idx... values if set, else +# the log... values. +#daemloglevel = 3 + +# +# +# Override logfilename for the indexer in real time +# mode.The default is to use the idx... values if set, else +# the log... values. +#daemlogfilename = /dev/null + +# +# +# Indexing process current directory. The input +# handlers sometimes leave temporary files in the current directory, so it +# makes sense to have recollindex chdir to some temporary directory. If the +# value is empty, the current directory is not changed. If the +# value is (literal) tmp, we use the temporary directory as set by the +# environment (RECOLL_TMPDIR else TMPDIR else /tmp). If the value is an +# absolute path to a directory, we go there. +idxrundir = tmp + +# +# +# Script used to heuristically check if we need to retry indexing +# files which previously failed. The default script checks +# the modified dates on /usr/bin and /usr/local/bin. A relative path will +# be looked up in the filters dirs, then in the path. Use an absolute path +# to do otherwise. +checkneedretryindexscript = rclcheckneedretry.sh + +# +# +# Additional places to search for helper executables. +# This is only used on Windows for now. +#recollhelperpath = c:/someprog/bin;c:/someotherprog/bin + +# +# +# Length of abstracts we store while indexing. +# Recoll stores an abstract for each indexed file. +# The text can come from an actual 'abstract' section in the +# document or will just be the beginning of the document. It is stored in +# the index so that it can be displayed inside the result lists without +# decoding the original file. The idxabsmlen parameter +# defines the size of the stored abstract. The default value is 250 +# bytes. The search interface gives you the choice to display this stored +# text or a synthetic abstract built by extracting text around the search +# terms. If you always prefer the synthetic abstract, you can reduce this +# value and save a little space. +#idxabsmlen = 250 + +# +# +# Truncation length of stored metadata fields.This +# does not affect indexing (the whole field is processed anyway), just the +# amount of data stored in the index for the purpose of displaying fields +# inside result lists or previews. The default value is 150 bytes which +# may be too low if you have custom fields. +#idxmetastoredlen = 150 + +# +# +# Language definitions to use when creating the aspell +# dictionary.The value must match a set of aspell language +# definition files. You can type "aspell dicts" to see a list The default +# if this is not set is to use the NLS environment to guess the +# value. +#aspellLanguage = en + +# +# +# Additional option and parameter to aspell dictionary creation +# command.Some aspell packages may need an additional option +# (e.g. on Debian Jessie: --local-data-dir=/usr/lib/aspell). See Debian bug +# 772415. +#aspellAddCreateParam = --local-data-dir=/usr/lib/aspell + +# +# +# Set this to have a look at aspell dictionary creation +# errors.There are always many, so this is mostly for +# debugging. +#aspellKeepStderr = 1 + +# +# +# Disable aspell use.The aspell dictionary generation +# takes time, and some combinations of aspell version, language, and local +# terms, result in aspell crashing, so it sometimes makes sense to just +# disable the thing. +#noaspell = 1 + +# +# +# Auxiliary database update interval.The real time +# indexer only updates the auxiliary databases (stemdb, aspell) +# periodically, because it would be too costly to do it for every document +# change. The default period is one hour. +#monauxinterval = 3600 + +# +# +# Minimum interval (seconds) between processings of the indexing +# queue.The real time indexer does not process each event +# when it comes in, but lets the queue accumulate, to diminish overhead and +# to aggregate multiple events affecting the same file. Default 30 +# S. +#monixinterval = 30 + +# +# +# Timing parameters for the real time indexing. +# Definitions for files which get a longer delay before reindexing +# is allowed. This is for fast-changing files, that should only be +# reindexed once in a while. A list of wildcardPattern:seconds pairs. The +# patterns are matched with fnmatch(pattern, path, 0) You can quote entries +# containing white space with double quotes (quote the whole entry, not the +# pattern). The default is empty. +# Example: mondelaypatterns = *.log:20 "*with spaces.*:30" +#mondelaypatterns = *.log:20 "*with spaces.*:30" + +# +# +# ionice class for the real time indexing process +# On platforms where this is supported. The default value is +# 3. +# monioniceclass = 3 + +# +# +# ionice class parameter for the real time indexing process. +# On platforms where this is supported. The default is +# empty. +#monioniceclassdata = + + + +# Query-time parameters (no impact on the +# index) + +# +# +# auto-trigger diacritics sensitivity (raw index only). +# IF the index is not stripped, decide if we automatically trigger +# diacritics sensitivity if the search term has accented characters (not in +# unac_except_trans). Else you need to use the query language and the "D" +# modifier to specify diacritics sensitivity. Default is no. +autodiacsens = 0 + +# +# +# auto-trigger case sensitivity (raw index only).IF +# the index is not stripped (see indexStripChars), decide if we +# automatically trigger character case sensitivity if the search term has +# upper-case characters in any but the first position. Else you need to use +# the query language and the "C" modifier to specify character-case +# sensitivity. Default is yes. +autocasesens = 1 + +# Maximum query expansion count +# for a single term (e.g.: when using wildcards).This only +# affects queries, not indexing. We used to not limit this at all (except +# for filenames where the limit was too low at 1000), but it is +# unreasonable with a big index. Default 10000. +maxTermExpand = 10000 + +# Maximum number of clauses +# we add to a single Xapian query.This only affects queries, +# not indexing. In some cases, the result of term expansion can be +# multiplicative, and we want to avoid eating all the memory. Default +# 50000. +maxXapianClauses = 50000 + +# +# +# Maximum number of positions we walk while populating a snippet for +# the result list.The default of 1,000,000 may be +# insufficient for very big documents, the consequence would be snippets +# with possibly meaning-altering missing words. +snippetMaxPosWalk = 1000000 + + +# Parameters for the PDF input script + +# +# +# Attempt OCR of PDF files with no text content if both tesseract and +# pdftoppm are installed.The default is off because OCR is so +# very slow. +#pdfocr = 0 + +# +# +# Enable PDF attachment extraction by executing pdftk (if +# available).This is +# normally disabled, because it does slow down PDF indexing a bit even if +# not one attachment is ever found. +#pdfattach = 0 + + +# Parameters set for specific +# locations + +# You could specify different parameters for a subdirectory like this: +#[~/hungariandocs/plain] +#defaultcharset = iso-8859-2 + +[/usr/share/man] +followLinks = 1 + +# +# +# Enable thunderbird/mozilla-seamonkey mbox format quirks +# Set this for the directory where the email mbox files are +# stored. +[~/.thunderbird] +mhmboxquirks = tbird +[~/.mozilla] +mhmboxquirks = tbird + +# pidgin / purple directories for irc chats have names beginning with # +[~/.purple] +skippedNames = diff --git a/src/sampleconf/recoll.conf.in b/src/sampleconf/recoll.conf.in deleted file mode 100644 index ef1d8953..00000000 --- a/src/sampleconf/recoll.conf.in +++ /dev/null @@ -1,395 +0,0 @@ -# (C) 2004 J.F.Dockes. License: GPL -# -# Recoll default configuration file. This typically lives in -# @prefix@/share/recoll/examples and provides default values. You can -# override selected parameters by adding assigments to -# ~/.recoll/recoll.conf (or $RECOLL_CONFDIR/recoll.conf) -# -# Almost all values in this file can be set from the GUI configuration -# menus, which may be an easier approach than direct editing. -# - -# Space-separated list of directories to index. Next line indexes $HOME -topdirs = ~ - -# Wildcard expressions for names of files and directories that we should -# ignore. If you need index mozilla/thunderbird mail folders, don't put -# ".*" in there (as was the case with an older sample config) -# These are simple names, not paths (must contain no / ) -skippedNames = #* bin CVS Cache cache* .cache caughtspam tmp \ - .thumbnails .svn \ - *~ .beagle .git .hg .bzr loop.ps .xsession-errors \ - .recoll* xapiandb recollrc recoll.conf - -# Wildcard expressions for paths we shouldn't go into. The database and -# configuration directories will automatically be added in there. -# We add the usual mount point for removable media by default to remind -# people that it is a bad idea to naively have recoll work on these -# (esp. with the monitor: media gets indexed on mount, all data gets erased -# on unmount...). Typically the presence of /media is mostly a reminder, it -# would only have effect for someone who's indexing / ... -# Explicitely adding /media/xxx to the topdirs will override this. -skippedPaths = /media - -# List of suffixes for which we don't try mime type identification (and -# don't uncompress or index content obviously). This complements the now -# obsoleted mimemap recoll_noindex list, which will go away in a future -# release (the move from mimemap to recoll.conf allows editing the list -# through the GUI). This is different from skippedNames because these are -# name ending matches only (not wildcard patterns), and the file name -# itself gets indexed normally. -noContentSuffixes = .md5 .map \ - .o .lib .dll .a .sys .exe .com \ - .mpp .mpt .vsd \ - .img .img.gz .img.bz2 .img.xz .image .image.gz .image.bz2 .image.xz \ - .dat .bak .rdf .log.gz .log .db .msf .pid \ - ,v ~ # - -# Same for real time indexing. The idea here is that there is stuff that -# you might want to initially index but not monitor. If daemSkippedPaths is -# not set, the daemon uses skippedPaths. -#daemSkippedPaths = - -# Recoll uses FNM_PATHNAME by default when matching skipped paths, which -# means that /dir1/dir2/dir3 is not matched by */dir3. Can't change the -# default now, but you can set the following variable to 0 to disable the -# use of FNM_PATHNAME (see fnmatch(3) man page) -#skippedPathsFnmPathname = 1 - -# Option to follow symbolic links. We normally don't, to avoid duplicated -# indexing (in any case, no effort is made to identify or avoid multiple -# indexing of linked files) -#followLinks = 0 - -# Debug messages. 2 is errors/warnings only. 3 information like doc -# updates, 4 is quite verbose and 6 very verbose -loglevel = 3 -logfilename = stderr - -# Specific versions of log file name and level for the indexing daemon. The -# default is to use the above values. -# daemloglevel = 3 -# daemlogfilename = /dev/null - -# Run directory for the indexing process. The filters sometimes leave -# garbage in the current directory, so it makes sense to have recollindex -# chdir to some garbage bin. 3 possible values: -# - (literal) tmp : go to temp dir as set by env (RECOLL_TMPDIR else -# TMPDIR else /tmp) -# - Empty: stay where started -# - Absolute path value: go there. -idxrundir = tmp - -# Decide if we store character case and diacritics in the index. If we do, -# searches sensitive to case and diacritics can be performed, but the index -# will be bigger, and some marginal weirdness may sometimes occur. We -# default to a stripped index for now. -indexStripChars = 1 - -# IF the index is not stripped. Decide if we automatically trigger -# diacritics sensitivity if the search term has accented characters (not in -# unac_except_trans). Else you need to use the query language and the "D" -# modifier to specify diacritics sensitivity. Default is no. -autodiacsens = 0 - -# IF the index is not stripped. Decide if we automatically trigger -# character case sensitivity if the search term has upper-case characters -# in any but the first position. Else you need to use the query language -# and the "C" modifier to specify character-case sensitivity. Default is -# yes. -autocasesens = 1 - -# Languages for which to build stemming databases at the end of -# indexing. Stemmer names can be found on http://www.xapian.org -# The flag to perform stem expansion at query time is now set from the GUI -indexstemminglanguages = english - -# Default character set. Values found inside files, ie content tag in html -# documents, will override this. It can be specified per directory (see -# below). Used when converting to utf-8 (internal storage format), so it -# may be quite important for pure text files. -# The default used to be set to iso8859-1, but we now take it from the nls -# environment (LC_ALL/LC_CTYPE/LANG). The ultimate hardwired default is -# still 8859-1. If for some reason you want a general default which doesnt -# match your LANG and is not 8859-1, set it here. -# defaultcharset = iso-8859-1 - -# A list of characters, encoded in UTF-8, which should be handled specially -# when converting text to unaccented lowercase. For example, in Swedish, -# the letter a with diaeresis has full alphabet citizenship and should not -# be turned into an a. -# Each element in the space-separated list has the special character as -# first element and the translation following. The handling of both the -# lowercase and upper-case versions of a character should be specified, as -# appartenance to the list will turn-off both standard accent and case -# processing. Examples: -# Swedish: -# unac_except_trans = ää Ää öö Öö üü Üü ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl åå Ã…Ã¥ -# German: -# unac_except_trans = ää Ää öö Öö üü Üü ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl -# In French, you probably want to decompose oe and ae and nobody would type -# a German ß -# unac_except_trans = ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl -# Reasonable default for all until someone protests. These decompositions -# are not performed by unac, but I cant imagine someone typing the composed -# forms in a search. -unac_except_trans = ßss Å“oe Å’oe æae Æae ffff ï¬fi flfl - -# Maximum expansion count for a single term (ie: when using wildcards). -# We used to not limit this at all (except for filenames where the limit -# was too low at 1000), but it is unreasonable with a big index. -# Default 10 000 -maxTermExpand = 10000 - -# Maximum number of clauses we add to a single Xapian query. In some cases, -# the result of term expansion can be multiplicative, and we want to avoid -# eating all the memory. Default 50000 -maxXapianClauses = 50000 - -# Where to store the database (directory). This may be an absolute path, -# else it is taken as relative to the configuration directory (-c argument -# or $RECOLL_CONFDIR). -# If nothing is specified, the default is then ~/.recoll/xapiandb/ -dbdir = xapiandb - -# Indexing process threads configuration. If Recoll is configured for -# multithreading, this defines what queues are active and how many threads -# to start for any of them. The default values were found good on a -# quad-core processor. The three steps are file conversion, term extraction -# and conversion and Xapian index update. The three queue values define the -# max number of jobs waiting on one of the corresponding queues. Setting a -# value to -1 disables a queue (replaced by a direct call). The thrTcounts -# values define the number of threads to start for each queue. The last -# value can only be one (as Xapian is single-threaded). -# If the first element in thrQSizes is 0, recollindex will attempt to set -# roughly guestimated values based on the number of CPUs. -# -# The following are the best setup on my core i5 system (4 cores, no -# hyperthreading, multiple disks). -#thrQSizes = 2 2 2 -#thrTCounts = 4 2 1 -# The default is to let recoll guess. -thrQSizes = 0 - -# Maximum file system occupation before we stop indexing. The default value -# is 0, meaning no checking. The value is a percentage, corresponding to -# what the "Capacity" df output column shows. -maxfsoccuppc = 0 - -# Threshold (megabytes of new data) where we flush from memory to disk -# index. Setting this (ie to 10) can help control memory usage. -# -# A value of 0 means no explicit flushing, which lets Xapian perform its -# own thing, meaning flushing every XAPIAN_FLUSH_THRESHOLD documents -# created, modified or deleted. XAPIAN_FLUSH_THRESHOLD is an environment -# variable. As memory usage depends on average document size, not only -# document count, this is not very useful. -# -# The default value of 10 MB may be a bit low. If you are looking for -# maximum speed, you may want to experiment with values between 20 and -# 80. In my experience, values beyond 100 are always counterproductive. If -# you find otherwise, please drop me a note. -idxflushmb = 10 - -# Place to search for executable filters. If RECOLL_FILTERSDIR is set in -# the environment, we use it instead -filtersdir = @prefix@/share/recoll/filters - -# Place to search for icons. The only reason to change this would be if you -# want to change the icons displayed in the result list -iconsdir = @prefix@/share/recoll/images - -# Should we use the system's 'file -i' command as a final step in file type -# identification ? This may be useful, but will usually cause the -# indexation of many bogus 'text' files -usesystemfilecommand = 1 -# Actual command to use as "file -i" workalike. xdg-mime works fine here. -# The file path will be added as a last parameter to the command line. If -# that's not what your preferred command would like, use an intermediary -# script. -# systemfilecommand = xdg-mime query filetype -# systemfilecommand = file -i filetype - -# Should we index the file names of files with mime types we don't -# know? (we can otherwise just ignore them) -indexallfilenames = 1 - -# A restrictive list of indexed mime types. Normally not set. If it is set, -# only the types from the list will have their contents indexed (the names -# will be indexed anyway if indexallfilenames is set as by default). Mime -# type names should be taken from the mimemap file. -# -# indexedmimetypes = - -# An excluded list of mime types. It can be redefined in subdirectories, -# so can be used to locally exclude some types. -# -# excludededmimetypes = - -# -# Size limit for archive members. This is passed to the filters in the -# environment as RECOLL_FILTER_MAXMEMBERKB -# -membermaxkbs = 50000 - -# Size limit for compressed files. We need to decompress these in a -# temporary directory for identification, which can be wasteful in some -# cases. Limit the waste. Negative means no limit. 0 results in no -# processing of any compressed file. Used to be -1 by default. -compressedfilemaxkbs = 20000 - -# Size limit for text files. This is for skipping monster logs -textfilemaxmbs = 20 - -# Page size for text files. If this is set, text/plain files will be -# divided into documents of approximately this size. May be useful to -# access pieces of big text files which would be problematic to load as one -# piece into the preview window. Might be useful for big logs -textfilepagekbs = 1000 - -# Maximum external filter execution time. Default 20mn. This is mainly -# to avoid infinite loops in postscript files (loop.ps) -filtermaxseconds = 1200 -# Maximum virtual memory space for filter process (setrlimit(RLIMIT_AS)), -# in megabytes. Note that this includes any mapped libs (there is no -# reliable Linux way to limit the data space only), so we need to be a -# bit generous here. Anything over 2000 will be ignored on 32 bits machines. -filtermaxmbytes = 2000 - -# Length of abstracts we store while indexing. Longer will make for a -# bigger db -# idxabsmlen = 250 - -# Truncation length of stored metadata fields. This does not affect -# indexing, just what can be displayed inside results. -# idxmetastoredlen = 150 - -# Language definitions to use when creating the aspell dictionary. -# The value must match a set of aspell language definition files. -# You can type "aspell config" to see where these are installed. -# The default if this is not set is to use the NLS environment to guess the -# value -# aspellLanguage = en - -# Disabling aspell use. The aspell dictionary generation takes some time, -# and some combinations of aspell version, language, and local terms, -# result in aspell dumping core each time. You can disable the aspell -# dictionary generation by setting the following variable: -# noaspell = 1 - -# Timing parameters for the real time mode: -# -# Seconds between auxiliary databases updates (stemdb, aspell): -# monauxinterval = 3600 -# -# Resting time (seconds) during which we let the queue accumulate, in hope -# that events to the same file will merge, before we start indexing: -# monixinterval = 30 -# -# Definitions for files which get a longer delay before reindexing is -# allowed. This is for fast-changing files, that should only be reindexed -# once in a while. A list of wildcardPattern:seconds pairs. The patterns -# are matched with fnmatch(pattern, path, 0) You can quote entries containing -# white space with double quotes. The default is empty, here follows an -# example: -# mondelaypatterns = *.log:20 "*with spaces.*:30" - -# ionice class for monitor (on platforms where this is supported) -# monioniceclass = 3 -# ionice class param for monitor (on platforms where this is supported) -# monioniceclassdata = - -# If this is set, process the directory where the Recoll Web browser plugins -# copy visited pages for indexing. -processwebqueue = 0 -# The path to the Web indexing queue. This is hard-coded in the -# plugin as ~/.recollweb/ToIndex so there should be no need to change it. -#webqueuedir = ~/.recollweb/ToIndex -# This is only used by the web history indexing code, and -# defines where the cache for visited pages will live. Default: -# $RECOLL_CONFDIR/webcache -webcachedir = webcache -# This is only used by the web history indexing code, and -# defines the maximum size for the web page cache. Default: 40 MB. -# ** Quite unfortunately, this is only used when creating the file, you -# need to delete the cache for a change to be taken into account ** -webcachemaxmbs = 40 - -# The directory where mbox message offsets cache files are held. This is -# normally $RECOLL_CONFDIR/mboxcache, but it may be useful to share a -# directory between different configurations. -#mboxcachedir = mboxcache - -# The minimum mbox file size over which we cache the offsets. There is -# really no sense in caching offsets for small files. The default is 5 MB. -#mboxcacheminmbs = 5 - -# Maximum number of positions we walk while populating a snippet for the -# result list. The default of 1 000 000 may be insufficient for big -# documents, the consequence would be snippets with possibly -# meaning-altering missing words. -snippetMaxPosWalk = 1000000 - -# Use mtime instead of default ctime to determine if a file has been -# modified (in addition to size, which is always used). -# Setting this can reduce re-indexing on systems where extended attributes -# are used (by some other applications), but not indexed (changing -# ext. attrs. only affects ctime). -# Notes: -# - this may prevent detection of change in some marginal file rename cases -# (the target would need to have the same size and mtime). -# - You should probably also set noxattrfields to 1 in this case, except if -# you still prefer to perform xattr indexing, for example if the local -# file update pattern makes it of value (as in general, there is a risk -# for pure extended attributes updates without file modification to go -# undetected). Perform a full index reset after changing this. -testmodifusemtime = 0 - -# Script used to heuristically check if we need to retry indexing files -# which previously failed. The default script checks the modified dates on -# /usr/bin and /usr/local/bin. A relative path will be looked up in the -# filters dirs, then in the path. Use an absolute path to do otherwise. -checkneedretryindexscript = rclcheckneedretry.sh - -# Disable extended attributes conversion to metadata fields. This probably -# needs to be set if testmodifusemtime is set. -noxattrfields = 0 - -# You could specify different parameters for a subdirectory like this: -#[~/hungariandocs/plain] -#defaultcharset = iso-8859-2 - -# You can set fields on all files of a specific fs area. (rclaptg can be -# used for application selection inside mimeview). -# Syntax is the usual name = value ; attr1 = val1 ; ... with an empty value -# so needs initial semi-colon -#[/some/app/directory] -#localfields = ; rclaptg = someapp; otherfield = somevalue - -# It's also possible to execute external commands to gather external -# metadata, for example tmsu tags. -# There can be several entries, separated by semi-colons, each defining -# which field name the data goes into and the command to use. Don't forget the -# initial semi-colon. All the field names must be different. You can use -# aliases in the "field" file if necessary. -# As a not too pretty hack conceded to convenience, any field name -# beginning with "rclmulti" will be taken as an indication that the command -# returns multiple field values inside a text blob formatted as a recoll -# configuration file ("fieldname = fieldvalue" lines). The rclmultixx name -# will be ignored, and field names and values will be parsed from the data. -#[/some/area/of/the/fs] -#metadatacmds = ; tags = tmsu tags %f; rclmulti1 = cmdOutputsConf %f - -[/usr/share/man] -followLinks = 1 - -# Enable thunderbird mbox format quirks where appropriate, and same for -# mozilla/seamonkey -[~/.thunderbird] -mhmboxquirks = tbird -[~/.mozilla] -mhmboxquirks = tbird - -# pidgin / purple directories for irc chats have names beginning with # -[~/.purple] -skippedNames = diff --git a/src/unac/unac.c b/src/unac/unac.c index 8356e6ae..60b91098 100644 --- a/src/unac/unac.c +++ b/src/unac/unac.c @@ -16,23 +16,25 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef HAVE_CONFIG_H -#ifdef RECOLL_DATADIR +#ifdef BUILDING_RECOLL #include "autoconfig.h" #else #include "config.h" #endif /* RECOLL */ -#endif /* HAVE_CONFIG_H */ -#ifdef RECOLL_DATADIR +#ifdef BUILDING_RECOLL /* Yes, recoll unac is actually c++, lets face modernity, I will not be caught writing another binary search */ #include #include #include #include -#include "unordered_defs.h" +#include +#include +#include + using std::string; +using std::vector; #include "smallut.h" @@ -42,17 +44,16 @@ using std::string; instead according to some local rule. There will usually be very few of them, but they must be looked up for every translated char. */ -STD_UNORDERED_MAP except_trans; +std::unordered_map except_trans; static inline bool is_except_char(unsigned short c, string& trans) { - STD_UNORDERED_MAP::const_iterator it - = except_trans.find(c); + auto it = except_trans.find(c); if (it == except_trans.end()) return false; trans = it->second; return true; } -#endif /* RECOLL_DATADIR */ +#endif /* BUILDING_RECOLL*/ /* * If configure.in has not defined this symbol, assume const. It @@ -74,7 +75,6 @@ static inline bool is_except_char(unsigned short c, string& trans) #include #include #endif /* HAVE_VSNPRINTF */ -#include #include "unac.h" #include "unac_version.h" @@ -14170,9 +14170,9 @@ int unacmaybefold_string_utf16(const char* in, size_t in_length, char** outp, size_t* out_lengthp, int what) { char* out; - int out_size; - int out_length; - unsigned int i; + size_t out_size; + size_t out_length; + size_t i; out_size = in_length > 0 ? in_length : 1024; @@ -14190,13 +14190,13 @@ int unacmaybefold_string_utf16(const char* in, size_t in_length, for(i = 0; i < in_length; i += 2) { unsigned short c; unsigned short* p; - int l; - int k; + size_t l; + size_t k; c = (in[i] << 8) | (in[i + 1] & 0xff); /* * Lookup the tables for decomposition information */ -#ifdef RECOLL_DATADIR +#ifdef BUILDING_RECOLL // Exception unac/fold values set by user. There should be 3 arrays for // unac/fold/unac+fold. For now there is only one array, which used to // be set for unac+fold, and is mostly or only used to prevent diacritics @@ -14219,11 +14219,11 @@ int unacmaybefold_string_utf16(const char* in, size_t in_length, l = trans.size() / 2; } } else { -#endif /* RECOLL_DATADIR */ +#endif /* BUILDING_RECOLL */ unac_uf_char_utf16_(c, p, l, what) -#ifdef RECOLL_DATADIR +#ifdef BUILDING_RECOLL } -#endif /* RECOLL_DATADIR */ +#endif /* BUILDING_RECOLL */ /* * Explain what's done in great detail @@ -14236,7 +14236,7 @@ int unacmaybefold_string_utf16(const char* in, size_t in_length, if(l == 0) { DEBUG_APPEND("untouched\n"); } else { - int i; + size_t i; for(i = 0; i < l; i++) DEBUG_APPEND("0x%04x ", p[i]); DEBUG_APPEND("\n"); @@ -14312,14 +14312,7 @@ int fold_string_utf16(const char* in, size_t in_length, static const char *utf16be = "UTF-16BE"; static iconv_t u8tou16_cd = (iconv_t)-1; static iconv_t u16tou8_cd = (iconv_t)-1; -static pthread_mutex_t o_unac_mutex; -static int unac_mutex_is_init; -// Call this or take your chances with the auto init. -void unac_init_mt() -{ - pthread_mutex_init(&o_unac_mutex, 0); - unac_mutex_is_init = 1; -} +static std::mutex o_unac_mutex; /* * Convert buffer containing string encoded in charset into @@ -14341,14 +14334,7 @@ static int convert(const char* from, const char* to, int from_utf16, from_utf8, to_utf16, to_utf8, u8tou16, u16tou8; const char space[] = { 0x00, 0x20 }; - /* Note: better call explicit unac_init_mt() before starting threads than - rely on this. - */ - if (unac_mutex_is_init == 0) { - pthread_mutex_init(&o_unac_mutex, 0); - unac_mutex_is_init = 1; - } - pthread_mutex_lock(&o_unac_mutex); + std::unique_lock lock(o_unac_mutex); if (!strcmp(utf16be, from)) { from_utf8 = 0; @@ -14436,10 +14422,11 @@ static int convert(const char* from, const char* to, const char* tmp = space; size_t tmp_length = 2; if(iconv(cd, (ICONV_CONST char **) &tmp, &tmp_length, &out, &out_remain) == (size_t)-1) { - if(errno == E2BIG) + if(errno == E2BIG) { /* fall thru to the E2BIG case below */; - else - goto out; + } else { + goto out; + } } else { /* The offending character was replaced by a SPACE, skip it. */ in += 2; @@ -14455,7 +14442,7 @@ static int convert(const char* from, const char* to, /* * The output does not fit in the current out buffer, enlarge it. */ - int length = out - out_base; + size_t length = out - out_base; out_size *= 2; { char *saved = out_base; @@ -14491,7 +14478,6 @@ static int convert(const char* from, const char* to, ret = 0; out: - pthread_mutex_unlock(&o_unac_mutex); return ret; } @@ -14561,7 +14547,7 @@ const char* unac_version(void) return UNAC_VERSION; } -#ifdef RECOLL_DATADIR +#ifdef BUILDING_RECOLL void unac_set_except_translations(const char *spectrans) { except_trans.clear(); @@ -14614,4 +14600,4 @@ void unac_set_except_translations(const char *spectrans) free(out); } } -#endif /* RECOLL_DATADIR */ +#endif /* BUILDING_RECOLL */ diff --git a/src/unac/unac.cpp b/src/unac/unac.cpp deleted file mode 120000 index 874fe657..00000000 --- a/src/unac/unac.cpp +++ /dev/null @@ -1 +0,0 @@ -unac.c \ No newline at end of file diff --git a/src/unac/unac.cpp b/src/unac/unac.cpp new file mode 100644 index 00000000..780132b0 --- /dev/null +++ b/src/unac/unac.cpp @@ -0,0 +1 @@ +#include "unac.c" diff --git a/src/unac/unac.h b/src/unac/unac.h index cfc2cfd3..3b39f489 100644 --- a/src/unac/unac.h +++ b/src/unac/unac.h @@ -111,10 +111,7 @@ int fold_string(const char* charset, const char* in, size_t in_length, char** out, size_t* out_length); -/* To be called before starting threads in mt programs */ -void unac_init_mt(); - -#ifdef RECOLL_DATADIR +#ifdef BUILDING_RECOLL #include /** * Set exceptions for unaccenting, for characters which should not be @@ -128,7 +125,7 @@ void unac_init_mt(); * can't be an exception character, deal with it... */ void unac_set_except_translations(const char *spectrans); -#endif /* RECOLL_DATADIR */ +#endif /* BUILDING_RECOLL */ /* * Return unac version number. diff --git a/src/utils/Makefile b/src/utils/Makefile index e26601cf..fbeda035 100644 --- a/src/utils/Makefile +++ b/src/utils/Makefile @@ -1,17 +1,12 @@ -depth = .. -include $(depth)/mk/sysconf -# Use a static link for some of the utility programs so that they can be -# executed independantly of the installed version of recoll. None of them -# are part of the official distrib anyway -LIBRECOLL = ../lib/librecoll.a +include ../utils/utmkdefs.mk PROGS = pxattr trclosefrom trecrontab \ trnetcon trcopyfile trcircache trmd5 trreadfile trfileudi \ trconftree wipedir smallut trfstreewalk trpathut transcode trbase64 \ - trmimeparse trexecmd utf8iter idfile + trmimeparse trexecmd utf8iter idfile workqueue trappformime -all: librecoll +all: $(PROGS) PXATTROBJS = trpxattr.o pxattr.o pxattr: $(PXATTROBJS) @@ -19,76 +14,82 @@ pxattr: $(PXATTROBJS) trpxattr.o : pxattr.cpp $(CXX) -c $(CXXFLAGS) -DTEST_PXATTR -o $@ pxattr.cpp +CHRONOOBJS = trchrono.o chrono.o +trchrono: $(CHRONOOBJS) + $(CXX) -o chrono $(CHRONOOBJS) +trchrono.o : chrono.cpp + $(CXX) -c $(CXXFLAGS) -DTEST_CHRONO -o $@ chrono.cpp + +WORKQUEUEOBJS = workqueue.o +workqueue: $(WORKQUEUEOBJS) + $(CXX) -o workqueue $(WORKQUEUEOBJS) $(LIBRECOLL) -lpthread +workqueue.o : workqueue.cpp + $(CXX) -c $(CXXFLAGS) -o $@ $< + ECRONTAB_OBJS= trecrontab.o trecrontab : $(ECRONTAB_OBJS) - $(CXX) -o trecrontab $(ECRONTAB_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) -o trecrontab $(ECRONTAB_OBJS) $(LIBRECOLL) trecrontab.o : ecrontab.cpp ecrontab.h $(CXX) -o trecrontab.o -c $(ALL_CXXFLAGS) \ -DTEST_ECRONTAB ecrontab.cpp CLOSEFROM_OBJS= trclosefrom.o trclosefrom : $(CLOSEFROM_OBJS) - $(CXX) -o trclosefrom $(CLOSEFROM_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) -o trclosefrom $(CLOSEFROM_OBJS) $(LIBRECOLL) trclosefrom.o : closefrom.cpp closefrom.h $(CXX) -o trclosefrom.o -c $(ALL_CXXFLAGS) \ -DTEST_CLOSEFROM closefrom.cpp FSTREEWALK_OBJS= trfstreewalk.o trfstreewalk : $(FSTREEWALK_OBJS) - $(CXX) -o trfstreewalk $(FSTREEWALK_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) -o trfstreewalk $(FSTREEWALK_OBJS) $(LIBRECOLL) trfstreewalk.o : fstreewalk.cpp fstreewalk.h $(CXX) -o trfstreewalk.o -c $(ALL_CXXFLAGS) \ -DTEST_FSTREEWALK fstreewalk.cpp APPFORMIME_OBJS= trappformime.o trappformime : $(APPFORMIME_OBJS) - $(CXX) -o trappformime $(APPFORMIME_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) -o trappformime $(APPFORMIME_OBJS) $(LIBRECOLL) trappformime.o : appformime.cpp $(CXX) -o trappformime.o -c $(ALL_CXXFLAGS) \ -DTEST_APPFORMIME appformime.cpp READFILE_OBJS= trreadfile.o trreadfile : $(READFILE_OBJS) - $(CXX) -o trreadfile $(READFILE_OBJS) $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) -o trreadfile $(READFILE_OBJS) $(LIBRECOLL) trreadfile.o : readfile.cpp readfile.h $(CXX) -o trreadfile.o -c $(ALL_CXXFLAGS) \ -DTEST_READFILE readfile.cpp CPUCONF_OBJS= trcpuconf.o trcpuconf : $(CPUCONF_OBJS) - $(CXX) -o trcpuconf $(CPUCONF_OBJS) $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) -o trcpuconf $(CPUCONF_OBJS) $(LIBRECOLL) trcpuconf.o : cpuconf.cpp cpuconf.h $(CXX) -o trcpuconf.o -c $(ALL_CXXFLAGS) -DTEST_CPUCONF cpuconf.cpp CIRCACHE_OBJS= trcircache.o trcircache : $(CIRCACHE_OBJS) - $(CXX) -o trcircache $(CIRCACHE_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) -lz + $(CXX) -o trcircache $(CIRCACHE_OBJS) $(LIBRECOLL) trcircache.o : circache.cpp circache.h $(CXX) -o trcircache.o -c $(ALL_CXXFLAGS) \ -DTEST_CIRCACHE circache.cpp COPYFILE_OBJS= trcopyfile.o trcopyfile : $(COPYFILE_OBJS) - $(CXX) -o trcopyfile $(COPYFILE_OBJS) $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) -o trcopyfile $(COPYFILE_OBJS) $(LIBRECOLL) trcopyfile.o : copyfile.cpp copyfile.h $(CXX) -o trcopyfile.o -c $(ALL_CXXFLAGS) \ -DTEST_COPYFILE copyfile.cpp -MD5_OBJS= trmd5.o md5.o +MD5_OBJS= trmd5.o trmd5 : $(MD5_OBJS) - $(CXX) -o trmd5 $(MD5_OBJS) $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) -o trmd5 $(MD5_OBJS) $(LIBRECOLL) trmd5.o : md5ut.cpp md5ut.h md5.h $(CXX) -o trmd5.o -c $(ALL_CXXFLAGS) -DTEST_MD5 md5ut.cpp PATHUT_OBJS= trpathut.o trpathut : $(PATHUT_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o trpathut $(PATHUT_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) $(ALL_CXXFLAGS) -o trpathut $(PATHUT_OBJS) $(LIBRECOLL) trpathut.o : pathut.cpp pathut.h $(CXX) -o trpathut.o -c $(ALL_CXXFLAGS) -DTEST_PATHUT pathut.cpp @@ -101,72 +102,63 @@ trnetcon.o : netcon.cpp netcon.h FILEUDI_OBJS= trfileudi.o trfileudi : $(FILEUDI_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o trfileudi $(FILEUDI_OBJS) + $(CXX) $(ALL_CXXFLAGS) -o trfileudi $(FILEUDI_OBJS) $(LIBRECOLL) trfileudi.o : fileudi.cpp fileudi.h $(CXX) -o trfileudi.o -c $(ALL_CXXFLAGS) -DTEST_FILEUDI fileudi.cpp EXECMD_OBJS= trexecmd.o trexecmd : $(EXECMD_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o trexecmd $(EXECMD_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) -trexecmd.o : execmd.cpp execmd.h - $(CXX) -o trexecmd.o -c $(ALL_CXXFLAGS) -DTEST_EXECMD execmd.cpp + $(CXX) $(ALL_CXXFLAGS) -o trexecmd $(EXECMD_OBJS) $(LIBRECOLL) +trexecmd.o : trexecmd.cpp execmd.h + $(CXX) -o trexecmd.o -c $(ALL_CXXFLAGS) -I../xaposix trexecmd.cpp TRANSCODE_OBJS= trtranscode.o transcode : $(TRANSCODE_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o transcode $(TRANSCODE_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) $(ALL_CXXFLAGS) -o transcode $(TRANSCODE_OBJS) $(LIBRECOLL) trtranscode.o : transcode.cpp $(CXX) $(ALL_CXXFLAGS) -DTEST_TRANSCODE -c -o trtranscode.o \ transcode.cpp IDFILE_OBJS= tridfile.o idfile : $(IDFILE_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o idfile $(IDFILE_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) $(ALL_CXXFLAGS) -o idfile $(IDFILE_OBJS) $(LIBRECOLL) tridfile.o : idfile.cpp $(CXX) $(ALL_CXXFLAGS) -DTEST_IDFILE -c -o tridfile.o idfile.cpp MIMEPARSE_OBJS= trmimeparse.o trmimeparse : $(MIMEPARSE_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o trmimeparse $(MIMEPARSE_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) $(ALL_CXXFLAGS) -o trmimeparse $(MIMEPARSE_OBJS) $(LIBRECOLL) trmimeparse.o : mimeparse.cpp $(CXX) $(ALL_CXXFLAGS) -DTEST_MIMEPARSE -c -o trmimeparse.o \ mimeparse.cpp -SMALLUT_OBJS= trsmallut.o ../lib/smallut.o +SMALLUT_OBJS= trsmallut.o smallut : $(SMALLUT_OBJS) smallut.h - $(CXX) $(ALL_CXXFLAGS) -o smallut $(SMALLUT_OBJS) \ - $(LIBRECOLL) $(LIBICONV) + $(CXX) $(ALL_CXXFLAGS) -o smallut $(SMALLUT_OBJS) $(LIBRECOLL) trsmallut.o : smallut.cpp smallut.h $(CXX) $(ALL_CXXFLAGS) -DTEST_SMALLUT -c -o trsmallut.o smallut.cpp -../lib/smallut.o: smallut.cpp smallut.h - cd ../lib;make smallut.o WIPEDIR_OBJS= trwipedir.o wipedir : $(WIPEDIR_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o wipedir $(WIPEDIR_OBJS) $(LIBICONV) $(LIBSYS) + $(CXX) $(ALL_CXXFLAGS) -o wipedir $(WIPEDIR_OBJS) $(LIBRECOLL) trwipedir.o : wipedir.cpp $(CXX) $(ALL_CXXFLAGS) -DTEST_WIPEDIR -c -o trwipedir.o wipedir.cpp UTF8ITER_OBJS= trutf8iter.o utf8iter : $(UTF8ITER_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o utf8iter $(UTF8ITER_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) $(ALL_CXXFLAGS) -o utf8iter $(UTF8ITER_OBJS) $(LIBRECOLL) trutf8iter.o : utf8iter.cpp utf8iter.h $(CXX) $(ALL_CXXFLAGS) -DTEST_UTF8ITER -c -o trutf8iter.o utf8iter.cpp CONFTREE_OBJS= trconftree.o trconftree : $(CONFTREE_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o trconftree $(CONFTREE_OBJS) \ - $(LIBRECOLL) $(LIBICONV) $(LIBSYS) + $(CXX) $(ALL_CXXFLAGS) -o trconftree $(CONFTREE_OBJS) $(LIBRECOLL) trconftree.o : conftree.cpp $(CXX) $(ALL_CXXFLAGS) -DTEST_CONFTREE -c -o trconftree.o conftree.cpp BASE64_OBJS= trbase64.o trbase64 : $(BASE64_OBJS) - $(CXX) $(ALL_CXXFLAGS) -o trbase64 $(BASE64_OBJS) + $(CXX) $(ALL_CXXFLAGS) -o trbase64 $(BASE64_OBJS) $(LIBRECOLL) trbase64.o : base64.cpp $(CXX) $(ALL_CXXFLAGS) -DTEST_BASE64 -c -o trbase64.o base64.cpp @@ -177,5 +169,3 @@ trx11mon.o : x11mon.cpp x11mon.h $(CXX) -o trx11mon.o -c $(ALL_CXXFLAGS) -DTEST_X11MON x11mon.cpp x11mon.o: x11mon.cpp $(CXX) -c -I/usr/X11R6/include $(ALL_CXXFLAGS) x11mon.cpp - -include $(depth)/mk/commontargets diff --git a/src/utils/appformime.cpp b/src/utils/appformime.cpp index 3d26e08b..60a41a7e 100644 --- a/src/utils/appformime.cpp +++ b/src/utils/appformime.cpp @@ -176,7 +176,6 @@ const string& DesktopDb::getReason() #include #include -#include #include #include @@ -212,8 +211,7 @@ int main(int argc, char **argv) vector appdefs; DesktopDb *ddb = DesktopDb::getDb(); if (ddb == 0) { - cerr << "Could not initialize desktop db: " << DesktopDb::getReason() - << endl; + cerr << "Could not create desktop db\n"; exit(1); } if (!ddb->appForMime(mime, &appdefs, &reason)) { diff --git a/src/utils/appformime.h b/src/utils/appformime.h index 4f9155a6..c979f346 100644 --- a/src/utils/appformime.h +++ b/src/utils/appformime.h @@ -18,6 +18,8 @@ #define _APPFORMIME_H_INCLUDED_ #include +#include +#include /** * Rather strangely, I could not find a reasonably simple piece of @@ -71,12 +73,12 @@ public: */ bool appByName(const string& nm, AppDef& app); - typedef map > AppMap; + typedef std::map > AppMap; private: /** This is used by getDb() and builds a db for the standard location */ DesktopDb(); - void build(const string& dir); + void build(const std::string& dir); DesktopDb(const DesktopDb &); DesktopDb& operator=(const DesktopDb &); diff --git a/src/utils/base64.cpp b/src/utils/base64.cpp index 29c6e902..82651b15 100644 --- a/src/utils/base64.cpp +++ b/src/utils/base64.cpp @@ -20,9 +20,7 @@ #include #include -#ifndef NO_NAMESPACES using std::string; -#endif /* NO_NAMESPACES */ #undef DEBUG_BASE64 #ifdef DEBUG_BASE64 @@ -219,7 +217,7 @@ void base64_encode(const string &in, string &out) out.clear(); - int srclength = in.length(); + string::size_type srclength = in.length(); int sidx = 0; while (2 < srclength) { input[0] = in[sidx++]; @@ -246,7 +244,7 @@ void base64_encode(const string &in, string &out) if (0 != srclength) { /* Get what's left. */ input[0] = input[1] = input[2] = '\0'; - for (int i = 0; i < srclength; i++) + for (string::size_type i = 0; i < srclength; i++) input[i] = in[sidx++]; output[0] = input[0] >> 2; @@ -357,8 +355,8 @@ int main(int argc, char **argv) fprintf(stderr, "Decoding failed\n"); exit(1); } - write(1, odata.c_str(), - odata.size() * sizeof(string::value_type)); + fwrite(odata.c_str(), 1, + odata.size() * sizeof(string::value_type), stdout); exit(0); } } diff --git a/src/utils/cancelcheck.cpp b/src/utils/cancelcheck.cpp new file mode 100644 index 00000000..4a376e38 --- /dev/null +++ b/src/utils/cancelcheck.cpp @@ -0,0 +1,24 @@ +/* Copyright (C) 2005 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "cancelcheck.h" + +CancelCheck& CancelCheck::instance() +{ + static CancelCheck ck; + return ck; +} diff --git a/src/utils/cancelcheck.h b/src/utils/cancelcheck.h index 70a78a7f..b388a154 100644 --- a/src/utils/cancelcheck.h +++ b/src/utils/cancelcheck.h @@ -39,16 +39,12 @@ class CancelExcept {}; class CancelCheck { public: - static CancelCheck& instance() { - static CancelCheck ck; - return ck; - } + static CancelCheck& instance(); void setCancel(bool on = true) { cancelRequested = on; } void checkCancel() { if (cancelRequested) { - cancelRequested = false; throw CancelExcept(); } } diff --git a/src/utils/chrono.cpp b/src/utils/chrono.cpp new file mode 100644 index 00000000..8f7aca98 --- /dev/null +++ b/src/utils/chrono.cpp @@ -0,0 +1,289 @@ +/* Copyright (C) 2014 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef TEST_CHRONO +#include "autoconfig.h" + +#include +#include + +#include "chrono.h" + +using namespace std; + +#ifndef CLOCK_REALTIME +typedef int clockid_t; +#define CLOCK_REALTIME 1 +#endif + + +#define SECONDS(TS1, TS2) \ + (float((TS2).tv_sec - (TS1).tv_sec) + \ + float((TS2).tv_nsec - (TS1).tv_nsec) * 1e-9) + +#define MILLIS(TS1, TS2) \ + ((long long)((TS2).tv_sec - (TS1).tv_sec) * 1000LL + \ + ((TS2).tv_nsec - (TS1).tv_nsec) / 1000000) + +#define MICROS(TS1, TS2) \ + ((long long)((TS2).tv_sec - (TS1).tv_sec) * 1000000LL + \ + ((TS2).tv_nsec - (TS1).tv_nsec) / 1000) + +#define NANOS(TS1, TS2) \ + ((long long)((TS2).tv_sec - (TS1).tv_sec) * 1000000000LL + \ + ((TS2).tv_nsec - (TS1).tv_nsec)) + + + +// Using clock_gettime() is nice because it gives us ns res and it helps with +// computing threads work times, but it's also a pita because it forces linking +// with -lrt. So keep it non-default, special development only. +// #define USE_CLOCK_GETTIME + +// And wont' bother with gettime() on these. +#if defined(__APPLE__) || defined(_WIN32) +#undef USE_CLOCK_GETTIME +#endif + +#ifdef _MSC_VER +#define WIN32_LEAN_AND_MEAN +#include +#include // portable: uint64_t MSVC: __int64 + +// MSVC defines this in winsock2.h!? +typedef struct timeval { + long tv_sec; + long tv_usec; +} timeval; + +int gettimeofday(struct timeval * tp, struct timezone * tzp) +{ + // Note: some broken versions only have 8 trailing zero's, the + // correct epoch has 9 trailing zero's + static const uint64_t EPOCH = ((uint64_t) 116444736000000000ULL); + + SYSTEMTIME system_time; + FILETIME file_time; + uint64_t time; + + GetSystemTime( &system_time ); + SystemTimeToFileTime( &system_time, &file_time ); + time = ((uint64_t)file_time.dwLowDateTime ) ; + time += ((uint64_t)file_time.dwHighDateTime) << 32; + + tp->tv_sec = (long) ((time - EPOCH) / 10000000L); + tp->tv_usec = (long) (system_time.wMilliseconds * 1000); + return 0; +} +#else // -> Not _WIN32 +#ifndef USE_CLOCK_GETTIME +// Using gettimeofday then, needs struct timeval +#include +#endif +#endif + + + +// We use gettimeofday instead of clock_gettime for now and get only +// uS resolution, because clock_gettime is more configuration trouble +// than it's worth +static void gettime(int +#ifdef USE_CLOCK_GETTIME + clk_id +#endif + , Chrono::TimeSpec *ts) +{ +#ifdef USE_CLOCK_GETTIME + struct timespec mts; + clock_gettime(clk_id, &mts); + ts->tv_sec = mts.tv_sec; + ts->tv_nsec = mts.tv_nsec; +#else + struct timeval tv; + gettimeofday(&tv, 0); + ts->tv_sec = tv.tv_sec; + ts->tv_nsec = tv.tv_usec * 1000; +#endif +} +///// End system interface + +// Note: this not protected against multithread access and not +// reentrant, but this is mostly debug code, and it won't crash, just +// show bad results. Also the frozen thing is not used that much +Chrono::TimeSpec Chrono::o_now; + +void Chrono::refnow() +{ + gettime(CLOCK_REALTIME, &o_now); +} + +Chrono::Chrono() +{ + restart(); +} + +// Reset and return value before rest in milliseconds +long Chrono::restart() +{ + TimeSpec now; + gettime(CLOCK_REALTIME, &now); + long ret = MILLIS(m_orig, now); + m_orig = now; + return ret; +} + +long Chrono::urestart() +{ + TimeSpec now; + gettime(CLOCK_REALTIME, &now); + long ret = MICROS(m_orig, now); + m_orig = now; + return ret; +} + +// Get current timer value, milliseconds +long Chrono::millis(bool frozen) +{ + if (frozen) { + return MILLIS(m_orig, o_now); + } else { + TimeSpec now; + gettime(CLOCK_REALTIME, &now); + return MILLIS(m_orig, now); + } +} + +// +long Chrono::micros(bool frozen) +{ + if (frozen) { + return MICROS(m_orig, o_now); + } else { + TimeSpec now; + gettime(CLOCK_REALTIME, &now); + return MICROS(m_orig, now); + } +} + +long long Chrono::amicros() const +{ + TimeSpec ts; + ts.tv_sec = 0; + ts.tv_nsec = 0; + return MICROS(ts, m_orig); +} + +// +long long Chrono::nanos(bool frozen) +{ + if (frozen) { + return NANOS(m_orig, o_now); + } else { + TimeSpec now; + gettime(CLOCK_REALTIME, &now); + return NANOS(m_orig, now); + } +} + +float Chrono::secs(bool frozen) +{ + if (frozen) { + return SECONDS(m_orig, o_now); + } else { + TimeSpec now; + gettime(CLOCK_REALTIME, &now); + return SECONDS(m_orig, now); + } +} + +#else + +///////////////////// test driver + + +#include +#include +#include +#include + +#include + +#include "chrono.h" + +using namespace std; + +static char *thisprog; +static void +Usage(void) +{ + fprintf(stderr, "Usage : %s \n", thisprog); + exit(1); +} + +Chrono achrono; +Chrono rchrono; + +void +showsecs(long msecs) +{ + fprintf(stderr, "%3.5f S", ((float)msecs) / 1000.0); +} + +void +sigint(int sig) +{ + signal(SIGINT, sigint); + signal(SIGQUIT, sigint); + + fprintf(stderr, "Absolute interval: "); + showsecs(achrono.millis()); + fprintf(stderr, ". Relative interval: "); + showsecs(rchrono.restart()); + cerr << " Abs micros: " << achrono.amicros() << + " Relabs micros: " << rchrono.amicros() - 1430477861905884LL + << endl; + fprintf(stderr, ".\n"); + if (sig == SIGQUIT) { + exit(0); + } +} + +int main(int argc, char **argv) +{ + + thisprog = argv[0]; + argc--; + argv++; + + if (argc != 0) { + Usage(); + } + + for (int i = 0; i < 50000000; i++); + + cerr << "Start secs: " << achrono.secs() << endl; + + fprintf(stderr, "Type ^C for intermediate result, ^\\ to stop\n"); + signal(SIGINT, sigint); + signal(SIGQUIT, sigint); + achrono.restart(); + rchrono.restart(); + while (1) { + pause(); + } +} + +#endif /*TEST_CHRONO*/ diff --git a/src/utils/chrono.h b/src/utils/chrono.h new file mode 100644 index 00000000..2dc08c38 --- /dev/null +++ b/src/utils/chrono.h @@ -0,0 +1,62 @@ +/* Copyright (C) 2014 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CHRONO_H_INCLUDED_ +#define _CHRONO_H_INCLUDED_ + +#include + +/** Easy interface to measuring time intervals */ +class Chrono { +public: + /** Initialize, setting the origin time */ + Chrono(); + + /** Re-store current time and return mS since init or last call */ + long restart(); + /** Re-store current time and return uS since init or last call */ + long urestart(); + + /** Snapshot current time to static storage */ + static void refnow(); + + /** Return interval value in various units. + * + * If frozen is set this gives the time since the last refnow call + * (this is to allow for using one actual system call to get + values from many chrono objects, like when examining timeouts + in a queue + */ + long long nanos(bool frozen = false); + long micros(bool frozen = false); + long millis(bool frozen = false); + float secs(bool frozen = false); + + /** Return the absolute value of the current origin */ + long long amicros() const; + + struct TimeSpec { + time_t tv_sec; /* Time in seconds */ + long tv_nsec; /* And nanoseconds (< 10E9) */ + }; + +private: + TimeSpec m_orig; + static TimeSpec o_now; +}; + +#endif /* _CHRONO_H_INCLUDED_ */ diff --git a/src/utils/circache.cpp b/src/utils/circache.cpp index fe257814..0b4699fc 100644 --- a/src/utils/circache.cpp +++ b/src/utils/circache.cpp @@ -19,17 +19,45 @@ #include "autoconfig.h" #include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include "safefcntl.h" +#include +#include "safesysstat.h" +#include "safeunistd.h" +#include #include #include +#include "chrono.h" +#include + + +#ifndef _WIN32 +#include +#define O_BINARY 0 +#else +struct iovec { + void *iov_base; + size_t iov_len; +}; +static ssize_t writev(int fd, const struct iovec *iov, int iovcnt) +{ + ssize_t tot = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t ret = ::write(fd, iov[i].iov_base, iov[i].iov_len); + if (ret > 0) { + tot += ret; + } + if (ret != (ssize_t)iov[i].iov_len) { + return ret == -1 ? -1 : tot; + } + } + return tot; +} +#endif + + #include #include #include @@ -37,7 +65,7 @@ #include "cstr.h" #include "circache.h" #include "conftree.h" -#include "debuglog.h" +#include "log.h" #include "smallut.h" #include "md5.h" @@ -46,29 +74,51 @@ typedef unsigned char UCHAR; typedef unsigned int UINT; typedef unsigned long ULONG; +/** Temp buffer with automatic deallocation */ +struct TempBuf { + TempBuf() + : m_buf(0) { + } + TempBuf(int n) { + m_buf = (char *)malloc(n); + } + ~TempBuf() { + if (m_buf) { + free(m_buf); + } + } + char *setsize(int n) { + return (m_buf = (char *)realloc(m_buf, n)); + } + char *buf() { + return m_buf; + } + char *m_buf; +}; + static bool inflateToDynBuf(void *inp, UINT inlen, void **outpp, UINT *outlenp); /* * File structure: * - Starts with a 1-KB header block, with a param dictionary. * - Stored items follow. Each item has a header and 2 segments for - * the metadata and the data. + * the metadata and the data. * The segment sizes are stored in the ascii header/marker: * circacheSizes = xxx yyy zzz * xxx bytes of metadata * yyy bytes of data * zzz bytes of padding up to next object (only one entry has non zero) * - * There is a write position, which can be at eof while + * There is a write position, which can be at eof while * the file is growing, or inside the file if we are recycling. This is stored * in the header (oheadoffs), together with the maximum size * - * If we are recycling, we have to take care to compute the size of the - * possible remaining area from the last object invalidated by the write, + * If we are recycling, we have to take care to compute the size of the + * possible remaining area from the last object invalidated by the write, * pad it with neutral data and store the size in the new header. To help with * this, the address for the last object written is also kept in the header * (nheadoffs, npadsize) - * + * */ // First block size @@ -95,13 +145,13 @@ class CCScanHook { public: virtual ~CCScanHook() {} enum status {Stop, Continue, Error, Eof}; - virtual status takeone(off_t offs, const string& udi, + virtual status takeone(off_t offs, const string& udi, const EntryHeaderData& d) = 0; }; // We have an auxiliary in-memory multimap of hashed-udi -> offset to // speed things up. This is created the first time the file is scanned -// (on the first get), and not saved to disk. +// (on the first get), and not saved to disk. // The map key: hashed udi. As a very short hash seems sufficient, // maybe we could find something faster/simpler than md5? @@ -110,8 +160,7 @@ class UdiH { public: UCHAR h[UDIHLEN]; - UdiH(const string& udi) - { + UdiH(const string& udi) { MD5_CTX ctx; MD5Init(&ctx); MD5Update(&ctx, (const UCHAR*)udi.c_str(), udi.length()); @@ -121,7 +170,7 @@ public: } string asHexString() const { - static const char hex[]="0123456789abcdef"; + static const char hex[] = "0123456789abcdef"; string out; for (int i = 0; i < UDIHLEN; i++) { out.append(1, hex[h[i] >> 4]); @@ -129,20 +178,21 @@ public: } return out; } - bool operator==(const UdiH& r) const - { + bool operator==(const UdiH& r) const { for (int i = 0; i < UDIHLEN; i++) - if (h[i] != r.h[i]) + if (h[i] != r.h[i]) { return false; + } return true; } - bool operator<(const UdiH& r) const - { + bool operator<(const UdiH& r) const { for (int i = 0; i < UDIHLEN; i++) { - if (h[i] < r.h[i]) + if (h[i] < r.h[i]) { return true; - if (h[i] > r.h[i]) + } + if (h[i] > r.h[i]) { return false; + } } return false; } @@ -155,16 +205,16 @@ public: int m_fd; ////// These are cache persistent state and written to the first block: // Maximum file size, after which we begin reusing old space - off_t m_maxsize; + off_t m_maxsize; // Offset of the oldest header, or max file offset (file size) // while the file is growing. This is the next write position. off_t m_oheadoffs; // Offset of last write (newest header) off_t m_nheadoffs; - // Pad size for newest entry. - int m_npadsize; + // Pad size for newest entry. + off_t m_npadsize; // Keep history or only last entry - bool m_uniquentries; + bool m_uniquentries; ///////////////////// End header entries // A place to hold data when reading @@ -184,65 +234,60 @@ public: bool m_ofskhcplt; // Has cache been fully read since open? // Add udi->offset translation to map - bool khEnter(const string& udi, off_t ofs) - { + bool khEnter(const string& udi, off_t ofs) { UdiH h(udi); - LOGDEB2(("Circache::khEnter: h %s offs %lu udi [%s]\n", - h.asHexString().c_str(), (ULONG)ofs, udi.c_str())); + LOGDEB2("Circache::khEnter: h " << (h.asHexString()) << " offs " << ((ULONG)ofs) << " udi [" << (udi) << "]\n" ); pair p = m_ofskh.equal_range(h); if (p.first != m_ofskh.end() && p.first->first == h) { for (kh_type::iterator it = p.first; it != p.second; it++) { - LOGDEB2(("Circache::khEnter: col h %s, ofs %lu\n", - it->first.asHexString().c_str(), - (ULONG)it->second)); + LOGDEB2("Circache::khEnter: col h " << (it->first.asHexString()) << ", ofs " << ((ULONG)it->second) << "\n" ); if (it->second == ofs) { // (h,offs) already there. Happens - LOGDEB2(("Circache::khEnter: already there\n")); + LOGDEB2("Circache::khEnter: already there\n" ); return true; } } } m_ofskh.insert(kh_value_type(h, ofs)); - LOGDEB2(("Circache::khEnter: inserted\n")); + LOGDEB2("Circache::khEnter: inserted\n" ); return true; } - void khDump() - { - for (kh_type::const_iterator it = m_ofskh.begin(); - it != m_ofskh.end(); it++) { - LOGDEB(("Circache::KHDUMP: %s %d\n", - it->first.asHexString().c_str(), (ULONG)it->second)); + void khDump() { + for (kh_type::const_iterator it = m_ofskh.begin(); + it != m_ofskh.end(); it++) { + LOGDEB("Circache::KHDUMP: " << (it->first.asHexString()) << " " << ((ULONG)it->second) << "\n" ); } } // Return vector of candidate offsets for udi (possibly several // because there may be hash collisions, and also multiple // instances). - bool khFind(const string& udi, vector& ofss) - { + bool khFind(const string& udi, vector& ofss) { ofss.clear(); UdiH h(udi); - LOGDEB2(("Circache::khFind: h %s udi [%s]\n", - h.asHexString().c_str(), udi.c_str())); + LOGDEB2("Circache::khFind: h " << (h.asHexString()) << " udi [" << (udi) << "]\n" ); pair p = m_ofskh.equal_range(h); #if 0 - if (p.first == m_ofskh.end()) LOGDEB(("KHFIND: FIRST END()\n")); - if (p.second == m_ofskh.end()) LOGDEB(("KHFIND: SECOND END()\n")); + if (p.first == m_ofskh.end()) { + LOGDEB("KHFIND: FIRST END()\n" ); + } + if (p.second == m_ofskh.end()) { + LOGDEB("KHFIND: SECOND END()\n" ); + } if (!(p.first->first == h)) - LOGDEB(("KHFIND: NOKEY: %s %s\n", - p.first->first.asHexString().c_str(), - p.second->first.asHexString().c_str())); -#endif + LOGDEB("KHFIND: NOKEY: " << (p.first->first.asHexString()) << " " << (p.second->first.asHexString()) << "\n" ); +#endif - if (p.first == m_ofskh.end() || !(p.first->first == h)) + if (p.first == m_ofskh.end() || !(p.first->first == h)) { return false; + } for (kh_type::iterator it = p.first; it != p.second; it++) { ofss.push_back(it->second); @@ -250,34 +295,33 @@ public: return true; } // Clear entry for udi/offs - bool khClear(const pair& ref) - { + bool khClear(const pair& ref) { UdiH h(ref.first); pair p = m_ofskh.equal_range(h); if (p.first != m_ofskh.end() && (p.first->first == h)) { - for (kh_type::iterator it = p.first; it != p.second; ) { + for (kh_type::iterator it = p.first; it != p.second;) { kh_type::iterator tmp = it++; - if (tmp->second == ref.second) + if (tmp->second == ref.second) { m_ofskh.erase(tmp); + } } } return true; } // Clear entries for vector of udi/offs - bool khClear(const vector >& udis) - { - for (vector >::const_iterator it = udis.begin(); - it != udis.end(); it++) + bool khClear(const vector >& udis) { + for (vector >::const_iterator it = udis.begin(); + it != udis.end(); it++) { khClear(*it); + } return true; } // Clear all entries for udi - bool khClear(const string& udi) - { + bool khClear(const string& udi) { UdiH h(udi); pair p = m_ofskh.equal_range(h); if (p.first != m_ofskh.end() && (p.first->first == h)) { - for (kh_type::iterator it = p.first; it != p.second; ) { + for (kh_type::iterator it = p.first; it != p.second;) { kh_type::iterator tmp = it++; m_ofskh.erase(tmp); } @@ -285,23 +329,24 @@ public: return true; } CirCacheInternal() - : m_fd(-1), m_maxsize(-1), m_oheadoffs(-1), + : m_fd(-1), m_maxsize(-1), m_oheadoffs(-1), m_nheadoffs(0), m_npadsize(0), m_uniquentries(false), - m_buffer(0), m_bufsiz(0), m_ofskhcplt(false) - {} - - ~CirCacheInternal() - { - if (m_fd >= 0) - close(m_fd); - if (m_buffer) - free(m_buffer); + m_buffer(0), m_bufsiz(0), m_ofskhcplt(false) { } - char *buf(size_t sz) - { - if (m_bufsiz >= sz) + ~CirCacheInternal() { + if (m_fd >= 0) { + close(m_fd); + } + if (m_buffer) { + free(m_buffer); + } + } + + char *buf(size_t sz) { + if (m_bufsiz >= sz) { return m_buffer; + } if ((m_buffer = (char *)realloc(m_buffer, sz))) { m_bufsiz = sz; } else { @@ -312,29 +357,27 @@ public: } // Name for the cache file - string datafn(const string& d) - { + string datafn(const string& d) { return path_cat(d, "circache.crch"); } - bool writefirstblock() - { - if (m_fd < 0) { - m_reason << "writefirstblock: not open "; - return false; - } + bool writefirstblock() { + if (m_fd < 0) { + m_reason << "writefirstblock: not open "; + return false; + } ostringstream s; - s << - "maxsize = " << m_maxsize << "\n" << - "oheadoffs = " << m_oheadoffs << "\n" << - "nheadoffs = " << m_nheadoffs << "\n" << - "npadsize = " << m_npadsize << "\n" << - "unient = " << m_uniquentries << "\n" << - " " << - " " << - " " << - "\0"; + s << + "maxsize = " << m_maxsize << "\n" << + "oheadoffs = " << m_oheadoffs << "\n" << + "nheadoffs = " << m_nheadoffs << "\n" << + "npadsize = " << m_npadsize << "\n" << + "unient = " << m_uniquentries << "\n" << + " " << + " " << + " " << + "\0"; int sz = int(s.str().size()); assert(sz < CIRCACHE_FIRSTBLOCK_SIZE); @@ -346,18 +389,17 @@ public: return true; } - bool readfirstblock() - { - if (m_fd < 0) { - m_reason << "readfirstblock: not open "; - return false; - } + bool readfirstblock() { + if (m_fd < 0) { + m_reason << "readfirstblock: not open "; + return false; + } char bf[CIRCACHE_FIRSTBLOCK_SIZE]; lseek(m_fd, 0, 0); - if (read(m_fd, bf, CIRCACHE_FIRSTBLOCK_SIZE) != - CIRCACHE_FIRSTBLOCK_SIZE) { + if (read(m_fd, bf, CIRCACHE_FIRSTBLOCK_SIZE) != + CIRCACHE_FIRSTBLOCK_SIZE) { m_reason << "readfirstblock: read() failed: errno " << errno; return false; } @@ -390,40 +432,50 @@ public: m_uniquentries = stringToBool(value); } return true; - } + } - bool writeEntryHeader(off_t offset, const EntryHeaderData& d) - { - if (m_fd < 0) { - m_reason << "writeEntryHeader: not open "; - return false; - } + bool writeEntryHeader(off_t offset, const EntryHeaderData& d, + bool eraseData = false) { + if (m_fd < 0) { + m_reason << "writeEntryHeader: not open "; + return false; + } char bf[CIRCACHE_HEADER_SIZE]; memset(bf, 0, CIRCACHE_HEADER_SIZE); - snprintf(bf, CIRCACHE_HEADER_SIZE, - headerformat, d.dicsize, d.datasize, d.padsize, d.flags); + snprintf(bf, CIRCACHE_HEADER_SIZE, + headerformat, d.dicsize, d.datasize, d.padsize, d.flags); if (lseek(m_fd, offset, 0) != offset) { - m_reason << "CirCache::weh: lseek(" << offset << - ") failed: errno " << errno; + m_reason << "CirCache::weh: lseek(" << offset << + ") failed: errno " << errno; return false; } if (write(m_fd, bf, CIRCACHE_HEADER_SIZE) != CIRCACHE_HEADER_SIZE) { m_reason << "CirCache::weh: write failed. errno " << errno; return false; } + if (eraseData == true) { + if (d.dicsize || d.datasize) { + m_reason << "CirCache::weh: erase requested but not empty"; + return false; + } + string buf(d.padsize, ' '); + if (write(m_fd, buf.c_str(), d.padsize) != (ssize_t)d.padsize) { + m_reason << "CirCache::weh: write failed. errno " << errno; + return false; + } + } return true; } - CCScanHook::status readEntryHeader(off_t offset, EntryHeaderData& d) - { - if (m_fd < 0) { - m_reason << "readEntryHeader: not open "; + CCScanHook::status readEntryHeader(off_t offset, EntryHeaderData& d) { + if (m_fd < 0) { + m_reason << "readEntryHeader: not open "; return CCScanHook::Error; - } + } if (lseek(m_fd, offset, 0) != offset) { - m_reason << "readEntryHeader: lseek(" << offset << - ") failed: errno " << errno; + m_reason << "readEntryHeader: lseek(" << offset << + ") failed: errno " << errno; return CCScanHook::Error; } char bf[CIRCACHE_HEADER_SIZE]; @@ -438,28 +490,26 @@ public: m_reason << " readheader: read failed errno " << errno; return CCScanHook::Error; } - if (sscanf(bf, headerformat, &d.dicsize, &d.datasize, + if (sscanf(bf, headerformat, &d.dicsize, &d.datasize, &d.padsize, &d.flags) != 4) { - m_reason << " readEntryHeader: bad header at " << - offset << " [" << bf << "]"; + m_reason << " readEntryHeader: bad header at " << + offset << " [" << bf << "]"; return CCScanHook::Error; } - LOGDEB2(("Circache:readEntryHeader: dcsz %u dtsz %u pdsz %u flgs %hu\n", - d.dicsize, d.datasize, d.padsize, d.flags)); + LOGDEB2("Circache:readEntryHeader: dcsz " << (d.dicsize) << " dtsz " << (d.datasize) << " pdsz " << (d.padsize) << " flgs " << (d.flags) << "\n" ); return CCScanHook::Continue; } - CCScanHook::status scan(off_t startoffset, CCScanHook *user, - bool fold = false) - { - if (m_fd < 0) { - m_reason << "scan: not open "; + CCScanHook::status scan(off_t startoffset, CCScanHook *user, + bool fold = false) { + if (m_fd < 0) { + m_reason << "scan: not open "; return CCScanHook::Error; - } + } off_t so0 = startoffset; bool already_folded = false; - + while (true) { if (already_folded && startoffset == so0) { m_ofskhcplt = true; @@ -469,23 +519,24 @@ public: EntryHeaderData d; CCScanHook::status st; switch ((st = readEntryHeader(startoffset, d))) { - case CCScanHook::Continue: break; + case CCScanHook::Continue: + break; case CCScanHook::Eof: if (fold && !already_folded) { already_folded = true; startoffset = CIRCACHE_FIRSTBLOCK_SIZE; continue; } - /* FALLTHROUGH */ + /* FALLTHROUGH */ default: return st; } - + string udi; if (d.dicsize) { // d.dicsize is 0 for erased entries char *bf; - if ((bf = buf(d.dicsize+1)) == 0) { + if ((bf = buf(d.dicsize + 1)) == 0) { return CCScanHook::Error; } bf[d.dicsize] = 0; @@ -495,7 +546,7 @@ public: } string b(bf, d.dicsize); ConfSimple conf(b, 1); - + if (!conf.get("udi", udi, cstr_null)) { m_reason << "scan: no udi in dic"; return CCScanHook::Error; @@ -504,27 +555,28 @@ public: } // Call callback - CCScanHook::status a = + CCScanHook::status a = user->takeone(startoffset, udi, d); switch (a) { - case CCScanHook::Continue: + case CCScanHook::Continue: break; default: return a; } - startoffset += CIRCACHE_HEADER_SIZE + d.dicsize + - d.datasize + d.padsize; + startoffset += CIRCACHE_HEADER_SIZE + d.dicsize + + d.datasize + d.padsize; } } - bool readHUdi(off_t hoffs, EntryHeaderData& d, string& udi) - { - if (readEntryHeader(hoffs, d) != CCScanHook::Continue) + bool readHUdi(off_t hoffs, EntryHeaderData& d, string& udi) { + if (readEntryHeader(hoffs, d) != CCScanHook::Continue) { return false; + } string dic; - if (!readDicData(hoffs, d, dic, 0)) + if (!readDicData(hoffs, d, dic, 0)) { return false; + } if (d.dicsize == 0) { // This is an erased entry udi.erase(); @@ -538,23 +590,23 @@ public: return true; } - bool readDicData(off_t hoffs, EntryHeaderData& hd, string& dic, - string* data) - { + bool readDicData(off_t hoffs, EntryHeaderData& hd, string& dic, + string* data) { off_t offs = hoffs + CIRCACHE_HEADER_SIZE; // This syscall could be avoided in some cases if we saved the offset // at each seek. In most cases, we just read the header and we are // at the right position if (lseek(m_fd, offs, 0) != offs) { - m_reason << "CirCache::get: lseek(" << offs << ") failed: " << - errno; + m_reason << "CirCache::get: lseek(" << offs << ") failed: " << + errno; return false; } char *bf = 0; if (hd.dicsize) { bf = buf(hd.dicsize); - if (bf == 0) + if (bf == 0) { return false; + } if (read(m_fd, bf, hd.dicsize) != int(hd.dicsize)) { m_reason << "CirCache::get: read() failed: errno " << errno; return false; @@ -563,20 +615,22 @@ public: } else { dic.erase(); } - if (data == 0) + if (data == 0) { return true; + } if (hd.datasize) { bf = buf(hd.datasize); - if (bf == 0) + if (bf == 0) { return false; - if (read(m_fd, bf, hd.datasize) != int(hd.datasize)){ + } + if (read(m_fd, bf, hd.datasize) != int(hd.datasize)) { m_reason << "CirCache::get: read() failed: errno " << errno; return false; } if (hd.flags & EFDataCompressed) { - LOGDEB1(("Circache:readdicdata: data compressed\n")); + LOGDEB1("Circache:readdicdata: data compressed\n" ); void *uncomp; unsigned int uncompsize; if (!inflateToDynBuf(bf, hd.datasize, &uncomp, &uncompsize)) { @@ -586,7 +640,7 @@ public: data->assign((char *)uncomp, uncompsize); free(uncomp); } else { - LOGDEB1(("Circache:readdicdata: data NOT compressed\n")); + LOGDEB1("Circache:readdicdata: data NOT compressed\n" ); data->assign(bf, hd.datasize); } } else { @@ -601,7 +655,7 @@ CirCache::CirCache(const string& dir) : m_dir(dir) { m_d = new CirCacheInternal; - LOGDEB0(("CirCache: [%s]\n", m_dir.c_str())); + LOGDEB0("CirCache: [" << (m_dir) << "]\n" ); } CirCache::~CirCache() @@ -623,16 +677,13 @@ public: off_t headoffs; off_t padsize; CCScanHookRecord() - : headoffs(0), padsize(0) - { + : headoffs(0), padsize(0) { } - virtual status takeone(off_t offs, const string& udi, - const EntryHeaderData& d) - { - headoffs = offs; - padsize = d.padsize; - LOGDEB2(("CCScanHookRecord::takeone: offs %lld padsize %lld\n", - headoffs, padsize)); + virtual status takeone(off_t offs, const string& udi, + const EntryHeaderData& d) { + headoffs = offs; + padsize = d.padsize; + LOGDEB2("CCScanHookRecord::takeone: offs " << (lltodecstr(headoffs)) << " padsize " << (lltodecstr(padsize)) << "\n" ); return Continue; } }; @@ -644,63 +695,58 @@ string CirCache::getpath() bool CirCache::create(off_t maxsize, int flags) { - LOGDEB(("CirCache::create: [%s] maxsz %lld flags 0x%x\n", - m_dir.c_str(), maxsize, flags)); + LOGDEB("CirCache::create: [" << (m_dir) << "] maxsz " << (lltodecstr((long long)maxsize)) << " flags 0x" << (flags) << "\n" ); if (m_d == 0) { - LOGERR(("CirCache::create: null data\n")); - return false; + LOGERR("CirCache::create: null data\n" ); + return false; } struct stat st; if (stat(m_dir.c_str(), &st) < 0) { - // Directory does not exist, create it - if (mkdir(m_dir.c_str(), 0777) < 0) { - m_d->m_reason << "CirCache::create: mkdir(" << m_dir << - ") failed" << " errno " << errno; - return false; - } + // Directory does not exist, create it + if (mkdir(m_dir.c_str(), 0777) < 0) { + m_d->m_reason << "CirCache::create: mkdir(" << m_dir << + ") failed" << " errno " << errno; + return false; + } } else { - // If the file exists too, and truncate is not set, switch - // to open-mode. Still may need to update header params. - if (access(m_d->datafn(m_dir).c_str(), 0) >= 0 && - !(flags & CC_CRTRUNCATE)) { - if (!open(CC_OPWRITE)) { - return false; - } - if (maxsize == m_d->m_maxsize && - ((flags & CC_CRUNIQUE) != 0) == m_d->m_uniquentries) { - LOGDEB(("Header unchanged, no rewrite\n")); - return true; - } - // If the new maxsize is bigger than current size, we need - // to stop recycling if this is what we are doing. - if (maxsize > m_d->m_maxsize && maxsize > st.st_size) { - // Scan the file to find the last physical record. The - // ohead is set at physical eof, and nhead is the last - // scanned record - CCScanHookRecord rec; - m_d->scan(CIRCACHE_FIRSTBLOCK_SIZE, &rec, false); - m_d->m_oheadoffs = lseek(m_d->m_fd, 0, SEEK_END); - m_d->m_nheadoffs = rec.headoffs; - m_d->m_npadsize = rec.padsize; - } - m_d->m_maxsize = maxsize; - m_d->m_uniquentries = ((flags & CC_CRUNIQUE) != 0); - LOGDEB(("CirCache::create: rewriting header with " - "maxsize %lld oheadoffs %lld nheadoffs %lld " - "npadsize %d unient %d\n", - m_d->m_maxsize, m_d->m_oheadoffs, m_d->m_nheadoffs, - m_d->m_npadsize, int(m_d->m_uniquentries))); - return m_d->writefirstblock(); - } - // Else fallthrough to create file + // If the file exists too, and truncate is not set, switch + // to open-mode. Still may need to update header params. + if (access(m_d->datafn(m_dir).c_str(), 0) >= 0 && + !(flags & CC_CRTRUNCATE)) { + if (!open(CC_OPWRITE)) { + return false; + } + if (maxsize == m_d->m_maxsize && + ((flags & CC_CRUNIQUE) != 0) == m_d->m_uniquentries) { + LOGDEB("Header unchanged, no rewrite\n" ); + return true; + } + // If the new maxsize is bigger than current size, we need + // to stop recycling if this is what we are doing. + if (maxsize > m_d->m_maxsize && maxsize > st.st_size) { + // Scan the file to find the last physical record. The + // ohead is set at physical eof, and nhead is the last + // scanned record + CCScanHookRecord rec; + m_d->scan(CIRCACHE_FIRSTBLOCK_SIZE, &rec, false); + m_d->m_oheadoffs = lseek(m_d->m_fd, 0, SEEK_END); + m_d->m_nheadoffs = rec.headoffs; + m_d->m_npadsize = rec.padsize; + } + m_d->m_maxsize = maxsize; + m_d->m_uniquentries = ((flags & CC_CRUNIQUE) != 0); + LOGDEB2("CirCache::create: rewriting header with maxsize " << (lltodecstr(m_d->m_maxsize)) << " oheadoffs " << (lltodecstr(m_d->m_oheadoffs)) << " nheadoffs " << (lltodecstr(m_d->m_nheadoffs)) << " npadsize " << (m_d->m_npadsize) << " unient " << (int(m_d->m_uniquentries)) << "\n" ); + return m_d->writefirstblock(); + } + // Else fallthrough to create file } - if ((m_d->m_fd = ::open(m_d->datafn(m_dir).c_str(), - O_CREAT | O_RDWR | O_TRUNC, 0666)) < 0) { - m_d->m_reason << "CirCache::create: open/creat(" << - m_d->datafn(m_dir) << ") failed " << "errno " << errno; - return false; + if ((m_d->m_fd = ::open(m_d->datafn(m_dir).c_str(), + O_CREAT | O_RDWR | O_TRUNC | O_BINARY, 0666)) < 0) { + m_d->m_reason << "CirCache::create: open/creat(" << + m_d->datafn(m_dir) << ") failed " << "errno " << errno; + return false; } m_d->m_maxsize = maxsize; @@ -709,9 +755,9 @@ bool CirCache::create(off_t maxsize, int flags) char buf[CIRCACHE_FIRSTBLOCK_SIZE]; memset(buf, 0, CIRCACHE_FIRSTBLOCK_SIZE); - if (::write(m_d->m_fd, buf, CIRCACHE_FIRSTBLOCK_SIZE) != - CIRCACHE_FIRSTBLOCK_SIZE) { - m_d->m_reason << "CirCache::create: write header failed, errno " + if (::write(m_d->m_fd, buf, CIRCACHE_FIRSTBLOCK_SIZE) != + CIRCACHE_FIRSTBLOCK_SIZE) { + m_d->m_reason << "CirCache::create: write header failed, errno " << errno; return false; } @@ -721,17 +767,19 @@ bool CirCache::create(off_t maxsize, int flags) bool CirCache::open(OpMode mode) { if (m_d == 0) { - LOGERR(("CirCache::open: null data\n")); - return false; + LOGERR("CirCache::open: null data\n" ); + return false; } - if (m_d->m_fd >= 0) + if (m_d->m_fd >= 0) { ::close(m_d->m_fd); + } - if ((m_d->m_fd = ::open(m_d->datafn(m_dir).c_str(), - mode == CC_OPREAD ? O_RDONLY : O_RDWR)) < 0) { - m_d->m_reason << "CirCache::open: open(" << m_d->datafn(m_dir) << - ") failed " << "errno " << errno; + if ((m_d->m_fd = ::open(m_d->datafn(m_dir).c_str(), + mode == CC_OPREAD ? + O_RDONLY | O_BINARY : O_RDWR | O_BINARY)) < 0) { + m_d->m_reason << "CirCache::open: open(" << m_d->datafn(m_dir) << + ") failed " << "errno " << errno; return false; } return m_d->readfirstblock(); @@ -739,13 +787,12 @@ bool CirCache::open(OpMode mode) class CCScanHookDump : public CCScanHook { public: - virtual status takeone(off_t offs, const string& udi, - const EntryHeaderData& d) - { - cout << "Scan: offs " << offs << " dicsize " << d.dicsize - << " datasize " << d.datasize << " padsize " << d.padsize << - " flags " << d.flags << - " udi [" << udi << "]" << endl; + virtual status takeone(off_t offs, const string& udi, + const EntryHeaderData& d) { + cout << "Scan: offs " << offs << " dicsize " << d.dicsize + << " datasize " << d.datasize << " padsize " << d.padsize << + " flags " << d.flags << + " udi [" << udi << "]" << endl; return Continue; } }; @@ -759,14 +806,14 @@ bool CirCache::dump() off_t start = m_d->m_oheadoffs; switch (m_d->scan(start, &dumper, true)) { - case CCScanHook::Stop: + case CCScanHook::Stop: cout << "Scan returns Stop??" << endl; return false; - case CCScanHook::Continue: + case CCScanHook::Continue: cout << "Scan returns Continue ?? " << CCScanHook::Continue << " " << - getReason() << endl; + getReason() << endl; return false; - case CCScanHook::Error: + case CCScanHook::Error: cout << "Scan returns Error: " << getReason() << endl; return false; case CCScanHook::Eof: @@ -786,29 +833,26 @@ public: off_t m_offs; EntryHeaderData m_hd; - CCScanHookGetter(const string &udi, int ti) - : m_udi(udi), m_targinstance(ti), m_instance(0), m_offs(0){} + CCScanHookGetter(const string& udi, int ti) + : m_udi(udi), m_targinstance(ti), m_instance(0), m_offs(0) {} - virtual status takeone(off_t offs, const string& udi, - const EntryHeaderData& d) - { - LOGDEB2(("Circache:Scan: off %ld udi [%s] dcsz %u dtsz %u pdsz %u " - " flgs %hu\n", - long(offs), udi.c_str(), (UINT)d.dicsize, - (UINT)d.datasize, (UINT)d.padsize, d.flags)); + virtual status takeone(off_t offs, const string& udi, + const EntryHeaderData& d) { + LOGDEB2("Circache:Scan: off " << (long(offs)) << " udi [" << (udi) << "] dcsz " << ((UINT)d.dicsize) << " dtsz " << ((UINT)d.datasize) << " pdsz " << ((UINT)d.padsize) << " flgs " << (d.flags) << "\n" ); if (!m_udi.compare(udi)) { m_instance++; m_offs = offs; m_hd = d; - if (m_instance == m_targinstance) + if (m_instance == m_targinstance) { return Stop; + } } return Continue; } }; // instance == -1 means get latest. Otherwise specify from 1+ -bool CirCache::get(const string& udi, string& dic, string& data, int instance) +bool CirCache::get(const string& udi, string& dic, string *data, int instance) { Chrono chron; if (m_d->m_fd < 0) { @@ -816,25 +860,26 @@ bool CirCache::get(const string& udi, string& dic, string& data, int instance) return false; } - LOGDEB0(("CirCache::get: udi [%s], instance %d\n", udi.c_str(), instance)); + LOGDEB0("CirCache::get: udi [" << (udi) << "], instance " << (instance) << "\n" ); // If memory map is up to date, use it: if (m_d->m_ofskhcplt) { - LOGDEB1(("CirCache::get: using ofskh\n")); + LOGDEB1("CirCache::get: using ofskh\n" ); //m_d->khDump(); vector ofss; if (m_d->khFind(udi, ofss)) { - LOGDEB1(("Circache::get: h found, colls %d\n", ofss.size())); + LOGDEB1("Circache::get: h found, colls " << (ofss.size()) << "\n" ); int finst = 1; EntryHeaderData d_good; off_t o_good = 0; for (vector::iterator it = ofss.begin(); - it != ofss.end(); it++) { - LOGDEB1(("Circache::get: trying offs %lu\n", (ULONG)*it)); + it != ofss.end(); it++) { + LOGDEB1("Circache::get: trying offs " << ((ULONG)*it) << "\n" ); EntryHeaderData d; string fudi; - if (!m_d->readHUdi(*it, d, fudi)) + if (!m_d->readHUdi(*it, d, fudi)) { return false; + } if (!fudi.compare(udi)) { // Found one, memorize offset. Done if instance // matches, else go on. If instance is -1 need to @@ -850,9 +895,8 @@ bool CirCache::get(const string& udi, string& dic, string& data, int instance) } // Did we read an appropriate entry ? if (o_good != 0 && (instance == -1 || instance == finst)) { - bool ret = m_d->readDicData(o_good, d_good, dic, &data); - LOGDEB0(("Circache::get: hfound, %d mS\n", - chron.millis())); + bool ret = m_d->readDicData(o_good, d_good, dic, data); + LOGDEB0("Circache::get: hfound, " << (chron.millis()) << " mS\n" ); return ret; } // Else try to scan anyway. @@ -864,22 +908,22 @@ bool CirCache::get(const string& udi, string& dic, string& data, int instance) CCScanHook::status ret = m_d->scan(start, &getter, true); if (ret == CCScanHook::Eof) { - if (getter.m_instance == 0) + if (getter.m_instance == 0) { return false; + } } else if (ret != CCScanHook::Stop) { return false; } - bool bret = - m_d->readDicData(getter.m_offs, getter.m_hd, dic, &data); - LOGDEB0(("Circache::get: scanfound, %d mS\n", chron.millis())); + bool bret = m_d->readDicData(getter.m_offs, getter.m_hd, dic, data); + LOGDEB0("Circache::get: scanfound, " << (chron.millis()) << " mS\n" ); return bret; } -bool CirCache::erase(const string& udi) +bool CirCache::erase(const string& udi, bool reallyclear) { if (m_d == 0) { - LOGERR(("CirCache::erase: null data\n")); + LOGERR("CirCache::erase: null data\n" ); return false; } if (m_d->m_fd < 0) { @@ -887,15 +931,15 @@ bool CirCache::erase(const string& udi) return false; } - LOGDEB0(("CirCache::erase: udi [%s]\n", udi.c_str())); + LOGDEB0("CirCache::erase: udi [" << (udi) << "]\n" ); // If the mem cache is not up to date, update it, we're too lazy // to do a scan if (!m_d->m_ofskhcplt) { - string dic, data; - get("nosuchudi probably exists", dic, data); + string dic; + get("nosuchudi probably exists", dic); if (!m_d->m_ofskhcplt) { - LOGERR(("CirCache::erase : cache not updated after get\n")); + LOGERR("CirCache::erase : cache not updated after get\n" ); return false; } } @@ -903,25 +947,27 @@ bool CirCache::erase(const string& udi) vector ofss; if (!m_d->khFind(udi, ofss)) { // Udi not in there, erase ok - LOGDEB(("CirCache::erase: khFind returns none\n")); + LOGDEB("CirCache::erase: khFind returns none\n" ); return true; } for (vector::iterator it = ofss.begin(); it != ofss.end(); it++) { - LOGDEB(("CirCache::erase: reading at %lu\n", (unsigned long)*it)); + LOGDEB2("CirCache::erase: reading at " << ((unsigned long)*it) << "\n" ); EntryHeaderData d; string fudi; - if (!m_d->readHUdi(*it, d, fudi)) + if (!m_d->readHUdi(*it, d, fudi)) { return false; - LOGDEB(("CirCache::erase: found fudi [%s]\n", fudi.c_str())); + } + LOGDEB2("CirCache::erase: found fudi [" << (fudi) << "]\n" ); if (!fudi.compare(udi)) { EntryHeaderData nd; nd.padsize = d.dicsize + d.datasize + d.padsize; - LOGDEB(("CirCache::erase: rewriting at %lu\n", (unsigned long)*it)); - if (*it == m_d->m_nheadoffs) + LOGDEB2("CirCache::erase: rewrite at " << ((unsigned long)*it) << "\n" ); + if (*it == m_d->m_nheadoffs) { m_d->m_npadsize = nd.padsize; - if(!m_d->writeEntryHeader(*it, nd)) { - LOGERR(("CirCache::erase: write header failed\n")); + } + if (!m_d->writeEntryHeader(*it, nd, reallyclear)) { + LOGERR("CirCache::erase: write header failed\n" ); return false; } } @@ -931,33 +977,34 @@ bool CirCache::erase(const string& udi) } // Used to scan the file ahead until we accumulated enough space for the new -// entry. +// entry. class CCScanHookSpacer : public CCScanHook { public: - UINT sizewanted; - UINT sizeseen; + off_t sizewanted; + off_t sizeseen; vector > squashed_udis; - CCScanHookSpacer(int sz) - : sizewanted(sz), sizeseen(0) {assert(sz > 0);} + CCScanHookSpacer(off_t sz) + : sizewanted(sz), sizeseen(0) { + assert(sz > 0); + } - virtual status takeone(off_t offs, const string& udi, - const EntryHeaderData& d) - { - LOGDEB2(("Circache:ScanSpacer:off %u dcsz %u dtsz %u pdsz %u udi[%s]\n", - (UINT)offs, d.dicsize, d.datasize, d.padsize, udi.c_str())); + virtual status takeone(off_t offs, const string& udi, + const EntryHeaderData& d) { + LOGDEB2("Circache:ScanSpacer:off " << ((UINT)offs) << " dcsz " << (d.dicsize) << " dtsz " << (d.datasize) << " pdsz " << (d.padsize) << " udi[" << (udi) << "]\n" ); sizeseen += CIRCACHE_HEADER_SIZE + d.dicsize + d.datasize + d.padsize; squashed_udis.push_back(make_pair(udi, offs)); - if (sizeseen >= sizewanted) + if (sizeseen >= sizewanted) { return Stop; + } return Continue; } }; -bool CirCache::put(const string& udi, const ConfSimple *iconf, +bool CirCache::put(const string& udi, const ConfSimple *iconf, const string& data, unsigned int iflags) { if (m_d == 0) { - LOGERR(("CirCache::put: null data\n")); + LOGERR("CirCache::put: null data\n" ); return false; } if (m_d->m_fd < 0) { @@ -969,15 +1016,14 @@ bool CirCache::put(const string& udi, const ConfSimple *iconf, string dic; if (!iconf || !iconf->get("udi", dic) || dic.empty() || dic.compare(udi)) { m_d->m_reason << "No/bad 'udi' entry in input dic"; - LOGERR(("Circache::put: no/bad udi: DIC:[%s] UDI [%s]\n", - dic.c_str(), udi.c_str())); + LOGERR("Circache::put: no/bad udi: DIC:[" << (dic) << "] UDI [" << (udi) << "]\n" ); return false; } // Possibly erase older entries. Need to do this first because we may be // able to reuse the space if the same udi was last written if (m_d->m_uniquentries && !erase(udi)) { - LOGERR(("CirCache::put: can't erase older entries\n")); + LOGERR("CirCache::put: can't erase older entries\n" ); return false; } @@ -987,15 +1033,15 @@ bool CirCache::put(const string& udi, const ConfSimple *iconf, // Data compression ? const char *datap = data.c_str(); - unsigned int datalen = data.size(); + size_t datalen = data.size(); unsigned short flags = 0; TempBuf compbuf; if (!(iflags & NoCompHint)) { - ULONG len = compressBound(data.size()); + uLong len = compressBound(static_cast(data.size())); char *bf = compbuf.setsize(len); if (bf != 0 && - compress((Bytef*)bf, &len, (Bytef*)data.c_str(), data.size()) - == Z_OK) { + compress((Bytef*)bf, &len, (Bytef*)data.c_str(), + static_cast(data.size())) == Z_OK) { if (float(len) < 0.9 * float(data.size())) { // bf is local but it's our static buffer address datap = bf; @@ -1012,37 +1058,38 @@ bool CirCache::put(const string& udi, const ConfSimple *iconf, } // Characteristics for the new entry. - int nsize = CIRCACHE_HEADER_SIZE + dic.size() + datalen; - int nwriteoffs = m_d->m_oheadoffs; - int npadsize = 0; + off_t nsize = CIRCACHE_HEADER_SIZE + dic.size() + datalen; + off_t nwriteoffs = m_d->m_oheadoffs; + off_t npadsize = 0; bool extending = false; - LOGDEB(("CirCache::put: nsz %d oheadoffs %d\n", nsize, m_d->m_oheadoffs)); + LOGDEB("CirCache::put: nsz " << (nsize) << " oheadoffs " << (m_d->m_oheadoffs) << "\n" ); // Check if we can recover some pad space from the (physically) previous // entry. - int recovpadsize = m_d->m_oheadoffs == CIRCACHE_FIRSTBLOCK_SIZE ? - 0 : m_d->m_npadsize; + off_t recovpadsize = m_d->m_oheadoffs == CIRCACHE_FIRSTBLOCK_SIZE ? + 0 : m_d->m_npadsize; if (recovpadsize != 0) { - // Need to read the latest entry's header, to rewrite it with a + // Need to read the latest entry's header, to rewrite it with a // zero pad size EntryHeaderData pd; - if (m_d->readEntryHeader(m_d->m_nheadoffs, pd) != CCScanHook::Continue){ + if (m_d->readEntryHeader(m_d->m_nheadoffs, pd) != CCScanHook::Continue) { + return false; + } + if (int(pd.padsize) != m_d->m_npadsize) { + m_d->m_reason << "CirCache::put: logic error: bad padsize "; return false; } - if (int(pd.padsize) != m_d->m_npadsize) { - m_d->m_reason << "CirCache::put: logic error: bad padsize "; - return false; - } if (pd.dicsize == 0) { // erased entry. Also recover the header space, no need to rewrite // the header, we're going to write on it. recovpadsize += CIRCACHE_HEADER_SIZE; } else { - LOGDEB(("CirCache::put: recov. prev. padsize %d\n", pd.padsize)); + LOGDEB("CirCache::put: recov. prev. padsize " << (pd.padsize) << "\n" ); pd.padsize = 0; - if (!m_d->writeEntryHeader(m_d->m_nheadoffs, pd)) + if (!m_d->writeEntryHeader(m_d->m_nheadoffs, pd)) { return false; + } // If we fail between here and the end, the file is broken. } nwriteoffs = m_d->m_oheadoffs - recovpadsize; @@ -1051,39 +1098,36 @@ bool CirCache::put(const string& udi, const ConfSimple *iconf, if (nsize <= recovpadsize) { // If the new entry fits entirely in the pad area from the // latest one, no need to recycle stuff - LOGDEB(("CirCache::put: new fits in old padsize %d\n", recovpadsize)); + LOGDEB("CirCache::put: new fits in old padsize " << (recovpadsize) << "\n" ); npadsize = recovpadsize - nsize; } else if (st.st_size < m_d->m_maxsize) { - // Still growing the file. + // Still growing the file. npadsize = 0; extending = true; } else { // Scan the file until we have enough space for the new entry, // and determine the pad size up to the 1st preserved entry - int scansize = nsize - recovpadsize; - LOGDEB(("CirCache::put: scanning for size %d from offs %u\n", - scansize, (UINT)m_d->m_oheadoffs)); + off_t scansize = nsize - recovpadsize; + LOGDEB("CirCache::put: scanning for size " << (scansize) << " from offs " << ((UINT)m_d->m_oheadoffs) << "\n" ); CCScanHookSpacer spacer(scansize); switch (m_d->scan(m_d->m_oheadoffs, &spacer)) { - case CCScanHook::Stop: - LOGDEB(("CirCache::put: Scan ok, sizeseen %d\n", - spacer.sizeseen)); + case CCScanHook::Stop: + LOGDEB("CirCache::put: Scan ok, sizeseen " << (spacer.sizeseen) << "\n" ); npadsize = spacer.sizeseen - scansize; break; case CCScanHook::Eof: npadsize = 0; extending = true; break; - case CCScanHook::Continue: - case CCScanHook::Error: + case CCScanHook::Continue: + case CCScanHook::Error: return false; } // Take the recycled entries off the multimap m_d->khClear(spacer.squashed_udis); } - - LOGDEB(("CirCache::put: writing %d at %d padsize %d\n", - nsize, nwriteoffs, npadsize)); + + LOGDEB("CirCache::put: writing " << (nsize) << " at " << (nwriteoffs) << " padsize " << (npadsize) << "\n" ); if (lseek(m_d->m_fd, nwriteoffs, 0) != nwriteoffs) { m_d->m_reason << "CirCache::put: lseek failed: " << errno; @@ -1092,8 +1136,8 @@ bool CirCache::put(const string& udi, const ConfSimple *iconf, char head[CIRCACHE_HEADER_SIZE]; memset(head, 0, CIRCACHE_HEADER_SIZE); - snprintf(head, CIRCACHE_HEADER_SIZE, - headerformat, dic.size(), datalen, npadsize, flags); + snprintf(head, CIRCACHE_HEADER_SIZE, + headerformat, dic.size(), datalen, npadsize, flags); struct iovec vecs[3]; vecs[0].iov_base = head; vecs[0].iov_len = CIRCACHE_HEADER_SIZE; @@ -1127,28 +1171,28 @@ bool CirCache::put(const string& udi, const ConfSimple *iconf, bool CirCache::rewind(bool& eof) { if (m_d == 0) { - LOGERR(("CirCache::rewind: null data\n")); + LOGERR("CirCache::rewind: null data\n" ); return false; } eof = false; - off_t fsize = lseek(m_d->m_fd, 0, SEEK_END); - if (fsize == (off_t)-1) { - LOGERR(("CirCache::rewind: seek to EOF failed\n")); - return false; - } + off_t fsize = lseek(m_d->m_fd, 0, SEEK_END); + if (fsize == (off_t) - 1) { + LOGERR("CirCache::rewind: seek to EOF failed\n" ); + return false; + } // Read oldest header. This is either at the position pointed to // by oheadoffs, or after the first block if the file is still // growing. - if (m_d->m_oheadoffs == fsize) { - m_d->m_itoffs = CIRCACHE_FIRSTBLOCK_SIZE; - } else { - m_d->m_itoffs = m_d->m_oheadoffs; - } + if (m_d->m_oheadoffs == fsize) { + m_d->m_itoffs = CIRCACHE_FIRSTBLOCK_SIZE; + } else { + m_d->m_itoffs = m_d->m_oheadoffs; + } CCScanHook::status st = m_d->readEntryHeader(m_d->m_itoffs, m_d->m_ithd); - switch(st) { + switch (st) { case CCScanHook::Eof: eof = true; return false; @@ -1162,15 +1206,15 @@ bool CirCache::rewind(bool& eof) bool CirCache::next(bool& eof) { if (m_d == 0) { - LOGERR(("CirCache::next: null data\n")); + LOGERR("CirCache::next: null data\n" ); return false; } eof = false; // Skip to next header, using values stored from previous one - m_d->m_itoffs += CIRCACHE_HEADER_SIZE + m_d->m_ithd.dicsize + - m_d->m_ithd.datasize + m_d->m_ithd.padsize; + m_d->m_itoffs += CIRCACHE_HEADER_SIZE + m_d->m_ithd.dicsize + + m_d->m_ithd.datasize + m_d->m_ithd.padsize; // Looped back ? if (m_d->m_itoffs == m_d->m_oheadoffs) { @@ -1190,31 +1234,34 @@ bool CirCache::next(bool& eof) st = m_d->readEntryHeader(m_d->m_itoffs, m_d->m_ithd); } - if (st == CCScanHook::Continue) + if (st == CCScanHook::Continue) { return true; + } return false; } bool CirCache::getCurrentUdi(string& udi) { if (m_d == 0) { - LOGERR(("CirCache::getCurrentUdi: null data\n")); + LOGERR("CirCache::getCurrentUdi: null data\n" ); return false; } - - if (!m_d->readHUdi(m_d->m_itoffs, m_d->m_ithd, udi)) + + if (!m_d->readHUdi(m_d->m_itoffs, m_d->m_ithd, udi)) { return false; + } return true; } -bool CirCache::getCurrent(string& udi, string& dic, string& data) +bool CirCache::getCurrent(string& udi, string& dic, string *data) { if (m_d == 0) { - LOGERR(("CirCache::getCurrent: null data\n")); + LOGERR("CirCache::getCurrent: null data\n" ); return false; } - if (!m_d->readDicData(m_d->m_itoffs, m_d->m_ithd, dic, &data)) + if (!m_d->readDicData(m_d->m_itoffs, m_d->m_ithd, dic, data)) { return false; + } ConfSimple conf(dic, 1); conf.get("udi", udi, cstr_null); @@ -1222,11 +1269,11 @@ bool CirCache::getCurrent(string& udi, string& dic, string& data) } static void *allocmem( - void *cp, /* The array to grow. may be NULL */ - int sz, /* Unit size in bytes */ + void *cp, /* The array to grow. may be NULL */ + int sz, /* Unit size in bytes */ int *np, /* Pointer to current allocation number */ - int min, /* Number to allocate the first time */ - int maxinc) /* Maximum increment */ + int min, /* Number to allocate the first time */ + int maxinc) /* Maximum increment */ { if (cp == 0) { cp = malloc(min * sz); @@ -1235,8 +1282,9 @@ static void *allocmem( } int inc = (*np > maxinc) ? maxinc : *np; - if ((cp = realloc(cp, (*np + inc) * sz)) != 0) + if ((cp = realloc(cp, (*np + inc) * sz)) != 0) { *np += inc; + } return cp; } @@ -1244,7 +1292,7 @@ static bool inflateToDynBuf(void* inp, UINT inlen, void **outpp, UINT *outlenp) { z_stream d_stream; /* decompression stream */ - LOGDEB0(("inflateToDynBuf: inlen %u\n", inlen)); + LOGDEB0("inflateToDynBuf: inlen " << (inlen) << "\n" ); d_stream.zalloc = (alloc_func)0; d_stream.zfree = (free_func)0; @@ -1263,32 +1311,31 @@ static bool inflateToDynBuf(void* inp, UINT inlen, void **outpp, UINT *outlenp) int err; if ((err = inflateInit(&d_stream)) != Z_OK) { - LOGERR(("Inflate: inflateInit: err %d msg %s\n", err, d_stream.msg)); + LOGERR("Inflate: inflateInit: err " << (err) << " msg " << (d_stream.msg) << "\n" ); free(outp); return false; } for (;;) { - LOGDEB2(("InflateToDynBuf: avail_in %d total_in %d avail_out %d " - "total_out %d\n", d_stream.avail_in, d_stream.total_in, - d_stream.avail_out, d_stream.total_out)); + LOGDEB2("InflateToDynBuf: avail_in " << (d_stream.avail_in) << " total_in " << (d_stream.total_in) << " avail_out " << (d_stream.avail_out) << " total_out " << (d_stream.total_out) << "\n" ); if (d_stream.avail_out == 0) { - if ((outp = (char*)allocmem(outp, inlen, &alloc, - imul, mxinc)) == 0) { - LOGERR(("Inflate: out of memory, current alloc %d\n", - alloc*inlen)); + if ((outp = (char*)allocmem(outp, inlen, &alloc, + imul, mxinc)) == 0) { + LOGERR("Inflate: out of memory, current alloc " << (alloc * inlen) << "\n" ); inflateEnd(&d_stream); return false; } else { - LOGDEB2(("inflateToDynBuf: realloc(%d) ok\n", alloc * inlen)); + LOGDEB2("inflateToDynBuf: realloc(" << (alloc * inlen) << ") ok\n" ); } d_stream.avail_out = alloc * inlen - d_stream.total_out; d_stream.next_out = (Bytef*)(outp + d_stream.total_out); } err = inflate(&d_stream, Z_NO_FLUSH); - if (err == Z_STREAM_END) break; + if (err == Z_STREAM_END) { + break; + } if (err != Z_OK) { - LOGERR(("Inflate: error %d msg %s\n", err, d_stream.msg)); + LOGERR("Inflate: error " << (err) << " msg " << (d_stream.msg) << "\n" ); inflateEnd(&d_stream); free(outp); return false; @@ -1298,82 +1345,30 @@ static bool inflateToDynBuf(void* inp, UINT inlen, void **outpp, UINT *outlenp) *outpp = (Bytef *)outp; if ((err = inflateEnd(&d_stream)) != Z_OK) { - LOGERR(("Inflate: inflateEnd error %d msg %s\n", err, d_stream.msg)); + LOGERR("Inflate: inflateEnd error " << (err) << " msg " << (d_stream.msg) << "\n" ); return false; } - LOGDEB0(("inflateToDynBuf: ok, output size %d\n", d_stream.total_out)); + LOGDEB0("inflateToDynBuf: ok, output size " << (d_stream.total_out) << "\n" ); return true; } -#else // TEST -> -#include "autoconfig.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "circache.h" -#include "fileudi.h" -#include "conftree.h" -#include "readfile.h" -#include "debuglog.h" - -using namespace std; - - -bool resizecc(const string& dir, int newmbs) +// Copy all entries from occ to ncc. Both are already open. +static bool copyall(std::shared_ptr occ, + std::shared_ptr ncc, int& nentries, + ostringstream& msg) { - RefCntr occ(new CirCache(dir)); - string ofn = occ->getpath(); - - string backupfn = ofn + ".orig"; - if (access(backupfn.c_str(), 0) >= 0) { - cerr << "Backup file " << backupfn << - " exists, please move it out of the way" << endl; - return false; - } - - if (!occ->open(CirCache::CC_OPWRITE)) { - cerr << "Open failed in " << dir << " : " << occ->getReason() << endl; - return false; - } - string tmpdir = path_cat(dir, "tmp"); - if (access(tmpdir.c_str(), 0) < 0) { - if (mkdir(tmpdir.c_str(), 0700) < 0) { - cerr << "Cant create temporary directory " << tmpdir << " "; - perror("mkdir"); + bool eof = false; + if (!occ->rewind(eof)) { + if (!eof) { + msg << "Initial rewind failed" << endl; return false; } } - - RefCntr ncc(new CirCache(tmpdir)); - string nfn = ncc->getpath(); - if (!ncc->create(off_t(newmbs) * 1000 * 1024, - CirCache::CC_CRUNIQUE | CirCache::CC_CRTRUNCATE)) { - cerr << "Cant create new file in " << tmpdir << " : " << - ncc->getReason() << endl; - return false; - } - - bool eof = false; - if (!occ->rewind(eof)) { - if (!eof) { - cerr << "Initial rewind failed" << endl; - return false; - } - } - int nentries = 0; + nentries = 0; while (!eof) { string udi, sdic, data; - if (!occ->getCurrent(udi, sdic, data)) { - cerr << "getCurrent failed: " << occ->getReason() << endl; + if (!occ->getCurrent(udi, sdic, &data)) { + msg << "getCurrent failed: " << occ->getReason() << endl; return false; } // Shouldn't getcurrent deal with this ? @@ -1384,50 +1379,97 @@ bool resizecc(const string& dir, int newmbs) } ConfSimple dic(sdic); if (!dic.ok()) { - cerr << "Could not parse entry attributes dic" << endl; + msg << "Could not parse entry attributes dic" << endl; return false; } //cerr << "UDI: " << udi << endl; if (!ncc->put(udi, &dic, data)) { - cerr << "put failed: " << ncc->getReason() << " sdic [" << sdic << - "]" << endl; + msg << "put failed: " << ncc->getReason() << " sdic [" << sdic << + "]" << endl; return false; } nentries++; occ->next(eof); } - - // Done with our objects here, there is no close() method, so delete them - occ.release(); - ncc.release(); - - if (rename(ofn.c_str(), backupfn.c_str()) < 0) { - cerr << "Could not create backup " << backupfn << " : "; - perror("rename"); - return false; - } - cout << "Created backup file " << backupfn << endl; - if (rename(nfn.c_str(), ofn.c_str()) < 0) { - cerr << "Could not rename new file from " << nfn << " to " << ofn << " : "; - perror("rename"); - return false; - } - cout << "Resize done, copied " << nentries << " entries " << endl; return true; } +// Append all entries from sdir to ddir +int CirCache::append(const string ddir, const string& sdir, string *reason) +{ + ostringstream msg; + // Open source file + std::shared_ptr occ(new CirCache(sdir)); + if (!occ->open(CirCache::CC_OPREAD)) { + if (reason) { + msg << "Open failed in " << sdir << " : " << + occ->getReason() << endl; + *reason = msg.str(); + } + return -1; + } + // Open dest file + std::shared_ptr ncc(new CirCache(ddir)); + if (!ncc->open(CirCache::CC_OPWRITE)) { + if (reason) { + msg << "Open failed in " << ddir << " : " << + ncc->getReason() << endl; + *reason = msg.str(); + } + return -1; + } + + int nentries; + if (!copyall(occ, ncc, nentries, msg)) { + if (reason) { + *reason = msg.str(); + } + return -1; + } + + return nentries; +} + + +#else // TEST -> +#include "autoconfig.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "circache.h" +#include "fileudi.h" +#include "conftree.h" +#include "readfile.h" +#include "log.h" + +#include "smallut.h" + +using namespace std; static char *thisprog; -static char usage [] = -" -c [-u] : create\n" -" -p [apath ...] : put files\n" -" -d : dump\n" -" -g [-i instance] [-D] : get\n" -" -D: also dump data\n" -" -e : erase\n" -" -s : resize\n" -; +static char usage [] = + " -c [-u] : create\n" + " -p [apath ...] : put files\n" + " -d : dump\n" + " -g [-i instance] [-D] : get\n" + " -D: also dump data\n" + " -e : erase\n" + " -a

          [ ...]: append old content to target\n" + " The target should be first resized to hold all the data, else only\n" + " as many entries as capacity permit will be retained\n" + ; + static void Usage(FILE *fp = stderr) { @@ -1437,7 +1479,7 @@ Usage(FILE *fp = stderr) static int op_flags; #define OPT_MOINS 0x1 -#define OPT_c 0x2 +#define OPT_c 0x2 #define OPT_p 0x8 #define OPT_g 0x10 #define OPT_d 0x20 @@ -1445,134 +1487,196 @@ static int op_flags; #define OPT_D 0x80 #define OPT_u 0x100 #define OPT_e 0x200 -#define OPT_s 0x400 +#define OPT_a 0x800 int main(int argc, char **argv) { int instance = -1; thisprog = argv[0]; - argc--; argv++; + argc--; + argv++; while (argc > 0 && **argv == '-') { - (*argv)++; - if (!(**argv)) - /* Cas du "adb - core" */ - Usage(); - while (**argv) - switch (*(*argv)++) { - case 'c': op_flags |= OPT_c; break; - case 'D': op_flags |= OPT_D; break; - case 'd': op_flags |= OPT_d; break; - case 'e': op_flags |= OPT_e; break; - case 'g': op_flags |= OPT_g; break; - case 'i': op_flags |= OPT_i; if (argc < 2) Usage(); - if ((sscanf(*(++argv), "%d", &instance)) != 1) - Usage(); - argc--; - goto b1; - case 'p': op_flags |= OPT_p; break; - case 's': op_flags |= OPT_s; break; - case 'u': op_flags |= OPT_u; break; - default: Usage(); break; - } - b1: argc--; argv++; + (*argv)++; + if (!(**argv)) + /* Cas du "adb - core" */ + { + Usage(); + } + while (**argv) + switch (*(*argv)++) { + case 'a': + op_flags |= OPT_a; + break; + case 'c': + op_flags |= OPT_c; + break; + case 'D': + op_flags |= OPT_D; + break; + case 'd': + op_flags |= OPT_d; + break; + case 'e': + op_flags |= OPT_e; + break; + case 'g': + op_flags |= OPT_g; + break; + case 'i': + op_flags |= OPT_i; + if (argc < 2) { + Usage(); + } + if ((sscanf(*(++argv), "%d", &instance)) != 1) { + Usage(); + } + argc--; + goto b1; + case 'p': + op_flags |= OPT_p; + break; + case 'u': + op_flags |= OPT_u; + break; + default: + Usage(); + break; + } +b1: + argc--; + argv++; } DebugLog::getdbl()->setloglevel(DEBERR); DebugLog::setfilename("stderr"); - if (argc < 1) - Usage(); - string dir = *argv++;argc--; + if (argc < 1) { + Usage(); + } + string dir = *argv++; + argc--; CirCache cc(dir); if (op_flags & OPT_c) { - int flags = 0; - if (op_flags & OPT_u) - flags |= CirCache::CC_CRUNIQUE; - if (!cc.create(100*1024, flags)) { - cerr << "Create failed:" << cc.getReason() << endl; - exit(1); - } - } else if (op_flags & OPT_s) { if (argc != 1) { Usage(); } - int newmbs = atoi(*argv++);argc--; - if (!resizecc(dir, newmbs)) { + off_t sizekb = atoi(*argv++); + argc--; + int flags = 0; + if (op_flags & OPT_u) { + flags |= CirCache::CC_CRUNIQUE; + } + if (!cc.create(sizekb * 1024, flags)) { + cerr << "Create failed:" << cc.getReason() << endl; exit(1); } + } else if (op_flags & OPT_a) { + if (argc < 1) { + Usage(); + } + while (argc) { + string reason; + if (CirCache::append(dir, *argv++, &reason) < 0) { + cerr << reason << endl; + return 1; + } + argc--; + } } else if (op_flags & OPT_p) { - if (argc < 1) - Usage(); - if (!cc.open(CirCache::CC_OPWRITE)) { - cerr << "Open failed: " << cc.getReason() << endl; - exit(1); - } - while (argc) { - string fn = *argv++;argc--; - char dic[1000]; - string data, reason; - if (!file_to_string(fn, data, &reason)) { - cerr << "File_to_string: " << reason << endl; - exit(1); - } - string udi; - make_udi(fn, "", udi); - sprintf(dic, "#whatever...\nmimetype = text/plain\nudi=%s\n", - udi.c_str()); - string sdic; - sdic.assign(dic, strlen(dic)); - ConfSimple conf(sdic); - - if (!cc.put(udi, &conf, data, 0)) { - cerr << "Put failed: " << cc.getReason() << endl; - cerr << "conf: ["; conf.write(cerr); cerr << "]" << endl; - exit(1); - } - } - cc.open(CirCache::CC_OPREAD); + if (argc < 1) { + Usage(); + } + if (!cc.open(CirCache::CC_OPWRITE)) { + cerr << "Open failed: " << cc.getReason() << endl; + exit(1); + } + while (argc) { + string fn = *argv++; + argc--; + char dic[1000]; + string data, reason; + if (!file_to_string(fn, data, &reason)) { + cerr << "File_to_string: " << reason << endl; + exit(1); + } + string udi; + make_udi(fn, "", udi); + string cmd("xdg-mime query filetype "); + // Should do more quoting here... + cmd += "'" + fn + "'"; + FILE *fp = popen(cmd.c_str(), "r"); + char* buf=0; + size_t sz = 0; + ::getline(&buf, &sz, fp); + pclose(fp); + string mimetype(buf); + free(buf); + trimstring(mimetype, "\n\r"); + cout << "Got [" << mimetype << "]\n"; + + string s; + ConfSimple conf(s); + conf.set("udi", udi); + conf.set("mimetype", mimetype); + //ostringstream str; conf.write(str); cout << str.str() << endl; + + if (!cc.put(udi, &conf, data, 0)) { + cerr << "Put failed: " << cc.getReason() << endl; + cerr << "conf: ["; + conf.write(cerr); + cerr << "]" << endl; + exit(1); + } + } + cc.open(CirCache::CC_OPREAD); } else if (op_flags & OPT_g) { - if (!cc.open(CirCache::CC_OPREAD)) { - cerr << "Open failed: " << cc.getReason() << endl; - exit(1); - } - while (argc) { - string udi = *argv++;argc--; - string dic, data; - if (!cc.get(udi, dic, data, instance)) { - cerr << "Get failed: " << cc.getReason() << endl; - exit(1); - } - cout << "Dict: [" << dic << "]" << endl; - if (op_flags & OPT_D) - cout << "Data: [" << data << "]" << endl; - } + if (!cc.open(CirCache::CC_OPREAD)) { + cerr << "Open failed: " << cc.getReason() << endl; + exit(1); + } + while (argc) { + string udi = *argv++; + argc--; + string dic, data; + if (!cc.get(udi, dic, &data, instance)) { + cerr << "Get failed: " << cc.getReason() << endl; + exit(1); + } + cout << "Dict: [" << dic << "]" << endl; + if (op_flags & OPT_D) { + cout << "Data: [" << data << "]" << endl; + } + } } else if (op_flags & OPT_e) { - if (!cc.open(CirCache::CC_OPWRITE)) { - cerr << "Open failed: " << cc.getReason() << endl; - exit(1); - } - while (argc) { - string udi = *argv++;argc--; - string dic, data; - if (!cc.erase(udi)) { - cerr << "Erase failed: " << cc.getReason() << endl; - exit(1); - } - } + if (!cc.open(CirCache::CC_OPWRITE)) { + cerr << "Open failed: " << cc.getReason() << endl; + exit(1); + } + while (argc) { + string udi = *argv++; + argc--; + string dic, data; + if (!cc.erase(udi)) { + cerr << "Erase failed: " << cc.getReason() << endl; + exit(1); + } + } } else if (op_flags & OPT_d) { - if (!cc.open(CirCache::CC_OPREAD)) { - cerr << "Open failed: " << cc.getReason() << endl; - exit(1); - } - cc.dump(); - } else - Usage(); + if (!cc.open(CirCache::CC_OPREAD)) { + cerr << "Open failed: " << cc.getReason() << endl; + exit(1); + } + cc.dump(); + } else { + Usage(); + } exit(0); } #endif + diff --git a/src/utils/circache.h b/src/utils/circache.h index d8303680..b7562ad5 100644 --- a/src/utils/circache.h +++ b/src/utils/circache.h @@ -24,14 +24,14 @@ * specified maximum size, then is rewritten from the start, * overwriting older entries. * - * Data objects inside the cache each have two parts: a data segment and an + * Data objects inside the cache each have two parts: a data segment and an * attribute (metadata) dictionary. * They are named using the same identifiers that are used inside the Recoll * index (the UDI). * * Inside the file. the UDIs are stored inside the entry dictionary * under the key "udi". - * + * * It is assumed that the dictionary are small (they are routinely read/parsed) * */ @@ -40,26 +40,23 @@ #include -#ifndef NO_NAMESPACES -using std::string; -#endif - class ConfSimple; class CirCacheInternal; class CirCache { public: - CirCache(const string& dir); + CirCache(const std::string& dir); virtual ~CirCache(); - virtual string getReason(); + virtual std::string getReason(); - enum CreateFlags {CC_CRNONE=0, - // Unique entries: erase older instances when same udi - // is stored. - CC_CRUNIQUE=1, - // Truncate file (restart from scratch). - CC_CRTRUNCATE = 2}; + enum CreateFlags {CC_CRNONE = 0, + // Unique entries: erase older instances when same udi + // is stored. + CC_CRUNIQUE = 1, + // Truncate file (restart from scratch). + CC_CRTRUNCATE = 2 + }; virtual bool create(off_t maxsize, int flags); enum OpMode {CC_OPREAD, CC_OPWRITE}; @@ -67,15 +64,16 @@ public: virtual std::string getpath(); - virtual bool get(const string& udi, string& dic, string& data, - int instance = -1); + // Set data to 0 if you just want the header + virtual bool get(const std::string& udi, std::string& dic, + std::string *data = 0, int instance = -1); // Note: the dicp MUST have an udi entry enum PutFlags {NoCompHint = 1}; - virtual bool put(const string& udi, const ConfSimple *dicp, - const string& data, unsigned int flags = 0); + virtual bool put(const std::string& udi, const ConfSimple *dicp, + const std::string& data, unsigned int flags = 0); - virtual bool erase(const string& udi); + virtual bool erase(const std::string& udi, bool reallyclear = false); /** Walk the archive. * @@ -86,22 +84,37 @@ public: /** Back to oldest */ virtual bool rewind(bool& eof); /** Get entry under cursor */ - virtual bool getCurrent(string& udi, string& dic, string& data); + virtual bool getCurrent(std::string& udi, std::string& dic, + std::string *data = 0); /** Get current entry udi only. Udi can be empty (erased empty), caller * should call again */ - virtual bool getCurrentUdi(string& udi); + virtual bool getCurrentUdi(std::string& udi); /** Skip to next. (false && !eof) -> error, (false&&eof)->EOF. */ virtual bool next(bool& eof); /* Debug. This writes the entry headers to stdout */ virtual bool dump(); + /* Utility: append all entries from sdir to ddir. + * + * This does not need to be a member at all, just using the namespace here. + * + * @param ddir destination circache (must be previously created + * with appropriate size) + * @param sdir source circache + * @ret number of entries copied or -a + */ + static int append(const std::string ddir, const std::string& sdir, + std::string *reason = 0); + protected: CirCacheInternal *m_d; - string m_dir; + std::string m_dir; private: CirCache(const CirCache&) {} - CirCache& operator=(const CirCache&) {return *this;} + CirCache& operator=(const CirCache&) { + return *this; + } }; #endif /* _circache_h_included_ */ diff --git a/src/utils/conftree.cpp b/src/utils/conftree.cpp index 7cb89875..12eb4b50 100644 --- a/src/utils/conftree.cpp +++ b/src/utils/conftree.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2003 J.F.Dockes +/* Copyright (C) 2003-2016 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 @@ -14,126 +14,144 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef HAVE_CONFIG_H +#ifdef BUILDING_RECOLL +#include "autoconfig.h" +#else #include "config.h" #endif -#ifndef TEST_CONFTREE +#include "conftree.h" -#include // for access(2) #include #include +#ifdef _WIN32 +#include "safesysstat.h" +#else +#include #include +#include +#include +#endif -#include -#include #include -#include #include +#include +#include +#include +#include + +#include "pathut.h" +#include "smallut.h" +#include "log.h" using namespace std; -#include "conftree.h" -#include "pathut.h" -#include "smallut.h" - -#undef DEBUG -#ifdef DEBUG -#define LOGDEB(X) fprintf X +#undef DEBUG_CONFTREE +#ifdef DEBUG_CONFTREE +#define CONFDEB LOGDEB #else -#define LOGDEB(X) +#define CONFDEB LOGDEB2 #endif -#define LL 1024 -void ConfSimple::parseinput(istream &input) +static const SimpleRegexp varcomment_rx("[ \t]*#[ \t]*([a-zA-Z0-9]+)[ \t]*=", + 0, 1); + +void ConfSimple::parseinput(istream& input) { string submapkey; - char cline[LL]; + string cline; bool appending = false; string line; bool eof = false; for (;;) { - cline[0] = 0; - input.getline(cline, LL-1); - LOGDEB((stderr, "Parse:line: [%s] status %d\n", cline, int(status))); - if (!input.good()) { - if (input.bad()) { - LOGDEB((stderr, "Parse: input.bad()\n")); - status = STATUS_ERROR; - return; - } - LOGDEB((stderr, "Parse: eof\n")); - // Must be eof ? But maybe we have a partial line which - // must be processed. This happens if the last line before - // eof ends with a backslash, or there is no final \n + cline.clear(); + std::getline(input, cline); + CONFDEB("Parse:line: [" << cline << "] status " << status << "\n"); + if (!input.good()) { + if (input.bad()) { + CONFDEB("Parse: input.bad()\n"); + status = STATUS_ERROR; + return; + } + CONFDEB("Parse: eof\n"); + // Must be eof ? But maybe we have a partial line which + // must be processed. This happens if the last line before + // eof ends with a backslash, or there is no final \n eof = true; - } + } { - int ll = strlen(cline); - while (ll > 0 && (cline[ll-1] == '\n' || cline[ll-1] == '\r')) { - cline[ll-1] = 0; - ll--; + string::size_type pos = cline.find_last_not_of("\n\r"); + if (pos == string::npos) { + cline.clear(); + } else if (pos != cline.length() - 1) { + cline.erase(pos + 1); } } - if (appending) - line += cline; - else - line = cline; + if (appending) { + line += cline; + } else { + line = cline; + } - // Note that we trim whitespace before checking for backslash-eol - // This avoids invisible whitespace problems. - trimstring(line); - if (line.empty() || line.at(0) == '#') { - if (eof) + // Note that we trim whitespace before checking for backslash-eol + // This avoids invisible whitespace problems. + trimstring(line); + if (line.empty() || line.at(0) == '#') { + if (eof) { break; - m_order.push_back(ConfLine(ConfLine::CFL_COMMENT, line)); - continue; - } - if (line[line.length() - 1] == '\\') { - line.erase(line.length() - 1); - appending = true; - continue; - } - appending = false; + } + if (varcomment_rx.simpleMatch(line)) { + m_order.push_back(ConfLine(ConfLine::CFL_VARCOMMENT, line, + varcomment_rx.getMatch(line, 1))); + } else { + m_order.push_back(ConfLine(ConfLine::CFL_COMMENT, line)); + } + continue; + } + if (line[line.length() - 1] == '\\') { + line.erase(line.length() - 1); + appending = true; + continue; + } + appending = false; - if (line[0] == '[') { - trimstring(line, "[]"); - if (dotildexpand) - submapkey = path_tildexpand(line); - else - submapkey = line; - // No need for adding sk to order, will be done with first - // variable insert. Also means that empty section are - // expandable (won't be output when rewriting) - // Another option would be to add the subsec to m_order here - // and not do it inside i_set() if init is true - continue; - } + if (line[0] == '[') { + trimstring(line, "[]"); + if (dotildexpand) { + submapkey = path_tildexpand(line); + } else { + submapkey = line; + } + m_subkeys_unsorted.push_back(submapkey); + m_order.push_back(ConfLine(ConfLine::CFL_SK, submapkey)); + continue; + } - // Look for first equal sign - string::size_type eqpos = line.find("="); - if (eqpos == string::npos) { - m_order.push_back(ConfLine(ConfLine::CFL_COMMENT, line)); - continue; - } + // Look for first equal sign + string::size_type eqpos = line.find("="); + if (eqpos == string::npos) { + m_order.push_back(ConfLine(ConfLine::CFL_COMMENT, line)); + continue; + } - // Compute name and value, trim white space - string nm, val; - nm = line.substr(0, eqpos); - trimstring(nm); - val = line.substr(eqpos+1, string::npos); - trimstring(val); - - if (nm.length() == 0) { - m_order.push_back(ConfLine(ConfLine::CFL_COMMENT, line)); - continue; - } - i_set(nm, val, submapkey, true); - if (eof) + // Compute name and value, trim white space + string nm, val; + nm = line.substr(0, eqpos); + trimstring(nm); + val = line.substr(eqpos + 1, string::npos); + trimstring(val); + + if (nm.length() == 0) { + m_order.push_back(ConfLine(ConfLine::CFL_COMMENT, line)); + continue; + } + i_set(nm, val, submapkey, true); + if (eof) { break; + } } } @@ -167,31 +185,31 @@ ConfSimple::ConfSimple(const char *fname, int readonly, bool tildexp) ifstream input; if (readonly) { - input.open(fname, ios::in); + input.open(fname, ios::in); } else { - ios::openmode mode = ios::in|ios::out; - // It seems that there is no separate 'create if not exists' - // open flag. Have to truncate to create, but dont want to do - // this to an existing file ! - if (access(fname, 0) < 0) { - mode |= ios::trunc; - } - input.open(fname, mode); - if (input.is_open()) { - status = STATUS_RW; - } else { - input.clear(); - input.open(fname, ios::in); - if (input.is_open()) { - status = STATUS_RO; - } - } + ios::openmode mode = ios::in | ios::out; + // It seems that there is no separate 'create if not exists' + // open flag. Have to truncate to create, but dont want to do + // this to an existing file ! + if (!path_exists(fname)) { + mode |= ios::trunc; + } + input.open(fname, mode); + if (input.is_open()) { + status = STATUS_RW; + } else { + input.clear(); + input.open(fname, ios::in); + if (input.is_open()) { + status = STATUS_RO; + } + } } if (!input.is_open()) { - status = STATUS_ERROR; - return; - } + status = STATUS_ERROR; + return; + } parseinput(input); i_changed(true); @@ -200,21 +218,24 @@ ConfSimple::ConfSimple(const char *fname, int readonly, bool tildexp) ConfSimple::StatusCode ConfSimple::getStatus() const { switch (status) { - case STATUS_RO: return STATUS_RO; - case STATUS_RW: return STATUS_RW; - default: return STATUS_ERROR; + case STATUS_RO: + return STATUS_RO; + case STATUS_RW: + return STATUS_RW; + default: + return STATUS_ERROR; } } bool ConfSimple::sourceChanged() const { if (!m_filename.empty()) { - struct stat st; - if (stat(m_filename.c_str(), &st) == 0) { - if (m_fmtime != st.st_mtime) { - return true; - } - } + struct stat st; + if (stat(m_filename.c_str(), &st) == 0) { + if (m_fmtime != st.st_mtime) { + return true; + } + } } return false; } @@ -222,134 +243,172 @@ bool ConfSimple::sourceChanged() const bool ConfSimple::i_changed(bool upd) { if (!m_filename.empty()) { - struct stat st; - if (stat(m_filename.c_str(), &st) == 0) { - if (m_fmtime != st.st_mtime) { - if (upd) - m_fmtime = st.st_mtime; - return true; - } - } + struct stat st; + if (stat(m_filename.c_str(), &st) == 0) { + if (m_fmtime != st.st_mtime) { + if (upd) { + m_fmtime = st.st_mtime; + } + return true; + } + } } return false; } -int ConfSimple::get(const string &nm, string &value, const string &sk) const +int ConfSimple::get(const string& nm, string& value, const string& sk) const { - if (!ok()) - return 0; + if (!ok()) { + return 0; + } // Find submap map >::const_iterator ss; - if ((ss = m_submaps.find(sk)) == m_submaps.end()) - return 0; + if ((ss = m_submaps.find(sk)) == m_submaps.end()) { + return 0; + } // Find named value map::const_iterator s; - if ((s = ss->second.find(nm)) == ss->second.end()) - return 0; + if ((s = ss->second.find(nm)) == ss->second.end()) { + return 0; + } value = s->second; return 1; } -// Appropriately output a subkey (nm=="") or variable line. -// Splits long lines -static ConfSimple::WalkerCode varprinter(void *f, const string &nm, - const string &value) +int ConfSimple::get(const string& nm, int *value, const string& sk) const { - ostream *output = (ostream *)f; + string sval; + if (!get(nm, sval, sk)) { + return 0; + } + *value = atoi(sval.c_str()); + return 1; +} + +// Appropriately output a subkey (nm=="") or variable line. +// We can't make any assumption about the data except that it does not +// contain line breaks. +// Avoid long lines if possible (for hand-editing) +// We used to break at arbitrary places, but this was ennoying for +// files with pure UTF-8 encoding (some files can be binary anyway), +// because it made later editing difficult, as the file would no +// longer have a valid encoding. +// Any ASCII byte would be a safe break point for utf-8, but could +// break some other encoding with, e.g. escape sequences? So break at +// whitespace (is this safe with all encodings?). +// Note that the choice of break point does not affect the validity of +// the file data (when read back by conftree), only its ease of +// editing with a normal editor. +static ConfSimple::WalkerCode varprinter(void *f, const string& nm, + const string& value) +{ + ostream& output = *((ostream *)f); if (nm.empty()) { - *output << "\n[" << value << "]\n"; + output << "\n[" << value << "]\n"; } else { - string value1; - if (value.length() < 60) { - value1 = value; - } else { - string::size_type pos = 0; - while (pos < value.length()) { - string::size_type len = MIN(60, value.length() - pos); - value1 += value.substr(pos, len); - pos += len; - if (pos < value.length()) - value1 += "\\\n"; - } - } - *output << nm << " = " << value1 << "\n"; + output << nm << " = "; + if (nm.length() + value.length() < 75) { + output << value; + } else { + string::size_type ll = 0; + for (string::size_type pos = 0; pos < value.length(); pos++) { + string::value_type c = value[pos]; + output << c; + ll++; + // Break at whitespace if line too long and "a lot" of + // remaining data + if (ll > 50 && (value.length() - pos) > 10 && + (c == ' ' || c == '\t')) { + ll = 0; + output << "\\\n"; + } + } + } + output << "\n"; } return ConfSimple::WALK_CONTINUE; } // Set variable and rewrite data -int ConfSimple::set(const std::string &nm, const std::string &value, - const string &sk) +int ConfSimple::set(const std::string& nm, const std::string& value, + const string& sk) { - if (status != STATUS_RW) - return 0; - LOGDEB((stderr, "ConfSimple::set [%s]:[%s] -> [%s]\n", sk.c_str(), - nm.c_str(), value.c_str())); - if (!i_set(nm, value, sk)) - return 0; + if (status != STATUS_RW) { + return 0; + } + CONFDEB("ConfSimple::set ["< [" << value << "]\n"); + if (!i_set(nm, value, sk)) { + return 0; + } return write(); } +int ConfSimple::set(const string& nm, long long val, + const string& sk) +{ + return this->set(nm, lltodecstr(val), sk); +} + // Internal set variable: no rw checking or file rewriting. If init is // set, we're doing initial parsing, else we are changing a parsed // tree (changes the way we update the order data) -int ConfSimple::i_set(const std::string &nm, const std::string &value, - const string &sk, bool init) +int ConfSimple::i_set(const std::string& nm, const std::string& value, + const string& sk, bool init) { - LOGDEB((stderr, "ConfSimple::i_set: nm[%s] val[%s] key[%s], init %d\n", - nm.c_str(), value.c_str(), sk.c_str(), init)); + CONFDEB("ConfSimple::i_set: nm[" << nm << "] val[" << value << + "] key[" << sk << "], init " << init << "\n"); // Values must not have embedded newlines if (value.find_first_of("\n\r") != string::npos) { - LOGDEB((stderr, "ConfSimple::i_set: LF in value\n")); - return 0; + CONFDEB("ConfSimple::i_set: LF in value\n"); + return 0; } bool existing = false; map >::iterator ss; // Test if submap already exists, else create it, and insert variable: if ((ss = m_submaps.find(sk)) == m_submaps.end()) { - LOGDEB((stderr, "ConfSimple::i_set: new submap\n")); - map submap; - submap[nm] = value; - m_submaps[sk] = submap; + CONFDEB("ConfSimple::i_set: new submap\n"); + map submap; + submap[nm] = value; + m_submaps[sk] = submap; - // Maybe add sk entry to m_order data: - if (!sk.empty()) { - ConfLine nl(ConfLine::CFL_SK, sk); - // Append SK entry only if it's not already there (erase - // does not remove entries from the order data, adn it may - // be being recreated after deletion) - if (find(m_order.begin(), m_order.end(), nl) == m_order.end()) { - m_order.push_back(nl); - } - } + // Maybe add sk entry to m_order data, if not already there. + if (!sk.empty()) { + ConfLine nl(ConfLine::CFL_SK, sk); + // Append SK entry only if it's not already there (erase + // does not remove entries from the order data, and it may + // be being recreated after deletion) + if (find(m_order.begin(), m_order.end(), nl) == m_order.end()) { + m_order.push_back(nl); + } + } } else { - // Insert or update variable in existing map. - map::iterator it; - it = ss->second.find(nm); - if (it == ss->second.end()) { - ss->second.insert(pair(nm, value)); - } else { - it->second = value; - existing = true; - } + // Insert or update variable in existing map. + map::iterator it; + it = ss->second.find(nm); + if (it == ss->second.end()) { + ss->second.insert(pair(nm, value)); + } else { + it->second = value; + existing = true; + } } // If the variable already existed, no need to change the m_order data if (existing) { - LOGDEB((stderr, "ConfSimple::i_set: existing var: no order update\n")); - return 1; + CONFDEB("ConfSimple::i_set: existing var: no order update\n"); + return 1; } // Add the new variable at the end of its submap in the order data. if (init) { - // During the initial construction, just append: - LOGDEB((stderr, "ConfSimple::i_set: init true: append\n")); - m_order.push_back(ConfLine(ConfLine::CFL_VAR, nm)); - return 1; - } + // During the initial construction, just append: + CONFDEB("ConfSimple::i_set: init true: append\n"); + m_order.push_back(ConfLine(ConfLine::CFL_VAR, nm)); + return 1; + } // Look for the start and end of the subkey zone. Start is either // at begin() for a null subkey, or just behind the subkey @@ -357,91 +416,111 @@ int ConfSimple::i_set(const std::string &nm, const std::string &value, // list. We insert the new entry just before end. vector::iterator start, fin; if (sk.empty()) { - start = m_order.begin(); - LOGDEB((stderr,"ConfSimple::i_set: null sk, start at top of order\n")); + start = m_order.begin(); + CONFDEB("ConfSimple::i_set: null sk, start at top of order\n"); } else { - start = find(m_order.begin(), m_order.end(), - ConfLine(ConfLine::CFL_SK, sk)); - if (start == m_order.end()) { - // This is not logically possible. The subkey must - // exist. We're doomed - std::cerr << "Logical failure during configuration variable " - "insertion" << endl; - abort(); - } + start = find(m_order.begin(), m_order.end(), + ConfLine(ConfLine::CFL_SK, sk)); + if (start == m_order.end()) { + // This is not logically possible. The subkey must + // exist. We're doomed + std::cerr << "Logical failure during configuration variable " + "insertion" << endl; + abort(); + } } fin = m_order.end(); if (start != m_order.end()) { - // The null subkey has no entry (maybe it should) - if (!sk.empty()) - start++; - for (vector::iterator it = start; it != m_order.end(); it++) { - if (it->m_kind == ConfLine::CFL_SK) { - fin = it; - break; - } - } + // The null subkey has no entry (maybe it should) + if (!sk.empty()) { + start++; + } + for (vector::iterator it = start; it != m_order.end(); it++) { + if (it->m_kind == ConfLine::CFL_SK) { + fin = it; + break; + } + } } // It may happen that the order entry already exists because erase doesnt // update m_order if (find(start, fin, ConfLine(ConfLine::CFL_VAR, nm)) == fin) { - m_order.insert(fin, ConfLine(ConfLine::CFL_VAR, nm)); + // Look for a varcomment line, insert the value right after if + // it's there. + bool inserted(false); + vector::iterator it; + for (it = start; it != fin; it++) { + if (it->m_kind == ConfLine::CFL_VARCOMMENT && it->m_aux == nm) { + it++; + m_order.insert(it, ConfLine(ConfLine::CFL_VAR, nm)); + inserted = true; + break; + } + } + if (!inserted) { + m_order.insert(fin, ConfLine(ConfLine::CFL_VAR, nm)); + } } + return 1; } -int ConfSimple::erase(const string &nm, const string &sk) +int ConfSimple::erase(const string& nm, const string& sk) { - if (status != STATUS_RW) - return 0; + if (status != STATUS_RW) { + return 0; + } map >::iterator ss; if ((ss = m_submaps.find(sk)) == m_submaps.end()) { - return 0; + return 0; } - + ss->second.erase(nm); if (ss->second.empty()) { - m_submaps.erase(ss); + m_submaps.erase(ss); } return write(); } -int ConfSimple::eraseKey(const string &sk) +int ConfSimple::eraseKey(const string& sk) { vector nms = getNames(sk); for (vector::iterator it = nms.begin(); it != nms.end(); it++) { - erase(*it, sk); + erase(*it, sk); } return write(); } // Walk the tree, calling user function at each node -ConfSimple::WalkerCode -ConfSimple::sortwalk(WalkerCode (*walker)(void *,const string&,const string&), - void *clidata) const +ConfSimple::WalkerCode +ConfSimple::sortwalk(WalkerCode(*walker)(void *, const string&, const string&), + void *clidata) const { - if (!ok()) - return WALK_STOP; + if (!ok()) { + return WALK_STOP; + } // For all submaps: - for (map >::const_iterator sit = - m_submaps.begin(); - sit != m_submaps.end(); sit++) { + for (map >::const_iterator sit = + m_submaps.begin(); + sit != m_submaps.end(); sit++) { - // Possibly emit submap name: - if (!sit->first.empty() && walker(clidata, string(), sit->first.c_str()) - == WALK_STOP) - return WALK_STOP; + // Possibly emit submap name: + if (!sit->first.empty() && walker(clidata, string(), sit->first.c_str()) + == WALK_STOP) { + return WALK_STOP; + } - // Walk submap - const map &sm = sit->second; - for (map::const_iterator it = sm.begin();it != sm.end(); - it++) { - if (walker(clidata, it->first, it->second) == WALK_STOP) - return WALK_STOP; - } + // Walk submap + const map& sm = sit->second; + for (map::const_iterator it = sm.begin(); it != sm.end(); + it++) { + if (walker(clidata, it->first, it->second) == WALK_STOP) { + return WALK_STOP; + } + } } return WALK_CONTINUE; } @@ -450,21 +529,24 @@ ConfSimple::sortwalk(WalkerCode (*walker)(void *,const string&,const string&), // a file bool ConfSimple::write() { - if (!ok()) - return false; - if (m_holdWrites) - return true; + if (!ok()) { + return false; + } + if (m_holdWrites) { + return true; + } if (m_filename.length()) { - ofstream output(m_filename.c_str(), ios::out|ios::trunc); - if (!output.is_open()) - return 0; - return write(output); + ofstream output(m_filename.c_str(), ios::out | ios::trunc); + if (!output.is_open()) { + return 0; + } + return write(output); } else { - // No backing store, no writing. Maybe one day we'll need it with + // No backing store, no writing. Maybe one day we'll need it with // some kind of output string. This can't be the original string which // is currently readonly. - //ostringstream output(m_ostring, ios::out | ios::trunc); - return 1; + //ostringstream output(m_ostring, ios::out | ios::trunc); + return 1; } } @@ -473,74 +555,80 @@ bool ConfSimple::write() // lets ie: showall work even when holdWrites is set bool ConfSimple::write(ostream& out) const { - if (!ok()) - return false; + if (!ok()) { + return false; + } string sk; - for (vector::const_iterator it = m_order.begin(); - it != m_order.end(); it++) { - switch(it->m_kind) { - case ConfLine::CFL_COMMENT: - out << it->m_data << endl; - if (!out.good()) - return false; - break; - case ConfLine::CFL_SK: - sk = it->m_data; - LOGDEB((stderr, "ConfSimple::write: SK [%s]\n", sk.c_str())); - // Check that the submap still exists, and only output it if it - // does - if (m_submaps.find(sk) != m_submaps.end()) { - out << "[" << it->m_data << "]" << endl; - if (!out.good()) - return false; - } - break; - case ConfLine::CFL_VAR: - string nm = it->m_data; - LOGDEB((stderr, "ConfSimple::write: VAR [%s], sk [%s]\n", - nm.c_str(), sk.c_str())); - // As erase() doesnt update m_order we can find unexisting - // variables, and must not output anything for them. Have - // to use a ConfSimple::get() to check here, because - // ConfTree's could retrieve from an ancestor even if the - // local var is gone. - string value; - if (ConfSimple::get(nm, value, sk)) { - varprinter(&out, nm, value); - if (!out.good()) - return false; - break; - } - LOGDEB((stderr, "ConfSimple::write: no value: nm[%s] sk[%s]\n", - nm.c_str(), sk.c_str())); - break; - } + for (vector::const_iterator it = m_order.begin(); + it != m_order.end(); it++) { + switch (it->m_kind) { + case ConfLine::CFL_COMMENT: + case ConfLine::CFL_VARCOMMENT: + out << it->m_data << endl; + if (!out.good()) { + return false; + } + break; + case ConfLine::CFL_SK: + sk = it->m_data; + CONFDEB("ConfSimple::write: SK [" << sk << "]\n"); + // Check that the submap still exists, and only output it if it + // does + if (m_submaps.find(sk) != m_submaps.end()) { + out << "[" << it->m_data << "]" << endl; + if (!out.good()) { + return false; + } + } + break; + case ConfLine::CFL_VAR: + string nm = it->m_data; + CONFDEB("ConfSimple::write: VAR [" << nm << "], sk [" < ConfSimple::getNames(const string &sk, const char *pattern) const +vector ConfSimple::getNames(const string& sk, const char *pattern) const { vector mylist; - if (!ok()) - return mylist; + if (!ok()) { + return mylist; + } map >::const_iterator ss; if ((ss = m_submaps.find(sk)) == m_submaps.end()) { - return mylist; + return mylist; } mylist.reserve(ss->second.size()); map::const_iterator it; for (it = ss->second.begin(); it != ss->second.end(); it++) { - if (pattern && 0 != fnmatch(pattern, it->first.c_str(), 0)) + if (pattern && 0 != fnmatch(pattern, it->first.c_str(), 0)) { continue; - mylist.push_back(it->first); + } + mylist.push_back(it->first); } return mylist; } @@ -548,12 +636,13 @@ vector ConfSimple::getNames(const string &sk, const char *pattern) const vector ConfSimple::getSubKeys() const { vector mylist; - if (!ok()) - return mylist; + if (!ok()) { + return mylist; + } mylist.reserve(m_submaps.size()); map >::const_iterator ss; for (ss = m_submaps.begin(); ss != m_submaps.end(); ss++) { - mylist.push_back(ss->first); + mylist.push_back(ss->first); } return mylist; } @@ -561,25 +650,56 @@ vector ConfSimple::getSubKeys() const bool ConfSimple::hasNameAnywhere(const string& nm) const { vectorkeys = getSubKeys(); - for (vector::const_iterator it = keys.begin(); - it != keys.end(); it++) { + for (vector::const_iterator it = keys.begin(); + it != keys.end(); it++) { string val; - if (get(nm, val, *it)) + if (get(nm, val, *it)) { return true; + } } return false; } +bool ConfSimple::commentsAsXML(ostream& out) +{ + const vector& lines = getlines(); + + out << "\n"; + + string sk; + for (vector::const_iterator it = lines.begin(); + it != lines.end(); it++) { + switch (it->m_kind) { + case ConfLine::CFL_COMMENT: + case ConfLine::CFL_VARCOMMENT: + { + string::size_type pos = it->m_data.find_first_not_of("# "); + if (pos != string::npos) { + out << it->m_data.substr(pos) << endl; + } + break; + } + default: + break; + } + } + out << "\n"; + + return true; +} + + // ////////////////////////////////////////////////////////////////////////// // ConfTree Methods: conftree interpret keys like a hierarchical file tree // ////////////////////////////////////////////////////////////////////////// -int ConfTree::get(const std::string &name, string &value, const string &sk) - const +int ConfTree::get(const std::string& name, string& value, const string& sk) +const { - if (sk.empty() || sk[0] != '/') { - // LOGDEB((stderr, "ConfTree::get: looking in global space\n")); - return ConfSimple::get(name, value, sk); + if (sk.empty() || !path_isabsolute(sk)) { + LOGDEB2("ConfTree::get: looking in global space for [" << + sk << "]\n"); + return ConfSimple::get(name, value, sk); } // Get writable copy of subkey path @@ -591,432 +711,23 @@ int ConfTree::get(const std::string &name, string &value, const string &sk) // Look in subkey and up its parents until root ('') for (;;) { - // LOGDEB((stderr,"ConfTree::get: looking for '%s' in '%s'\n", - // name.c_str(), msk.c_str())); - if (ConfSimple::get(name, value, msk)) - return 1; - string::size_type pos = msk.rfind("/"); - if (pos != string::npos) { - msk.replace(pos, string::npos, string()); - } else - break; + LOGDEB2("ConfTree::get: looking for [" << name << "] in [" << + msk << "]\n"); + if (ConfSimple::get(name, value, msk)) { + return 1; + } + string::size_type pos = msk.rfind("/"); + if (pos != string::npos) { + msk.replace(pos, string::npos, string()); + } else { +#ifdef _WIN32 + if (msk.size() == 2 && isalpha(msk[0]) && msk[1] == ':') { + msk.clear(); + } else +#endif + break; + } } return 0; } -#else // TEST_CONFTREE - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "conftree.h" -#include "smallut.h" -#include "readfile.h" - -using namespace std; - -static char *thisprog; - -bool complex_updates(const string& fn) -{ - int fd; - if ((fd = open(fn.c_str(), O_RDWR|O_TRUNC|O_CREAT, 0666)) < 0) { - perror("open/create"); - return false; - } - close(fd); - - ConfTree conf(fn.c_str()); - if (!conf.ok()) { - cerr << "Config init failed" << endl; - return false; - } - - conf.set("nm-1", "val-1", ""); - conf.set("nm-2", "val-2", ""); - - conf.set("nm-1", "val1-1", "/dir1"); - conf.set("nm-2", "val1-2", "/dir1"); - - conf.set("nm-1", "val2-1", "/dir2"); - conf.set("nm-2", "val2-2", "/dir2"); - - conf.set("nm-1", "val11-1", "/dir1/dir1"); - conf.set("nm-2", "val11-2", "/dir1/dir1"); - - conf.eraseKey("/dir2"); - conf.set("nm-1", "val2-1", "/dir2"); - conf.set("nm-2", "val2-2", "/dir2"); - - conf.erase("nm-1", ""); - conf.erase("nm-2", ""); - conf.eraseKey("/dir1"); - conf.eraseKey("/dir2"); - conf.eraseKey("/dir1/dir1"); - - conf.set("nm-1", "val1-1", "/dir1"); - conf.set("nm-2", "val1-2", "/dir1"); - conf.set("nm-1", "val-1", ""); - - conf.set("nm-1", "val2-1", "/dir2"); - conf.set("nm-2", "val2-2", "/dir2"); - - conf.set("nm-1", "val11-1", "/dir1/dir1"); - conf.set("nm-2", "val11-2", "/dir1/dir1"); - - conf.erase("nm-1", "/dir2"); - conf.erase("nm-2", "/dir2"); - conf.erase("nm-1", "/dir1/dir1"); - conf.erase("nm-2", "/dir1/dir1"); - - string data; - file_to_string(fn, data, 0); - const string ref = - "nm-1 = val-1\n" - "[/dir1]\n" - "nm-1 = val1-1\n" - "nm-2 = val1-2\n" - ; - if (data.compare(ref)) { - cerr << "Final file:" << endl << data << endl << "Differs from ref:" << - endl << ref << endl; - return false; - } else { - cout << "Updates test Ok" << endl; - } - return true; -} - -ConfSimple::WalkerCode mywalker(void *, const string &nm, const string &value) -{ - if (nm.empty()) - printf("\n[%s]\n", value.c_str()); - else - printf("'%s' -> '%s'\n", nm.c_str(), value.c_str()); - return ConfSimple::WALK_CONTINUE; -} - -const char *longvalue = -"Donnees012345678901234567890123456789012345678901234567890123456789AA" -"0123456789012345678901234567890123456789012345678901234567890123456789FIN" - ; - -void memtest(ConfSimple &c) -{ - cout << "Initial:" << endl; - c.showall(); - if (c.set("nom", "avec nl \n 2eme ligne", "")) { - fprintf(stderr, "set with embedded nl succeeded !\n"); - exit(1); - } - if (!c.set(string("parm1"), string("1"), string("subkey1"))) { - fprintf(stderr, "Set error"); - exit(1); - } - if (!c.set("sparm", "Parametre \"string\" bla", "s2")) { - fprintf(stderr, "Set error"); - exit(1); - } - if (!c.set("long", longvalue, "")) { - fprintf(stderr, "Set error"); - exit(1); - } - - cout << "Final:" << endl; - c.showall(); -} - -bool readwrite(ConfNull *conf) -{ - if (conf->ok()) { - // It's ok for the file to not exist here - string value; - - if (conf->get("mypid", value)) { - cout << "Value for mypid is [" << value << "]" << endl; - } else { - cout << "mypid not set" << endl; - } - - if (conf->get("unstring", value)) { - cout << "Value for unstring is ["<< value << "]" << endl; - } else { - cout << "unstring not set" << endl; - } - } - char spid[100]; - sprintf(spid, "%d", getpid()); - if (!conf->set("mypid", spid)) { - cerr << "Set mypid failed" << endl; - } - - ostringstream ost; - ost << "mypid" << getpid(); - if (!conf->set(ost.str(), spid, "")) { - cerr << "Set mypid failed (2)" << endl; - } - if (!conf->set("unstring", "Une jolie phrase pour essayer")) { - cerr << "Set unstring failed" << endl; - } - return true; -} - -bool query(ConfNull *conf, const string& nm, const string& sub) -{ - if (!conf->ok()) { - cerr << "Error opening or parsing file\n" << endl; - return false; - } - string value; - if (!conf->get(nm, value, sub)) { - cerr << "name [" << nm << "] not found in [" << sub << "]" << endl; - return false; - } - cout << "[" << sub << "] " << nm << " " << value << endl; - return true; -} - -bool erase(ConfNull *conf, const string& nm, const string& sub) -{ - if (!conf->ok()) { - cerr << "Error opening or parsing file\n" << endl; - return false; - } - - if (!conf->erase(nm, sub)) { - cerr << "delete name [" << nm << "] in ["<< sub << "] failed" << endl; - return false; - } - return true; -} - -bool eraseKey(ConfNull *conf, const string& sub) -{ - if (!conf->ok()) { - cerr << "Error opening or parsing file\n" << endl; - return false; - } - - if (!conf->eraseKey(sub)) { - cerr << "delete key [" << sub << "] failed" << endl; - return false; - } - return true; -} - -bool setvar(ConfNull *conf, const string& nm, const string& value, - const string& sub) -{ - if (!conf->ok()) { - cerr << "Error opening or parsing file\n" << endl; - return false; - } - if (!conf->set(nm, value, sub)) { - cerr << "Set error\n" << endl; - return false; - } - return true; -} - -static char usage [] = - "testconftree [opts] filename\n" - "[-w] : read/write test.\n" - "[-s] : string parsing test. Filename must hold parm 'strings'\n" - "-a nm value sect : add/set nm,value in 'sect' which can be ''\n" - "-q nm sect : subsection test: look for nm in 'sect' which can be ''\n" - "-d nm sect : delete nm in 'sect' which can be ''\n" - "-E sect : erase key (and all its names)\n" - "-S : string io test. No filename in this case\n" - "-V : volatile config test. No filename in this case\n" - "-U : complex update test. Will erase the named file parameter\n" - ; - -void Usage() { - fprintf(stderr, "%s:%s\n", thisprog, usage); - exit(1); -} -static int op_flags; -#define OPT_MOINS 0x1 -#define OPT_w 0x2 -#define OPT_q 0x4 -#define OPT_s 0x8 -#define OPT_S 0x10 -#define OPT_d 0x20 -#define OPT_V 0x40 -#define OPT_a 0x80 -#define OPT_k 0x100 -#define OPT_E 0x200 -#define OPT_U 0x400 - -int main(int argc, char **argv) -{ - const char *nm = 0; - const char *sub = 0; - const char *value = 0; - - thisprog = argv[0]; - argc--; argv++; - - while (argc > 0 && **argv == '-') { - (*argv)++; - if (!(**argv)) - /* Cas du "adb - core" */ - Usage(); - while (**argv) - switch (*(*argv)++) { - case 'a': - op_flags |= OPT_a; - if (argc < 4) - Usage(); - nm = *(++argv);argc--; - value = *(++argv);argc--; - sub = *(++argv);argc--; - goto b1; - case 'd': - op_flags |= OPT_d; - if (argc < 3) - Usage(); - nm = *(++argv);argc--; - sub = *(++argv);argc--; - goto b1; - case 'E': - op_flags |= OPT_E; - if (argc < 2) - Usage(); - sub = *(++argv);argc--; - goto b1; - case 'k': op_flags |= OPT_k; break; - case 'q': - op_flags |= OPT_q; - if (argc < 3) - Usage(); - nm = *(++argv);argc--; - sub = *(++argv);argc--; - goto b1; - case 's': op_flags |= OPT_s; break; - case 'S': op_flags |= OPT_S; break; - case 'V': op_flags |= OPT_V; break; - case 'U': op_flags |= OPT_U; break; - case 'w': op_flags |= OPT_w; break; - - default: Usage(); break; - } - b1: argc--; argv++; - } - - if ((op_flags & OPT_S)) { - // String storage test - if (argc != 0) - Usage(); - string s; - ConfSimple c(s); - memtest(c); - exit(0); - } else if ((op_flags & OPT_V)) { - // No storage test - if (argc != 0) - Usage(); - ConfSimple c; - memtest(c); - exit(0); - } - - // Other tests use file(s) as backing store - if (argc < 1) - Usage(); - - if (op_flags & OPT_U) { - exit(!complex_updates(argv[0])); - } - vector flist; - while (argc--) { - flist.push_back(*argv++); - } - bool ro = !(op_flags & (OPT_w|OPT_a|OPT_d|OPT_E)); - ConfNull *conf = 0; - switch (flist.size()) { - case 0: - Usage(); - break; - case 1: - conf = new ConfTree(flist.front().c_str(), ro); - break; - default: - conf = new ConfStack(flist, ro); - break; - } - - if (op_flags & OPT_w) { - exit(!readwrite(conf)); - } else if (op_flags & OPT_q) { - exit(!query(conf, nm, sub)); - } else if (op_flags & OPT_k) { - if (!conf->ok()) { - cerr << "conf init error" << endl; - exit(1); - } - vectorlst = conf->getSubKeys(); - for (vector::const_iterator it = lst.begin(); - it != lst.end(); it++) { - cout << *it << endl; - } - exit(0); - } else if (op_flags & OPT_a) { - exit(!setvar(conf, nm, value, sub)); - } else if (op_flags & OPT_d) { - exit(!erase(conf, nm, sub)); - } else if (op_flags & OPT_E) { - exit(!eraseKey(conf, sub)); - } else if (op_flags & OPT_s) { - if (!conf->ok()) { - cerr << "Cant open /parse conf file " << endl; - exit(1); - } - - string source; - if (!conf->get(string("strings"), source, "")) { - cerr << "Cant get param 'strings'" << endl; - exit(1); - } - cout << "source: [" << source << "]" << endl; - vector strings; - if (!stringToStrings(source, strings)) { - cerr << "parse failed" << endl; - exit(1); - } - - for (vector::iterator it = strings.begin(); - it != strings.end(); it++) { - cout << "[" << *it << "]" << endl; - } - - } else { - if (!conf->ok()) { - fprintf(stderr, "Open failed\n"); - exit(1); - } - printf("LIST\n"); - conf->showall(); - //printf("WALK\n");conf->sortwalk(mywalker, 0); - printf("\nNAMES in global space:\n"); - vector names = conf->getNames(""); - for (vector::iterator it = names.begin(); - it!=names.end(); it++) - cout << *it << " "; - cout << endl; - printf("\nNAMES in global space matching t* \n"); - names = conf->getNames("", "t*"); - for (vector::iterator it = names.begin(); - it!=names.end(); it++) - cout << *it << " "; - cout << endl; - } -} - -#endif diff --git a/src/utils/conftree.h b/src/utils/conftree.h index 7ea85a69..f976506c 100644 --- a/src/utils/conftree.h +++ b/src/utils/conftree.h @@ -1,4 +1,4 @@ -/* +/* Copyright (C) 2006 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 @@ -23,23 +23,23 @@ * Configuration files have lines like 'name = value', and/or like '[subkey]' * * Lines like '[subkey]' in the file define subsections, with independant - * configuration namespaces. Only subsections holding at least one variable are + * configuration namespaces. Only subsections holding at least one variable are * significant (empty subsections may be deleted during an update, or not) * * Whitespace around name and value is insignificant. * * The names are case-sensitive but don't depend on it, this might change * - * Values can be queried for, or set. + * Values can be queried for, or set. * * Any line without a '=' is a comment (a line like #var = value * actually assigns a variable named '#var', which is not a big issue) * * A configuration object can be created empty or by reading from a file or * a string. - * All 'set' calls cause an immediate rewrite of the backing object if any - * (file or string) - * + * All 'set' calls cause an immediate rewrite of the backing object if any + * (file or string) + * * The ConfTree derived class interprets the subkeys as file paths and * lets subdir keys hierarchically inherit the properties from * parents. @@ -49,10 +49,11 @@ * (useful to have central/personal config files) */ -#include -#include -#include +#include #include +#include +#include +#include // rh7.3 likes iostream better... #if defined(__GNUC__) && __GNUC__ < 3 @@ -62,48 +63,45 @@ #include #endif -#ifndef NO_NAMESPACES +#include "pathut.h" + using std::string; using std::vector; using std::map; using std::istream; using std::ostream; -#endif // NO_NAMESPACES - -#include "pathut.h" /** Internal class used for storing presentation information */ class ConfLine { public: - enum Kind {CFL_COMMENT, CFL_SK, CFL_VAR}; + enum Kind {CFL_COMMENT, CFL_SK, CFL_VAR, CFL_VARCOMMENT}; Kind m_kind; string m_data; - ConfLine(Kind k, const string& d) - : m_kind(k), m_data(d) - { + string m_aux; + ConfLine(Kind k, const string& d, string a = string()) + : m_kind(k), m_data(d), m_aux(a) { } - bool operator==(const ConfLine& o) - { - return o.m_kind == m_kind && o.m_data == m_data; + bool operator==(const ConfLine& o) { + return o.m_kind == m_kind && o.m_data == m_data; } }; -/** +/** * Virtual base class used to define an interface mostly useful for testing */ class ConfNull { public: - enum StatusCode {STATUS_ERROR=0, STATUS_RO=1, STATUS_RW=2}; + enum StatusCode {STATUS_ERROR = 0, STATUS_RO = 1, STATUS_RW = 2}; virtual ~ConfNull() {}; - virtual int get(const string &name, string &value, - const string &sk = string()) const = 0; + virtual int get(const string& name, string& value, + const string& sk = string()) const = 0; virtual bool hasNameAnywhere(const string& nm) const = 0; - virtual int set(const string &nm, const string &val, - const string &sk = string()) = 0; + virtual int set(const string& nm, const string& val, + const string& sk = string()) = 0; virtual bool ok() const = 0; - virtual vector getNames(const string &sk, const char* = 0)const = 0; - virtual int erase(const string &, const string &) = 0; - virtual int eraseKey(const string &) = 0; + virtual vector getNames(const string& sk, const char* = 0)const = 0; + virtual int erase(const string&, const string&) = 0; + virtual int eraseKey(const string&) = 0; virtual void showall() const {}; virtual vector getSubKeys() const = 0; virtual vector getSubKeys(bool) const = 0; @@ -111,7 +109,7 @@ public: virtual bool sourceChanged() const = 0; }; -/** +/** * Manages a simple configuration file with subsections. */ class ConfSimple : public ConfNull { @@ -127,7 +125,7 @@ public: /** * Build the object by reading content from a string - * @param data points to the data to parse. + * @param data points to the data to parse. * @param readonly if true open readonly, else rw * @param tildexp try tilde (home dir) expansion for subsection names */ @@ -145,120 +143,148 @@ public: /** Origin file changed. Only makes sense if we read the data from a file */ virtual bool sourceChanged() const; - /** + /** * Decide if we actually rewrite the backing-store after modifying the * tree. */ - virtual bool holdWrites(bool on) - { - m_holdWrites = on; - if (on == false) { - return write(); - } else - return true; + virtual bool holdWrites(bool on) { + m_holdWrites = on; + if (on == false) { + return write(); + } else { + return true; + } } /** Clear, then reparse from string */ void reparse(const string& in); /** Clear all content */ - void clear() - { - m_submaps.clear(); - m_order.clear(); + void clear() { + m_submaps.clear(); + m_order.clear(); } - /** - * Get value for named parameter, from specified subsection (looks in - * global space if sk is empty). + /** + * Get string value for named parameter, from specified subsection (looks + * in global space if sk is empty). * @return 0 if name not found, 1 else */ - virtual int get(const string &name, string &value, - const string &sk = string()) const; + virtual int get(const string& name, string& value, + const string& sk = string()) const; - /** - * Set value for named parameter in specified subsection (or global) + /** + * Get integer value for named parameter, from specified subsection (looks + * in global space if sk is empty). + * @return 0 if name not found, 1 else + */ + virtual int get(const string& name, int* value, + const string& sk = string()) const; + + + /** + * Set value for named string parameter in specified subsection (or global) * @return 0 for error, 1 else */ - virtual int set(const string &nm, const string &val, - const string &sk = string()); + virtual int set(const string& nm, const string& val, + const string& sk = string()); + /** + * Set value for named integer parameter in specified subsection (or global) + * @return 0 for error, 1 else + */ + virtual int set(const string& nm, long long val, + const string& sk = string()); /** * Remove name and value from config */ - virtual int erase(const string &name, const string &sk); + virtual int erase(const string& name, const string& sk); /** * Erase all names under given subkey (and subkey itself) */ - virtual int eraseKey(const string &sk); + virtual int eraseKey(const string& sk); virtual StatusCode getStatus() const; - virtual bool ok() const {return getStatus() != STATUS_ERROR;} + virtual bool ok() const { + return getStatus() != STATUS_ERROR; + } - /** + /** * Walk the configuration values, calling function for each. - * The function is called with a null nm when changing subsections (the + * The function is called with a null nm when changing subsections (the * value is then the new subsection name) - * @return WALK_STOP when/if the callback returns WALK_STOP, + * @return WALK_STOP when/if the callback returns WALK_STOP, * WALK_CONTINUE else (got to end of config) */ enum WalkerCode {WALK_STOP, WALK_CONTINUE}; - virtual WalkerCode sortwalk(WalkerCode - (*wlkr)(void *cldata, const string &nm, - const string &val), - void *clidata) const; + virtual WalkerCode sortwalk(WalkerCode + (*wlkr)(void *cldata, const string& nm, + const string& val), + void *clidata) const; /** Print all values to stdout */ virtual void showall() const; /** Return all names in given submap. */ - virtual vector getNames(const string &sk, const char *pattern = 0) - const; + virtual vector getNames(const string& sk, const char *pattern = 0) + const; /** Check if name is present in any submap. This is relatively expensive * but useful for saving further processing sometimes */ virtual bool hasNameAnywhere(const string& nm) const; /** - * Return all subkeys + * Return all subkeys */ - virtual vector getSubKeys(bool) const - { - return getSubKeys(); + virtual vector getSubKeys(bool) const { + return getSubKeys(); } virtual vector getSubKeys() const; - /** Test for subkey existence */ - virtual bool hasSubKey(const string& sk) const - { - return m_submaps.find(sk) != m_submaps.end(); + + /** Return subkeys in file order. BEWARE: only for the original from the + * file: the data is not duplicated to further copies */ + virtual vector getSubKeys_unsorted(bool = false) const { + return m_subkeys_unsorted; } - virtual string getFilename() const - {return m_filename;} + /** Test for subkey existence */ + virtual bool hasSubKey(const string& sk) const { + return m_submaps.find(sk) != m_submaps.end(); + } + virtual string getFilename() const { + return m_filename; + } + + /** Used with config files with specially formatted, xml-like comments. + * Extract the comments as text */ + virtual bool commentsAsXML(ostream& out); + + /** !! Note that assignment and copy constructor do not copy the + auxiliary data (m_order and subkeys_unsorted). */ + /** * Copy constructor. Expensive but less so than a full rebuild */ - ConfSimple(const ConfSimple &rhs) - : ConfNull() - { - if ((status = rhs.status) == STATUS_ERROR) - return; - m_filename = rhs.m_filename; - m_submaps = rhs.m_submaps; + ConfSimple(const ConfSimple& rhs) + : ConfNull() { + if ((status = rhs.status) == STATUS_ERROR) { + return; + } + m_filename = rhs.m_filename; + m_submaps = rhs.m_submaps; } /** * Assignement. This is expensive */ - ConfSimple& operator=(const ConfSimple &rhs) - { - if (this != &rhs && (status = rhs.status) != STATUS_ERROR) { - m_filename = rhs.m_filename; - m_submaps = rhs.m_submaps; - } - return *this; + ConfSimple& operator=(const ConfSimple& rhs) { + if (this != &rhs && (status = rhs.status) != STATUS_ERROR) { + m_filename = rhs.m_filename; + m_submaps = rhs.m_submaps; + } + return *this; } /** @@ -266,16 +292,22 @@ public: */ bool write(ostream& out) const; + /** Give access to semi-parsed file contents */ + const vector& getlines() const { + return m_order; + } + protected: bool dotildexpand; StatusCode status; private: // Set if we're working with a file - string m_filename; + string m_filename; time_t m_fmtime; // Configuration data submaps (one per subkey, the main data has a // null subkey) map > m_submaps; + vector m_subkeys_unsorted; // Presentation data. We keep the comments, empty lines and // variable and subkey ordering information in there (for // rewriting the file while keeping hand-edited information) @@ -286,8 +318,8 @@ private: void parseinput(istream& input); bool write(); // Internal version of set: no RW checking - virtual int i_set(const string &nm, const string &val, - const string &sk, bool init = false); + virtual int i_set(const string& nm, const string& val, + const string& sk, bool init = false); bool i_changed(bool upd); }; @@ -295,7 +327,7 @@ private: * This is a configuration class which attaches tree-like signification to the * submap names. * - * If a given variable is not found in the specified section, it will be + * If a given variable is not found in the specified section, it will be * looked up the tree of section names, and in the global space. * * submap names should be '/' separated paths (ie: /sub1/sub2). No checking @@ -305,43 +337,43 @@ private: * designated by '/' but by '' (empty subkey). A '/' subkey will not * be searched at all. * - * Note: getNames() : uses ConfSimple method, this does *not* inherit + * Note: getNames() : uses ConfSimple method, this does *not* inherit * names from englobing submaps. */ class ConfTree : public ConfSimple { public: - /* The constructors just call ConfSimple's, asking for key tilde + /* The constructors just call ConfSimple's, asking for key tilde * expansion */ - ConfTree(const char *fname, int readonly = 0) - : ConfSimple(fname, readonly, true) {} - ConfTree(const string &data, int readonly = 0) - : ConfSimple(data, readonly, true) {} + ConfTree(const char *fname, int readonly = 0) + : ConfSimple(fname, readonly, true) {} + ConfTree(const string& data, int readonly = 0) + : ConfSimple(data, readonly, true) {} ConfTree(int readonly = 0) - : ConfSimple(readonly, true) {} + : ConfSimple(readonly, true) {} virtual ~ConfTree() {}; - ConfTree(const ConfTree& r) : ConfSimple(r) {}; - ConfTree& operator=(const ConfTree& r) - { - ConfSimple::operator=(r); - return *this; + ConfTree(const ConfTree& r) : ConfSimple(r) {}; + ConfTree& operator=(const ConfTree& r) { + ConfSimple::operator=(r); + return *this; } - /** - * Get value for named parameter, from specified subsection, or its + /** + * Get value for named parameter, from specified subsection, or its * parents. * @return 0 if name not found, 1 else */ - virtual int get(const string &name, string &value, const string &sk) const; + virtual int get(const string& name, string& value, const string& sk) const; + using ConfSimple::get; }; -/** +/** * Use several config files, trying to get values from each in order. Used to * have a central config, with possible overrides from more specific * (ie personal) ones. * * Notes: it's ok for some of the files not to exist, but the last - * one must or we generate an error. We open all trees readonly, except the + * one must or we generate an error. We open all trees readonly, except the * topmost one if requested. All writes go to the topmost file. Note that * erase() won't work except for parameters only defined in the topmost * file (it erases only from there). @@ -351,178 +383,171 @@ public: /// Construct from configuration file names. The earler /// files in have priority when fetching values. Only the first /// file will be updated if ro is false and set() is used. - ConfStack(const vector &fns, bool ro = true) - { - construct(fns, ro); + ConfStack(const vector& fns, bool ro = true) { + construct(fns, ro); } /// Construct out of single file name and multiple directories - ConfStack(const string& nm, const vector& dirs, bool ro = true) - { - vector fns; - for (vector::const_iterator it = dirs.begin(); - it != dirs.end(); it++){ - fns.push_back(path_cat(*it, nm)); - } - ConfStack::construct(fns, ro); + ConfStack(const string& nm, const vector& dirs, bool ro = true) { + vector fns; + for (vector::const_iterator it = dirs.begin(); + it != dirs.end(); it++) { + fns.push_back(path_cat(*it, nm)); + } + ConfStack::construct(fns, ro); } - ConfStack(const ConfStack &rhs) - : ConfNull() - { - init_from(rhs); + ConfStack(const ConfStack& rhs) + : ConfNull() { + init_from(rhs); } - virtual ~ConfStack() - { - clear(); - m_ok = false; + virtual ~ConfStack() { + clear(); + m_ok = false; } - ConfStack& operator=(const ConfStack &rhs) - { - if (this != &rhs){ - clear(); - m_ok = rhs.m_ok; - if (m_ok) - init_from(rhs); - } - return *this; + ConfStack& operator=(const ConfStack& rhs) { + if (this != &rhs) { + clear(); + m_ok = rhs.m_ok; + if (m_ok) { + init_from(rhs); + } + } + return *this; } - virtual bool sourceChanged() const - { - typename vector::const_iterator it; - for (it = m_confs.begin();it != m_confs.end();it++) { - if ((*it)->sourceChanged()) - return true; - } - return false; + virtual bool sourceChanged() const { + typename vector::const_iterator it; + for (it = m_confs.begin(); it != m_confs.end(); it++) { + if ((*it)->sourceChanged()) { + return true; + } + } + return false; } - virtual int get(const string &name, string &value, const string &sk, - bool shallow) const - { - typename vector::const_iterator it; - for (it = m_confs.begin();it != m_confs.end();it++) { - if ((*it)->get(name, value, sk)) - return true; - if (shallow) + virtual int get(const string& name, string& value, const string& sk, + bool shallow) const { + typename vector::const_iterator it; + for (it = m_confs.begin(); it != m_confs.end(); it++) { + if ((*it)->get(name, value, sk)) { + return true; + } + if (shallow) { break; - } - return false; + } + } + return false; } - virtual int get(const string &name, string &value, const string &sk) const { + virtual int get(const string& name, string& value, const string& sk) const { return get(name, value, sk, false); } - virtual bool hasNameAnywhere(const string& nm) const - { - typename vector::const_iterator it; - for (it = m_confs.begin();it != m_confs.end();it++) { - if ((*it)->hasNameAnywhere(nm)) - return true; - } - return false; + virtual bool hasNameAnywhere(const string& nm) const { + typename vector::const_iterator it; + for (it = m_confs.begin(); it != m_confs.end(); it++) { + if ((*it)->hasNameAnywhere(nm)) { + return true; + } + } + return false; } - virtual int set(const string &nm, const string &val, - const string &sk = string()) - { - if (!m_ok) - return 0; - //LOGDEB2(("ConfStack::set [%s]:[%s] -> [%s]\n", sk.c_str(), - //nm.c_str(), val.c_str())); - // Avoid adding unneeded entries: if the new value matches the - // one out from the deeper configs, erase or dont add it - // from/to the topmost file - typename vector::iterator it = m_confs.begin(); - it++; - while (it != m_confs.end()) { - string value; - if ((*it)->get(nm, value, sk)) { - // This file has value for nm/sk. If it is the same as the new - // one, no need for an entry in the topmost file. Else, stop - // looking and add the new entry - if (value == val) { - m_confs.front()->erase(nm, sk); - return true; - } else { - break; - } - } - it++; - } + virtual int set(const string& nm, const string& val, + const string& sk = string()) { + if (!m_ok) { + return 0; + } + //LOGDEB2(("ConfStack::set [%s]:[%s] -> [%s]\n", sk.c_str(), + //nm.c_str(), val.c_str())); + // Avoid adding unneeded entries: if the new value matches the + // one out from the deeper configs, erase or dont add it + // from/to the topmost file + typename vector::iterator it = m_confs.begin(); + it++; + while (it != m_confs.end()) { + string value; + if ((*it)->get(nm, value, sk)) { + // This file has value for nm/sk. If it is the same as the new + // one, no need for an entry in the topmost file. Else, stop + // looking and add the new entry + if (value == val) { + m_confs.front()->erase(nm, sk); + return true; + } else { + break; + } + } + it++; + } - return m_confs.front()->set(nm, val, sk); + return m_confs.front()->set(nm, val, sk); } - virtual int erase(const string &nm, const string &sk) - { - return m_confs.front()->erase(nm, sk); + virtual int erase(const string& nm, const string& sk) { + return m_confs.front()->erase(nm, sk); } - virtual int eraseKey(const string &sk) - { - return m_confs.front()->eraseKey(sk); + virtual int eraseKey(const string& sk) { + return m_confs.front()->eraseKey(sk); } - virtual bool holdWrites(bool on) - { - return m_confs.front()->holdWrites(on); + virtual bool holdWrites(bool on) { + return m_confs.front()->holdWrites(on); } - virtual vector getNames(const string &sk, const char *pattern = 0) - const - { - return getNames1(sk, pattern, false); + virtual vector getNames(const string& sk, const char *pattern = 0) + const { + return getNames1(sk, pattern, false); } - virtual vector getNamesShallow(const string &sk, - const char *patt = 0) const - { - return getNames1(sk, patt, true); + virtual vector getNamesShallow(const string& sk, + const char *patt = 0) const { + return getNames1(sk, patt, true); } - virtual vector getNames1(const string &sk, const char *pattern, - bool shallow) const - { - vector nms; - typename vector::const_iterator it; - bool skfound = false; - for (it = m_confs.begin(); it != m_confs.end(); it++) { - if ((*it)->hasSubKey(sk)) { - skfound = true; - vector lst = (*it)->getNames(sk, pattern); - nms.insert(nms.end(), lst.begin(), lst.end()); - } - if (shallow && skfound) - break; - } - sort(nms.begin(), nms.end()); - vector::iterator uit = unique(nms.begin(), nms.end()); - nms.resize(uit - nms.begin()); - return nms; + virtual vector getNames1(const string& sk, const char *pattern, + bool shallow) const { + vector nms; + typename vector::const_iterator it; + bool skfound = false; + for (it = m_confs.begin(); it != m_confs.end(); it++) { + if ((*it)->hasSubKey(sk)) { + skfound = true; + vector lst = (*it)->getNames(sk, pattern); + nms.insert(nms.end(), lst.begin(), lst.end()); + } + if (shallow && skfound) { + break; + } + } + sort(nms.begin(), nms.end()); + vector::iterator uit = unique(nms.begin(), nms.end()); + nms.resize(uit - nms.begin()); + return nms; } - virtual vector getSubKeys() const - { - return getSubKeys(false); + virtual vector getSubKeys() const { + return getSubKeys(false); } - virtual vector getSubKeys(bool shallow) const - { - vector sks; - typename vector::const_iterator it; - for (it = m_confs.begin(); it != m_confs.end(); it++) { - vector lst; - lst = (*it)->getSubKeys(); - sks.insert(sks.end(), lst.begin(), lst.end()); - if (shallow) - break; - } - sort(sks.begin(), sks.end()); - vector::iterator uit = unique(sks.begin(), sks.end()); - sks.resize(uit - sks.begin()); - return sks; + virtual vector getSubKeys(bool shallow) const { + vector sks; + typename vector::const_iterator it; + for (it = m_confs.begin(); it != m_confs.end(); it++) { + vector lst; + lst = (*it)->getSubKeys(); + sks.insert(sks.end(), lst.begin(), lst.end()); + if (shallow) { + break; + } + } + sort(sks.begin(), sks.end()); + vector::iterator uit = unique(sks.begin(), sks.end()); + sks.resize(uit - sks.begin()); + return sks; } - virtual bool ok() const {return m_ok;} + virtual bool ok() const { + return m_ok; + } private: bool m_ok; @@ -530,44 +555,44 @@ private: /// Reset to pristine void clear() { - typename vector::iterator it; - for (it = m_confs.begin();it != m_confs.end();it++) { - delete (*it); - } - m_confs.clear(); + typename vector::iterator it; + for (it = m_confs.begin(); it != m_confs.end(); it++) { + delete(*it); + } + m_confs.clear(); } /// Common code to initialize from existing object - void init_from(const ConfStack &rhs) { - if ((m_ok = rhs.m_ok)) { - typename vector::const_iterator it; - for (it = rhs.m_confs.begin();it != rhs.m_confs.end();it++) { - m_confs.push_back(new T(**it)); - } - } + void init_from(const ConfStack& rhs) { + if ((m_ok = rhs.m_ok)) { + typename vector::const_iterator it; + for (it = rhs.m_confs.begin(); it != rhs.m_confs.end(); it++) { + m_confs.push_back(new T(**it)); + } + } } /// Common construct from file names code - void construct(const vector &fns, bool ro) { - vector::const_iterator it; - bool lastok = false; - for (it = fns.begin(); it != fns.end(); it++) { - T* p = new T(it->c_str(), ro); - if (p && p->ok()) { - m_confs.push_back(p); - lastok = true; - } else { - delete p; - lastok = false; - if (!ro) { - // For rw acccess, the topmost file needs to be ok - // (ro is set to true after the first file) - break; - } - } - ro = true; - } - m_ok = lastok; + void construct(const vector& fns, bool ro) { + vector::const_iterator it; + bool lastok = false; + for (it = fns.begin(); it != fns.end(); it++) { + T* p = new T(it->c_str(), ro); + if (p && p->ok()) { + m_confs.push_back(p); + lastok = true; + } else { + delete p; + lastok = false; + if (!ro) { + // For rw acccess, the topmost file needs to be ok + // (ro is set to true after the first file) + break; + } + } + ro = true; + } + m_ok = lastok; } }; diff --git a/src/utils/copyfile.cpp b/src/utils/copyfile.cpp index fee9c440..5d3807d3 100644 --- a/src/utils/copyfile.cpp +++ b/src/utils/copyfile.cpp @@ -15,19 +15,24 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef TEST_COPYFILE +#include "autoconfig.h" + #include -#include #include -#include +#include "safefcntl.h" #include -#include +#include "safesysstat.h" +#include "safeunistd.h" +#ifndef _WIN32 #include #include +#define O_BINARY 0 +#endif #include #include "copyfile.h" -#include "debuglog.h" +#include "log.h" using namespace std; @@ -39,11 +44,11 @@ bool copyfile(const char *src, const char *dst, string &reason, int flags) int dfd = -1; bool ret = false; char buf[CPBSIZ]; - int oflags = O_WRONLY|O_CREAT|O_TRUNC; + int oflags = O_WRONLY|O_CREAT|O_TRUNC|O_BINARY; - LOGDEB(("copyfile: %s to %s\n", src, dst)); + LOGDEB("copyfile: " << (src) << " to " << (dst) << "\n" ); - if ((sfd = ::open(src, O_RDONLY)) < 0) { + if ((sfd = ::open(src, O_RDONLY, 0)) < 0) { reason += string("open ") + src + ": " + strerror(errno); goto out; } @@ -89,12 +94,12 @@ out: bool stringtofile(const string& dt, const char *dst, string& reason, int flags) { - LOGDEB(("stringtofile:\n")); + LOGDEB("stringtofile:\n" ); int dfd = -1; bool ret = false; - int oflags = O_WRONLY|O_CREAT|O_TRUNC; + int oflags = O_WRONLY|O_CREAT|O_TRUNC|O_BINARY; - LOGDEB(("stringtofile: %u bytes to %s\n", (unsigned int)dt.size(), dst)); + LOGDEB("stringtofile: " << ((unsigned int)dt.size()) << " bytes to " << (dst) << "\n" ); if (flags & COPYFILE_EXCL) { oflags |= O_EXCL; @@ -149,6 +154,7 @@ bool renameormove(const char *src, const char *dst, string &reason) return false; } +#ifndef _WIN32 // Try to preserve modes, owner, times. This may fail for a number // of reasons if ((st1.st_mode & 0777) != (st.st_mode & 0777)) { @@ -167,7 +173,7 @@ bool renameormove(const char *src, const char *dst, string &reason) times[1].tv_sec = st.st_mtime; times[1].tv_usec = 0; utimes(dst, times); - +#endif // All ok, get rid of origin if (unlink(src) < 0) { reason += string("Can't unlink ") + src + "Error : " + strerror(errno); @@ -256,3 +262,4 @@ int main(int argc, const char **argv) } #endif + diff --git a/src/utils/cpuconf.cpp b/src/utils/cpuconf.cpp index 98899210..533adbd6 100644 --- a/src/utils/cpuconf.cpp +++ b/src/utils/cpuconf.cpp @@ -14,54 +14,31 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ - #ifndef TEST_CPUCONF #include "autoconfig.h" + + #include "cpuconf.h" -#include "execmd.h" -#include "smallut.h" -#if defined(__gnu_linux__) -bool getCpuConf(CpuConf& conf) -{ - vector cmdv = create_vector("sh")("-c") - ("egrep ^processor /proc/cpuinfo | wc -l"); +#include - string result; - if (!ExecCmd::backtick(cmdv, result)) - return false; - conf.ncpus = atoi(result.c_str()); - if (conf.ncpus < 1 || conf.ncpus > 100) - conf.ncpus = 1; - return true; -} - -#elif defined(__FreeBSD__) -bool getCpuConf(CpuConf& conf) -{ - vector cmdv = create_vector("sysctl")("hw.ncpu"); - - string result; - if (!ExecCmd::backtick(cmdv, result)) - return false; - conf.ncpus = atoi(result.c_str()); - if (conf.ncpus < 1 || conf.ncpus > 100) - conf.ncpus = 1; - return true; -} -//#elif defined(__APPLE__) - -#else // Any other system - -// Generic, pretend there is one +// Go c++11 ! bool getCpuConf(CpuConf& cpus) { +#if defined(_WIN32) + // On windows, indexing is actually twice slower with threads + // enabled + there is a bug and the process does not exit at the + // end of indexing. Until these are solved, pretend there is only + // 1 cpu cpus.ncpus = 1; +#else + // c++11 + cpus.ncpus = std::thread::hardware_concurrency(); +#endif + return true; } -#endif - #else // TEST_CPUCONF diff --git a/src/utils/debuglog.cpp b/src/utils/debuglog.cpp deleted file mode 100644 index 3d76c52e..00000000 --- a/src/utils/debuglog.cpp +++ /dev/null @@ -1,481 +0,0 @@ -/* Copyright (C) 2006 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 - * (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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the - * Free Software Foundation, Inc., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ -#ifndef TEST_DEBUGLOG - -#define __USE_GNU -#include -#include -#include -#include -#include - -#ifdef INCLUDE_NEW_H -#include -#endif - -#include -#include -using std::set; -using std::string; - -#include "debuglog.h" -#include "pathut.h" -#include "smallut.h" -#include "ptmutex.h" - -#ifndef freeZ -#define freeZ(X) {if (X) {free(X);X=0;}} -#endif - -#ifndef NO_NAMESPACES -using namespace std; -namespace DebugLog { - -#endif // NO_NAMESPACES - -bool DebugLog::isspecialname(const char *logname) -{ - return !strcmp(logname, "stdout") || !strcmp(logname, "stderr"); -} - -class DebugLogWriter { - public: - virtual ~DebugLogWriter() {} - virtual int put(const char *s) = 0; -}; - -class DLFWImpl { - char *filename; - FILE *fp; - int truncate; - public: - // Open output file if needed, return 0 if ok - void maybeopenfp() { - if (fp) - return; - if (filename == 0) - return; - if (!strcmp(filename, "stdout")) { - fp = stdout; - } else if (!strcmp(filename, "stderr")) { - fp = stderr; - } else { - fp = fopen(filename, (truncate) ? "w" : "a"); - if (fp) { - setvbuf(fp, 0, _IOLBF, 0); -#ifdef O_APPEND - { - int flgs = 0; - fcntl(fileno(fp), F_GETFL, &flgs); - fcntl(fileno(fp), F_SETFL, flgs|O_APPEND); - } -#endif - } - } - return; - } - - void maybeclosefp() { -#ifdef DEBUGDEBUG - fprintf(stderr, "DebugLogImpl::maybeclosefp: filename %p, fp %p\n", - filename, fp); -#endif - // Close current file if open, and not stdout/stderr - if (fp && (filename == 0 || - (strcmp(filename, "stdout") && - strcmp(filename, "stderr")))) { - fclose(fp); - } - fp = 0; - freeZ(filename); - } - - public: - - DLFWImpl() - : filename(0), fp(0), truncate(1) - { - setfilename("stderr", 0); - } - ~DLFWImpl() { - maybeclosefp(); - } - int setfilename(const char *fn, int trnc) { - maybeclosefp(); - filename = strdup(fn); - truncate = trnc; - maybeopenfp(); - return 0; - } - const char *getfilename() { - return filename; - } - int put(const char *s) { - maybeopenfp(); - if (fp) - return fputs(s, fp); - return -1; - } -}; - -class DebugLogFileWriter : public DebugLogWriter { - DLFWImpl *impl; - PTMutexInit loglock; - public: - DebugLogFileWriter() - { - impl = new DLFWImpl; - } - - virtual ~DebugLogFileWriter() - { - delete impl; - } - - virtual int setfilename(const char *fn, int trnc) - { - PTMutexLocker lock(loglock); - return impl ? impl->setfilename(fn, trnc) : -1; - } - virtual const char *getfilename() - { - PTMutexLocker lock(loglock); - return impl ? impl->getfilename() : 0; - } - virtual int reopen() - { - PTMutexLocker lock(loglock); - if (!impl) - return -1; - string fn = impl->getfilename(); - return impl->setfilename(fn.c_str(), 1); - } - virtual int put(const char *s) - { - PTMutexLocker lock(loglock); - return impl ? impl->put(s) : -1; - }; -}; - - -static set yesfiles; -static void initfiles() -{ - const char *cp = getenv("DEBUGLOG_FILES"); - if (!cp) - return; - vector files; - stringToTokens(cp, files, ","); - yesfiles.insert(files.begin(), files.end()); -} -static bool fileInFiles(const string& file) -{ - string sf = path_getsimple(file); - if (yesfiles.find(sf) != yesfiles.end()) { - //fprintf(stderr, "Debug ON: %s \n", file.c_str()); - return true; - } - //fprintf(stderr, "Debug OFF: %s \n", file.c_str()); - return false; -} - -#ifdef _WINDOWS -#include -static void datestring(char *d, int sz) { - SYSTEMTIME buf; - GetLocalTime(&buf); - int year = buf.wYear % 100; - - snprintf(d, sz, "%02d%02d%02d%02d%02d%02d", year, int(buf.wMonth), - int(buf.wDay), int(buf.wHour), int(buf.wMinute), int(buf.wSecond)); -} -#define vsnprintf _vsnprintf - -#else // !WINDOWS -> - -#include -static void datestring(char *d, int sz) -{ - struct tm *tmp; - time_t tim = time((time_t*)0); - tmp = localtime(&tim); - int year = tmp->tm_year % 100; - snprintf(d, sz, "%02d%02d%02d%02d%02d%02d", year, tmp->tm_mon+1, - tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec); -} - -#endif // !WINDOWS - -void -DebugLog::prolog(int lev, const char *f, int line) -{ - if (!writer) - return; - if (!yesfiles.empty() && !fileInFiles(f)) { - fileyes = false; - return; - } else { - fileyes = true; - } - if (dodate) { - char dts[100]; - datestring(dts, 100); - writer->put(dts); - } - char buf[100]; - sprintf(buf, ":%d:", lev); - writer->put(buf); -#if DEBUGLOG_SHOW_PID - sprintf(buf, "%d:", getpid()); - writer->put(buf); -#endif -#if DEBUGLOG_SHOW_THREAD - sprintf(buf, "%lx:", (unsigned long)pthread_self()); - writer->put(buf); -#endif - writer->put(f); - sprintf(buf, ":%d:", line); - writer->put(buf); -} - -void -DebugLog::log(const char *s ...) -{ - if (!writer || !fileyes) - return; - va_list ap; - va_start(ap,s); - -#ifdef HAVE_VASPRINTF_nono // not sure vasprintf is really such a great idea - char *buf; - vasprintf(&buf, s, ap); - if (buf) { -#else - char buf[4096]; - // It's possible that they also wouldn't have vsnprintf but what then ? - vsnprintf(buf, 4096, s, ap); - { -#endif - writer->put(buf); - } - -#ifdef HAVE_VASPRINTF_nono - if (buf) - free(buf); -#endif -} - -void -DebugLog::setloglevel(int lev) -{ - debuglevel = lev; - while (!levels.empty()) - levels.pop(); - pushlevel(lev); -} - -void DebugLog::pushlevel(int lev) -{ - debuglevel = lev; - levels.push(lev); -} - -void DebugLog::poplevel() -{ - if (levels.empty()) - debuglevel = 0; - if (levels.size() > 1) - levels.pop(); - debuglevel = levels.top(); -} - - -//////////////////////////////////////////////////////////// -// Global functions -////////////////////////////////////// -static DebugLogFileWriter lwriter; -static DebugLogFileWriter *theWriter = &lwriter; - -const char *getfilename() -{ - return theWriter ? theWriter->getfilename() : 0; -} - -int setfilename(const char *fname, int trnc) -{ - return theWriter ? theWriter->setfilename(fname, trnc) : -1; -} - -int reopen() -{ - return theWriter ? theWriter->reopen() : -1; - -} - -#if DEBUGLOG_USE_THREADS -#include -static pthread_key_t dbl_key; -static pthread_once_t key_once = PTHREAD_ONCE_INIT; - -static void thrdatadel(void *data) -{ - // fprintf(stderr, "DebugLog:: thrdatadel: %p\n", data); - DebugLog *dbl = (DebugLog *)data; - delete dbl; - pthread_setspecific(dbl_key, 0); -} -static void once_routine(void) -{ - int status; - status = pthread_key_create(&dbl_key, thrdatadel); - if (status != 0) { - fprintf(stderr, "debuglog: cant initialize pthread " - "thread private storage key\n"); - abort(); - } -} - -DebugLog *getdbl() -{ - int status = pthread_once(&key_once, once_routine); - if (status != 0) { - fprintf(stderr, "debuglog: cant initialize pthread " - "thread private storage key (pthread_once)\n"); - abort(); - } - DebugLog *dbl; - if (!(dbl = (DebugLog *)pthread_getspecific(dbl_key))) { - if ((dbl = new DebugLog) == 0) { - fprintf(stderr, "debuglog: new DebugLog returned 0! "); - abort(); - } - - dbl->setwriter(theWriter); - initfiles(); - status = pthread_setspecific(dbl_key, dbl); - if (status) { - fprintf(stderr, "debuglog: cant initialize pthread " - "thread private storage key (pthread_setspecific)\n"); - abort(); - } - } - return dbl; -} - -#else // No threads -> - -static DebugLog *dbl; -DebugLog *getdbl() -{ - if (!dbl) { - dbl = new DebugLog; - dbl->setwriter(theWriter); - initfiles(); - } - return dbl; -} -#endif - -#ifndef NO_NAMESPACES -} -#endif // NO_NAMESPACES - -////////////////////////////////////////// TEST DRIVER ////////////////// -#else /* TEST_DEBUGLOG */ - -#include -#include -#include -#include - -#include "debuglog.h" - -#if DEBUGLOG_USE_THREADS -//#define TEST_THREADS -#endif - -#ifdef TEST_THREADS -#include -#endif - -const int iloop = 5; -void *thread_test(void *data) -{ - const char *s = (const char *)data; - int lev = atoi(s); - DebugLog::getdbl()->setloglevel(DEBDEB); - for (int i = 1; i < iloop;i++) { - switch (lev) { - case 1: LOGFATAL(("Thread: %s count: %d\n", s, i));break; - case 2: LOGERR(("Thread: %s count: %d\n", s, i));break; - default: - case 3: LOGINFO(("Thread: %s count: %d\n", s, i));break; - } - sleep(1); - } - return 0; -} - - -int -main(int argc, char **argv) -{ -#ifdef TEST_THREADS - pthread_t t1, t2, t3; - - char name1[20]; - strcpy(name1, "1"); - pthread_create(&t1, 0, thread_test, name1); - - char name2[20]; - strcpy(name2, "2"); - pthread_create(&t2, 0, thread_test, name2); - - char name3[20]; - strcpy(name3, "3"); - pthread_create(&t3, 0, thread_test, name3); - - DebugLog::getdbl()->setloglevel(DEBDEB); - for (int i = 1; i < iloop;i++) { - LOGINFO(("LOGGING FROM MAIN\n")); - sleep(1); - } - sleep(2); - exit(0); -#else - LOGFATAL(("FATAL\n","Val")); - DebugLog::getdbl()->logdate(1); - LOGERR(("ERR\n","Val")); - LOGINFO(("INFO\n","Val")); - LOGDEB0(("DEBUG %s\n","valeur")); - - int lev; - printf("Testing push. Initial level: %d\n", DebugLog::getdbl()->getlevel()); - for (lev = 0; lev < 4;lev++) { - DebugLog::getdbl()->pushlevel(lev); - printf("Lev now %d\n", DebugLog::getdbl()->getlevel()); - } - printf("Testing pop\n"); - for (lev = 0; lev < 7;lev++) { - DebugLog::getdbl()->poplevel(); - printf("Lev now %d\n", DebugLog::getdbl()->getlevel()); - } -#endif -} - - -#endif /* TEST_DEBUGLOG */ diff --git a/src/utils/debuglog.h b/src/utils/debuglog.h deleted file mode 100644 index 04921bc6..00000000 --- a/src/utils/debuglog.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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 - * (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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the - * Free Software Foundation, Inc., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ -#ifndef _DEBUGLOG_H_ -#define _DEBUGLOG_H_ -/* Macros for log and debug messages */ -#include - -namespace DebugLog { - -#ifndef DEBUGLOG_USE_THREADS -#define DEBUGLOG_USE_THREADS 1 -#endif - -#define DEBFATAL 1 -#define DEBERR 2 -#define DEBINFO 3 -#define DEBDEB 4 -#define DEBDEB0 5 -#define DEBDEB1 6 -#define DEBDEB2 7 -#define DEBDEB3 8 - -#ifndef STATICVERBOSITY -#define STATICVERBOSITY DEBDEB0 -#endif - -class DebugLogWriter; - -class DebugLog { - std::stack levels; - int debuglevel; - int dodate; - DebugLogWriter *writer; - bool fileyes; - public: - DebugLog() : debuglevel(10), dodate(0), writer(0), fileyes(true) {} - DebugLog(DebugLogWriter *w) : debuglevel(-1), dodate(0), writer(w), - fileyes(true) {} - virtual ~DebugLog() {} - virtual void setwriter(DebugLogWriter *w) {writer = w;} - virtual DebugLogWriter *getwriter() {return writer;} - virtual void prolog(int lev, const char *srcfname, int line); - virtual void log(const char *s ...); - virtual void setloglevel(int lev); - inline int getlevel() {return debuglevel;} - virtual void pushlevel(int lev); - virtual void poplevel(); - virtual void logdate(int onoff) {dodate = onoff;} - static bool isspecialname(const char *logname); -}; - -extern DebugLog *getdbl(); -extern const char *getfilename(); -extern int setfilename(const char *fname, int trnc = 1); -extern int reopen(); - -#if STATICVERBOSITY >= DEBFATAL -#define LOGFATAL(X) {if (DebugLog::getdbl()->getlevel()>=DEBFATAL){DebugLog::getdbl()->prolog(DEBFATAL,__FILE__,__LINE__) ;DebugLog::getdbl()->log X;}} -#else -#define LOGFATAL(X) -#endif -#if STATICVERBOSITY >= DEBERR -#define LOGERR(X) {if (DebugLog::getdbl()->getlevel()>=DEBERR){DebugLog::getdbl()->prolog(DEBERR,__FILE__,__LINE__) ;DebugLog::getdbl()->log X;}} -#else -#define LOGERR(X) -#endif -#if STATICVERBOSITY >= DEBINFO -#define LOGINFO(X) {if (DebugLog::getdbl()->getlevel()>=DEBINFO){DebugLog::getdbl()->prolog(DEBINFO,__FILE__,__LINE__) ;DebugLog::getdbl()->log X;}} -#else -#define LOGINFO(X) -#endif -#if STATICVERBOSITY >= DEBDEB -#define LOGDEB(X) {if (DebugLog::getdbl()->getlevel()>=DEBDEB){DebugLog::getdbl()->prolog(DEBDEB,__FILE__,__LINE__) ;DebugLog::getdbl()->log X;}} -#else -#define LOGDEB(X) -#endif -#if STATICVERBOSITY >= DEBDEB0 -#define LOGDEB0(X) {if (DebugLog::getdbl()->getlevel()>=DEBDEB0){DebugLog::getdbl()->prolog(DEBDEB0,__FILE__,__LINE__) ;DebugLog::getdbl()->log X;}} -#else -#define LOGDEB0(X) -#endif -#if STATICVERBOSITY >= DEBDEB1 -#define LOGDEB1(X) {if (DebugLog::getdbl()->getlevel()>=DEBDEB1){DebugLog::getdbl()->prolog(DEBDEB1,__FILE__,__LINE__) ;DebugLog::getdbl()->log X;}} -#else -#define LOGDEB1(X) -#endif -#if STATICVERBOSITY >= DEBDEB2 -#define LOGDEB2(X) {if (DebugLog::getdbl()->getlevel()>=DEBDEB2){DebugLog::getdbl()->prolog(DEBDEB2,__FILE__,__LINE__) ;DebugLog::getdbl()->log X;}} -#else -#define LOGDEB2(X) -#endif -#if STATICVERBOSITY >= DEBDEB3 -#define LOGDEB3(X) {if (DebugLog::getdbl()->getlevel()>=DEBDEB3){DebugLog::getdbl()->prolog(DEBDEB3,__FILE__,__LINE__) ;DebugLog::getdbl()->log X;}} -#else -#define LOGDEB3(X) -#endif -} -#endif /* _DEBUGLOG_H_ */ diff --git a/src/utils/ecrontab.cpp b/src/utils/ecrontab.cpp index 1b147ed2..17416f86 100644 --- a/src/utils/ecrontab.cpp +++ b/src/utils/ecrontab.cpp @@ -22,7 +22,7 @@ #include "ecrontab.h" #include "execmd.h" #include "smallut.h" -#include "debuglog.h" +#include "log.h" // Read crontab file and split it into lines. static bool eCrontabGetLines(vector& lines) @@ -128,8 +128,7 @@ bool checkCrontabUnmanaged(const string& marker, const string& data) bool getCrontabSched(const string& marker, const string& id, vector& sched) { - LOGDEB0(("getCrontabSched: marker[%s], id[%s]\n", - marker.c_str(), id.c_str())); + LOGDEB0("getCrontabSched: marker[" << (marker) << "], id[" << (id) << "]\n" ); vector lines; if (!eCrontabGetLines(lines)) { // No crontab, answer is no @@ -270,3 +269,4 @@ int main(int argc, char **argv) exit(0); } #endif // TEST + diff --git a/src/utils/execmd.cpp b/src/utils/execmd.cpp index 267052c3..c27eeaef 100644 --- a/src/utils/execmd.cpp +++ b/src/utils/execmd.cpp @@ -15,7 +15,7 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef TEST_EXECMD -#ifdef RECOLL_DATADIR +#ifdef BUILDING_RECOLL #include "autoconfig.h" #else #include "config.h" @@ -37,6 +37,7 @@ #include #include +#include #ifdef HAVE_SPAWN_H #ifndef __USE_GNU #define __USE_GNU @@ -49,70 +50,104 @@ #endif #include "execmd.h" - #include "netcon.h" #include "closefrom.h" +#include "smallut.h" +#include "log.h" using namespace std; extern char **environ; -bool ExecCmd::o_useVfork = false; - -#ifdef RECOLL_DATADIR -#include "debuglog.h" -#include "smallut.h" - -#else -// If compiling outside of recoll, make the file as standalone as reasonable. - -#define LOGFATAL(X) -#define LOGERR(X) -#define LOGINFO(X) -#define LOGDEB(X) -#define LOGDEB0(X) -#define LOGDEB1(X) -#define LOGDEB2(X) -#define LOGDEB3(X) -#define LOGDEB4(X) - -#ifndef MIN -#define MIN(A,B) ((A) < (B) ? (A) : (B)) -#endif - -static void stringToTokens(const string &s, vector &tokens, - const string &delims = " \t", bool skipinit=true); - -static void stringToTokens(const string& str, vector& tokens, - const string& delims, bool skipinit) -{ - string::size_type startPos = 0, pos; - - // Skip initial delims, return empty if this eats all. - if (skipinit && - (startPos = str.find_first_not_of(delims, 0)) == string::npos) { - return; +class ExecCmd::Internal { +public: + Internal() + : m_advise(0), m_provide(0), m_timeoutMs(1000), + m_rlimit_as_mbytes(0) { } - while (startPos < str.size()) { - // Find next delimiter or end of string (end of token) - pos = str.find_first_of(delims, startPos); - // Add token to the vector and adjust start - if (pos == string::npos) { - tokens.push_back(str.substr(startPos)); - break; - } else if (pos == startPos) { - // Dont' push empty tokens after first - if (tokens.empty()) - tokens.push_back(string()); - startPos = ++pos; - } else { - tokens.push_back(str.substr(startPos, pos - startPos)); - startPos = ++pos; - } + static bool o_useVfork; + + std::vector m_env; + ExecCmdAdvise *m_advise; + ExecCmdProvide *m_provide; + bool m_killRequest; + int m_timeoutMs; + int m_rlimit_as_mbytes; + string m_stderrFile; + // Pipe for data going to the command + int m_pipein[2]; + std::shared_ptr m_tocmd; + // Pipe for data coming out + int m_pipeout[2]; + std::shared_ptr m_fromcmd; + // Subprocess id + pid_t m_pid; + // Saved sigmask + sigset_t m_blkcld; + + // Reset internal state indicators. Any resources should have been + // previously freed + void reset() { + m_killRequest = false; + m_pipein[0] = m_pipein[1] = m_pipeout[0] = m_pipeout[1] = -1; + m_pid = -1; + sigemptyset(&m_blkcld); + } + // Child process code + inline void dochild(const std::string& cmd, const char **argv, + const char **envv, bool has_input, bool has_output); +}; +bool ExecCmd::Internal::o_useVfork = false; + +ExecCmd::ExecCmd(int) +{ + m = new Internal(); + if (m) { + m->reset(); } } -#endif // RECOLL_DATADIR +void ExecCmd::setAdvise(ExecCmdAdvise *adv) +{ + m->m_advise = adv; +} +void ExecCmd::setProvide(ExecCmdProvide *p) +{ + m->m_provide = p; +} +void ExecCmd::setTimeout(int mS) +{ + if (mS > 30) { + m->m_timeoutMs = mS; + } +} +void ExecCmd::setStderr(const std::string& stderrFile) +{ + m->m_stderrFile = stderrFile; +} +pid_t ExecCmd::getChildPid() +{ + return m->m_pid; +} +void ExecCmd::setKill() +{ + m->m_killRequest = true; +} +void ExecCmd::zapChild() +{ + setKill(); + (void)wait(); +} + +bool ExecCmd::requestChildExit() +{ + if (m->m_pid > 0) { + if (kill(m->m_pid, SIGTERM) == 0) { + return true; + } + } + return false; +} /* From FreeBSD's which command */ static bool exec_is_there(const char *candidate) @@ -121,47 +156,50 @@ static bool exec_is_there(const char *candidate) /* XXX work around access(2) false positives for superuser */ if (access(candidate, X_OK) == 0 && - stat(candidate, &fin) == 0 && - S_ISREG(fin.st_mode) && - (getuid() != 0 || - (fin.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0)) { - return true; + stat(candidate, &fin) == 0 && + S_ISREG(fin.st_mode) && + (getuid() != 0 || + (fin.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0)) { + return true; } return false; } bool ExecCmd::which(const string& cmd, string& exepath, const char* path) { - if (cmd.empty()) - return false; + if (cmd.empty()) { + return false; + } if (cmd[0] == '/') { - if (exec_is_there(cmd.c_str())) { - exepath = cmd; - return true; - } else { - return false; - } + if (exec_is_there(cmd.c_str())) { + exepath = cmd; + return true; + } else { + return false; + } } const char *pp; if (path) { - pp = path; + pp = path; } else { - pp = getenv("PATH"); + pp = getenv("PATH"); + } + if (pp == 0) { + return false; } - if (pp == 0) - return false; vector pels; stringToTokens(pp, pels, ":"); for (vector::iterator it = pels.begin(); it != pels.end(); it++) { - if (it->empty()) - *it = "."; - string candidate = (it->empty() ? string(".") : *it) + "/" + cmd; - if (exec_is_there(candidate.c_str())) { - exepath = candidate; - return true; - } + if (it->empty()) { + *it = "."; + } + string candidate = (it->empty() ? string(".") : *it) + "/" + cmd; + if (exec_is_there(candidate.c_str())) { + exepath = candidate; + return true; + } } return false; } @@ -174,15 +212,15 @@ void ExecCmd::useVfork(bool on) // an executable file, we have a problem. const char *argv[] = {"/", 0}; execve("/", (char *const *)argv, environ); - o_useVfork = on; + Internal::o_useVfork = on; } -void ExecCmd::putenv(const string &ea) +void ExecCmd::putenv(const string& ea) { - m_env.push_back(ea); + m->m_env.push_back(ea); } -void ExecCmd::putenv(const string &name, const string& value) +void ExecCmd::putenv(const string& name, const string& value) { string ea = name + "=" + value; putenv(ea); @@ -196,70 +234,83 @@ static void msleep(int millis) nanosleep(&spec, 0); } -/** A resource manager to ensure that execcmd cleans up if an exception is +/** A resource manager to ensure that execcmd cleans up if an exception is * raised in the callback, or at different places on errors occurring * during method executions */ class ExecCmdRsrc { public: - ExecCmdRsrc(ExecCmd *parent) : m_parent(parent), m_active(true) {} - void inactivate() {m_active = false;} + ExecCmdRsrc(ExecCmd::Internal *parent) + : m_parent(parent), m_active(true) { + } + void inactivate() { + m_active = false; + } ~ExecCmdRsrc() { - if (!m_active || !m_parent) - return; - LOGDEB1(("~ExecCmdRsrc: working. mypid: %d\n", (int)getpid())); + if (!m_active || !m_parent) { + return; + } + LOGDEB1("~ExecCmdRsrc: working. mypid: " << getpid() << "\n"); - // Better to close the descs first in case the child is waiting in read - if (m_parent->m_pipein[0] >= 0) - close(m_parent->m_pipein[0]); - if (m_parent->m_pipein[1] >= 0) - close(m_parent->m_pipein[1]); - if (m_parent->m_pipeout[0] >= 0) - close(m_parent->m_pipeout[0]); - if (m_parent->m_pipeout[1] >= 0) - close(m_parent->m_pipeout[1]); + // Better to close the descs first in case the child is waiting in read + if (m_parent->m_pipein[0] >= 0) { + close(m_parent->m_pipein[0]); + } + if (m_parent->m_pipein[1] >= 0) { + close(m_parent->m_pipein[1]); + } + if (m_parent->m_pipeout[0] >= 0) { + close(m_parent->m_pipeout[0]); + } + if (m_parent->m_pipeout[1] >= 0) { + close(m_parent->m_pipeout[1]); + } - // It's apparently possible for m_pid to be > 0 and getpgid to fail. In - // this case, we have to conclude that the child process does - // not exist. Not too sure what causes this, but the previous code - // definitely tried to call killpg(-1,) from time to time. - pid_t grp; - if (m_parent->m_pid > 0 && (grp = getpgid(m_parent->m_pid)) > 0) { - LOGDEB(("ExecCmd: killpg(%d, SIGTERM)\n", grp)); + // It's apparently possible for m_pid to be > 0 and getpgid to fail. In + // this case, we have to conclude that the child process does + // not exist. Not too sure what causes this, but the previous code + // definitely tried to call killpg(-1,) from time to time. + pid_t grp; + if (m_parent->m_pid > 0 && (grp = getpgid(m_parent->m_pid)) > 0) { + LOGDEB("ExecCmd: killpg(" << (grp) << ", SIGTERM)\n"); int ret = killpg(grp, SIGTERM); - if (ret == 0) { - for (int i = 0; i < 3; i++) { - msleep(i == 0 ? 5 : (i == 1 ? 100 : 2000)); - int status; - (void)waitpid(m_parent->m_pid, &status, WNOHANG); - if (kill(m_parent->m_pid, 0) != 0) - break; - if (i == 2) { - LOGDEB(("ExecCmd: killpg(%d, SIGKILL)\n", grp)); - killpg(grp, SIGKILL); - (void)waitpid(m_parent->m_pid, &status, WNOHANG); - } - } - } else { - LOGERR(("ExecCmd: error killing process group %d: %d\n", - grp, errno)); + if (ret == 0) { + for (int i = 0; i < 3; i++) { + msleep(i == 0 ? 5 : (i == 1 ? 100 : 2000)); + int status; + (void)waitpid(m_parent->m_pid, &status, WNOHANG); + if (kill(m_parent->m_pid, 0) != 0) { + break; + } + if (i == 2) { + LOGDEB("ExecCmd: killpg(" << (grp) << ", SIGKILL)\n"); + killpg(grp, SIGKILL); + (void)waitpid(m_parent->m_pid, &status, WNOHANG); + } + } + } else { + LOGERR("ExecCmd: error killing process group " << (grp) << + ": " << errno << "\n"); } - } - m_parent->m_tocmd.reset(); - m_parent->m_fromcmd.reset(); - pthread_sigmask(SIG_UNBLOCK, &m_parent->m_blkcld, 0); - m_parent->reset(); + } + m_parent->m_tocmd.reset(); + m_parent->m_fromcmd.reset(); + pthread_sigmask(SIG_UNBLOCK, &m_parent->m_blkcld, 0); + m_parent->reset(); } private: - ExecCmd *m_parent; + ExecCmd::Internal *m_parent; bool m_active; }; ExecCmd::~ExecCmd() { - ExecCmdRsrc(this); + ExecCmdRsrc(this->m); + if (m) { + delete m; + } } -// In child process. Set up pipes and exec command. +// In child process. Set up pipes and exec command. // This must not return. _exit() on error. // *** This can be called after a vfork, so no modification of the // process memory at all is allowed *** @@ -267,19 +318,19 @@ ExecCmd::~ExecCmd() // errors, which we would most definitely want to have a hint about. // // Note that any of the LOGXX calls could block on a mutex set in the -// father process, so that only absolutely exceptional conditions, +// father process, so that only absolutely exceptional conditions, // should be logged, for debugging and post-mortem purposes // If one of the calls block, the problem manifests itself by 20mn // (filter timeout) of looping on "ExecCmd::doexec: selectloop // returned 1', because the father is waiting on the read descriptor -inline void ExecCmd::dochild(const string &cmd, const char **argv, - const char **envv, - bool has_input, bool has_output) +inline void ExecCmd::Internal::dochild(const string& cmd, const char **argv, + const char **envv, + bool has_input, bool has_output) { // Start our own process group if (setpgid(0, getpid())) { - LOGINFO(("ExecCmd::DOCHILD: setpgid(0, %d) failed: errno %d\n", - getpid(), errno)); + LOGINFO("ExecCmd::DOCHILD: setpgid(0, " << getpid() << + ") failed: errno " << errno << "\n"); } // Restore SIGTERM to default. Really, signal handling should be @@ -295,7 +346,7 @@ inline void ExecCmd::dochild(const string &cmd, const char **argv, // to mess with the global process signal disposition. if (signal(SIGTERM, SIG_DFL) == SIG_ERR) { - //LOGERR(("ExecCmd::DOCHILD: signal() failed, errno %d\n", errno)); + //LOGERR("ExecCmd::DOCHILD: signal() failed, errno " << errno << "\n"); } sigset_t sset; sigfillset(&sset); @@ -305,69 +356,71 @@ inline void ExecCmd::dochild(const string &cmd, const char **argv, #ifdef HAVE_SETRLIMIT #if defined RLIMIT_AS || defined RLIMIT_VMEM || defined RLIMIT_DATA if (m_rlimit_as_mbytes > 2000 && sizeof(rlim_t) < 8) { - // Impossible limit, don't use it - m_rlimit_as_mbytes = 0; + // Impossible limit, don't use it + m_rlimit_as_mbytes = 0; } if (m_rlimit_as_mbytes > 0) { - struct rlimit ram_limit = { - static_cast(m_rlimit_as_mbytes * 1024 * 1024), - RLIM_INFINITY - }; - int resource; + struct rlimit ram_limit = { + static_cast(m_rlimit_as_mbytes * 1024 * 1024), + RLIM_INFINITY + }; + int resource; - // RLIMIT_AS and RLIMIT_VMEM are usually synonyms when VMEM is - // defined. RLIMIT_AS is Posix. Both don't really do what we - // want, because they count e.g. shared lib mappings, which we - // don't really care about. - // RLIMIT_DATA only limits the data segment. Modern mallocs - // use mmap and will not be bound. (Otoh if we only have this, - // we're probably not modern). - // So we're unsatisfied either way. + // RLIMIT_AS and RLIMIT_VMEM are usually synonyms when VMEM is + // defined. RLIMIT_AS is Posix. Both don't really do what we + // want, because they count e.g. shared lib mappings, which we + // don't really care about. + // RLIMIT_DATA only limits the data segment. Modern mallocs + // use mmap and will not be bound. (Otoh if we only have this, + // we're probably not modern). + // So we're unsatisfied either way. #ifdef RLIMIT_AS - resource = RLIMIT_AS; + resource = RLIMIT_AS; #elif defined RLIMIT_VMEM - resource = RLIMIT_VMEM; + resource = RLIMIT_VMEM; #else - resource = RLIMIT_DATA; + resource = RLIMIT_DATA; #endif - setrlimit(resource, &ram_limit); + setrlimit(resource, &ram_limit); } #endif #endif // have_setrlimit if (has_input) { - close(m_pipein[1]); - if (m_pipein[0] != 0) { - dup2(m_pipein[0], 0); - close(m_pipein[0]); - } + close(m_pipein[1]); + if (m_pipein[0] != 0) { + dup2(m_pipein[0], 0); + close(m_pipein[0]); + } } if (has_output) { - close(m_pipeout[0]); - if (m_pipeout[1] != 1) { - if (dup2(m_pipeout[1], 1) < 0) { - LOGERR(("ExecCmd::DOCHILD: dup2() failed. errno %d\n", errno)); - } - if (close(m_pipeout[1]) < 0) { - LOGERR(("ExecCmd::DOCHILD: close() failed. errno %d\n", errno)); - } - } + close(m_pipeout[0]); + if (m_pipeout[1] != 1) { + if (dup2(m_pipeout[1], 1) < 0) { + LOGERR("ExecCmd::DOCHILD: dup2() failed. errno " << + errno << "\n"); + } + if (close(m_pipeout[1]) < 0) { + LOGERR("ExecCmd::DOCHILD: close() failed. errno " << + errno << "\n"); + } + } } // Do we need to redirect stderr ? if (!m_stderrFile.empty()) { - int fd = open(m_stderrFile.c_str(), O_WRONLY|O_CREAT + int fd = open(m_stderrFile.c_str(), O_WRONLY | O_CREAT #ifdef O_APPEND - |O_APPEND + | O_APPEND #endif - , 0600); - if (fd < 0) { - close(2); - } else { - if (fd != 2) { - dup2(fd, 2); - } - lseek(2, 0, 2); - } + , 0600); + if (fd < 0) { + close(2); + } else { + if (fd != 2) { + dup2(fd, 2); + } + lseek(2, 0, 2); + } } // Close all descriptors except 0,1,2 @@ -377,39 +430,40 @@ inline void ExecCmd::dochild(const string &cmd, const char **argv, // Hu ho. This should never have happened as we checked the // existence of the executable before calling dochild... Until we // did this check, this was the chief cause of LOG mutex deadlock - LOGERR(("ExecCmd::DOCHILD: execve(%s) failed. errno %d\n", cmd.c_str(), - errno)); + LOGERR("ExecCmd::DOCHILD: execve(" << cmd << ") failed. errno " << + errno << "\n"); _exit(127); } void ExecCmd::setrlimit_as(int mbytes) { - m_rlimit_as_mbytes = mbytes; + m->m_rlimit_as_mbytes = mbytes; } -int ExecCmd::startExec(const string &cmd, const vector& args, - bool has_input, bool has_output) +int ExecCmd::startExec(const string& cmd, const vector& args, + bool has_input, bool has_output) { - { // Debug and logging - string command = cmd + " "; - for (vector::const_iterator it = args.begin(); - it != args.end(); it++) { - command += "{" + *it + "} "; - } - LOGDEB(("ExecCmd::startExec: (%d|%d) %s\n", - has_input, has_output, command.c_str())); + { + // Debug and logging + string command = cmd + " "; + for (vector::const_iterator it = args.begin(); + it != args.end(); it++) { + command += "{" + *it + "} "; + } + LOGDEB("ExecCmd::startExec: (" << has_input << "|" << has_output << + ") " << command << "\n"); } // The resource manager ensures resources are freed if we return early - ExecCmdRsrc e(this); + ExecCmdRsrc e(this->m); - if (has_input && pipe(m_pipein) < 0) { - LOGERR(("ExecCmd::startExec: pipe(2) failed. errno %d\n", errno)); - return -1; + if (has_input && pipe(m->m_pipein) < 0) { + LOGERR("ExecCmd::startExec: pipe(2) failed. errno " << errno << "\n" ); + return -1; } - if (has_output && pipe(m_pipeout) < 0) { - LOGERR(("ExecCmd::startExec: pipe(2) failed. errno %d\n", errno)); - return -1; + if (has_output && pipe(m->m_pipeout) < 0) { + LOGERR("ExecCmd::startExec: pipe(2) failed. errno " << errno << "\n"); + return -1; } @@ -422,9 +476,9 @@ int ExecCmd::startExec(const string &cmd, const vector& args, // Allocate arg vector (2 more for arg0 + final 0) typedef const char *Ccharp; Ccharp *argv; - argv = (Ccharp *)malloc((args.size()+2) * sizeof(char *)); + argv = (Ccharp *)malloc((args.size() + 2) * sizeof(char *)); if (argv == 0) { - LOGERR(("ExecCmd::doexec: malloc() failed. errno %d\n", errno)); + LOGERR("ExecCmd::doexec: malloc() failed. errno " << errno << "\n"); return -1; } // Fill up argv @@ -432,34 +486,36 @@ int ExecCmd::startExec(const string &cmd, const vector& args, int i = 1; vector::const_iterator it; for (it = args.begin(); it != args.end(); it++) { - argv[i++] = it->c_str(); + argv[i++] = it->c_str(); } argv[i] = 0; Ccharp *envv; int envsize; - for (envsize = 0; ; envsize++) - if (environ[envsize] == 0) - break; - envv = (Ccharp *)malloc((envsize + m_env.size() + 2) * sizeof(char *)); + for (envsize = 0; ; envsize++) + if (environ[envsize] == 0) { + break; + } + envv = (Ccharp *)malloc((envsize + m->m_env.size() + 2) * sizeof(char *)); if (envv == 0) { - LOGERR(("ExecCmd::doexec: malloc() failed. errno %d\n", errno)); + LOGERR("ExecCmd::doexec: malloc() failed. errno " << errno << "\n"); free(argv); return -1; } int eidx; - for (eidx = 0; eidx < envsize; eidx++) - envv[eidx] = environ[eidx]; - for (vector::const_iterator it = m_env.begin(); - it != m_env.end(); it++) { - envv[eidx++] = it->c_str(); + for (eidx = 0; eidx < envsize; eidx++) { + envv[eidx] = environ[eidx]; + } + for (vector::const_iterator it = m->m_env.begin(); + it != m->m_env.end(); it++) { + envv[eidx++] = it->c_str(); } envv[eidx] = 0; // As we are going to use execve, not execvp, do the PATH thing. string exe; if (!which(cmd, exe)) { - LOGERR(("ExecCmd::startExec: %s not found\n", cmd.c_str())); + LOGERR("ExecCmd::startExec: " << (cmd) << " not found\n"); free(argv); free(envv); return -1; @@ -470,7 +526,7 @@ int ExecCmd::startExec(const string &cmd, const vector& args, // Note that posix_spawn provides no way to setrlimit() the child. { posix_spawnattr_t attrs; - posix_spawnattr_init (&attrs); + posix_spawnattr_init(&attrs); short flags; posix_spawnattr_getflags(&attrs, &flags); @@ -481,7 +537,7 @@ int ExecCmd::startExec(const string &cmd, const vector& args, sigset_t sset; sigemptyset(&sset); - posix_spawnattr_setsigmask (&attrs, &sset); + posix_spawnattr_setsigmask(&attrs, &sset); flags |= POSIX_SPAWN_SETSIGMASK; sigemptyset(&sset); @@ -495,30 +551,30 @@ int ExecCmd::startExec(const string &cmd, const vector& args, posix_spawn_file_actions_init(&facts); if (has_input) { - posix_spawn_file_actions_addclose(&facts, m_pipein[1]); - if (m_pipein[0] != 0) { - posix_spawn_file_actions_adddup2(&facts, m_pipein[0], 0); - posix_spawn_file_actions_addclose(&facts, m_pipein[0]); + posix_spawn_file_actions_addclose(&facts, m->m_pipein[1]); + if (m->m_pipein[0] != 0) { + posix_spawn_file_actions_adddup2(&facts, m->m_pipein[0], 0); + posix_spawn_file_actions_addclose(&facts, m->m_pipein[0]); } } if (has_output) { - posix_spawn_file_actions_addclose(&facts, m_pipeout[0]); - if (m_pipeout[1] != 1) { - posix_spawn_file_actions_adddup2(&facts, m_pipeout[1], 1); - posix_spawn_file_actions_addclose(&facts, m_pipeout[1]); + posix_spawn_file_actions_addclose(&facts, m->m_pipeout[0]); + if (m->m_pipeout[1] != 1) { + posix_spawn_file_actions_adddup2(&facts, m->m_pipeout[1], 1); + posix_spawn_file_actions_addclose(&facts, m->m_pipeout[1]); } } // Do we need to redirect stderr ? - if (!m_stderrFile.empty()) { - int oflags = O_WRONLY|O_CREAT; + if (!m->m_stderrFile.empty()) { + int oflags = O_WRONLY | O_CREAT; #ifdef O_APPEND oflags |= O_APPEND; #endif - posix_spawn_file_actions_addopen(&facts, 2, m_stderrFile.c_str(), + posix_spawn_file_actions_addopen(&facts, 2, m->m_stderrFile.c_str(), oflags, 0600); } - LOGDEB1(("using SPAWN\n")); + LOGDEB1("using SPAWN\n"); // posix_spawn() does not have any standard way to ask for // calling closefrom(). Afaik there is a solaris extension for this, @@ -527,36 +583,36 @@ int ExecCmd::startExec(const string &cmd, const vector& args, posix_spawn_file_actions_addclose(&facts, i); } - int ret = posix_spawn(&m_pid, exe.c_str(), &facts, &attrs, + int ret = posix_spawn(&m->m_pid, exe.c_str(), &facts, &attrs, (char *const *)argv, (char *const *)envv); posix_spawnattr_destroy(&attrs); posix_spawn_file_actions_destroy(&facts); if (ret) { - LOGERR(("ExecCmd::startExec: posix_spawn() failed. errno %d\n", - ret)); + LOGERR("ExecCmd::startExec: posix_spawn() failed. errno " << ret << + "\n"); return -1; } } #else - if (o_useVfork) { - LOGDEB1(("using VFORK\n")); - m_pid = vfork(); + if (Internal::o_useVfork) { + LOGDEB1("using VFORK\n"); + m->m_pid = vfork(); } else { - LOGDEB1(("using FORK\n")); - m_pid = fork(); + LOGDEB1("using FORK\n"); + m->m_pid = fork(); } - if (m_pid < 0) { - LOGERR(("ExecCmd::startExec: fork(2) failed. errno %d\n", errno)); - return -1; + if (m->m_pid < 0) { + LOGERR("ExecCmd::startExec: fork(2) failed. errno " << errno << "\n"); + return -1; } - if (m_pid == 0) { - // e.inactivate() is not needed. As we do not return, the call - // stack won't be unwound and destructors of local objects - // won't be called. - dochild(exe, argv, envv, has_input, has_output); - // dochild does not return. Just in case... - _exit(1); + if (m->m_pid == 0) { + // e.inactivate() is not needed. As we do not return, the call + // stack won't be unwound and destructors of local objects + // won't be called. + m->dochild(exe, argv, envv, has_input, has_output); + // dochild does not return. Just in case... + _exit(1); } #endif @@ -570,30 +626,30 @@ int ExecCmd::startExec(const string &cmd, const vector& args, // Set the process group for the child. This is also done in the // child process see wikipedia(Process_group) - if (setpgid(m_pid, m_pid)) { - // This can fail with EACCES if the son has already done execve + if (setpgid(m->m_pid, m->m_pid)) { + // This can fail with EACCES if the son has already done execve // (linux at least) - LOGDEB2(("ExecCmd: father setpgid(son)(%d,%d) errno %d (ok)\n", - m_pid, m_pid, errno)); + LOGDEB2("ExecCmd: father setpgid(son)(" << m->m_pid << "," << + m->m_pid << ") errno " << errno << " (ok)\n"); } - sigemptyset(&m_blkcld); - sigaddset(&m_blkcld, SIGCHLD); - pthread_sigmask(SIG_BLOCK, &m_blkcld, 0); + sigemptyset(&m->m_blkcld); + sigaddset(&m->m_blkcld, SIGCHLD); + pthread_sigmask(SIG_BLOCK, &m->m_blkcld, 0); if (has_input) { - close(m_pipein[0]); - m_pipein[0] = -1; - NetconCli *iclicon = new NetconCli(); - iclicon->setconn(m_pipein[1]); - m_tocmd = NetconP(iclicon); + close(m->m_pipein[0]); + m->m_pipein[0] = -1; + NetconCli *iclicon = new NetconCli(); + iclicon->setconn(m->m_pipein[1]); + m->m_tocmd = std::shared_ptr(iclicon); } if (has_output) { - close(m_pipeout[1]); - m_pipeout[1] = -1; - NetconCli *oclicon = new NetconCli(); - oclicon->setconn(m_pipeout[0]); - m_fromcmd = NetconP(oclicon); + close(m->m_pipeout[1]); + m->m_pipeout[1] = -1; + NetconCli *oclicon = new NetconCli(); + oclicon->setconn(m->m_pipeout[0]); + m->m_fromcmd = std::shared_ptr(oclicon); } /* Don't want to undo what we just did ! */ @@ -605,40 +661,51 @@ int ExecCmd::startExec(const string &cmd, const vector& args, // Netcon callback. Send data to the command's input class ExecWriter : public NetconWorker { public: - ExecWriter(const string *input, ExecCmdProvide *provide) - : m_input(input), m_cnt(0), m_provide(provide) - {} - virtual int data(NetconData *con, Netcon::Event reason) - { - if (!m_input) return -1; - LOGDEB1(("ExecWriter: input m_cnt %d input length %d\n", m_cnt, - m_input->length())); - if (m_cnt >= m_input->length()) { - // Fd ready for more but we got none. - if (m_provide) { - m_provide->newData(); - if (m_input->empty()) { - return 0; - } else { - m_cnt = 0; - } - LOGDEB2(("ExecWriter: provide m_cnt %d input length %d\n", - m_cnt, m_input->length())); - } else { - return 0; - } - } - int ret = con->send(m_input->c_str() + m_cnt, - m_input->length() - m_cnt); - LOGDEB2(("ExecWriter: wrote %d to command\n", ret)); - if (ret <= 0) { - LOGERR(("ExecWriter: data: can't write\n")); - return -1; - } - m_cnt += ret; - return ret; + ExecWriter(const string *input, ExecCmdProvide *provide, + ExecCmd::Internal *parent) + : m_cmd(parent), m_input(input), m_cnt(0), m_provide(provide) { + } + void shutdown() { + close(m_cmd->m_pipein[1]); + m_cmd->m_pipein[1] = -1; + m_cmd->m_tocmd.reset(); + } + virtual int data(NetconData *con, Netcon::Event reason) { + if (!m_input) { + return -1; + } + LOGDEB1("ExecWriter: input m_cnt " << m_cnt << " input length " << + m_input->length() << "\n"); + if (m_cnt >= m_input->length()) { + // Fd ready for more but we got none. Try to get data, else + // shutdown; + if (!m_provide) { + shutdown(); + return 0; + } + m_provide->newData(); + if (m_input->empty()) { + shutdown(); + return 0; + } else { + // Ready with new buffer, reset use count + m_cnt = 0; + } + LOGDEB2("ExecWriter: provide m_cnt " << m_cnt << + " input length " << m_input->length() << "\n"); + } + int ret = con->send(m_input->c_str() + m_cnt, + m_input->length() - m_cnt); + LOGDEB2("ExecWriter: wrote " << (ret) << " to command\n"); + if (ret <= 0) { + LOGERR("ExecWriter: data: can't write\n"); + return -1; + } + m_cnt += ret; + return ret; } private: + ExecCmd::Internal *m_cmd; const string *m_input; unsigned int m_cnt; // Current offset inside m_input ExecCmdProvide *m_provide; @@ -647,22 +714,22 @@ private: // Netcon callback. Get data from the command output. class ExecReader : public NetconWorker { public: - ExecReader(string *output, ExecCmdAdvise *advise) - : m_output(output), m_advise(advise) - {} - virtual int data(NetconData *con, Netcon::Event reason) - { - char buf[8192]; - int n = con->receive(buf, 8192); - LOGDEB1(("ExecReader: got %d from command\n", n)); - if (n < 0) { - LOGERR(("ExecCmd::doexec: receive failed. errno %d\n", errno)); - } else if (n > 0) { - m_output->append(buf, n); - if (m_advise) - m_advise->newData(n); - } // else n == 0, just return - return n; + ExecReader(string *output, ExecCmdAdvise *advise) + : m_output(output), m_advise(advise) { + } + virtual int data(NetconData *con, Netcon::Event reason) { + char buf[8192]; + int n = con->receive(buf, 8192); + LOGDEB1("ExecReader: got " << (n) << " from command\n"); + if (n < 0) { + LOGERR("ExecCmd::doexec: receive failed. errno " << errno << "\n"); + } else if (n > 0) { + m_output->append(buf, n); + if (m_advise) { + m_advise->newData(n); + } + } // else n == 0, just return + return n; } private: string *m_output; @@ -670,72 +737,74 @@ private: }; -int ExecCmd::doexec(const string &cmd, const vector& args, - const string *input, string *output) +int ExecCmd::doexec(const string& cmd, const vector& args, + const string *input, string *output) { if (startExec(cmd, args, input != 0, output != 0) < 0) { - return -1; + return -1; } // Cleanup in case we return early - ExecCmdRsrc e(this); + ExecCmdRsrc e(this->m); SelectLoop myloop; int ret = 0; if (input || output) { // Setup output - if (output) { - NetconCli *oclicon = dynamic_cast(m_fromcmd.get()); - if (!oclicon) { - LOGERR(("ExecCmd::doexec: no connection from command\n")); - return -1; - } - oclicon->setcallback(RefCntr - (new ExecReader(output, m_advise))); - myloop.addselcon(m_fromcmd, Netcon::NETCONPOLL_READ); - // Give up ownership - m_fromcmd.reset(); - } + if (output) { + NetconCli *oclicon = m->m_fromcmd.get(); + if (!oclicon) { + LOGERR("ExecCmd::doexec: no connection from command\n"); + return -1; + } + oclicon->setcallback(std::shared_ptr + (new ExecReader(output, m->m_advise))); + myloop.addselcon(m->m_fromcmd, Netcon::NETCONPOLL_READ); + // Give up ownership + m->m_fromcmd.reset(); + } // Setup input - if (input) { - NetconCli *iclicon = dynamic_cast(m_tocmd.get()); - if (!iclicon) { - LOGERR(("ExecCmd::doexec: no connection from command\n")); - return -1; - } - iclicon->setcallback(RefCntr - (new ExecWriter(input, m_provide))); - myloop.addselcon(m_tocmd, Netcon::NETCONPOLL_WRITE); - // Give up ownership - m_tocmd.reset(); - } + if (input) { + NetconCli *iclicon = m->m_tocmd.get(); + if (!iclicon) { + LOGERR("ExecCmd::doexec: no connection from command\n"); + return -1; + } + iclicon->setcallback(std::shared_ptr + (new ExecWriter(input, m->m_provide, m))); + myloop.addselcon(m->m_tocmd, Netcon::NETCONPOLL_WRITE); + // Give up ownership + m->m_tocmd.reset(); + } // Do the actual reading/writing/waiting - myloop.setperiodichandler(0, 0, m_timeoutMs); - while ((ret = myloop.doLoop()) > 0) { - LOGDEB(("ExecCmd::doexec: selectloop returned %d\n", ret)); - if (m_advise) - m_advise->newData(0); - if (m_killRequest) { - LOGINFO(("ExecCmd::doexec: cancel request\n")); - break; - } - } - LOGDEB0(("ExecCmd::doexec: selectloop returned %d\n", ret)); + myloop.setperiodichandler(0, 0, m->m_timeoutMs); + while ((ret = myloop.doLoop()) > 0) { + LOGDEB("ExecCmd::doexec: selectloop returned " << (ret) << "\n"); + if (m->m_advise) { + m->m_advise->newData(0); + } + if (m->m_killRequest) { + LOGINFO("ExecCmd::doexec: cancel request\n"); + break; + } + } + LOGDEB0("ExecCmd::doexec: selectloop returned " << (ret) << "\n"); // Check for interrupt request: we won't want to waitpid() - if (m_advise) - m_advise->newData(0); + if (m->m_advise) { + m->m_advise->newData(0); + } // The netcons don't take ownership of the fds: we have to close them - // (have to do it before wait, this may be the signal the child is + // (have to do it before wait, this may be the signal the child is // waiting for exiting). if (input) { - close(m_pipein[1]); - m_pipein[1] = -1; + close(m->m_pipein[1]); + m->m_pipein[1] = -1; } if (output) { - close(m_pipeout[0]); - m_pipeout[0] = -1; + close(m->m_pipeout[0]); + m->m_pipeout[0] = -1; } } @@ -743,38 +812,40 @@ int ExecCmd::doexec(const string &cmd, const vector& args, e.inactivate(); int ret1 = ExecCmd::wait(); - if (ret) - return -1; + if (ret) { + return -1; + } return ret1; } int ExecCmd::send(const string& data) { - NetconCli *con = dynamic_cast(m_tocmd.get()); + NetconCli *con = m->m_tocmd.get(); if (con == 0) { - LOGERR(("ExecCmd::send: outpipe is closed\n")); - return -1; + LOGERR("ExecCmd::send: outpipe is closed\n"); + return -1; } unsigned int nwritten = 0; while (nwritten < data.length()) { - if (m_killRequest) - break; - int n = con->send(data.c_str() + nwritten, data.length() - nwritten); - if (n < 0) { - LOGERR(("ExecCmd::send: send failed\n")); - return -1; - } - nwritten += n; + if (m->m_killRequest) { + break; + } + int n = con->send(data.c_str() + nwritten, data.length() - nwritten); + if (n < 0) { + LOGERR("ExecCmd::send: send failed\n"); + return -1; + } + nwritten += n; } return nwritten; } int ExecCmd::receive(string& data, int cnt) { - NetconCli *con = dynamic_cast(m_fromcmd.get()); + NetconCli *con = m->m_fromcmd.get(); if (con == 0) { - LOGERR(("ExecCmd::receive: inpipe is closed\n")); - return -1; + LOGERR("ExecCmd::receive: inpipe is closed\n"); + return -1; } const int BS = 4096; char buf[BS]; @@ -783,13 +854,13 @@ int ExecCmd::receive(string& data, int cnt) int toread = cnt > 0 ? MIN(cnt - ntot, BS) : BS; int n = con->receive(buf, toread); if (n < 0) { - LOGERR(("ExecCmd::receive: error\n")); + LOGERR("ExecCmd::receive: error\n"); return -1; } else if (n > 0) { ntot += n; data.append(buf, n); } else { - LOGDEB(("ExecCmd::receive: got 0\n")); + LOGDEB("ExecCmd::receive: got 0\n"); break; } } while (cnt > 0 && ntot < cnt); @@ -798,70 +869,132 @@ int ExecCmd::receive(string& data, int cnt) int ExecCmd::getline(string& data) { - NetconCli *con = dynamic_cast(m_fromcmd.get()); + NetconCli *con = m->m_fromcmd.get(); if (con == 0) { - LOGERR(("ExecCmd::receive: inpipe is closed\n")); - return -1; + LOGERR("ExecCmd::receive: inpipe is closed\n"); + return -1; } const int BS = 1024; char buf[BS]; - int n = con->getline(buf, BS); + int timeosecs = m->m_timeoutMs / 1000; + if (timeosecs == 0) { + timeosecs = 1; + } + + // Note that we only go once through here, except in case of + // timeout, which is why I think that the goto is more expressive + // than a loop +again: + int n = con->getline(buf, BS, timeosecs); if (n < 0) { - LOGERR(("ExecCmd::getline: error\n")); + if (con->timedout()) { + LOGDEB0("ExecCmd::getline: select timeout, report and retry\n"); + if (m->m_advise) { + m->m_advise->newData(0); + } + goto again; + } + LOGERR("ExecCmd::getline: error\n"); } else if (n > 0) { - data.append(buf, n); + data.append(buf, n); } else { - LOGDEB(("ExecCmd::getline: got 0\n")); + LOGDEB("ExecCmd::getline: got 0\n"); } return n; } +class GetlineWatchdog : public ExecCmdAdvise { +public: + GetlineWatchdog(int secs) : m_secs(secs), tstart(time(0)) {} + void newData(int cnt) { + if (time(0) - tstart >= m_secs) { + throw std::runtime_error("getline timeout"); + } + } + int m_secs; + time_t tstart; +}; + +int ExecCmd::getline(string& data, int timeosecs) +{ + GetlineWatchdog gwd(timeosecs); + setAdvise(&gwd); + try { + return getline(data); + } catch (...) { + return -1; + } +} + + // Wait for command status and clean up all resources. +// We would like to avoid blocking here too, but there is no simple +// way to do this. The 2 possible approaches would be to: +// - Use signals (alarm), waitpid() is interruptible. but signals and +// threads... This would need a specialized thread, inter-thread comms etc. +// - Use an intermediary process when starting the command. The +// process forks a timer process, and the real command, then calls +// a blocking waitpid on all at the end, and is guaranteed to get +// at least the timer process status, thus yielding a select() +// equivalent. This is bad too, because the timeout is on the whole +// exec, not just the wait +// Just calling waitpid() with WNOHANG with a sleep() between tries +// does not work: the first waitpid() usually comes too early and +// reaps nothing, resulting in almost always one sleep() or more. +// +// So no timeout here. This has not been a problem in practise inside recoll. +// In case of need, using a semi-busy loop with short sleeps +// increasing from a few mS might work without creating too much +// overhead. int ExecCmd::wait() { - ExecCmdRsrc e(this); + ExecCmdRsrc e(this->m); int status = -1; - if (!m_killRequest && m_pid > 0) { - if (waitpid(m_pid, &status, 0) < 0) { - LOGERR(("ExecCmd::waitpid: returned -1 errno %d\n", errno)); - status = -1; - } - LOGDEB(("ExecCmd::wait: got status 0x%x\n", status)); - m_pid = -1; + if (!m->m_killRequest && m->m_pid > 0) { + if (waitpid(m->m_pid, &status, 0) < 0) { + LOGERR("ExecCmd::waitpid: returned -1 errno " << errno << "\n"); + status = -1; + } + LOGDEB("ExecCmd::wait: got status 0x" << (status) << "\n"); + m->m_pid = -1; } - // Let the ExecCmdRsrc cleanup + // Let the ExecCmdRsrc cleanup, it will do the killing/waiting if needed return status; } bool ExecCmd::maybereap(int *status) { - ExecCmdRsrc e(this); + ExecCmdRsrc e(this->m); *status = -1; - if (m_pid <= 0) { - // Already waited for ?? - return true; + if (m->m_pid <= 0) { + // Already waited for ?? + return true; } - pid_t pid = waitpid(m_pid, status, WNOHANG); + pid_t pid = waitpid(m->m_pid, status, WNOHANG); if (pid < 0) { - LOGERR(("ExecCmd::maybereap: returned -1 errno %d\n", errno)); - m_pid = -1; - return true; + LOGERR("ExecCmd::maybereap: returned -1 errno " << errno << "\n"); + m->m_pid = -1; + return true; } else if (pid == 0) { - LOGDEB1(("ExecCmd::maybereap: not exited yet\n")); - e.inactivate(); - return false; + LOGDEB1("ExecCmd::maybereap: not exited yet\n"); + e.inactivate(); + return false; } else { - LOGDEB(("ExecCmd::maybereap: got status 0x%x\n", status)); - m_pid = -1; - return true; + LOGDEB("ExecCmd::maybereap: got status 0x" << (status) << "\n"); + m->m_pid = -1; + return true; } } // Static bool ExecCmd::backtick(const vector cmd, string& out) { + if (cmd.empty()) { + LOGERR("ExecCmd::backtick: empty command\n"); + return false; + } vector::const_iterator it = cmd.begin(); it++; vector args(it, cmd.end()); @@ -879,43 +1012,45 @@ ReExec::ReExec(int argc, char *args[]) void ReExec::init(int argc, char *args[]) { for (int i = 0; i < argc; i++) { - m_argv.push_back(args[i]); + m_argv.push_back(args[i]); } m_cfd = open(".", 0); char *cd = getcwd(0, 0); - if (cd) - m_curdir = cd; + if (cd) { + m_curdir = cd; + } free(cd); } void ReExec::insertArgs(const vector& args, int idx) { vector::iterator it, cit; - unsigned int cmpoffset = (unsigned int)-1; + unsigned int cmpoffset = (unsigned int) - 1; if (idx == -1 || string::size_type(idx) >= m_argv.size()) { - it = m_argv.end(); - if (m_argv.size() >= args.size()) { - cmpoffset = m_argv.size() - args.size(); - } + it = m_argv.end(); + if (m_argv.size() >= args.size()) { + cmpoffset = m_argv.size() - args.size(); + } } else { - it = m_argv.begin() + idx; - if (idx + args.size() <= m_argv.size()) { - cmpoffset = idx; - } + it = m_argv.begin() + idx; + if (idx + args.size() <= m_argv.size()) { + cmpoffset = idx; + } } // Check that the option is not already there - if (cmpoffset != (unsigned int)-1) { - bool allsame = true; - for (unsigned int i = 0; i < args.size(); i++) { - if (m_argv[cmpoffset + i] != args[i]) { - allsame = false; - break; - } - } - if (allsame) - return; + if (cmpoffset != (unsigned int) - 1) { + bool allsame = true; + for (unsigned int i = 0; i < args.size(); i++) { + if (m_argv[cmpoffset + i] != args[i]) { + allsame = false; + break; + } + } + if (allsame) { + return; + } } m_argv.insert(it, args.begin(), args.end()); @@ -923,10 +1058,11 @@ void ReExec::insertArgs(const vector& args, int idx) void ReExec::removeArg(const string& arg) { - for (vector::iterator it = m_argv.begin(); - it != m_argv.end(); it++) { - if (*it == arg) - it = m_argv.erase(it); + for (vector::iterator it = m_argv.begin(); + it != m_argv.end(); it++) { + if (*it == arg) { + it = m_argv.erase(it); + } } } @@ -936,30 +1072,30 @@ void ReExec::reexec() #if 0 char *cwd; - cwd = getcwd(0,0); + cwd = getcwd(0, 0); FILE *fp = stdout; //fopen("/tmp/exectrace", "w"); if (fp) { - fprintf(fp, "reexec: pwd: [%s] args: ", cwd?cwd:"getcwd failed"); - for (vector::const_iterator it = m_argv.begin(); - it != m_argv.end(); it++) { - fprintf(fp, "[%s] ", it->c_str()); - } - fprintf(fp, "\n"); + fprintf(fp, "reexec: pwd: [%s] args: ", cwd ? cwd : "getcwd failed"); + for (vector::const_iterator it = m_argv.begin(); + it != m_argv.end(); it++) { + fprintf(fp, "[%s] ", it->c_str()); + } + fprintf(fp, "\n"); } #endif // Execute the atexit funcs while (!m_atexitfuncs.empty()) { - (m_atexitfuncs.top())(); - m_atexitfuncs.pop(); + (m_atexitfuncs.top())(); + m_atexitfuncs.pop(); } // Try to get back to the initial working directory if (m_cfd < 0 || fchdir(m_cfd) < 0) { - LOGINFO(("ReExec::reexec: fchdir failed, trying chdir\n")); - if (!m_curdir.empty() && chdir(m_curdir.c_str())) { - LOGERR(("ReExec::reexec: chdir failed\n")); - } + LOGINFO("ReExec::reexec: fchdir failed, trying chdir\n"); + if (!m_curdir.empty() && chdir(m_curdir.c_str())) { + LOGERR("ReExec::reexec: chdir failed\n"); + } } // Close all descriptors except 0,1,2 @@ -968,17 +1104,17 @@ void ReExec::reexec() // Allocate arg vector (1 more for final 0) typedef const char *Ccharp; Ccharp *argv; - argv = (Ccharp *)malloc((m_argv.size()+1) * sizeof(char *)); + argv = (Ccharp *)malloc((m_argv.size() + 1) * sizeof(char *)); if (argv == 0) { - LOGERR(("ExecCmd::doexec: malloc() failed. errno %d\n", errno)); - return; + LOGERR("ExecCmd::doexec: malloc() failed. errno " << errno << "\n"); + return; } - + // Fill up argv int i = 0; vector::const_iterator it; for (it = m_argv.begin(); it != m_argv.end(); it++) { - argv[i++] = it->c_str(); + argv[i++] = it->c_str(); } argv[i] = 0; execvp(m_argv[0].c_str(), (char *const*)argv); @@ -987,170 +1123,371 @@ void ReExec::reexec() //////////////////////////////////////////////////////////////////// #else // TEST + #include #include #include #include +#include #include #include +#include #include + +#include "log.h" + +#include "execmd.h" +#ifdef BUILDING_RECOLL +#include "smallut.h" +#include "cancelcheck.h" +#endif + using namespace std; -#include "debuglog.h" -#include "cancelcheck.h" -#include "execmd.h" +#ifdef BUILDING_RECOLL +// Testing the rclexecm protocol outside of recoll. Here we use the +// rcldoc.py filter, you can try with rclaudio too, adjust the file arg +// accordingly +bool exercise_mhexecm(const string& cmdstr, const string& mimetype, + vector& files) +{ + ExecCmd cmd; -static int op_flags; -#define OPT_MOINS 0x1 -#define OPT_b 0x4 -#define OPT_w 0x8 -#define OPT_c 0x10 -#define OPT_r 0x20 + vector myparams; -const char *data = "Une ligne de donnees\n"; -class MEAdv : public ExecCmdAdvise { -public: - ExecCmd *cmd; - void newData(int cnt) { - if (op_flags & OPT_c) { - static int callcnt; - if (callcnt++ == 3) { - throw CancelExcept(); - } - } - cerr << "newData(" << cnt << ")" << endl; - // CancelCheck::instance().setCancel(); - // CancelCheck::instance().checkCancel(); - // cmd->setCancel(); + if (cmd.startExec(cmdstr, myparams, 1, 1) < 0) { + cerr << "startExec " << cmdstr << " failed. Missing command?\n"; + return false; } -}; -class MEPv : public ExecCmdProvide { -public: - FILE *m_fp; - string *m_input; - MEPv(string *i) - : m_input(i) - { - m_fp = fopen("/etc/group", "r"); - } - ~MEPv() { - if (m_fp) - fclose(m_fp); - } - void newData() { - char line[1024]; - if (m_fp && fgets(line, 1024, m_fp)) { - m_input->assign((const char *)line); - } else { - m_input->erase(); - } - } -}; + for (vector::const_iterator it = files.begin(); + it != files.end(); it++) { + // Build request message + ostringstream obuf; + obuf << "Filename: " << (*it).length() << "\n" << (*it); + obuf << "Mimetype: " << mimetype.length() << "\n" << mimetype; + // Bogus parameter should be skipped by filter + obuf << "BogusParam: " << string("bogus").length() << "\n" << "bogus"; + obuf << "\n"; + cerr << "SENDING: [" << obuf.str() << "]\n"; + // Send it + if (cmd.send(obuf.str()) < 0) { + // The real code calls zapchild here, but we don't need it as + // this will be handled by ~ExecCmd + //cmd.zapChild(); + cerr << "send error\n"; + return false; + } + // Read answer + for (int loop = 0;; loop++) { + string name, data; + + // Code from mh_execm.cpp: readDataElement + string ibuf; + // Read name and length + if (cmd.getline(ibuf) <= 0) { + cerr << "getline error\n"; + return false; + } + // Empty line (end of message) + if (!ibuf.compare("\n")) { + cerr << "Got empty line\n"; + name.clear(); + break; + } + + // Filters will sometimes abort before entering the real + // protocol, ie if a module can't be loaded. Check the + // special filter error first word: + if (ibuf.find("RECFILTERROR ") == 0) { + cerr << "Got RECFILTERROR\n"; + return false; + } + + // We're expecting something like Name: len\n + vector tokens; + stringToTokens(ibuf, tokens); + if (tokens.size() != 2) { + cerr << "bad line in filter output: [" << ibuf << "]\n"; + return false; + } + vector::iterator it = tokens.begin(); + name = *it++; + string& slen = *it; + int len; + if (sscanf(slen.c_str(), "%d", &len) != 1) { + cerr << "bad line in filter output (no len): [" << + ibuf << "]\n"; + return false; + } + // Read element data + data.erase(); + if (len > 0 && cmd.receive(data, len) != len) { + cerr << "MHExecMultiple: expected " << len << + " bytes of data, got " << data.length() << endl; + return false; + } + + // Empty element: end of message + if (name.empty()) { + break; + } + cerr << "Got name: [" << name << "] data [" << data << "]\n"; + } + } + return true; +} +#endif static char *thisprog; static char usage [] = -"trexecmd [-c|-r] cmd [arg1 arg2 ...]\n" -" -c : test cancellation (ie: trexecmd -c sleep 1000)\n" -" -r : test reexec\n" -"trexecmd -w cmd : do the which thing\n" -; + "trexecmd [-c -r -i -o] cmd [arg1 arg2 ...]\n" + " -c : test cancellation (ie: trexecmd -c sleep 1000)\n" + " -r : run reexec. Must be separate option.\n" + " -i : command takes input\n" + " -o : command produces output\n" + " If -i is set, we send /etc/group contents to whatever command is run\n" + " If -o is set, we print whatever comes out\n" + "trexecmd -m [file ...]: test execm:\n" + " should be the path to an execm filter\n" + " the type of the file parameters\n" + "trexecmd -w cmd : do the 'which' thing\n" + "trexecmd -l cmd test getline\n" + ; + static void Usage(void) { fprintf(stderr, "%s: usage:\n%s", thisprog, usage); exit(1); } -ReExec reexec; +static int op_flags; +#define OPT_MOINS 0x1 +#define OPT_i 0x4 +#define OPT_w 0x8 +#define OPT_c 0x10 +#define OPT_r 0x20 +#define OPT_m 0x40 +#define OPT_o 0x80 +#define OPT_l 0x100 +// Data sink for data coming out of the command. We also use it to set +// a cancellation after a moment. +class MEAdv : public ExecCmdAdvise { +public: + void newData(int cnt) { + if (op_flags & OPT_c) { +#ifdef BUILDING_RECOLL + static int callcnt; + if (callcnt++ == 10) { + // Just sets the cancellation flag + CancelCheck::instance().setCancel(); + // Would be called from somewhere else and throws an + // exception. We call it here for simplicity + CancelCheck::instance().checkCancel(); + } +#endif + } + cerr << "newData(" << cnt << ")" << endl; + } +}; + +// Data provider, used if the -i flag is set +class MEPv : public ExecCmdProvide { +public: + FILE *m_fp; + string *m_input; + MEPv(string *i) + : m_input(i) { + m_fp = fopen("/etc/group", "r"); + } + ~MEPv() { + if (m_fp) { + fclose(m_fp); + } + } + void newData() { + char line[1024]; + if (m_fp && fgets(line, 1024, m_fp)) { + m_input->assign((const char *)line); + } else { + m_input->erase(); + } + } +}; + + + +ReExec reexec; int main(int argc, char *argv[]) { reexec.init(argc, argv); if (0) { - vector newargs; - newargs.push_back("newarg"); - newargs.push_back("newarg1"); - newargs.push_back("newarg2"); - newargs.push_back("newarg3"); - newargs.push_back("newarg4"); - reexec.insertArgs(newargs, 2); + // Disabled: For testing reexec arg handling + vector newargs; + newargs.push_back("newarg"); + newargs.push_back("newarg1"); + newargs.push_back("newarg2"); + newargs.push_back("newarg3"); + newargs.push_back("newarg4"); + reexec.insertArgs(newargs, 2); } thisprog = argv[0]; - argc--; argv++; + argc--; + argv++; while (argc > 0 && **argv == '-') { - (*argv)++; - if (!(**argv)) - /* Cas du "adb - core" */ - Usage(); - while (**argv) - switch (*(*argv)++) { - case 'c': op_flags |= OPT_c; break; - case 'r': op_flags |= OPT_r; break; - case 'w': op_flags |= OPT_w; break; - default: Usage(); break; - } - b1: argc--; argv++; + (*argv)++; + if (!(**argv)) + /* Cas du "adb - core" */ + { + Usage(); + } + while (**argv) + switch (*(*argv)++) { + case 'c': + op_flags |= OPT_c; + break; + case 'r': + op_flags |= OPT_r; + break; + case 'w': + op_flags |= OPT_w; + break; +#ifdef BUILDING_RECOLL + case 'm': + op_flags |= OPT_m; + break; +#endif + case 'i': + op_flags |= OPT_i; + break; + case 'l': + op_flags |= OPT_l; + break; + case 'o': + op_flags |= OPT_o; + break; + default: + Usage(); + break; + } + argc--; + argv++; } - if (argc < 1) - Usage(); + if (argc < 1) { + Usage(); + } - string cmd = *argv++; argc--; + string arg1 = *argv++; + argc--; vector l; while (argc > 0) { - l.push_back(*argv++); argc--; + l.push_back(*argv++); + argc--; } + +#ifdef BUILDING_RECOLL DebugLog::getdbl()->setloglevel(DEBDEB1); DebugLog::setfilename("stderr"); +#endif signal(SIGPIPE, SIG_IGN); if (op_flags & OPT_r) { - chdir("/"); + // Test reexec. Normally only once, next time we fall through + // because we remove the -r option (only works if it was isolated, not like -rc + chdir("/"); argv[0] = strdup(""); - sleep(1); + sleep(1); + cerr << "Calling reexec\n"; + // We remove the -r arg from list, otherwise we are going to + // loop (which you can try by commenting out the following + // line) + reexec.removeArg("-r"); reexec.reexec(); } if (op_flags & OPT_w) { - string path; - if (ExecCmd::which(cmd, path)) { - cout << path << endl; - exit(0); - } - exit(1); + // Test "which" method + string path; + if (ExecCmd::which(arg1, path)) { + cout << path << endl; + return 0; + } + return 1; +#ifdef BUILDING_RECOLL + } else if (op_flags & OPT_m) { + if (l.size() < 2) { + Usage(); + } + string mimetype = l[0]; + l.erase(l.begin()); + return exercise_mhexecm(arg1, mimetype, l) ? 0 : 1; +#endif + } else if (op_flags & OPT_l) { + ExecCmd mexec; + + if (mexec.startExec(arg1, l, false, true) < 0) { + cerr << "Startexec failed\n"; + exit(1); + } + string output; + int ret = mexec.getline(output, 2); + cerr << "Got ret " << ret << " output " << output << endl; + cerr << "Waiting\n"; + int status = mexec.wait(); + cerr << "Got status " << status << endl; + exit(status); + } else { + // Default: execute command line arguments + ExecCmd mexec; + + // Set callback to be called whenever there is new data + // available and at a periodic interval, to check for + // cancellation + MEAdv adv; + mexec.setAdvise(&adv); + mexec.setTimeout(5); + + // Stderr output goes there + mexec.setStderr("/tmp/trexecStderr"); + + // A few environment variables. Check with trexecmd env + mexec.putenv("TESTVARIABLE1=TESTVALUE1"); + mexec.putenv("TESTVARIABLE2=TESTVALUE2"); + mexec.putenv("TESTVARIABLE3=TESTVALUE3"); + + string input, output; + MEPv pv(&input); + + string *ip = 0; + if (op_flags & OPT_i) { + ip = &input; + mexec.setProvide(&pv); + } + string *op = 0; + if (op_flags & OPT_o) { + op = &output; + } + + int status = -1; + try { + status = mexec.doexec(arg1, l, ip, op); + } catch (...) { + cerr << "CANCELLED" << endl; + } + + fprintf(stderr, "Status: 0x%x\n", status); + if (op_flags & OPT_o) { + cout << output; + } + exit(status >> 8); } - ExecCmd mexec; - MEAdv adv; - adv.cmd = &mexec; - mexec.setAdvise(&adv); - mexec.setTimeout(5); - mexec.setStderr("/tmp/trexecStderr"); - mexec.putenv("TESTVARIABLE1=TESTVALUE1"); - mexec.putenv("TESTVARIABLE2=TESTVALUE2"); - mexec.putenv("TESTVARIABLE3=TESTVALUE3"); - - string input, output; - // input = data; - string *ip = 0; - ip = &input; - - MEPv pv(&input); - mexec.setProvide(&pv); - - int status = -1; - try { - status = mexec.doexec(cmd, l, ip, &output); - } catch (CancelExcept) { - cerr << "CANCELLED" << endl; - } - - fprintf(stderr, "Status: 0x%x\n", status); - cout << output; - exit (status >> 8); } #endif // TEST + diff --git a/src/utils/execmd.h b/src/utils/execmd.h index 9b42956a..93ee8607 100644 --- a/src/utils/execmd.h +++ b/src/utils/execmd.h @@ -17,35 +17,31 @@ #ifndef _EXECMD_H_INCLUDED_ #define _EXECMD_H_INCLUDED_ -#include - #include #include #include -#include "netcon.h" - -/** - * Callback function object to advise of new data arrival, or just periodic - * heartbeat if cnt is 0. +/** + * Callback function object to advise of new data arrival, or just periodic + * heartbeat if cnt is 0. * * To interrupt the command, the code using ExecCmd should either - * raise an exception inside newData() (and catch it in doexec's caller), or - * call ExecCmd::setKill() - * + * raise an exception inside newData() (and catch it in doexec's caller), or + * call ExecCmd::setKill() + * */ class ExecCmdAdvise { - public: +public: virtual ~ExecCmdAdvise() {} virtual void newData(int cnt) = 0; }; -/** +/** * Callback function object to get more input data. Data has to be provided - * in the initial input string, set it to empty to signify eof. + * into the initial input string, set it to empty to signify eof. */ class ExecCmdProvide { - public: +public: virtual ~ExecCmdProvide() {} virtual void newData() = 0; }; @@ -55,121 +51,158 @@ class ExecCmdProvide { * asynchronous io as appropriate for things to work). * * Input to the command can be provided either once in a parameter to doexec - * or provided in chunks by setting a callback which will be called to + * or provided in chunks by setting a callback which will be called to * request new data. In this case, the 'input' parameter to doexec may be * empty (but not null) * * Output from the command is normally returned in a single string, but a * callback can be set to be called whenever new data arrives, in which case * it is permissible to consume the data and erase the string. - * + * * Note that SIGPIPE should be ignored and SIGCLD blocked when calling doexec, * else things might fail randomly. (This is not done inside the class because * of concerns with multithreaded programs). * */ class ExecCmd { - public: +public: // Use vfork instead of fork. Our vfork usage is multithread-compatible as // far as I can see, but just in case... static void useVfork(bool on); - /** + /** * Add/replace environment variable before executing command. This must * be called before doexec() to have an effect (possibly multiple * times for several variables). * @param envassign an environment assignment string ("name=value") */ - void putenv(const std::string &envassign); - void putenv(const std::string &name, const std::string& value); + void putenv(const std::string& envassign); + void putenv(const std::string& name, const std::string& value); - /** + /** * Try to set a limit on child process vm size. This will use * setrlimit() and RLIMIT_AS/VMEM if available. Parameter is in - * units of 2**10. Must be called before starting the command, default + * units of 2**10. Must be called before starting the command, default * is inherit from parent. */ void setrlimit_as(int mbytes); - /** + /** * Set function objects to call whenever new data is available or on - * select timeout / whenever new data is needed to send. Must be called - * before doexec() + * select timeout. The data itself is stored in the output string. + * Must be set before calling doexec. */ - void setAdvise(ExecCmdAdvise *adv) {m_advise = adv;} - void setProvide(ExecCmdProvide *p) {m_provide = p;} + void setAdvise(ExecCmdAdvise *adv); + /* + * Set function object to call whenever new data is needed. The + * data should be stored in the input string. Must be set before + * calling doexec() + */ + void setProvide(ExecCmdProvide *p); /** - * Set select timeout in milliseconds. The default is 1 S. + * Set select timeout in milliseconds. The default is 1 S. * This is NOT a time after which an error will occur, but the period of - * the calls to the cancellation check routine. + * the calls to the advise routine (which normally checks for cancellation). */ - void setTimeout(int mS) {if (mS > 30) m_timeoutMs = mS;} + void setTimeout(int mS); - /** - * Set destination for stderr data. The default is to let it alone (will + /** + * Set destination for stderr data. The default is to let it alone (will * usually go to the terminal or to wherever the desktop messages go). * There is currently no option to put stderr data into a program variable * If the parameter can't be opened for writing, the command's * stderr will be closed. */ - void setStderr(const std::string &stderrFile) {m_stderrFile = stderrFile;} + void setStderr(const std::string& stderrFile); /** - * Execute command. + * Execute command. * - * Both input and output can be specified, and asynchronous + * Both input and output can be specified, and asynchronous * io (select-based) is used to prevent blocking. This will not * work if input and output need to be synchronized (ie: Q/A), but * works ok for filtering. - * The function is exception-safe. In case an exception occurs in the + * The function is exception-safe. In case an exception occurs in the * advise callback, fds and pids will be cleaned-up properly. * - * @param cmd the program to execute. This must be an absolute file name + * @param cmd the program to execute. This must be an absolute file name * or exist in the PATH. * @param args the argument vector (NOT including argv[0]). * @param input Input to send TO the command. * @param output Output FROM the command. - * @return the exec ouput status (0 if ok), or -1 + * @return the exec output status (0 if ok), or -1 */ - int doexec(const std::string &cmd, const std::vector& args, - const std::string *input = 0, - std::string *output = 0); + int doexec(const std::string& cmd, const std::vector& args, + const std::string *input = 0, + std::string *output = 0); + + /** Same as doexec but cmd and args in one vector */ + int doexec1(const std::vector& args, + const std::string *input = 0, + std::string *output = 0) { + if (args.empty()) { + return -1; + } + return doexec(args[0], + std::vector(args.begin() + 1, args.end()), + input, output); + } /* - * The next four methods can be used when a Q/A dialog needs to be + * The next four methods can be used when a Q/A dialog needs to be * performed with the command */ - int startExec(const std::string &cmd, const std::vector& args, - bool has_input, bool has_output); + int startExec(const std::string& cmd, const std::vector& args, + bool has_input, bool has_output); int send(const std::string& data); int receive(std::string& data, int cnt = -1); + + /** Read line. Will call back periodically to check for cancellation */ int getline(std::string& data); + + /** Read line. Timeout after timeosecs seconds */ + int getline(std::string& data, int timeosecs); + int wait(); - /** Wait with WNOHANG set. - @return true if process exited, false else. - @param O: status, the wait(2) call's status value */ + /** Wait with WNOHANG set. + @return true if process exited, false else. + @param O: status, the wait(2) call's status value */ bool maybereap(int *status); - pid_t getChildPid() {return m_pid;} - - /** - * Cancel/kill command. This can be called from another thread or - * from the advise callback, which could also raise an exception to - * accomplish the same thing - */ - void setKill() {m_killRequest = true;} + pid_t getChildPid(); /** - * Get rid of current process (become ready for start). + * Cancel/kill command. This can be called from another thread or + * from the advise callback, which could also raise an exception + * to accomplish the same thing. In the owner thread, any I/O loop + * will exit at the next iteration, and the process will be waited for. */ - void zapChild() {setKill(); (void)wait();} + void setKill(); - ExecCmd() - : m_advise(0), m_provide(0), m_timeoutMs(1000), m_rlimit_as_mbytes(0) - { - reset(); - } + /** + * Get rid of current process (become ready for start). This will signal + * politely the process to stop, wait a moment, then terminate it. This + * is a blocking call. + */ + void zapChild(); + + /** + * Request process termination (SIGTERM or equivalent). This returns + * immediately + */ + bool requestChildExit(); + + enum ExFlags {EXF_NONE, + // Only does anything on windows. Used when starting + // a viewer. The default is to hide the window, + // because it avoids windows appearing and + // disappearing when executing stuff for previewing + EXF_SHOWWINDOW = 1, + // Windows only: show maximized + EXF_MAXIMIZED = 2, + }; + ExecCmd(int flags = 0); ~ExecCmd(); /** @@ -190,68 +223,40 @@ class ExecCmd { */ static bool backtick(const std::vector cmd, std::string& out); - friend class ExecCmdRsrc; - private: - static bool o_useVfork; - - std::vector m_env; - ExecCmdAdvise *m_advise; - ExecCmdProvide *m_provide; - bool m_killRequest; - int m_timeoutMs; - int m_rlimit_as_mbytes; - std::string m_stderrFile; - // Pipe for data going to the command - int m_pipein[2]; - NetconP m_tocmd; - // Pipe for data coming out - int m_pipeout[2]; - NetconP m_fromcmd; - // Subprocess id - pid_t m_pid; - // Saved sigmask - sigset_t m_blkcld; - - // Reset internal state indicators. Any resources should have been - // previously freed - void reset() { - m_killRequest = false; - m_pipein[0] = m_pipein[1] = m_pipeout[0] = m_pipeout[1] = -1; - m_pid = -1; - sigemptyset(&m_blkcld); - } - // Child process code - inline void dochild(const std::string &cmd, const char **argv, - const char **envv, bool has_input, bool has_output); - /* Copyconst and assignment private and forbidden */ - ExecCmd(const ExecCmd &) {} - ExecCmd& operator=(const ExecCmd &) {return *this;}; + class Internal; +private: + Internal *m; + /* Copyconst and assignment are private and forbidden */ + ExecCmd(const ExecCmd&) {} + ExecCmd& operator=(const ExecCmd&) { + return *this; + }; }; -/** - * Rexecute self process with the same arguments. +/** + * Rexecute self process with the same arguments. * * Note that there are some limitations: - * - argv[0] has to be valid: an executable name which will be found in - * the path when exec is called in the initial working directory. This is + * - argv[0] has to be valid: an executable name which will be found in + * the path when exec is called in the initial working directory. This is * by no means guaranteed. The shells do this, but argv[0] could be an * arbitrary string. * - The initial working directory must be found and remain valid. * - We don't try to do anything with fd 0,1,2. If they were changed by the - * program, their initial meaning won't be the same as at the moment of the + * program, their initial meaning won't be the same as at the moment of the * initial invocation. - * - We don't restore the signals. Signals set to be blocked + * - We don't restore the signals. Signals set to be blocked * or ignored by the program will remain ignored even if this was not their * initial state. * - The environment is also not restored. * - Others system aspects ? - * - Other program state: application-dependant. Any external cleanup - * (temp files etc.) must be performed by the application. ReExec() - * duplicates the atexit() function to make this easier, but the + * - Other program state: application-dependant. Any external cleanup + * (temp files etc.) must be performed by the application. ReExec() + * duplicates the atexit() function to make this easier, but the * ReExec().atexit() calls must be done explicitely, this is not automatic - * - * In short, this is usable in reasonably controlled situations and if there + * + * In short, this is usable in reasonably controlled situations and if there * are no security issues involved, but this does not perform miracles. */ class ReExec { @@ -259,13 +264,14 @@ public: ReExec() {} ReExec(int argc, char *argv[]); void init(int argc, char *argv[]); - int atexit(void (*function)(void)) - { - m_atexitfuncs.push(function); - return 0; + int atexit(void (*function)(void)) { + m_atexitfuncs.push(function); + return 0; } void reexec(); - const std::string& getreason() {return m_reason;} + const std::string& getreason() { + return m_reason; + } // Insert new args into the initial argv. idx designates the place // before which the new args are inserted (the default of 1 // inserts after argv[0] which would probably be an appropriate diff --git a/src/utils/fileudi.cpp b/src/utils/fileudi.cpp index 77799747..bf4b3d7e 100644 --- a/src/utils/fileudi.cpp +++ b/src/utils/fileudi.cpp @@ -15,6 +15,7 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef TEST_FILEUDI +#include "autoconfig.h" #include #include diff --git a/src/utils/fstreewalk.cpp b/src/utils/fstreewalk.cpp index f8853401..532f3ae6 100644 --- a/src/utils/fstreewalk.cpp +++ b/src/utils/fstreewalk.cpp @@ -14,17 +14,16 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef HAVE_CONFIG_H + #include "autoconfig.h" -#endif #ifndef TEST_FSTREEWALK #include #include -#include #include #include +#include "safesysstat.h" #include #include @@ -34,19 +33,20 @@ #include #include "cstr.h" -#include "debuglog.h" +#include "log.h" #include "pathut.h" #include "fstreewalk.h" -#ifndef NO_NAMESPACES using namespace std; -#endif /* NO_NAMESPACES */ bool FsTreeWalker::o_useFnmPathname = true; const int FsTreeWalker::FtwTravMask = FtwTravNatural| FtwTravBreadth|FtwTravFilesThenDirs|FtwTravBreadthThenDepth; +#ifndef _WIN32 +// dev/ino means nothing on Windows. It seems that FileId could replace it +// but we only use this for cycle detection which we just disable. class DirId { public: dev_t dev; @@ -57,6 +57,7 @@ public: return dev < r.dev || (dev == r.dev && ino < r.ino); } }; +#endif class FsTreeWalker::Internal { public: @@ -75,7 +76,9 @@ public: // of directory paths to be processed, and we do not recurse. deque dirs; int errors; +#ifndef _WIN32 set donedirs; +#endif void logsyserr(const char *call, const string ¶m) { errors++; @@ -222,7 +225,7 @@ FsTreeWalker::Status FsTreeWalker::walk(const string& _top, data->basedepth = slashcount(top); // Only used for breadthxx struct stat st; // We always follow symlinks at this point. Makes more sense. - if (stat(top.c_str(), &st) == -1) { + if (path_fileprops(top, &st) == -1) { // Note that we do not return an error if the stat call // fails. A temp file may have gone away. data->logsyserr("stat", top); @@ -284,7 +287,7 @@ FsTreeWalker::Status FsTreeWalker::walk(const string& _top, // If changing parent directory, advise our user. if (!nfather.empty()) { - if (stat(nfather.c_str(), &st) == -1) { + if (path_fileprops(nfather, &st) == -1) { data->logsyserr("stat", nfather); return errno == ENOENT ? FtwOk : FtwError; } @@ -294,7 +297,7 @@ FsTreeWalker::Status FsTreeWalker::walk(const string& _top, } } - if (stat(dir.c_str(), &st) == -1) { + if (path_fileprops(dir, &st) == -1) { data->logsyserr("stat", dir); return errno == ENOENT ? FtwOk : FtwError; } @@ -332,7 +335,7 @@ FsTreeWalker::Status FsTreeWalker::iwalk(const string &top, int curdepth = slashcount(top) - data->basedepth; if (data->maxdepth >= 0 && curdepth >= data->maxdepth) { - LOGDEB1(("FsTreeWalker::iwalk: Maxdepth reached: [%s]\n", top.c_str())); + LOGDEB1("FsTreeWalker::iwalk: Maxdepth reached: [" << (top) << "]\n" ); return status; } @@ -344,16 +347,17 @@ FsTreeWalker::Status FsTreeWalker::iwalk(const string &top, // no point in entering again. // For now, we'll ignore the "other kind of cycle" part and only monitor // this is FtwFollow is set +#ifndef _WIN32 if (data->options & FtwFollow) { DirId dirid(stp->st_dev, stp->st_ino); if (data->donedirs.find(dirid) != data->donedirs.end()) { - LOGINFO(("Not processing [%s] (already seen as other path)\n", - top.c_str())); + LOGINFO("Not processing [" << (top) << "] (already seen as other path)\n" ); return status; } data->donedirs.insert(dirid); } - +#endif + DIR *d = opendir(top.c_str()); if (d == 0) { data->logsyserr("opendir", top); @@ -361,6 +365,10 @@ FsTreeWalker::Status FsTreeWalker::iwalk(const string &top, case EPERM: case EACCES: case ENOENT: +#ifdef _WIN32 + // We get this quite a lot, don't know why. To be checked. + case EINVAL: +#endif goto out; default: status = FtwError; @@ -386,12 +394,25 @@ FsTreeWalker::Status FsTreeWalker::iwalk(const string &top, } fn = path_cat(top, ent->d_name); - int statret = (data->options & FtwFollow) ? stat(fn.c_str(), &st) : - lstat(fn.c_str(), &st); +#ifdef _WIN32 + // readdir gets the useful attrs, no inode indirection on windows, + // spare the path_fileprops() call, but make sure we mimick it. + memset(&st, 0, sizeof(st)); + st.st_mtime = ent->d_mtime; + st.st_size = ent->d_size; + st.st_mode = ent->d_mode; + // ctime is really creation time on Windows. Just use mtime + // for all. We only use ctime on Unix to catch xattr changes + // anyway. + st.st_ctime = st.st_mtime; +#else + int statret = path_fileprops(fn.c_str(), &st, data->options&FtwFollow); if (statret == -1) { data->logsyserr("stat", fn); continue; } +#endif + if (!data->skippedPaths.empty()) { // We do not check the ancestors. This means that you can have // a topdirs member under a skippedPath, to index a portion of @@ -612,3 +633,4 @@ int main(int argc, const char **argv) } #endif // TEST_FSTREEWALK + diff --git a/src/utils/fstreewalk.h b/src/utils/fstreewalk.h index 27a221ef..9f0ee24a 100644 --- a/src/utils/fstreewalk.h +++ b/src/utils/fstreewalk.h @@ -120,6 +120,7 @@ class FsTreeWalker { class FsTreeWalkerCB { public: virtual ~FsTreeWalkerCB() {} + // Only st_mtime, st_ctime, st_size, st_mode (filetype bits: dir/reg/lnk), virtual FsTreeWalker::Status processone(const string &, const struct stat *, FsTreeWalker::CbFlag) = 0; diff --git a/src/utils/hldata.cpp b/src/utils/hldata.cpp new file mode 100644 index 00000000..44fcef94 --- /dev/null +++ b/src/utils/hldata.cpp @@ -0,0 +1,78 @@ +/* Copyright (C) 2016 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include "autoconfig.h" + +#include + +#include "hldata.h" + +using std::string; +using std::map; + +void HighlightData::toString(string& out) +{ + out.append("\nUser terms (orthograph): "); + for (std::set::const_iterator it = uterms.begin(); + it != uterms.end(); it++) { + out.append(" [").append(*it).append("]"); + } + out.append("\nUser terms to Query terms:"); + for (map::const_iterator it = terms.begin(); + it != terms.end(); it++) { + out.append("[").append(it->first).append("]->["); + out.append(it->second).append("] "); + } + out.append("\nGroups: "); + char cbuf[200]; + sprintf(cbuf, "Groups size %d grpsugidx size %d ugroups size %d", + int(groups.size()), int(grpsugidx.size()), int(ugroups.size())); + out.append(cbuf); + + size_t ugidx = (size_t) - 1; + for (unsigned int i = 0; i < groups.size(); i++) { + if (ugidx != grpsugidx[i]) { + ugidx = grpsugidx[i]; + out.append("\n("); + for (unsigned int j = 0; j < ugroups[ugidx].size(); j++) { + out.append("[").append(ugroups[ugidx][j]).append("] "); + } + out.append(") ->"); + } + out.append(" {"); + for (unsigned int j = 0; j < groups[i].size(); j++) { + out.append("[").append(groups[i][j]).append("]"); + } + sprintf(cbuf, "%d", slacks[i]); + out.append("}").append(cbuf); + } + out.append("\n"); +} + +void HighlightData::append(const HighlightData& hl) +{ + uterms.insert(hl.uterms.begin(), hl.uterms.end()); + terms.insert(hl.terms.begin(), hl.terms.end()); + size_t ugsz0 = ugroups.size(); + ugroups.insert(ugroups.end(), hl.ugroups.begin(), hl.ugroups.end()); + + groups.insert(groups.end(), hl.groups.begin(), hl.groups.end()); + slacks.insert(slacks.end(), hl.slacks.begin(), hl.slacks.end()); + for (std::vector::const_iterator it = hl.grpsugidx.begin(); + it != hl.grpsugidx.end(); it++) { + grpsugidx.push_back(*it + ugsz0); + } +} diff --git a/src/utils/hldata.h b/src/utils/hldata.h index 94c6ca27..93766d8a 100644 --- a/src/utils/hldata.h +++ b/src/utils/hldata.h @@ -4,6 +4,7 @@ #include #include #include +#include /** Store data about user search terms and their expansions. This is used * mostly for highlighting result text and walking the matches, generating @@ -46,7 +47,7 @@ struct HighlightData { * user term or group may generate many processed/expanded terms * or groups, this is how we relate an expansion to its source. */ - std::vector grpsugidx; + std::vector grpsugidx; void clear() { diff --git a/src/utils/idfile.cpp b/src/utils/idfile.cpp index 8be93f00..291b2c44 100644 --- a/src/utils/idfile.cpp +++ b/src/utils/idfile.cpp @@ -15,7 +15,8 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef TEST_IDFILE -#include // for access(2) +#include "autoconfig.h" + #include #include #include @@ -24,11 +25,9 @@ #include #include "idfile.h" -#include "debuglog.h" +#include "log.h" -#ifndef NO_NAMESPACES using namespace std; -#endif /* NO_NAMESPACES */ // Bogus code to avoid bogus valgrind mt warnings about the // initialization of treat_mbox_... which I can't even remember the @@ -77,7 +76,7 @@ static string idFileInternal(istream& input, const char *fn) input.getline(cline, LL-1); if (input.fail()) { if (input.bad()) { - LOGERR(("idfile: error while reading [%s]\n", fn)); + LOGERR("idfile: error while reading [" << (fn) << "]\n" ); return string(); } // Must be eof ? @@ -85,11 +84,11 @@ static string idFileInternal(istream& input, const char *fn) } // gcount includes the \n - int ll = input.gcount() - 1; + std::streamsize ll = input.gcount() - 1; if (ll > 0) gotnonempty = true; - LOGDEB2(("idfile: lnum %d ll %d: [%s]\n", lnum, ll, cline)); + LOGDEB2("idfile: lnum " << (lnum) << " ll " << ((unsigned int)ll) << ": [" << (cline) << "]\n" ); // Check for a few things that can't be found in a mail file, // (optimization to get a quick negative) @@ -99,7 +98,7 @@ static string idFileInternal(istream& input, const char *fn) // Accept a few empty lines at the beginning of the file, // otherwise this is the end of headers if (gotnonempty || lnum > 10) { - LOGDEB2(("Got empty line\n")); + LOGDEB2("Got empty line\n" ); break; } else { // Don't increment the line counter for initial empty lines. @@ -110,7 +109,7 @@ static string idFileInternal(istream& input, const char *fn) // emacs vm can insert VERY long header lines. if (ll > LL - 20) { - LOGDEB2(("idFile: Line too long\n")); + LOGDEB2("idFile: Line too long\n" ); return string(); } @@ -118,7 +117,7 @@ static string idFileInternal(istream& input, const char *fn) if (lnum == 1 && !strncmp("From ", cline, 5)) { if (treat_mbox_as_rfc822 == -1) { line1HasFrom = true; - LOGDEB2(("idfile: line 1 has From_\n")); + LOGDEB2("idfile: line 1 has From_\n" ); } continue; } @@ -126,10 +125,12 @@ static string idFileInternal(istream& input, const char *fn) // Except for a possible first line with 'From ', lines must // begin with whitespace or have a colon // (hope no one comes up with a longer header name ! - if (!isspace(cline[0])) { + // Take care to convert to unsigned char because ms ctype does + // like negative values + if (!isspace((unsigned char)cline[0])) { char *cp = strchr(cline, ':'); if (cp == 0 || (cp - cline) > 70) { - LOGDEB2(("idfile: can't be mail header line: [%s]\n", cline)); + LOGDEB2("idfile: can't be mail header line: [" << (cline) << "]\n" ); break; } } @@ -159,7 +160,7 @@ string idFile(const char *fn) ifstream input; input.open(fn, ios::in); if (!input.is_open()) { - LOGERR(("idFile: could not open [%s]\n", fn)); + LOGERR("idFile: could not open [" << (fn) << "]\n" ); return string(); } return idFileInternal(input, fn); @@ -179,12 +180,12 @@ string idFileMem(const string& data) #include #include -#include #include using namespace std; -#include "debuglog.h" +#include "log.h" + #include "idfile.h" int main(int argc, char **argv) @@ -203,3 +204,4 @@ int main(int argc, char **argv) } #endif + diff --git a/src/utils/listmem.cpp b/src/utils/listmem.cpp new file mode 100644 index 00000000..24bef9af --- /dev/null +++ b/src/utils/listmem.cpp @@ -0,0 +1,150 @@ +#include "listmem.h" + +#include +#include + +using namespace std; + +/* + * Functions to list a memory buffer: + */ + +/* Turn byte into Hexadecimal ascii representation */ +static char *hexa(unsigned int i) +{ + int j; + static char asc[3]; + + asc[0] = (i >> 4) & 0x0f; + asc[1] = i & 0x0f; + asc[2] = 0; + for (j = 0; j < 2; j++) + if (asc[j] > 9) { + asc[j] += 55; + } else { + asc[j] += 48; + } + return (asc); +} + +static void swap16(unsigned char *d, const unsigned char *s, int n) +{ + if (n & 1) { + n >>= 1; + n++; + } else { + n >>= 1; + } + while (n--) { + int i; + i = 2 * n; + d[i] = s[i + 1]; + d[i + 1] = s[i]; + } +} + +static void swap32(unsigned char *d, const unsigned char *s, int n) +{ + if (n & 3) { + n >>= 2; + n++; + } else { + n >>= 2; + } + while (n--) { + int i; + i = 4 * n; + d[i] = s[i + 3]; + d[i + 1] = s[i + 2]; + d[i + 2] = s[i + 1]; + d[i + 3] = s[i]; + } +} + +/* Turn byte buffer into hexadecimal representation */ +void charbuftohex(int len, unsigned char *dt, int maxlen, char *str) +{ + int i; + char *bf; + + for (i = 0, bf = str; i < len; i++) { + char *cp; + if (bf - str >= maxlen - 4) { + break; + } + cp = hexa((unsigned int)dt[i]); + *bf++ = *cp++; + *bf++ = *cp++; + *bf++ = ' '; + } + *bf++ = 0; +} + +void listmem(ostream& os, const void *_ptr, int siz, int adr, int opts) +{ + const unsigned char *ptr = (const unsigned char *)_ptr; + int i, j, c; + char lastlisted[16]; + int alreadysame = 0; + int oneout = 0; + unsigned char *mpt; + + if (opts & (LISTMEM_SWAP16 | LISTMEM_SWAP32)) { + if ((mpt = (unsigned char *)malloc(siz + 4)) == NULL) { + os << "OUT OF MEMORY\n"; + return; + } + if (opts & LISTMEM_SWAP16) { + swap16(mpt, ptr, siz); + } else if (opts & LISTMEM_SWAP32) { + swap32(mpt, ptr, siz); + } + } else { + mpt = (unsigned char *)ptr; + } + + for (i = 0; i < siz; i += 16) { + /* Check for same data (only print first line in this case) */ + if (oneout != 0 && + siz - i >= 16 && memcmp(lastlisted, mpt + i, 16) == 0) { + if (alreadysame == 0) { + os << "*\n"; + alreadysame = 1; + } + continue; + } + alreadysame = 0; + /* Line header */ + os << std::setw(4) << i + adr << " "; + + /* Hexadecimal representation */ + for (j = 0; j < 16; j++) { + if ((i + j) < siz) { + os << hexa(mpt[i + j]) << ((j & 1) ? " " : ""); + } else { + os << " " << ((j & 1) ? " " : ""); + } + } + os << " "; + + /* Also print ascii for values that fit */ + for (j = 0; j < 16; j++) { + if ((i + j) < siz) { + c = mpt[i + j]; + if (c >= 0x20 && c <= 0x7f) { + os << char(c); + } else { + os << "."; + } + } else { + os << " "; + } + } + os << "\n"; + memcpy(lastlisted, mpt + i, 16); + oneout = 1; + } + if (mpt != ptr) { + free(mpt); + } +} diff --git a/src/utils/listmem.h b/src/utils/listmem.h new file mode 100644 index 00000000..a7c8d3ee --- /dev/null +++ b/src/utils/listmem.h @@ -0,0 +1,11 @@ +#ifndef _LISTMEM_H_INCLUDED_ +#define _LISTMEM_H_INCLUDED_ +#include + +enum ListmemOpts {LISTMEM_SWAP16 = 1, LISTMEM_SWAP32 = 2}; + +/// @param startadr starting value for offset listings on the right +extern void listmem(std::ostream&, const void *ptr, int sz, + int startadr = 0, int opts = 0); + +#endif /* _LISTMEM_H_INCLUDED_ */ diff --git a/src/utils/log.cpp b/src/utils/log.cpp new file mode 100644 index 00000000..7b62248a --- /dev/null +++ b/src/utils/log.cpp @@ -0,0 +1,65 @@ +/* Copyright (C) 2006-2016 J.F.Dockes + * + * This library 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 library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#include "log.h" + +#include +#include + +using namespace std; + +Logger::Logger(const std::string& fn) + : m_tocerr(false), m_loglevel(LLDEB), m_fn(fn) +{ + reopen(fn); +} + +bool Logger::reopen(const std::string& fn) +{ +#if LOGGER_THREADSAFE + std::unique_lock lock(m_mutex); +#endif + if (!fn.empty()) { + m_fn = fn; + } + if (!m_tocerr && m_stream.is_open()) { + m_stream.close(); + } + if (!m_fn.empty() && m_fn.compare("stderr")) { + m_stream.open(m_fn, std::fstream::out | std::ofstream::trunc); + if (!m_stream.is_open()) { + cerr << "Logger::Logger: log open failed: for [" << + fn << "] errno " << errno << endl; + m_tocerr = true; + } else { + m_tocerr = false; + } + } else { + m_tocerr = true; + } + return true; +} + +static Logger *theLog; + +Logger *Logger::getTheLog(const string& fn) +{ + if (theLog == 0) + theLog = new Logger(fn); + return theLog; +} + diff --git a/src/utils/log.h b/src/utils/log.h new file mode 100644 index 00000000..1060ab4a --- /dev/null +++ b/src/utils/log.h @@ -0,0 +1,194 @@ +/* Copyright (C) 2014 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* Copyright (C) 2006-2016 J.F.Dockes + * + * This library 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 library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#ifndef _LOG_H_X_INCLUDED_ +#define _LOG_H_X_INCLUDED_ + +#include +#include +#include +#include + +#ifndef LOGGER_THREADSAFE +#define LOGGER_THREADSAFE 1 +#endif + +#if LOGGER_THREADSAFE +#include +#endif + +// Can't use the symbolic Logger::LLXX names in preproc. 6 is LLDEB1 +#ifndef LOGGER_STATICVERBOSITY +#define LOGGER_STATICVERBOSITY 5 +#endif + +class Logger { +public: + /** Initialize logging to file name. Use "stderr" for stderr + output. Creates the singleton logger object */ + static Logger *getTheLog(const std::string& fn); + + bool reopen(const std::string& fn); + + std::ostream& getstream() { + return m_tocerr ? std::cerr : m_stream; + } + enum LogLevel {LLNON=0, LLFAT=1, LLERR=2, LLINF=3, LLDEB=4, + LLDEB0=5, LLDEB1=6, LLDEB2=7}; + void setLogLevel(LogLevel level) { + m_loglevel = level; + } + int getloglevel() { + return m_loglevel; + } + +#if LOGGER_THREADSAFE + std::recursive_mutex& getmutex() { + return m_mutex; + } +#endif + +private: + bool m_tocerr; + int m_loglevel; + std::string m_fn; + std::ofstream m_stream; +#if LOGGER_THREADSAFE + std::recursive_mutex m_mutex; +#endif + + Logger(const std::string& fn); + Logger(const Logger &); + Logger& operator=(const Logger &); +}; + +#define LOGGER_PRT (Logger::getTheLog("")->getstream()) + +#if LOGGER_THREADSAFE +#define LOGGER_LOCK \ + std::unique_lock lock(Logger::getTheLog("")->getmutex()) +#else +#define LOGGER_LOCK +#endif + +#ifndef LOGGER_LOCAL_LOGINC +#define LOGGER_LOCAL_LOGINC 0 +#endif + +#define LOGGER_LEVEL (Logger::getTheLog("")->getloglevel() + \ + LOGGER_LOCAL_LOGINC) + +#define LOGGER_DOLOG(L,X) LOGGER_PRT << ":" << L << ":" << \ + __FILE__ << ":" << __LINE__ << "::" << X \ + << std::flush + +#if LOGGER_STATICVERBOSITY >= 7 +#define LOGDEB2(X) { \ + if (LOGGER_LEVEL >= Logger::LLDEB2) { \ + LOGGER_LOCK; \ + LOGGER_DOLOG(Logger::LLDEB2, X); \ + } \ + } +#else +#define LOGDEB2(X) +#endif + +#if LOGGER_STATICVERBOSITY >= 6 +#define LOGDEB1(X) { \ + if (LOGGER_LEVEL >= Logger::LLDEB1) { \ + LOGGER_LOCK; \ + LOGGER_DOLOG(Logger::LLDEB1, X); \ + } \ + } +#else +#define LOGDEB1(X) +#endif + +#if LOGGER_STATICVERBOSITY >= 5 +#define LOGDEB0(X) { \ + if (LOGGER_LEVEL >= Logger::LLDEB0) { \ + LOGGER_LOCK; \ + LOGGER_DOLOG(Logger::LLDEB0, X); \ + } \ + } +#else +#define LOGDEB0(X) +#endif + +#if LOGGER_STATICVERBOSITY >= 4 +#define LOGDEB(X) { \ + if (LOGGER_LEVEL >= Logger::LLDEB) { \ + LOGGER_LOCK; \ + LOGGER_DOLOG(Logger::LLDEB, X); \ + } \ + } +#else +#define LOGDEB(X) +#endif + +#if LOGGER_STATICVERBOSITY >= 3 +#define LOGINF(X) { \ + if (LOGGER_LEVEL >= Logger::LLINF) { \ + LOGGER_LOCK; \ + LOGGER_DOLOG(Logger::LLINF, X); \ + } \ + } +#else +#define LOGINF(X) +#endif +#define LOGINFO LOGINF + +#if LOGGER_STATICVERBOSITY >= 2 +#define LOGERR(X) { \ + if (LOGGER_LEVEL >= Logger::LLERR) { \ + LOGGER_LOCK; \ + LOGGER_DOLOG(Logger::LLERR, X); \ + } \ + } +#else +#define LOGERR(X) +#endif + +#if LOGGER_STATICVERBOSITY >= 1 +#define LOGFAT(X) { \ + if (LOGGER_LEVEL >= Logger::LLFAT) { \ + LOGGER_LOCK; \ + LOGGER_DOLOG(Logger::LLFAT, X); \ + } \ + } +#else +#define LOGFAT(X) +#endif +#define LOGFATAL LOGFAT + +#endif /* _LOG_H_X_INCLUDED_ */ diff --git a/src/utils/md5ut.cpp b/src/utils/md5ut.cpp index 43c990fe..6966080c 100644 --- a/src/utils/md5ut.cpp +++ b/src/utils/md5ut.cpp @@ -16,6 +16,8 @@ */ #ifndef TEST_MD5 +#include "autoconfig.h" + #include #include diff --git a/src/utils/mimeparse.cpp b/src/utils/mimeparse.cpp index c51b0e1b..a224b10c 100644 --- a/src/utils/mimeparse.cpp +++ b/src/utils/mimeparse.cpp @@ -16,6 +16,7 @@ */ #ifndef TEST_MIMEPARSE +#include "autoconfig.h" #include #include @@ -32,10 +33,7 @@ #include "transcode.h" #include "smallut.h" - -#ifndef NO_NAMESPACES using namespace std; -#endif /* NO_NAMESPACES */ //#define DEBUG_MIMEPARSE #ifdef DEBUG_MIMEPARSE diff --git a/src/utils/netcon.cpp b/src/utils/netcon.cpp index d0f01d47..71b52fd4 100644 --- a/src/utils/netcon.cpp +++ b/src/utils/netcon.cpp @@ -17,8 +17,14 @@ // Wrapper classes for the socket interface +#ifdef BUILDING_RECOLL +#include "autoconfig.h" +#else +#include "config.h" +#endif + +#include "netcon.h" -#ifndef TEST_NETCON #include #include #include @@ -40,24 +46,7 @@ #include - -#ifdef RECOLL_DATADIR -#include "debuglog.h" - -#else - -#define LOGFATAL(X) -#define LOGERR(X) -#define LOGINFO(X) -#define LOGDEB(X) -#define LOGDEB0(X) -#define LOGDEB1(X) -#define LOGDEB2(X) -#define LOGDEB3(X) -#define LOGDEB4(X) -#endif - -#include "netcon.h" +#include "log.h" using namespace std; @@ -78,22 +67,22 @@ using namespace std; static const int one = 1; static const int zero = 0; -#define LOGSYSERR(who, call, spar) \ - LOGERR(("%s: %s(%s) errno %d (%s)\n", who, call, \ - spar, errno, strerror(errno))) +#define LOGSYSERR(who, call, spar) \ + LOGERR(who << ": " << call << "(" << spar << ") errno " << \ + errno << " (" << strerror(errno) << ")\n") #ifndef MIN -#define MIN(a,b) (ab?a:b) +#define MAX(a,b) ((a)>(b)?(a):(b)) #endif #ifndef freeZ #define freeZ(X) if (X) {free(X);X=0;} #endif #define MILLIS(OLD, NEW) ( (long)(((NEW).tv_sec - (OLD).tv_sec) * 1000 + \ - ((NEW).tv_usec - (OLD).tv_usec) / 1000)) + ((NEW).tv_usec - (OLD).tv_usec) / 1000)) // Static method // Simplified interface to 'select()'. Only use one fd, for either @@ -110,13 +99,12 @@ int Netcon::select1(int fd, int timeo, int write) FD_ZERO(&rd); FD_SET(fd, &rd); if (write) { - ret = select(fd+1, 0, &rd, 0, &tv); + ret = select(fd + 1, 0, &rd, 0, &tv); } else { - ret = select(fd+1, &rd, 0, 0, &tv); + ret = select(fd + 1, &rd, 0, 0, &tv); } if (!FD_ISSET(fd, &rd)) { - LOGERR(("Netcon::select1: fd not ready after select ??\n")); - return -1; + LOGDEB2("Netcon::select1: fd " << (fd) << " timeout\n" ); } return ret; } @@ -183,7 +171,7 @@ int SelectLoop::doLoop() for (;;) { if (m_selectloopDoReturn) { m_selectloopDoReturn = false; - LOGDEB(("Netcon::selectloop: returning on request\n")); + LOGDEB("Netcon::selectloop: returning on request\n" ); return m_selectloopReturnValue; } int nfds; @@ -194,11 +182,12 @@ int SelectLoop::doLoop() // Walk the netcon map and set up the read and write fd_sets // for select() nfds = 0; - for (map::iterator it = m_polldata.begin(); + for (map::iterator it = m_polldata.begin(); it != m_polldata.end(); it++) { - NetconP &pll = it->second; + NetconP& pll = it->second; int fd = it->first; - LOGDEB2(("Selectloop: fd %d flags 0x%x\n",fd, pll->m_wantedEvents)); + LOGDEB2("Selectloop: fd " << fd << " flags 0x" << + pll->m_wantedEvents << "\n"); if (pll->m_wantedEvents & Netcon::NETCONPOLL_READ) { FD_SET(fd, &rd); nfds = MAX(nfds, fd + 1); @@ -218,11 +207,11 @@ int SelectLoop::doLoop() // Just in case there would still be open fds in there // (with no r/w flags set). Should not be needed, but safer m_polldata.clear(); - LOGDEB1(("Netcon::selectloop: no fds\n")); + LOGDEB1("Netcon::selectloop: no fds\n" ); return 0; } - LOGDEB2(("Netcon::selectloop: selecting, nfds = %d\n", nfds)); + LOGDEB2("Netcon::selectloop: selecting, nfds = " << nfds << "\n"); // Compute the next timeout according to what might need to be // done apart from waiting for data @@ -230,7 +219,7 @@ int SelectLoop::doLoop() periodictimeout(&tv); // Wait for something to happen int ret = select(nfds, &rd, &wd, 0, &tv); - LOGDEB2(("Netcon::selectloop: select returns %d\n", ret)); + LOGDEB2("Netcon::selectloop: select returns " << (ret) << "\n" ); if (ret < 0) { LOGSYSERR("Netcon::selectloop", "select", ""); return -1; @@ -263,38 +252,35 @@ int SelectLoop::doLoop() int canread = FD_ISSET(fd, &rd); int canwrite = FD_ISSET(fd, &wd); bool none = !canread && !canwrite; - LOGDEB2(("Netcon::selectloop: fd %d %s %s %s\n", fd, - none ? "blocked" : "can" , canread ? "read" : "", - canwrite ? "write" : "")); + LOGDEB2("Netcon::selectloop: fd " << (fd) << " " << (none ? "blocked" : "can") << " " << (canread ? "read" : "") << " " << (canwrite ? "write" : "") << "\n" ); if (none) { continue; } - map::iterator it = m_polldata.find(fd); + map::iterator it = m_polldata.find(fd); if (it == m_polldata.end()) { /// This should not happen actually - LOGDEB2(("Netcon::selectloop: fd %d not found\n", fd)); + LOGDEB2("Netcon::selectloop: fd " << (fd) << " not found\n" ); continue; } // Next start will be one beyond last serviced (modulo nfds) m_placetostart = fd + 1; - NetconP &pll = it->second; + NetconP& pll = it->second; if (canread && pll->cando(Netcon::NETCONPOLL_READ) <= 0) { pll->m_wantedEvents &= ~Netcon::NETCONPOLL_READ; } if (canwrite && pll->cando(Netcon::NETCONPOLL_WRITE) <= 0) { pll->m_wantedEvents &= ~Netcon::NETCONPOLL_WRITE; } - if (!(pll->m_wantedEvents & (Netcon::NETCONPOLL_WRITE|Netcon::NETCONPOLL_READ))) { - LOGDEB0(("Netcon::selectloop: fd %d has 0x%x mask, erasing\n", - it->first, it->second->m_wantedEvents)); + if (!(pll->m_wantedEvents & (Netcon::NETCONPOLL_WRITE | Netcon::NETCONPOLL_READ))) { + LOGDEB0("Netcon::selectloop: fd " << (it->first) << " has 0x" << (it->second->m_wantedEvents) << " mask, erasing\n" ); m_polldata.erase(it); } } // fd sweep } // forever loop - LOGERR(("SelectLoop::doLoop: got out of loop !\n")); + LOGERR("SelectLoop::doLoop: got out of loop !\n" ); return -1; } @@ -304,7 +290,7 @@ int SelectLoop::addselcon(NetconP con, int events) if (!con) { return -1; } - LOGDEB1(("Netcon::addselcon: fd %d\n", con->m_fd)); + LOGDEB1("Netcon::addselcon: fd " << (con->m_fd) << "\n" ); con->set_nonblock(1); con->setselevents(events); m_polldata[con->m_fd] = con; @@ -318,10 +304,10 @@ int SelectLoop::remselcon(NetconP con) if (!con) { return -1; } - LOGDEB1(("Netcon::remselcon: fd %d\n", con->m_fd)); - map::iterator it = m_polldata.find(con->m_fd); + LOGDEB1("Netcon::remselcon: fd " << (con->m_fd) << "\n" ); + map::iterator it = m_polldata.find(con->m_fd); if (it == m_polldata.end()) { - LOGDEB1(("Netcon::remselcon: con not found for fd %d\n", con->m_fd)); + LOGDEB1("Netcon::remselcon: con not found for fd " << (con->m_fd) << "\n" ); return -1; } con->setloop(0); @@ -331,7 +317,7 @@ int SelectLoop::remselcon(NetconP con) ////////////////////////////////////////////////////////// // Base class (Netcon) methods -Netcon::~Netcon() +Netcon::~Netcon() { closeconn(); if (m_peer) { @@ -364,9 +350,9 @@ void Netcon::setpeer(const char *hostname) int Netcon::settcpnodelay(int on) { - LOGDEB2(( "Netcon::settcpnodelay\n" )); + LOGDEB2("Netcon::settcpnodelay\n" ); if (m_fd < 0) { - LOGERR(("Netcon::settcpnodelay: connection not opened\n")); + LOGERR("Netcon::settcpnodelay: connection not opened\n" ); return -1; } char *cp = on ? (char *)&one : (char *)&zero; @@ -377,11 +363,12 @@ int Netcon::settcpnodelay(int on) return 0; } + // Set/reset non-blocking flag on fd int Netcon::set_nonblock(int onoff) { int flags = fcntl(m_fd, F_GETFL, 0); - if (flags != -1 ) { + if (flags != -1) { int newflags = onoff ? flags | O_NONBLOCK : flags & ~O_NONBLOCK; if (newflags != flags) if (fcntl(m_fd, F_SETFL, newflags) < 0) { @@ -394,23 +381,46 @@ int Netcon::set_nonblock(int onoff) ///////////////////////////////////////////////////////////////////// // Data socket (NetconData) methods -NetconData::~NetconData() +NetconData::NetconData(bool cancellable) + : m_buf(0), m_bufbase(0), m_bufbytes(0), m_bufsize(0), m_wkfds{-1,-1} +{ + if (cancellable) { + if (pipe(m_wkfds) < 0) { + LOGSYSERR("NetconData::NetconData", "pipe", ""); + m_wkfds[0] = m_wkfds[1] = -1; + } + LOGDEB2("NetconData:: m_wkfds[0] " << m_wkfds[0] << " m_wkfds[1] " << + m_wkfds[1] << endl); + for (int i = 0; i < 2; i++) { + int flags = fcntl(m_wkfds[i], F_GETFL, 0); + fcntl(m_wkfds[i], F_SETFL, flags | O_NONBLOCK); + } + } +} + +NetconData::~NetconData() { freeZ(m_buf); m_bufbase = 0; m_bufbytes = m_bufsize = 0; + for (int i = 0; i < 2; i++) { + if (m_wkfds[i] >= 0) { + close(m_wkfds[i]); + } + } } int NetconData::send(const char *buf, int cnt, int expedited) { - LOGDEB2(("NetconData::send: fd %d cnt %d expe %d\n", m_fd, cnt, expedited)); + LOGDEB2("NetconData::send: fd " << m_fd << " cnt " << cnt << + " expe " << expedited << "\n"); int flag = 0; if (m_fd < 0) { - LOGERR(("NetconData::send: connection not opened\n")); + LOGERR("NetconData::send: connection not opened\n" ); return -1; } if (expedited) { - LOGDEB2(("NetconData::send: expedited data, count %d bytes\n", cnt)); + LOGDEB2("NetconData::send: expedited data, count " <= 0) { + LOGDEB2("NetconData::cancelReceive: writing to " << m_wkfds[1] << endl); + ::write(m_wkfds[1], "!", 1); } - return select1(m_fd, 0); -} - -// Test for writable -int NetconData::writeready() -{ - LOGDEB2(("NetconData::writeready\n")); - if (m_fd < 0) { - LOGERR(("NetconData::writeready: connection not opened\n")); - return -1; - } - return select1(m_fd, 0, 1); } // Receive at most cnt bytes (maybe less) int NetconData::receive(char *buf, int cnt, int timeo) { - LOGDEB2(("NetconData::receive: cnt %d timeo %d m_buf 0x%x m_bufbytes %d\n", - cnt, timeo, m_buf, m_bufbytes)); + LOGDEB2("NetconData::receive: cnt " << cnt << " timeo " << timeo << + " m_buf 0x" << m_buf << " m_bufbytes " << m_bufbytes << "\n"); + if (m_fd < 0) { - LOGERR(("NetconData::receive: connection not opened\n")); + LOGERR("NetconData::receive: connection not opened\n" ); return -1; } + int fromibuf = 0; // Get whatever might have been left in the buffer by a previous // getline, except if we're called to fill the buffer of course @@ -471,31 +469,54 @@ int NetconData::receive(char *buf, int cnt, int timeo) m_bufbytes -= fromibuf; m_bufbase += fromibuf; cnt -= fromibuf; - LOGDEB2(("NetconData::receive: transferred %d from mbuf\n", fromibuf)); + LOGDEB2("NetconData::receive: got " << fromibuf << " from mbuf\n"); if (cnt <= 0) { return fromibuf; } } + if (timeo > 0) { - int ret = select1(m_fd, timeo); - if (ret == 0) { - LOGDEB2(("NetconData::receive timed out\n")); - m_didtimo = 1; - return -1; + struct timeval tv; + tv.tv_sec = timeo; + tv.tv_usec = 0; + fd_set rd; + FD_ZERO(&rd); + FD_SET(m_fd, &rd); + bool cancellable = (m_wkfds[0] >= 0); + if (cancellable) { + LOGDEB2("NetconData::receive: cancel fd " << m_wkfds[0] << endl); + FD_SET(m_wkfds[0], &rd); } + int nfds = MAX(m_fd, m_wkfds[0]) + 1; + + int ret = select(nfds, &rd, 0, 0, &tv); + LOGDEB2("NetconData::receive: select returned " << ret << endl); + + if (cancellable && FD_ISSET(m_wkfds[0], &rd)) { + char b[100]; + read(m_wkfds[0], b, 100); + return Cancelled; + } + + if (!FD_ISSET(m_fd, &rd)) { + m_didtimo = 1; + return TimeoutOrError; + } + if (ret < 0) { LOGSYSERR("NetconData::receive", "select", ""); - return -1; + m_didtimo = 0; + return TimeoutOrError; } } + m_didtimo = 0; if ((cnt = read(m_fd, buf + fromibuf, cnt)) < 0) { - char fdcbuf[20]; - sprintf(fdcbuf, "%d", m_fd); - LOGSYSERR("NetconData::receive", "read", fdcbuf); + LOGSYSERR("NetconData::receive", "read", m_fd); return -1; } - LOGDEB2(("NetconData::receive: normal return, cnt %d\n", cnt)); + LOGDEB2("NetconData::receive: normal return, fromibuf " << fromibuf << + " cnt " << cnt << "\n"); return fromibuf + cnt; } @@ -503,13 +524,13 @@ int NetconData::receive(char *buf, int cnt, int timeo) int NetconData::doreceive(char *buf, int cnt, int timeo) { int got, cur; - LOGDEB2(("Netcon::doreceive: cnt %d, timeo %d\n", cnt, timeo)); + LOGDEB2("Netcon::doreceive: cnt " << cnt << ", timeo " << timeo << "\n"); cur = 0; while (cnt > cur) { - got = receive(buf, cnt-cur, timeo); - LOGDEB2(("Netcon::doreceive: got %d\n", got)); + got = receive(buf, cnt - cur, timeo); + LOGDEB2("Netcon::doreceive: got " << (got) << "\n" ); if (got < 0) { - return -1; + return got; } if (got == 0) { return cur; @@ -531,7 +552,7 @@ int NetconData::doreceive(char *buf, int cnt, int timeo) static const int defbufsize = 200; int NetconData::getline(char *buf, int cnt, int timeo) { - LOGDEB2(("NetconData::getline: cnt %d, timeo %d\n", cnt, timeo)); + LOGDEB2("NetconData::getline: cnt " << (cnt) << ", timeo " << (timeo) << "\n" ); if (m_buf == 0) { if ((m_buf = (char *)malloc(defbufsize)) == 0) { LOGSYSERR("NetconData::getline: Out of mem", "malloc", ""); @@ -546,10 +567,9 @@ int NetconData::getline(char *buf, int cnt, int timeo) for (;;) { // Transfer from buffer. Have to take a lot of care to keep counts and // pointers consistant in all end cases - int maxtransf = MIN(m_bufbytes, cnt-1); + int maxtransf = MIN(m_bufbytes, cnt - 1); int nn = maxtransf; - LOGDEB2(("Before loop, bufbytes %d, maxtransf %d, nn: %d\n", - m_bufbytes, maxtransf, nn)); + LOGDEB2("Before loop, bufbytes " << (m_bufbytes) << ", maxtransf " << (maxtransf) << ", nn: " << (nn) << "\n" ); for (nn = maxtransf; nn > 0;) { // This is not pretty but we want nn to be decremented for // each byte copied (even newline), and not become -1 if @@ -563,8 +583,7 @@ int NetconData::getline(char *buf, int cnt, int timeo) maxtransf -= nn; // Actual count transferred m_bufbytes -= maxtransf; cnt -= maxtransf; - LOGDEB2(("After transfer: actual transf %d cnt %d, m_bufbytes %d\n", - maxtransf, cnt, m_bufbytes)); + LOGDEB2("After transfer: actual transf " << (maxtransf) << " cnt " << (cnt) << ", m_bufbytes " << (m_bufbytes) << "\n" ); // Finished ? if (cnt <= 1 || (cp > buf && cp[-1] == '\n')) { @@ -594,7 +613,7 @@ int NetconData::getline(char *buf, int cnt, int timeo) // and discard. int NetconData::cando(Netcon::Event reason) { - LOGDEB2(("NetconData::cando\n")); + LOGDEB2("NetconData::cando\n" ); if (m_user) { return m_user->data(this, reason); } @@ -622,7 +641,7 @@ int NetconData::cando(Netcon::Event reason) int NetconCli::openconn(const char *host, unsigned int port, int timeo) { int ret = -1; - LOGDEB2(("Netconcli::openconn: host %s, port %d\n", host, port)); + LOGDEB2("Netconcli::openconn: host " << (host) << ", port " << (port) << "\n" ); closeconn(); @@ -643,8 +662,7 @@ int NetconCli::openconn(const char *host, unsigned int port, int timeo) } else { struct hostent *hp; if ((hp = gethostbyname(host)) == 0) { - LOGERR(("NetconCli::openconn: gethostbyname(%s) failed\n", - host)); + LOGERR("NetconCli::openconn: gethostbyname(" << (host) << ") failed\n" ); return -1; } memcpy(&ip_addr.sin_addr, hp->h_addr, hp->h_length); @@ -660,7 +678,7 @@ int NetconCli::openconn(const char *host, unsigned int port, int timeo) memset(&unix_addr, 0, sizeof(unix_addr)); unix_addr.sun_family = AF_UNIX; if (strlen(host) > UNIX_PATH_MAX - 1) { - LOGERR(("NetconCli::openconn: name too long: %s\n", host)); + LOGERR("NetconCli::openconn: name too long: " << (host) << "\n" ); return -1; } strcpy(unix_addr.sun_path, host); @@ -695,13 +713,13 @@ connectok: set_nonblock(0); } - LOGDEB2(("NetconCli::connect: setting keepalive\n")); + LOGDEB2("NetconCli::connect: setting keepalive\n" ); if (setsockopt(m_fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof(one)) < 0) { LOGSYSERR("NetconCli::connect", "setsockopt", "KEEPALIVE"); } setpeer(host); - LOGDEB2(("NetconCli::openconn: connection opened ok\n")); + LOGDEB2("NetconCli::openconn: connection opened ok\n" ); ret = 0; out: if (ret < 0) { @@ -713,12 +731,12 @@ out: // Same as previous, but get the port number from services int NetconCli::openconn(const char *host, const char *serv, int timeo) { - LOGDEB2(("Netconcli::openconn: host %s, serv %s\n", host, serv)); + LOGDEB2("Netconcli::openconn: host " << (host) << ", serv " << (serv) << "\n" ); if (host[0] != '/') { struct servent *sp; if ((sp = getservbyname(serv, "tcp")) == 0) { - LOGERR(("NetconCli::openconn: getservbyname failed for %s\n",serv)); + LOGERR("NetconCli::openconn: getservbyname failed for " << (serv) << "\n" ); return -1; } // Callee expects the port number in host byte order @@ -731,7 +749,7 @@ int NetconCli::openconn(const char *host, const char *serv, int timeo) int NetconCli::setconn(int fd) { - LOGDEB2(("Netconcli::setconn: fd %d\n", fd)); + LOGDEB2("Netconcli::setconn: fd " << (fd) << "\n" ); closeconn(); m_fd = fd; @@ -744,7 +762,7 @@ int NetconCli::setconn(int fd) /////////////////////////////////////////////////////////////////////// // Methods for the main (listening) server connection -NetconServLis::~NetconServLis() +NetconServLis::~NetconServLis() { #ifdef NETCON_ACCESSCONTROL freeZ(okaddrs.intarray); @@ -754,7 +772,7 @@ NetconServLis::~NetconServLis() #if 0 // code for dumping a struct servent -static void dump_servent(struct servent *servp) +static void dump_servent(struct servent *servp) { fprintf(stderr, "Official name %s\n", servp->s_name); for (char **cpp = servp->s_aliases; *cpp; cpp++) { @@ -771,10 +789,10 @@ int NetconServLis::openservice(const char *serv, int backlog) int port; struct servent *servp; if (!serv) { - LOGERR(("NetconServLis::openservice: null serv??\n")); + LOGERR("NetconServLis::openservice: null serv??\n" ); return -1; } - LOGDEB1(("NetconServLis::openservice: serv %s\n", serv)); + LOGDEB1("NetconServLis::openservice: serv " << (serv) << "\n" ); #ifdef NETCON_ACCESSCONTROL if (initperms(serv) < 0) { return -1; @@ -784,16 +802,14 @@ int NetconServLis::openservice(const char *serv, int backlog) m_serv = serv; if (serv[0] != '/') { if ((servp = getservbyname(serv, "tcp")) == 0) { - LOGERR(("NetconServLis::openservice: getservbyname failed for %s\n", - serv)); + LOGERR("NetconServLis::openservice: getservbyname failed for " << (serv) << "\n" ); return -1; } port = (int)ntohs((short)servp->s_port); return openservice(port, backlog); } else { if (strlen(serv) > UNIX_PATH_MAX - 1) { - LOGERR(("NetconServLis::openservice: too long for AF_UNIX: %s\n", - serv)); + LOGERR("NetconServLis::openservice: too long for AF_UNIX: " << (serv) << "\n" ); return -1; } int ret = -1; @@ -815,9 +831,9 @@ int NetconServLis::openservice(const char *serv, int backlog) goto out; } - LOGDEB1(("NetconServLis::openservice: service opened ok\n")); + LOGDEB1("NetconServLis::openservice: service opened ok\n" ); ret = 0; - out: +out: if (ret < 0 && m_fd >= 0) { close(m_fd); m_fd = -1; @@ -829,21 +845,21 @@ int NetconServLis::openservice(const char *serv, int backlog) // Port is a natural host integer value int NetconServLis::openservice(int port, int backlog) { - LOGDEB1(("NetconServLis::openservice: port %d\n", port)); + LOGDEB1("NetconServLis::openservice: port " << (port) << "\n" ); #ifdef NETCON_ACCESSCONTROL if (initperms(port) < 0) { return -1; } #endif int ret = -1; - struct sockaddr_in ipaddr; + struct sockaddr_in ipaddr; if ((m_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { LOGSYSERR("NetconServLis", "socket", ""); return -1; } - (void) setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR,(char *)&one, sizeof(one)); + (void) setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)); #ifdef SO_REUSEPORT - (void) setsockopt(m_fd, SOL_SOCKET, SO_REUSEPORT,(char *)&one, sizeof(one)); + (void) setsockopt(m_fd, SOL_SOCKET, SO_REUSEPORT, (char *)&one, sizeof(one)); #endif /*SO_REUSEPORT*/ memset(&ipaddr, 0, sizeof(ipaddr)); ipaddr.sin_family = AF_INET; @@ -858,7 +874,7 @@ int NetconServLis::openservice(int port, int backlog) goto out; } - LOGDEB1(("NetconServLis::openservice: service opened ok\n")); + LOGDEB1("NetconServLis::openservice: service opened ok\n" ); ret = 0; out: if (ret < 0 && m_fd >= 0) { @@ -888,7 +904,7 @@ int NetconServLis::initperms(const char *serv) } if (serv == 0 || *serv == 0 || strlen(serv) > 80) { - LOGERR(("NetconServLis::initperms: bad service name %s\n", serv)); + LOGERR("NetconServLis::initperms: bad service name " << (serv) << "\n" ); return -1; } @@ -898,17 +914,17 @@ int NetconServLis::initperms(const char *serv) serv = "default"; sprintf(keyname, "%s_okaddrs", serv); if (genparams->getparam(keyname, &okaddrs) < 0) { - LOGERR(("NetconServLis::initperms: no okaddrs found in config file\n")); + LOGERR("NetconServLis::initperms: no okaddrs found in config file\n" ); return -1; } } sprintf(keyname, "%s_okmasks", serv); if (genparams->getparam(keyname, &okmasks)) { - LOGERR(("NetconServLis::initperms: okmasks not found\n")); + LOGERR("NetconServLis::initperms: okmasks not found\n" ); return -1; } if (okaddrs.len == 0 || okmasks.len == 0) { - LOGERR(("NetconServLis::initperms: len 0 for okmasks or okaddrs\n")); + LOGERR("NetconServLis::initperms: len 0 for okmasks or okaddrs\n" ); return -1; } @@ -930,12 +946,12 @@ int NetconServLis::cando(Netcon::Event reason) NetconServCon * NetconServLis::accept(int timeo) { - LOGDEB(("NetconServLis::accept\n")); + LOGDEB("NetconServLis::accept\n" ); if (timeo > 0) { int ret = select1(m_fd, timeo); if (ret == 0) { - LOGDEB2(("NetconServLis::accept timed out\n")); + LOGDEB2("NetconServLis::accept timed out\n" ); m_didtimo = 1; return 0; } @@ -971,17 +987,16 @@ NetconServLis::accept(int timeo) con = new NetconServCon(newfd); if (con == 0) { - LOGERR(("NetconServLis::accept: new NetconServCon failed\n")); + LOGERR("NetconServLis::accept: new NetconServCon failed\n" ); goto out; } // Retrieve peer's host name. Errors are non fatal if (m_serv.empty() || m_serv[0] != '/') { struct hostent *hp; - if ((hp = gethostbyaddr((char *) & (who.sin_addr), + if ((hp = gethostbyaddr((char *) & (who.sin_addr), sizeof(struct in_addr), AF_INET)) == 0) { - LOGERR(("NetconServLis::accept: gethostbyaddr failed for addr 0x%lx\n", - who.sin_addr.s_addr)); + LOGERR("NetconServLis::accept: gethostbyaddr failed for addr 0x" << (who.sin_addr.s_addr) << "\n" ); con->setpeer(inet_ntoa(who.sin_addr)); } else { con->setpeer(hp->h_name); @@ -990,12 +1005,12 @@ NetconServLis::accept(int timeo) con->setpeer(m_serv.c_str()); } - LOGDEB2(("NetconServLis::accept: setting keepalive\n")); + LOGDEB2("NetconServLis::accept: setting keepalive\n" ); if (setsockopt(newfd, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof(one)) < 0) { LOGSYSERR("NetconServLis::accept", "setsockopt", "KEEPALIVE"); } - LOGDEB2(("NetconServLis::accept: got connect from %s\n", con->getpeer())); + LOGDEB2("NetconServLis::accept: got connect from " << (con->getpeer()) << "\n" ); out: if (con == 0 && newfd >= 0) { @@ -1017,341 +1032,25 @@ NetconServLis::checkperms(void *cl, int) unsigned long ip_addr; if (addr->sa_family != AF_INET) { - LOGERR(("NetconServLis::checkperms: connection from non-INET addr !\n")); + LOGERR("NetconServLis::checkperms: connection from non-INET addr !\n" ); return -1; } ip_addr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr); - LOGDEB2(("checkperms: ip_addr: 0x%x\n", ip_addr)); + LOGDEB2("checkperms: ip_addr: 0x" << (ip_addr) << "\n" ); for (int i = 0; i < okaddrs.len; i++) { unsigned int mask; if (i < okmasks.len) { mask = okmasks.intarray[i]; } else { - mask = okmasks.intarray[okmasks.len-1]; + mask = okmasks.intarray[okmasks.len - 1]; } - LOGDEB2(("checkperms: trying okaddr 0x%x, mask 0x%x\n", - okaddrs.intarray[i], mask)); + LOGDEB2("checkperms: trying okaddr 0x" << (okaddrs.intarray[i]) << ", mask 0x" << (mask) << "\n" ); if ((ip_addr & mask) == (okaddrs.intarray[i] & mask)) { return (0); } } - LOGERR(("NetconServLis::checkperm: connection from bad address 0x%x\n", - ip_addr)); + LOGERR("NetconServLis::checkperm: connection from bad address 0x" << (ip_addr) << "\n" ); return -1; } #endif /* NETCON_ACCESSCONTROL */ - - -#else /* !TEST_NETCON */ -///////////////////////////////////////////////////////////////////////// -////////// TEST DRIVER -//////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include - -#include "debuglog.h" -#include "netcon.h" - -using namespace std; - -static char *thisprog; -static char usage[] = - "-c : Connects to trnetcon server, exchange message, then\n" - " sleeps 10 S, except if option -n is given (sleep forever)\n" - "\n" - "-s : open service \n" - ; -static void -Usage() -{ - fprintf(stderr, "Usage : %s:\n %s", thisprog, usage); - exit(1); -} - -static int op_flags; -#define OPT_MOINS 0x1 -#define OPT_s 0x2 /* Server */ -#define OPT_c 0x4 /* Client */ -#define OPT_n 0x8 /* Client sleeps forever */ - -extern int trycli(char *host, char *serv); -extern int tryserv(char *serv); - -int nloop = 10; - -int main(int argc, char **argv) -{ - char *host, *serv; - - thisprog = argv[0]; - argc--; - argv++; - - while (argc > 0 && **argv == '-') { - (*argv)++; - if (!(**argv)) - /* Cas du "adb - core" */ - { - Usage(); - } - while (**argv) - switch (*(*argv)++) { - case 's': - op_flags |= OPT_s; - break; - case 'c': - op_flags |= OPT_c; - break; - case 'n': - op_flags |= OPT_n; - break; - default: - Usage(); - break; - } - argc--; - argv++; - } - DebugLog::setfilename("stderr"); - DebugLog::getdbl()->setloglevel(DEBDEB2); - - if (op_flags & OPT_c) { - if (argc != 2) { - Usage(); - } - host = *argv++; - argc--; - serv = *argv++; - argc--; - exit(trycli(host, serv)); - } else if (op_flags & OPT_s) { - if (argc != 1) { - Usage(); - } - serv = *argv++; - argc--; - exit(tryserv(serv)); - } else { - Usage(); - } -} - - -static char buf[1024]; -static int buflen = 1023; -static char fromcli[200]; - -class CliNetconWorker : public NetconWorker { -public: - CliNetconWorker() : m_count(0) {} - int data(NetconData *con, Netcon::Event reason) { - LOGDEB(("clientdata\n")); - if (reason & Netcon::NETCONPOLL_WRITE) { - sprintf(fromcli, "Bonjour Bonjour client %d, count %d", - getpid(), m_count); - con->setselevents(Netcon::NETCONPOLL_READ); - if (con->send(fromcli, strlen(fromcli) + 1) < 0) { - fprintf(stderr, "send failed\n"); - return -1; - } - m_count++; - } - - if (reason & Netcon::NETCONPOLL_READ) { - con->setselevents(Netcon::NETCONPOLL_WRITE); - int n; - if ((n = con->receive(buf, buflen)) < 0) { - fprintf(stderr, "receive failed\n"); - return -1; - } - if (n == 0) { - // EOF, close connection - return -1; - } - buf[n] = 0; - fprintf(stderr, "%d received \"%s\"\n", getpid(), buf); - if (op_flags & OPT_n) { - pause(); - } else { - sleep(1); - } - } - if (m_count >= 10) { - fprintf(stderr, "Did 10, enough\n"); - if (con->getloop()) { - con->getloop()->loopReturn(0); - } - } - return 0; - } -private: - int m_count; -}; - -int trycli(char *host, char *serv) -{ - sprintf(fromcli, "Bonjour Bonjour je suis le client %d", getpid()); - - NetconCli *clicon = new NetconCli(); - NetconP con(clicon); - if (!con) { - fprintf(stderr, "new NetconCli failed\n"); - return 1; - } - int port = atoi(serv); - int ret = port > 0 ? - clicon->openconn(host, port) : clicon->openconn(host, serv); - if (ret < 0) { - fprintf(stderr, "openconn(%s, %s) failed\n", host, serv); - return 1; - } - fprintf(stderr, "openconn(%s, %s) ok\n", host, serv); -#ifdef NOSELLOOP - for (int i = 0; i < nloop; i++) { - if (con->send(fromcli, strlen(fromcli) + 1) < 0) { - fprintf(stderr, "%d: Send failed\n", getpid()); - return 1; - } - if (con->receive(buf, buflen) < 0) { - perror("receive:"); - fprintf(stderr, "%d: Receive failed\n", getpid()); - return 1; - } - fprintf(stderr, "%d Received \"%s\"\n", getpid(), buf); - if (op_flags & OPT_n) { - pause(); - } else { - sleep(1); - } - } -#else - RefCntr worker = - RefCntr(new CliNetconWorker()); - clicon->setcallback(worker); - SelectLoop myloop; - myloop.addselcon(con, Netcon::NETCONPOLL_WRITE); - fprintf(stderr, "client ready\n"); - ret = myloop.doLoop(); - if (ret < 0) { - fprintf(stderr, "selectloop failed\n"); - exit(1); - } - fprintf(stderr, "selectloop returned %d\n", ret); -#endif - return 0; -} - -////////////////////////////////////////////////////////////////// -// Server-side sample code -class ServNetconWorker : public NetconWorker { -public: - ServNetconWorker() : m_count(0) {} - int data(NetconData *con, Netcon::Event reason) { - LOGDEB(("serverdata\n")); - if (reason & Netcon::NETCONPOLL_WRITE) { - con->setselevents(Netcon::NETCONPOLL_READ); - char fromserv[200]; - sprintf(fromserv, - "Du serveur: mon fd pour ce client est %d, mon compte %d", - con->getfd(), ++m_count); - if (con->send(fromserv, strlen(fromserv) + 1) < 0) { - fprintf(stderr, "send failed\n"); - return -1; - } - } - if (reason & Netcon::NETCONPOLL_READ) { -#define LL 200 - char buf[LL+1]; - int n; - if ((n = con->receive(buf, LL)) < 0) { - fprintf(stderr, "receive failed\n"); - return -1; - } - if (n == 0) { - return -1; - } - buf[n] = 0; - fprintf(stderr, "%d received \"%s\"\n", getpid(), buf); - con->setselevents(Netcon::NETCONPOLL_READ|Netcon::NETCONPOLL_WRITE); - } - return 0; - } -private: - int m_count; -}; - -class MyNetconServLis : public NetconServLis { -public: - MyNetconServLis(SelectLoop &loop) - : NetconServLis(), m_loop(loop) { - } -protected: - int cando(Netcon::Event reason) { - NetconServCon *con = accept(); - if (con == 0) { - return -1; - } - RefCntr worker = - RefCntr(new ServNetconWorker()); - con->setcallback(worker); - m_loop.addselcon(NetconP(con), NETCONPOLL_READ); - return 1; - } - SelectLoop& m_loop; -}; - -NetconP lis; - -void -onexit(int sig) -{ - fprintf(stderr, "Onexit: "); - if (sig == SIGQUIT) { - kill(getpid(), SIGKILL); - } - fprintf(stderr, "Exiting\n"); - exit(0); -} - -int tryserv(char *serv) -{ - signal(SIGCHLD, SIG_IGN); - SelectLoop myloop; - MyNetconServLis *servlis = new MyNetconServLis(myloop); - lis = NetconP(servlis); - if (!lis) { - fprintf(stderr, "new NetconServLis failed\n"); - return 1; - } - - // Prepare for cleanup - struct sigaction sa; - sa.sa_flags = 0; - sa.sa_handler = onexit; - sigemptyset(&sa.sa_mask); - sigaction(SIGINT, &sa, 0); - sigaction(SIGQUIT, &sa, 0); - sigaction(SIGTERM, &sa, 0); - - int port = atoi(serv); - int ret = port > 0 ? - servlis->openservice(port) : servlis->openservice(serv); - if (ret < 0) { - fprintf(stderr, "openservice(%s) failed\n", serv); - return 1; - } - myloop.addselcon(lis, Netcon::NETCONPOLL_READ); - fprintf(stderr, "openservice(%s) Ok\n", serv); - if (myloop.doLoop() < 0) { - fprintf(stderr, "selectloop failed\n"); - exit(1); - } - return 0; -} - -#endif /* TEST_NETCON */ diff --git a/src/utils/netcon.h b/src/utils/netcon.h index 073676b8..471c90ae 100644 --- a/src/utils/netcon.h +++ b/src/utils/netcon.h @@ -16,11 +16,17 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#ifdef BUILDING_RECOLL +#include "autoconfig.h" +#else +#include "config.h" +#endif + #include #include #include -#include "refcntr.h" +#include /// A set of classes to manage client-server communication over a /// connection-oriented network, or a pipe. @@ -30,37 +36,37 @@ /// endpoints. Netcon also has server-side static code to handle a set /// of client connections in parallel. This should be moved to a /// friend class. -/// +/// /// The client data transfer class can also be used for /// timeout-protected/asynchronous io using a given fd (ie a pipe /// descriptor) /// Base class for all network endpoints: class Netcon; -typedef RefCntr NetconP; +typedef std::shared_ptr NetconP; class SelectLoop; class Netcon { public: - enum Event {NETCONPOLL_READ = 0x1, NETCONPOLL_WRITE=0x2}; - Netcon() - : m_peer(0), m_fd(-1), m_ownfd(true), m_didtimo(0), m_wantedEvents(0), - m_loop(0) { + enum Event {NETCONPOLL_READ = 0x1, NETCONPOLL_WRITE = 0x2}; + Netcon() + : m_peer(0), m_fd(-1), m_ownfd(true), m_didtimo(0), m_wantedEvents(0), + m_loop(0) { } virtual ~Netcon(); - /// Remember whom we're talking to. We let external code do this because + /// Remember whom we're talking to. We let external code do this because /// the application may have a non-dns method to find the peer name. virtual void setpeer(const char *hostname); /// Retrieve the peer's hostname. Only works if it was set before ! virtual const char *getpeer() { - return m_peer ? (const char *)m_peer : "none"; + return m_peer ? (const char *)m_peer : "none"; } - /// Set or reset the TCP_NODELAY option. - virtual int settcpnodelay(int on = 1); + /// Set or reset the TCP_NODELAY option. + virtual int settcpnodelay(int on = 1); /// Did the last receive() call time out ? Resets the flag. virtual int timedout() { - int s = m_didtimo; - m_didtimo = 0; + int s = m_didtimo; + m_didtimo = 0; return s; } /// Return string version of last syscall error @@ -105,7 +111,7 @@ public: static int select1(int fd, int secs, int writing = 0); protected: - char *m_peer; // Name of the connected host + char *m_peer; // Name of the connected host int m_fd; bool m_ownfd; int m_didtimo; @@ -121,7 +127,7 @@ protected: }; -/// The selectloop interface is used to implement parallel servers. +/// The selectloop interface is used to implement parallel servers. // The select loop mechanism allows several netcons to be used for io // in a program without blocking as long as there is data to be read // or written. In a multithread program which is also using select, it @@ -130,13 +136,13 @@ protected: class SelectLoop { public: SelectLoop() - : m_selectloopDoReturn(false), m_selectloopReturnValue(0), - m_placetostart(0), - m_periodichandler(0), m_periodicparam(0), m_periodicmillis(0) { + : m_selectloopDoReturn(false), m_selectloopReturnValue(0), + m_placetostart(0), + m_periodichandler(0), m_periodicparam(0), m_periodicmillis(0) { } /// Loop waiting for events on the connections and call the - /// cando() method on the object when something happens (this will in + /// cando() method on the object when something happens (this will in /// turn typically call the app callback set on the netcon). Possibly /// call the periodic handler (if set) at regular intervals. /// @return -1 for error. 0 if no descriptors left for i/o. 1 for periodic @@ -145,8 +151,8 @@ public: /// Call from data handler: make selectloop return the param value void loopReturn(int value) { - m_selectloopDoReturn = true; - m_selectloopReturnValue = value; + m_selectloopDoReturn = true; + m_selectloopReturnValue = value; } /// Add a connection to be monitored (this will usually be called /// from the server's listen connection's accept callback) @@ -156,11 +162,11 @@ public: int remselcon(NetconP con); /// Set a function to be called periodically, or a time before return. - /// @param handler the function to be called. + /// @param handler the function to be called. /// - if it is 0, selectloop() will return after ms mS (and can be called /// again - /// - if it is not 0, it will be called at ms mS intervals. If its return - /// value is <= 0, selectloop will return. + /// - if it is not 0, it will be called at ms mS intervals. If its return + /// value is <= 0, selectloop will return. /// @param clp client data to be passed to handler at every call. /// @param ms milliseconds interval between handler calls or /// before return. Set to 0 for no periodic handler. @@ -189,12 +195,12 @@ private: /////////////////////// class NetconData; -/// Class for the application callback routine (when in selectloop). +/// Class for the application callback routine (when in selectloop). /// /// This is set by the app on the NetconData by calling /// setcallback(). It is then called from the NetconData's cando() /// routine, itself called by selectloop. -/// +/// /// It would be nicer to override cando() in a subclass instead of /// setting a callback, but this can't be done conveniently because /// accept() always creates a base NetconData (another approach would @@ -209,8 +215,7 @@ public: /// Base class for connections that actually transfer data. T class NetconData : public Netcon { public: - NetconData() : m_buf(0), m_bufbase(0), m_bufbytes(0), m_bufsize(0) { - } + NetconData(bool cancellable = false); virtual ~NetconData(); /// Write data to the connection. @@ -223,41 +228,47 @@ public: /// Read from the connection /// @param buf the data buffer - /// @param cnt the number of bytes we should try to read (but we return + /// @param cnt the number of bytes we should try to read (but we return /// as soon as we get data) /// @param timeo maximum number of seconds we should be waiting for data. - /// @return the count of bytes actually read. 0 for timeout (call - /// didtimo() to discriminate from EOF). -1 if an error occurred. + /// @return the count of bytes actually read (0 for EOF), or + /// TimeoutOrError (-1) for timeout or error (call timedout() to + /// discriminate and reset), Cancelled (-2) if cancelled. + enum RcvReason {Eof = 0, TimeoutOrError = -1, Cancelled = -2}; virtual int receive(char *buf, int cnt, int timeo = -1); + virtual void cancelReceive(); + /// Loop on receive until cnt bytes are actually read or a timeout occurs virtual int doreceive(char *buf, int cnt, int timeo = -1); - /// Check for data being available for reading - virtual int readready(); - /// Check for data being available for writing - virtual int writeready(); + /// Read a line of text on an ascii connection. Returns -1 or byte count /// including final 0. \n is kept virtual int getline(char *buf, int cnt, int timeo = -1); + /// Set handler to be called when the connection is placed in the /// selectloop and an event occurs. - virtual void setcallback(RefCntr user) { + virtual void setcallback(std::shared_ptr user) { m_user = user; } private: - char *m_buf; // Buffer. Only used when doing getline()s + + char *m_buf; // Buffer. Only used when doing getline()s char *m_bufbase; // Pointer to current 1st byte of useful data - int m_bufbytes; // Bytes of data. - int m_bufsize; // Total buffer size - RefCntr m_user; + int m_bufbytes; // Bytes of data. + int m_bufsize; // Total buffer size + + int m_wkfds[2]; + + std::shared_ptr m_user; virtual int cando(Netcon::Event reason); // Selectloop slot }; /// Network endpoint, client side. -class NetconCli : public NetconData { +class NetconCli : public NetconData { public: - NetconCli(int silent = 0) { - m_silentconnectfailure = silent; + NetconCli(bool cancellable = false) + : NetconData(cancellable), m_silentconnectfailure(false) { } /// Open connection to specified host and named service. Set host @@ -270,7 +281,7 @@ public: /// AF_UNIX service. serv is ignored in this case. int openconn(const char *host, unsigned int port, int timeo = -1); - /// Reuse existing fd. + /// Reuse existing fd. /// We DONT take ownership of the fd, and do no closin' EVEN on an /// explicit closeconn() or setconn() (use getfd(), close, /// setconn(-1) if you need to really close the fd and have no @@ -278,12 +289,12 @@ public: int setconn(int fd); /// Do not log message if openconn() fails. - void setSilentFail(int onoff) { + void setSilentFail(bool onoff) { m_silentconnectfailure = onoff; } private: - int m_silentconnectfailure; // No logging of connection failures if set + bool m_silentconnectfailure; // No logging of connection failures if set }; class NetconServCon; @@ -310,9 +321,9 @@ class NetconServLis : public Netcon { public: NetconServLis() { #ifdef NETCON_ACCESSCONTROL - permsinit = 0; - okaddrs.len = okmasks.len = 0; - okaddrs.intarray = okmasks.intarray = 0; + permsinit = 0; + okaddrs.len = okmasks.len = 0; + okaddrs.intarray = okmasks.intarray = 0; #endif /* NETCON_ACCESSCONTROL */ } ~NetconServLis(); @@ -321,7 +332,7 @@ public: int openservice(const char *serv, int backlog = 10); /// Open service by port number. int openservice(int port, int backlog = 10); - /// Wait for incoming connection. Returned connected Netcon + /// Wait for incoming connection. Returned connected Netcon NetconServCon *accept(int timeo = -1); protected: @@ -331,7 +342,7 @@ protected: virtual int cando(Netcon::Event reason); // Empty if port was numeric, else service name or socket path - std::string m_serv; + std::string m_serv; private: #ifdef NETCON_ACCESSCONTROL @@ -347,16 +358,16 @@ private: /// Server-side accepted client connection. The only specific code /// allows closing the listening endpoint in the child process (in the /// case of a forking server) -class NetconServCon : public NetconData { +class NetconServCon : public NetconData { public: - NetconServCon(int newfd, Netcon* lis = 0) { - m_liscon = lis; - m_fd = newfd; + NetconServCon(int newfd, Netcon* lis = 0) { + m_liscon = lis; + m_fd = newfd; } /// This is for forked servers that want to get rid of the main socket void closeLisCon() { - if (m_liscon) { - m_liscon->closeconn(); + if (m_liscon) { + m_liscon->closeconn(); } } private: diff --git a/src/utils/pathut.cpp b/src/utils/pathut.cpp index b722de91..7af30cbe 100644 --- a/src/utils/pathut.cpp +++ b/src/utils/pathut.cpp @@ -16,34 +16,34 @@ */ #ifndef TEST_PATHUT +#ifdef BUILDING_RECOLL #include "autoconfig.h" +#else +#include "config.h" +#endif #include -#include -#include -#include -#include -#include #include #include -#include + +#ifdef _WIN32 +#include "dirent.h" +#include "safefcntl.h" +#include "safeunistd.h" +#include "safewindows.h" +#include "safesysstat.h" + +#else // Not windows -> +#include +#include +#include +#include #include #include -#include - -// Let's include all files where statfs can be defined and hope for no -// conflict... -#ifdef HAVE_SYS_MOUNT_H -#include -#endif -#ifdef HAVE_SYS_STATFS_H -#include -#endif -#ifdef HAVE_SYS_STATVFS_H +#include #include -#endif -#ifdef HAVE_SYS_VFS_H -#include +#include + #endif #include @@ -52,379 +52,477 @@ #include #include #include -using namespace std; +#include #include "pathut.h" -#include "transcode.h" -#include "wipedir.h" -#include "md5ut.h" +#include "smallut.h" -bool fsocc(const string &path, int *pc, long long *blocks) +using namespace std; + +#ifdef _WIN32 +/// Convert \ separators to / +void path_slashize(string& s) { -#ifdef sun + for (string::size_type i = 0; i < s.size(); i++) { + if (s[i] == '\\') { + s[i] = '/'; + } + } +} +void path_backslashize(string& s) +{ + for (string::size_type i = 0; i < s.size(); i++) { + if (s[i] == '/') { + s[i] = '\\'; + } + } +} +static bool path_strlookslikedrive(const string& s) +{ + return s.size() == 2 && isalpha(s[0]) && s[1] == ':'; +} + +static bool path_hasdrive(const string& s) +{ + if (s.size() >= 2 && isalpha(s[0]) && s[1] == ':') { + return true; + } + return false; +} +static bool path_isdriveabs(const string& s) +{ + if (s.size() >= 3 && isalpha(s[0]) && s[1] == ':' && s[2] == '/') { + return true; + } + return false; +} +#endif + +bool fsocc(const string& path, int *pc, long long *avmbs) +{ + static const int FSOCC_MB = 1024 * 1024; +#ifdef _WIN32 + ULARGE_INTEGER freebytesavail; + ULARGE_INTEGER totalbytes; + if (!GetDiskFreeSpaceEx(path.c_str(), &freebytesavail, + &totalbytes, NULL)) { + return false; + } + if (pc) { + *pc = int((100 * freebytesavail.QuadPart) / totalbytes.QuadPart); + } + if (avmbs) { + *avmbs = int(totalbytes.QuadPart / FSOCC_MB); + } + return true; +#else // not windows -> + struct statvfs buf; if (statvfs(path.c_str(), &buf) != 0) { - return false; + return false; } -#else - struct statfs buf; - if (statfs(path.c_str(), &buf) != 0) { - return false; + + if (pc) { + double fsocc_used = double(buf.f_blocks - buf.f_bfree); + double fsocc_totavail = fsocc_used + double(buf.f_bavail); + double fpc = 100.0; + if (fsocc_totavail > 0) { + fpc = 100.0 * fsocc_used / fsocc_totavail; + } + *pc = int(fpc); } + if (avmbs) { + *avmbs = 0; + if (buf.f_bsize > 0) { + int ratio = buf.f_frsize > FSOCC_MB ? buf.f_frsize / FSOCC_MB : + FSOCC_MB / buf.f_frsize; + + *avmbs = buf.f_frsize > FSOCC_MB ? + ((long long)buf.f_bavail) * ratio : + ((long long)buf.f_bavail) / ratio; + } + } + return true; #endif - - // used blocks - double fpc = 0.0; -#define FSOCC_USED (double(buf.f_blocks - buf.f_bfree)) -#define FSOCC_TOTAVAIL (FSOCC_USED + double(buf.f_bavail)) - if (FSOCC_TOTAVAIL > 0) { - fpc = 100.0 * FSOCC_USED / FSOCC_TOTAVAIL; - } - *pc = int(fpc); - if (blocks) { - *blocks = 0; -#define FSOCC_MB (1024*1024) - if (buf.f_bsize > 0) { - int ratio = buf.f_bsize > FSOCC_MB ? buf.f_bsize / FSOCC_MB : - FSOCC_MB / buf.f_bsize; - - *blocks = buf.f_bsize > FSOCC_MB ? - ((long long)buf.f_bavail) * ratio : - ((long long)buf.f_bavail) / ratio; - } - } - return true; } -const string& tmplocation() + +string path_PATHsep() { - static string stmpdir; - if (stmpdir.empty()) { - const char *tmpdir = getenv("RECOLL_TMPDIR"); - if (tmpdir == 0) - tmpdir = getenv("TMPDIR"); - if (tmpdir == 0) - tmpdir = "/tmp"; - stmpdir = string(tmpdir); - } - return stmpdir; -} - -bool maketmpdir(string& tdir, string& reason) -{ - tdir = path_cat(tmplocation(), "rcltmpXXXXXX"); - - char *cp = strdup(tdir.c_str()); - if (!cp) { - reason = "maketmpdir: out of memory (for file name !)\n"; - tdir.erase(); - return false; - } - - if (! -#ifdef HAVE_MKDTEMP - mkdtemp(cp) + static const string w(";"); + static const string u(":"); +#ifdef _WIN32 + return w; #else - mktemp(cp) -#endif // HAVE_MKDTEMP - ) { - free(cp); - reason = "maketmpdir: mktemp failed for [" + tdir + "] : " + - strerror(errno); - tdir.erase(); - return false; - } - tdir = cp; - free(cp); - -#ifndef HAVE_MKDTEMP - if (mkdir(tdir.c_str(), 0700) < 0) { - reason = string("maketmpdir: mkdir ") + tdir + " failed"; - tdir.erase(); - return false; - } + return u; #endif - - return true; } -TempFileInternal::TempFileInternal(const string& suffix) - : m_noremove(false) +void path_catslash(string& s) { - string filename = path_cat(tmplocation(), "rcltmpfXXXXXX"); - char *cp = strdup(filename.c_str()); - if (!cp) { - m_reason = "Out of memory (for file name !)\n"; - return; - } - - // Yes using mkstemp this way is awful (bot the suffix adding and - // using mkstemp() just to avoid the warnings) - int fd; - if ((fd = mkstemp(cp)) < 0) { - free(cp); - m_reason = "TempFileInternal: mkstemp failed\n"; - return; - } - close(fd); - unlink(cp); - - filename = cp; - free(cp); - - m_filename = filename + suffix; - if (close(open(m_filename.c_str(), O_CREAT|O_EXCL, 0600)) != 0) { - m_reason = string("Could not open/create") + m_filename; - m_filename.erase(); +#ifdef _WIN32 + path_slashize(s); +#endif + if (s.empty() || s[s.length() - 1] != '/') { + s += '/'; } } -TempFileInternal::~TempFileInternal() +string path_cat(const string& s1, const string& s2) { - if (!m_filename.empty() && !m_noremove) - unlink(m_filename.c_str()); -} - -TempDir::TempDir() -{ - if (!maketmpdir(m_dirname, m_reason)) { - m_dirname.erase(); - return; - } -} - -TempDir::~TempDir() -{ - if (!m_dirname.empty()) { - (void)wipedir(m_dirname, true, true); - m_dirname.erase(); - } -} - -bool TempDir::wipe() -{ - if (m_dirname.empty()) { - m_reason = "TempDir::wipe: no directory !\n"; - return false; - } - if (wipedir(m_dirname, false, true)) { - m_reason = "TempDir::wipe: wipedir failed\n"; - return false; - } - return true; -} - -void path_catslash(string &s) { - if (s.empty() || s[s.length() - 1] != '/') - s += '/'; -} - -string path_cat(const string &s1, const string &s2) { string res = s1; path_catslash(res); res += s2; return res; } -string path_getfather(const string &s) { +string path_getfather(const string& s) +{ string father = s; +#ifdef _WIN32 + path_slashize(father); +#endif // ?? - if (father.empty()) - return "./"; + if (father.empty()) { + return "./"; + } + + if (path_isroot(father)) { + return father; + } if (father[father.length() - 1] == '/') { - // Input ends with /. Strip it, handle special case for root - if (father.length() == 1) - return father; - father.erase(father.length()-1); + // Input ends with /. Strip it, root special case was tested above + father.erase(father.length() - 1); } string::size_type slp = father.rfind('/'); - if (slp == string::npos) - return "./"; + if (slp == string::npos) { + return "./"; + } father.erase(slp); path_catslash(father); return father; } -string path_getsimple(const string &s) { +string path_getsimple(const string& s) +{ string simple = s; +#ifdef _WIN32 + path_slashize(simple); +#endif - if (simple.empty()) - return simple; + if (simple.empty()) { + return simple; + } string::size_type slp = simple.rfind('/'); - if (slp == string::npos) - return simple; + if (slp == string::npos) { + return simple; + } - simple.erase(0, slp+1); + simple.erase(0, slp + 1); return simple; } -string path_basename(const string &s, const string &suff) +string path_basename(const string& s, const string& suff) { string simple = path_getsimple(s); string::size_type pos = string::npos; if (suff.length() && simple.length() > suff.length()) { - pos = simple.rfind(suff); - if (pos != string::npos && pos + suff.length() == simple.length()) - return simple.substr(0, pos); - } + pos = simple.rfind(suff); + if (pos != string::npos && pos + suff.length() == simple.length()) { + return simple.substr(0, pos); + } + } return simple; } string path_suffix(const string& s) { string::size_type dotp = s.rfind('.'); - if (dotp == string::npos) - return string(); - return s.substr(dotp+1); + if (dotp == string::npos) { + return string(); + } + return s.substr(dotp + 1); } string path_home() { +#ifdef _WIN32 + string dir; + const char *cp = getenv("USERPROFILE"); + if (cp != 0) { + dir = cp; + } + if (dir.empty()) { + cp = getenv("HOMEDRIVE"); + if (cp != 0) { + const char *cp1 = getenv("HOMEPATH"); + if (cp1 != 0) { + dir = string(cp) + string(cp1); + } + } + } + if (dir.empty()) { + dir = "C:\\"; + } + dir = path_canon(dir); + path_catslash(dir); + return dir; +#else uid_t uid = getuid(); struct passwd *entry = getpwuid(uid); if (entry == 0) { - const char *cp = getenv("HOME"); - if (cp) - return cp; - else - return "/"; + const char *cp = getenv("HOME"); + if (cp) { + return cp; + } else { + return "/"; + } } string homedir = entry->pw_dir; path_catslash(homedir); return homedir; +#endif } -string path_tildexpand(const string &s) +// The default place to store the default config and other stuff (e.g webqueue) +string path_homedata() { - if (s.empty() || s[0] != '~') - return s; +#ifdef _WIN32 + const char *cp = getenv("LOCALAPPDATA"); + string dir; + if (cp != 0) { + dir = path_canon(cp); + } + if (dir.empty()) { + dir = path_cat(path_home(), "AppData/Local/"); + } + return dir; +#else + // We should use an xdg-conforming location, but, history... + return path_home(); +#endif +} + +string path_tildexpand(const string& s) +{ + if (s.empty() || s[0] != '~') { + return s; + } string o = s; +#ifdef _WIN32 + path_slashize(o); +#endif + if (s.length() == 1) { - o.replace(0, 1, path_home()); - } else if (s[1] == '/') { - o.replace(0, 2, path_home()); + o.replace(0, 1, path_home()); + } else if (s[1] == '/') { + o.replace(0, 2, path_home()); } else { - string::size_type pos = s.find('/'); - int l = (pos == string::npos) ? s.length() - 1 : pos - 1; - struct passwd *entry = getpwnam(s.substr(1, l).c_str()); - if (entry) - o.replace(0, l+1, entry->pw_dir); + string::size_type pos = s.find('/'); + string::size_type l = (pos == string::npos) ? s.length() - 1 : pos - 1; +#ifdef _WIN32 + // Dont know what this means. Just replace with HOME + o.replace(0, l + 1, path_home()); +#else + struct passwd *entry = getpwnam(s.substr(1, l).c_str()); + if (entry) { + o.replace(0, l + 1, entry->pw_dir); + } +#endif } return o; } -string path_absolute(const string &is) +bool path_isroot(const string& path) { - if (is.length() == 0) - return is; + if (path.size() == 1 && path[0] == '/') { + return true; + } +#ifdef _WIN32 + if (path.size() == 3 && isalpha(path[0]) && path[1] == ':' && + (path[2] == '/' || path[2] == '\\')) { + return true; + } +#endif + return false; +} + +bool path_isabsolute(const string& path) +{ + if (!path.empty() && (path[0] == '/' +#ifdef _WIN32 + || path_isdriveabs(path) +#endif + )) { + return true; + } + return false; +} + +string path_absolute(const string& is) +{ + if (is.length() == 0) { + return is; + } string s = is; - if (s[0] != '/') { - char buf[MAXPATHLEN]; - if (!getcwd(buf, MAXPATHLEN)) { - return string(); - } - s = path_cat(string(buf), s); +#ifdef _WIN32 + path_slashize(s); +#endif + if (!path_isabsolute(s)) { + char buf[MAXPATHLEN]; + if (!getcwd(buf, MAXPATHLEN)) { + return string(); + } + s = path_cat(string(buf), s); +#ifdef _WIN32 + path_slashize(s); +#endif } return s; } -#include -string path_canon(const string &is, const string* cwd) +string path_canon(const string& is, const string* cwd) { - if (is.length() == 0) - return is; + if (is.length() == 0) { + return is; + } string s = is; - if (s[0] != '/') { - char buf[MAXPATHLEN]; - const char *cwdp = buf; - if (cwd) { - cwdp = cwd->c_str(); - } else { - if (!getcwd(buf, MAXPATHLEN)) { - return string(); - } - } - s = path_cat(string(cwdp), s); +#ifdef _WIN32 + path_slashize(s); + // fix possible path from file: absolute url + if (s.size() && s[0] == '/' && path_hasdrive(s.substr(1))) { + s = s.substr(1); + } +#endif + + if (!path_isabsolute(s)) { + char buf[MAXPATHLEN]; + const char *cwdp = buf; + if (cwd) { + cwdp = cwd->c_str(); + } else { + if (!getcwd(buf, MAXPATHLEN)) { + return string(); + } + } + s = path_cat(string(cwdp), s); } vector elems; stringToTokens(s, elems, "/"); vector cleaned; - for (vector::const_iterator it = elems.begin(); - it != elems.end(); it++){ - if (*it == "..") { - if (!cleaned.empty()) - cleaned.pop_back(); - } else if (it->empty() || *it == ".") { - } else { - cleaned.push_back(*it); - } + for (vector::const_iterator it = elems.begin(); + it != elems.end(); it++) { + if (*it == "..") { + if (!cleaned.empty()) { + cleaned.pop_back(); + } + } else if (it->empty() || *it == ".") { + } else { + cleaned.push_back(*it); + } } string ret; if (!cleaned.empty()) { - for (vector::const_iterator it = cleaned.begin(); - it != cleaned.end(); it++) { - ret += "/"; - ret += *it; - } + for (vector::const_iterator it = cleaned.begin(); + it != cleaned.end(); it++) { + ret += "/"; +#ifdef _WIN32 + if (it == cleaned.begin() && path_strlookslikedrive(*it)) { + // Get rid of just added initial "/" + ret.clear(); + } +#endif + ret += *it; + } } else { - ret = "/"; + ret = "/"; } return ret; } -bool makepath(const string& ipath) +bool path_makepath(const string& ipath, int mode) { string path = path_canon(ipath); vector elems; stringToTokens(path, elems, "/"); path = "/"; - for (vector::const_iterator it = elems.begin(); - it != elems.end(); it++){ - path += *it; - // Not using path_isdir() here, because this cant grok symlinks - // If we hit an existing file, no worry, mkdir will just fail. - if (access(path.c_str(), 0) != 0) { - if (mkdir(path.c_str(), 0700) != 0) { - return false; - } - } - path += "/"; + for (vector::const_iterator it = elems.begin(); + it != elems.end(); it++) { +#ifdef _WIN32 + if (it == elems.begin() && path_strlookslikedrive(*it)) { + path = ""; + } +#endif + path += *it; + // Not using path_isdir() here, because this cant grok symlinks + // If we hit an existing file, no worry, mkdir will just fail. + if (access(path.c_str(), 0) != 0) { + if (mkdir(path.c_str(), mode) != 0) { + return false; + } + } + path += "/"; } return true; } -vector path_dirglob(const string &dir, const string pattern) -{ - vector res; - glob_t mglob; - string mypat=path_cat(dir, pattern); - if (glob(mypat.c_str(), 0, 0, &mglob)) { - return res; - } - for (int i = 0; i < int(mglob.gl_pathc); i++) { - res.push_back(mglob.gl_pathv[i]); - } - globfree(&mglob); - return res; -} - bool path_isdir(const string& path) { struct stat st; - if (lstat(path.c_str(), &st) < 0) - return false; - if (S_ISDIR(st.st_mode)) - return true; + if (lstat(path.c_str(), &st) < 0) { + return false; + } + if (S_ISDIR(st.st_mode)) { + return true; + } return false; } +long long path_filesize(const string& path) +{ + struct stat st; + if (stat(path.c_str(), &st) < 0) { + return -1; + } + return (long long)st.st_size; +} + +int path_fileprops(const std::string path, struct stat *stp, bool follow) +{ + if (!stp) { + return -1; + } + memset(stp, 0, sizeof(struct stat)); + struct stat mst; + int ret = follow ? stat(path.c_str(), &mst) : lstat(path.c_str(), &mst); + if (ret != 0) { + return ret; + } + stp->st_size = mst.st_size; + stp->st_mode = mst.st_mode; + stp->st_mtime = mst.st_mtime; +#ifdef _WIN32 + stp->st_ctime = mst.st_mtime; +#else + stp->st_ino = mst.st_ino; + stp->st_dev = mst.st_dev; + stp->st_ctime = mst.st_ctime; +#endif + return 0; +} + +bool path_exists(const string& path) +{ + return access(path.c_str(), 0) == 0; +} + // Allowed punctuation in the path part of an URI according to RFC2396 // -_.!~*'():@&=+$, /* @@ -439,7 +537,7 @@ bool path_isdir(const string& path) 29 ) 2A * 2B + -2C , +2C , 2D - 2E . 2F / @@ -476,32 +574,32 @@ string url_encode(const string& url, string::size_type offs) string out = url.substr(0, offs); const char *cp = url.c_str(); for (string::size_type i = offs; i < url.size(); i++) { - unsigned int c; - const char *h = "0123456789ABCDEF"; - c = cp[i]; - if (c <= 0x20 || - c >= 0x7f || - c == '"' || - c == '#' || - c == '%' || - c == ';' || - c == '<' || - c == '>' || - c == '?' || - c == '[' || - c == '\\' || - c == ']' || - c == '^' || - c == '`' || - c == '{' || - c == '|' || - c == '}' ) { - out += '%'; - out += h[(c >> 4) & 0xf]; - out += h[c & 0xf]; - } else { - out += char(c); - } + unsigned int c; + const char *h = "0123456789ABCDEF"; + c = cp[i]; + if (c <= 0x20 || + c >= 0x7f || + c == '"' || + c == '#' || + c == '%' || + c == ';' || + c == '<' || + c == '>' || + c == '?' || + c == '[' || + c == '\\' || + c == ']' || + c == '^' || + c == '`' || + c == '{' || + c == '|' || + c == '}') { + out += '%'; + out += h[(c >> 4) & 0xf]; + out += h[c & 0xf]; + } else { + out += char(c); + } } return out; } @@ -510,20 +608,22 @@ string url_gpath(const string& url) { // Remove the access schema part (or whatever it's called) string::size_type colon = url.find_first_of(":"); - if (colon == string::npos || colon == url.size() - 1) + if (colon == string::npos || colon == url.size() - 1) { return url; + } // If there are non-alphanum chars before the ':', then there // probably is no scheme. Whatever... for (string::size_type i = 0; i < colon; i++) { - if (!isalnum(url.at(i))) + if (!isalnum(url.at(i))) { return url; + } } // In addition we canonize the path to remove empty host parts // (for compatibility with older versions of recoll where file:// // was hardcoded, but the local path was used for doc // identification. - return path_canon(url.substr(colon+1)); + return path_canon(url.substr(colon + 1)); } string url_parentfolder(const string& url) @@ -537,22 +637,53 @@ string url_parentfolder(const string& url) parenturl = url_gpath(url); } return isfileurl ? string("file://") + parenturl : - string("http://") + parenturl; + string("http://") + parenturl; } + // Convert to file path if url is like file: // Note: this only works with our internal pseudo-urls which are not // encoded/escaped string fileurltolocalpath(string url) { - if (url.find("file://") == 0) + if (url.find("file://") == 0) { url = url.substr(7, string::npos); - else + } else { return string(); - string::size_type pos; - if ((pos = url.find_last_of("#")) != string::npos) { - url.erase(pos); } + +#ifdef _WIN32 + // Absolute file urls are like: file:///c:/mydir/... + // Get rid of the initial '/' + if (url.size() >= 3 && url[0] == '/' && isalpha(url[1]) && url[2] == ':') { + url = url.substr(1); + } +#endif + + // Removing the fragment part. This is exclusively used when + // executing a viewer for the recoll manual, and we only strip the + // part after # if it is preceded by .html + string::size_type pos; + if ((pos = url.rfind(".html#")) != string::npos) { + url.erase(pos + 5); + } else if ((pos = url.rfind(".htm#")) != string::npos) { + url.erase(pos + 4); + } + + return url; +} + +static const string cstr_fileu("file://"); + +string path_pathtofileurl(const string& path) +{ + // We're supposed to receive a canonic absolute path, but on windows we + // may need to add a '/' in front of the drive spec + string url(cstr_fileu); + if (path.empty() || path[0] != '/') { + url.push_back('/'); + } + url += path; return url; } @@ -561,17 +692,6 @@ bool urlisfileurl(const string& url) return url.find("file://") == 0; } -// Printable url: this is used to transcode from the system charset -// into either utf-8 if transcoding succeeds, or url-encoded -bool printableUrl(const string &fcharset, const string &in, string &out) -{ - int ecnt = 0; - if (!transcode(in, out, fcharset, "UTF-8", &ecnt) || ecnt) { - out = url_encode(in, 7); - } - return true; -} - bool readdir(const string& dir, string& reason, set& entries) { struct stat st; @@ -580,37 +700,40 @@ bool readdir(const string& dir, string& reason, set& entries) DIR *d = 0; statret = lstat(dir.c_str(), &st); if (statret == -1) { - msg << "readdir: cant stat " << dir << " errno " << errno; - goto out; + msg << "readdir: cant stat " << dir << " errno " << errno; + goto out; } if (!S_ISDIR(st.st_mode)) { - msg << "readdir: " << dir << " not a directory"; - goto out; + msg << "readdir: " << dir << " not a directory"; + goto out; } if (access(dir.c_str(), R_OK) < 0) { - msg << "readdir: no read access to " << dir; - goto out; + msg << "readdir: no read access to " << dir; + goto out; } d = opendir(dir.c_str()); if (d == 0) { - msg << "readdir: cant opendir " << dir << ", errno " << errno; - goto out; + msg << "readdir: cant opendir " << dir << ", errno " << errno; + goto out; } struct dirent *ent; while ((ent = readdir(d)) != 0) { - if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) - continue; - entries.insert(ent->d_name); + if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) { + continue; + } + entries.insert(ent->d_name); } out: - if (d) - closedir(d); + if (d) { + closedir(d); + } reason = msg.str(); - if (reason.empty()) - return true; + if (reason.empty()) { + return true; + } return false; } @@ -621,36 +744,40 @@ out: // alone. Pidfile::~Pidfile() { - if (m_fd >= 0) - ::close(m_fd); + if (m_fd >= 0) { + ::close(m_fd); + } m_fd = -1; } pid_t Pidfile::read_pid() { int fd = ::open(m_path.c_str(), O_RDONLY); - if (fd == -1) - return (pid_t)-1; + if (fd == -1) { + return (pid_t) - 1; + } char buf[16]; int i = read(fd, buf, sizeof(buf) - 1); ::close(fd); - if (i <= 0) - return (pid_t)-1; + if (i <= 0) { + return (pid_t) - 1; + } buf[i] = '\0'; char *endptr; pid_t pid = strtol(buf, &endptr, 10); - if (endptr != &buf[i]) - return (pid_t)-1; + if (endptr != &buf[i]) { + return (pid_t) - 1; + } return pid; } int Pidfile::flopen() { const char *path = m_path.c_str(); - if ((m_fd = ::open(path, O_RDWR|O_CREAT, 0644)) == -1) { - m_reason = "Open failed: [" + m_path + "]: " + strerror(errno); - return -1; + if ((m_fd = ::open(path, O_RDWR | O_CREAT, 0644)) == -1) { + m_reason = "Open failed: [" + m_path + "]: " + strerror(errno); + return -1; } #ifdef sun @@ -660,30 +787,34 @@ int Pidfile::flopen() lockdata.l_type = F_WRLCK; lockdata.l_whence = SEEK_SET; if (fcntl(m_fd, F_SETLK, &lockdata) != 0) { - int serrno = errno; - (void)::close(m_fd); - errno = serrno; - m_reason = "fcntl lock failed"; - return -1; + int serrno = errno; + (void)::close(m_fd); + errno = serrno; + m_reason = "fcntl lock failed"; + return -1; } +#else +#ifdef _WIN32 + return 0; #else int operation = LOCK_EX | LOCK_NB; if (flock(m_fd, operation) == -1) { - int serrno = errno; - (void)::close(m_fd); - errno = serrno; - m_reason = "flock failed"; - return -1; + int serrno = errno; + (void)::close(m_fd); + errno = serrno; + m_reason = "flock failed"; + return -1; } +#endif // ! win32 #endif // ! sun if (ftruncate(m_fd, 0) != 0) { - /* can't happen [tm] */ - int serrno = errno; - (void)::close(m_fd); - errno = serrno; - m_reason = "ftruncate failed"; - return -1; + /* can't happen [tm] */ + int serrno = errno; + (void)::close(m_fd); + errno = serrno; + m_reason = "ftruncate failed"; + return -1; } return 0; } @@ -691,7 +822,7 @@ int Pidfile::flopen() pid_t Pidfile::open() { if (flopen() < 0) { - return read_pid(); + return read_pid(); } return (pid_t)0; } @@ -700,15 +831,15 @@ int Pidfile::write_pid() { /* truncate to allow multiple calls */ if (ftruncate(m_fd, 0) == -1) { - m_reason = "ftruncate failed"; - return -1; + m_reason = "ftruncate failed"; + return -1; } char pidstr[20]; sprintf(pidstr, "%u", int(getpid())); lseek(m_fd, 0, 0); if (::write(m_fd, pidstr, strlen(pidstr)) != (ssize_t)strlen(pidstr)) { - m_reason = "write failed"; - return -1; + m_reason = "write failed"; + return -1; } return 0; } @@ -723,78 +854,10 @@ int Pidfile::remove() return unlink(m_path.c_str()); } - -// Freedesktop standard paths for cache directory (thumbnails are now in there) -static const string& xdgcachedir() -{ - static string xdgcache; - if (xdgcache.empty()) { - const char *cp = getenv("XDG_CACHE_HOME"); - if (cp == 0) - xdgcache = path_cat(path_home(), ".cache"); - else - xdgcache = string(cp); - } - return xdgcache; -} -static const string& thumbnailsdir() -{ - static string thumbnailsd; - if (thumbnailsd.empty()) { - thumbnailsd = path_cat(xdgcachedir(), "thumbnails"); - if (access(thumbnailsd.c_str(), 0) != 0) { - thumbnailsd = path_cat(path_home(), ".thumbnails"); - } - } - return thumbnailsd; -} - -// Place for 256x256 files -static const string thmbdirlarge = "large"; -// 128x128 -static const string thmbdirnormal = "normal"; - -static void thumbname(const string& url, string& name) -{ - string digest; - string l_url = url_encode(url); - MD5String(l_url, digest); - MD5HexPrint(digest, name); - name += ".png"; -} - -bool thumbPathForUrl(const string& url, int size, string& path) -{ - string name; - thumbname(url, name); - if (size <= 128) { - path = path_cat(thumbnailsdir(), thmbdirnormal); - path = path_cat(path, name); - if (access(path.c_str(), R_OK) == 0) { - return true; - } - } - path = path_cat(thumbnailsdir(), thmbdirlarge); - path = path_cat(path, name); - if (access(path.c_str(), R_OK) == 0) { - return true; - } - - // File does not exist. Path corresponds to the large version at this point, - // fix it if needed. - if (size <= 128) { - path = path_cat(path_home(), thmbdirnormal); - path = path_cat(path, name); - } - return false; -} - // Call funcs that need static init (not initially reentrant) void pathut_init_mt() { path_home(); - tmplocation(); - thumbnailsdir(); } @@ -809,8 +872,9 @@ void path_to_thumb(const string& _input) { string input(_input); // Make absolute path if needed - if (input[0] != '/') + if (input[0] != '/') { input = path_absolute(input); + } input = string("file://") + path_canon(input); @@ -821,130 +885,171 @@ void path_to_thumb(const string& _input) } const char *tstvec[] = {"", "/", "/dir", "/dir/", "/dir1/dir2", - "/dir1/dir2", - "./dir", "./dir1/", "dir", "../dir", "/dir/toto.c", - "/dir/.c", "/dir/toto.txt", "toto.txt1" -}; + "/dir1/dir2", + "./dir", "./dir1/", "dir", "../dir", "/dir/toto.c", + "/dir/.c", "/dir/toto.txt", "toto.txt1" + }; const string ttvec[] = {"/dir", "", "~", "~/sub", "~root", "~root/sub", - "~nosuch", "~nosuch/sub"}; + "~nosuch", "~nosuch/sub" + }; int nttvec = sizeof(ttvec) / sizeof(string); const char *thisprog; int main(int argc, const char **argv) { - thisprog = *argv++;argc--; + thisprog = *argv++; + argc--; string s; vector::const_iterator it; #if 0 - for (unsigned int i = 0;i < sizeof(tstvec) / sizeof(char *); i++) { - cout << tstvec[i] << " Father " << path_getfather(tstvec[i]) << endl; + for (unsigned int i = 0; i < sizeof(tstvec) / sizeof(char *); i++) { + cout << tstvec[i] << " Father " << path_getfather(tstvec[i]) << endl; } - for (unsigned int i = 0;i < sizeof(tstvec) / sizeof(char *); i++) { - cout << tstvec[i] << " Simple " << path_getsimple(tstvec[i]) << endl; + for (unsigned int i = 0; i < sizeof(tstvec) / sizeof(char *); i++) { + cout << tstvec[i] << " Simple " << path_getsimple(tstvec[i]) << endl; } - for (unsigned int i = 0;i < sizeof(tstvec) / sizeof(char *); i++) { - cout << tstvec[i] << " Basename " << - path_basename(tstvec[i], ".txt") << endl; + for (unsigned int i = 0; i < sizeof(tstvec) / sizeof(char *); i++) { + cout << tstvec[i] << " Basename " << + path_basename(tstvec[i], ".txt") << endl; } #endif #if 0 for (int i = 0; i < nttvec; i++) { - cout << "tildexp: '" << ttvec[i] << "' -> '" << - path_tildexpand(ttvec[i]) << "'" << endl; + cout << "tildexp: '" << ttvec[i] << "' -> '" << + path_tildexpand(ttvec[i]) << "'" << endl; } #endif #if 0 - const string canontst[] = {"/dir1/../../..", "/////", "", - "/dir1/../../.././/////dir2///////", - "../../", - "../../../../../../../../../../" - }; + const string canontst[] = {"/dir1/../../..", "/////", "", + "/dir1/../../.././/////dir2///////", + "../../", + "../../../../../../../../../../" + }; unsigned int nttvec = sizeof(canontst) / sizeof(string); for (unsigned int i = 0; i < nttvec; i++) { - cout << "canon: '" << canontst[i] << "' -> '" << - path_canon(canontst[i]) << "'" << endl; + cout << "canon: '" << canontst[i] << "' -> '" << + path_canon(canontst[i]) << "'" << endl; } -#endif +#endif #if 0 if (argc != 2) { - cerr << "Usage: trpathut " << endl; - exit(1); + cerr << "Usage: trpathut " << endl; + exit(1); } - string dir = *argv++;argc--; - string pattern = *argv++;argc--; + string dir = *argv++; + argc--; + string pattern = *argv++; + argc--; vector matched = path_dirglob(dir, pattern); - for (it = matched.begin(); it != matched.end();it++) { - cout << *it << endl; + for (it = matched.begin(); it != matched.end(); it++) { + cout << *it << endl; } #endif #if 0 if (argc != 1) { - fprintf(stderr, "Usage: fsocc: trpathut \n"); - exit(1); + fprintf(stderr, "Usage: fsocc: trpathut \n"); + exit(1); } - string path = *argv++;argc--; + string path = *argv++; + argc--; - int pc; - long long blocks; - if (!fsocc(path, &pc, &blocks)) { - fprintf(stderr, "fsocc failed\n"); - return 1; - } - printf("pc %d, megabytes %ld\n", pc, blocks); + int pc; + long long blocks; + if (!fsocc(path, &pc, &blocks)) { + fprintf(stderr, "fsocc failed\n"); + return 1; + } + printf("pc %d, megabytes %ld\n", pc, blocks); #endif #if 0 - Pidfile pidfile("/tmp/pathutpidfile"); - pid_t pid; - if ((pid = pidfile.open()) != 0) { - cerr << "open failed. reason: " << pidfile.getreason() << - " return " << pid << endl; - exit(1); - } - pidfile.write_pid(); - sleep(10); - pidfile.close(); - pidfile.remove(); + Pidfile pidfile("/tmp/pathutpidfile"); + pid_t pid; + if ((pid = pidfile.open()) != 0) { + cerr << "open failed. reason: " << pidfile.getreason() << + " return " << pid << endl; + exit(1); + } + pidfile.write_pid(); + sleep(10); + pidfile.close(); + pidfile.remove(); #endif -#if 1 - if (argc > 1) { - cerr << "Usage: thumbpath " << endl; - exit(1); - } - string input; - if (argc == 1) { - input = *argv++; - if (input.empty()) { - cerr << "Usage: thumbpath " << endl; - exit(1); - } - path_to_thumb(input); - } else { - while (getline(cin, input)) - path_to_thumb(input); - } +#if 0 + if (argc > 1) { + cerr << "Usage: thumbpath " << endl; + exit(1); + } + string input; + if (argc == 1) { + input = *argv++; + if (input.empty()) { + cerr << "Usage: thumbpath " << endl; + exit(1); + } + path_to_thumb(input); + } else { + while (getline(cin, input)) { + path_to_thumb(input); + } + } - - exit(0); + + exit(0); #endif #if 0 if (argc != 1) { - cerr << "Usage: trpathut " << endl; - exit(1); + cerr << "Usage: trpathut " << endl; + exit(1); } - string fn = *argv++;argc--; + string fn = *argv++; + argc--; string ext = path_suffix(fn); cout << "Suffix: [" << ext << "]" << endl; return 0; #endif + +#if 0 + if (argc != 1) { + cerr << "Usage: trpathut url" << endl; + exit(1); + } + string url = *argv++; + argc--; + + cout << "File: [" << fileurltolocalpath(url) << "]\n"; + return 0; +#endif + +#if 1 + if (argc != 1) { + cerr << "Usage: trpathut path" << endl; + exit(1); + } + string path = *argv++; + argc--; + + int pc; + long long avmbs; + if (fsocc(path, &pc, &avmbs)) { + cout << "Percent occup " << pc << " avmbs " << avmbs << endl; + return 0; + } else { + cerr << "fsocc failed\n"; + return 1; + } +#endif + + + } #endif // TEST_PATHUT diff --git a/src/utils/pathut.h b/src/utils/pathut.h index 8fe73b59..e6b9e0cc 100644 --- a/src/utils/pathut.h +++ b/src/utils/pathut.h @@ -16,51 +16,48 @@ */ #ifndef _PATHUT_H_INCLUDED_ #define _PATHUT_H_INCLUDED_ -#include #include #include #include -#include "refcntr.h" +// Must be called in main thread before starting other threads +extern void pathut_init_mt(); /// Add a / at the end if none there yet. -extern void path_catslash(std::string &s); +extern void path_catslash(std::string& s); /// Concatenate 2 paths -extern std::string path_cat(const std::string &s1, const std::string &s2); +extern std::string path_cat(const std::string& s1, const std::string& s2); /// Get the simple file name (get rid of any directory path prefix -extern std::string path_getsimple(const std::string &s); +extern std::string path_getsimple(const std::string& s); /// Simple file name + optional suffix stripping -extern std::string path_basename(const std::string &s, - const std::string &suff = std::string()); +extern std::string path_basename(const std::string& s, + const std::string& suff = std::string()); /// Component after last '.' -extern std::string path_suffix(const std::string &s); +extern std::string path_suffix(const std::string& s); /// Get the father directory -extern std::string path_getfather(const std::string &s); +extern std::string path_getfather(const std::string& s); /// Get the current user's home directory extern std::string path_home(); -/// Expand ~ at the beginning of std::string -extern std::string path_tildexpand(const std::string &s); +/// Expand ~ at the beginning of std::string +extern std::string path_tildexpand(const std::string& s); /// Use getcwd() to make absolute path if needed. Beware: ***this can fail*** /// we return an empty path in this case. -extern std::string path_absolute(const std::string &s); +extern std::string path_absolute(const std::string& s); /// Clean up path by removing duplicated / and resolving ../ + make it absolute -extern std::string path_canon(const std::string &s, const std::string *cwd=0); +extern std::string path_canon(const std::string& s, const std::string *cwd = 0); /// Use glob(3) to return the file names matching pattern inside dir -extern std::vector path_dirglob(const std::string &dir, - const std::string pattern); +extern std::vector path_dirglob(const std::string& dir, + const std::string pattern); /// Encode according to rfc 1738 -extern std::string url_encode(const std::string& url, - std::string::size_type offs = 0); -/// Transcode to utf-8 if possible or url encoding, for display. -extern bool printableUrl(const std::string &fcharset, - const std::string &in, std::string &out); +extern std::string url_encode(const std::string& url, + std::string::size_type offs = 0); //// Convert to file path if url is like file://. This modifies the //// input (and returns a copy for convenience) extern std::string fileurltolocalpath(std::string url); /// Test for file:/// url extern bool urlisfileurl(const std::string& url); -/// +/// extern std::string url_parentfolder(const std::string& url); /// Return the host+path part of an url. This is not a general @@ -70,77 +67,66 @@ extern std::string url_gpath(const std::string& url); /// Stat parameter and check if it's a directory extern bool path_isdir(const std::string& path); +/// Retrieve file size +extern long long path_filesize(const std::string& path); + +/// Retrieve essential file attributes. This is used rather than a +/// bare stat() to ensure consistent use of the time fields (on +/// windows, we set ctime=mtime as ctime is actually the creation +/// time, for which we have no use). +/// Only st_mtime, st_ctime, st_size, st_mode (file type bits) are set on +/// all systems. st_dev and st_ino are set for special posix usage. +/// The rest is zeroed. +/// @ret 0 for success +struct stat; +extern int path_fileprops(const std::string path, struct stat *stp, + bool follow = true); + +/// Check that path is traversable and last element exists +/// Returns true if last elt could be checked to exist. False may mean that +/// the file/dir does not exist or that an error occurred. +extern bool path_exists(const std::string& path); + +/// Return separator for PATH environment variable +extern std::string path_PATHsep(); + /// Dump directory -extern bool readdir(const std::string& dir, std::string& reason, - std::set& entries); +extern bool readdir(const std::string& dir, std::string& reason, + std::set& entries); /** A small wrapper around statfs et al, to return percentage of disk - occupation */ -bool fsocc(const std::string &path, int *pc, // Percent occupied - long long *avmbs = 0 // Mbs available to non-superuser. Mb=1024*1024 - ); - -/// Retrieve the temp dir location: $RECOLL_TMPDIR else $TMPDIR else /tmp -extern const std::string& tmplocation(); - -/// Create temporary directory (inside the temp location) -extern bool maketmpdir(std::string& tdir, std::string& reason); + occupation + @param[output] pc percent occupied + @param[output] avmbs Mbs available to non-superuser. Mb=1024*1024 +*/ +bool fsocc(const std::string& path, int *pc, long long *avmbs = 0); /// mkdir -p -extern bool makepath(const std::string& path); +extern bool path_makepath(const std::string& path, int mode); -/// Temporary file class -class TempFileInternal { -public: - TempFileInternal(const std::string& suffix); - ~TempFileInternal(); - const char *filename() - { - return m_filename.c_str(); - } - const std::string &getreason() - { - return m_reason; - } - void setnoremove(bool onoff) - { - m_noremove = onoff; - } - bool ok() - { - return !m_filename.empty(); - } -private: - std::string m_filename; - std::string m_reason; - bool m_noremove; -}; +/// Where we create the user data subdirs +extern std::string path_homedata(); +/// Test if path is absolute +extern bool path_isabsolute(const std::string& s); -typedef RefCntr TempFile; +/// Test if path is root (x:/). root is defined by root/.. == root +extern bool path_isroot(const std::string& p); -/// Temporary directory class. Recursively deleted by destructor. -class TempDir { -public: - TempDir(); - ~TempDir(); - const char *dirname() {return m_dirname.c_str();} - const std::string &getreason() {return m_reason;} - bool ok() {return !m_dirname.empty();} - /// Recursively delete contents but not self. - bool wipe(); -private: - std::string m_dirname; - std::string m_reason; - TempDir(const TempDir &) {} - TempDir& operator=(const TempDir &) {return *this;}; -}; +/// Turn absolute path into file:// url +extern std::string path_pathtofileurl(const std::string& path); + +#ifdef _WIN32 +/// Convert \ separators to / +void path_slashize(std::string& s); +void path_backslashize(std::string& s); +#endif /// Lock/pid file class. This is quite close to the pidfile_xxx /// utilities in FreeBSD with a bit more encapsulation. I'd have used /// the freebsd code if it was available elsewhere class Pidfile { public: - Pidfile(const std::string& path) : m_path(path), m_fd(-1) {} + Pidfile(const std::string& path) : m_path(path), m_fd(-1) {} ~Pidfile(); /// Open/create the pid file. /// @return 0 if ok, > 0 for pid of existing process, -1 for other error. @@ -152,7 +138,9 @@ public: int close(); /// Delete the pid file int remove(); - const std::string& getreason() {return m_reason;} + const std::string& getreason() { + return m_reason; + } private: std::string m_path; int m_fd; @@ -161,14 +149,4 @@ private: int flopen(); }; - - -// Freedesktop thumbnail standard path routine -// On return, path will have the appropriate value in all cases, -// returns true if the file already exists -extern bool thumbPathForUrl(const std::string& url, int size, std::string& path); - -// Must be called in main thread before starting other threads -extern void pathut_init_mt(); - #endif /* _PATHUT_H_INCLUDED_ */ diff --git a/src/utils/ptmutex.cpp b/src/utils/ptmutex.cpp deleted file mode 100644 index 7e6e2e87..00000000 --- a/src/utils/ptmutex.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* Copyright (C) 2004 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 - * (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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the - * Free Software Foundation, Inc., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -// -// Small test program to evaluate the cost of using mutex locks: calls -// to methods doing a small (150 bytes) base64 encoding job + string -// manips, with and without locking. The performance cost is -// negligible on all machines I tested (around 0.3% to 2% depending on -// the system and machine), but not inexistent, you would not want -// this in a tight loop. - -#include -#include -#include - -#include -using namespace std; - -#include "ptmutex.h" -#include "base64.h" - -static char *thisprog; -static char usage [] = -"ptmutex [-l] count\n" -"\n" -; -static void -Usage(void) -{ - fprintf(stderr, "%s: usage:\n%s", thisprog, usage); - exit(1); -} - -static int op_flags; -#define OPT_MOINS 0x1 -#define OPT_l 0x2 - -static const string convertbuffer = - "* The recoll GUI program sometimes crashes when running a query while\ - the indexing thread is active. Possible workarounds:"; - -static PTMutexInit o_lock; -void workerlock(string& out) -{ - PTMutexLocker locker(o_lock); - base64_encode(convertbuffer, out); -} - -void workernolock(string& out) -{ - base64_encode(convertbuffer, out); -} - -int main(int argc, char **argv) -{ - int count = 0; - thisprog = argv[0]; - argc--; argv++; - - while (argc > 0 && **argv == '-') { - (*argv)++; - if (!(**argv)) - /* Cas du "adb - core" */ - Usage(); - while (**argv) - switch (*(*argv)++) { - case 'l': op_flags |= OPT_l; break; - default: Usage(); break; - } - b1: argc--; argv++; - } - - if (argc != 1) - Usage(); - count = atoi(*argv++);argc--; - - if (op_flags & OPT_l) { - fprintf(stderr, "Looping %d, locking\n", count); - for (int i = 0; i < count; i++) { - string s; - workerlock(s); - } - } else { - fprintf(stderr, "Looping %d, no locking\n", count); - for (int i = 0; i < count; i++) { - string s; - workernolock(s); - } - } - exit(0); -} - diff --git a/src/utils/ptmutex.h b/src/utils/ptmutex.h deleted file mode 100644 index 4cd9324f..00000000 --- a/src/utils/ptmutex.h +++ /dev/null @@ -1,64 +0,0 @@ -/* Copyright (C) 2011 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 - * (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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the - * Free Software Foundation, Inc., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ -#ifndef _PTMUTEX_H_INCLUDED_ -#define _PTMUTEX_H_INCLUDED_ - -#include - -/// A trivial wrapper/helper for pthread mutex locks - - -/// Lock storage with auto-initialization. Must be created before any -/// lock-using thread of course (possibly as a static object). -class PTMutexInit { -public: - pthread_mutex_t m_mutex; - int m_status; - PTMutexInit() - { - m_status = pthread_mutex_init(&m_mutex, 0); - } -}; - -/// Take the lock when constructed, release when deleted. Can be disabled -/// by constructor params for conditional use. -class PTMutexLocker { -public: - // The nolock arg enables conditional locking - PTMutexLocker(PTMutexInit& l, bool nolock = false) - : m_lock(l), m_status(-1) - { - if (!nolock) - m_status = pthread_mutex_lock(&m_lock.m_mutex); - } - ~PTMutexLocker() - { - if (m_status == 0) - pthread_mutex_unlock(&m_lock.m_mutex); - } - int ok() {return m_status == 0;} - // For pthread_cond_wait etc. - pthread_mutex_t *getMutex() - { - return &m_lock.m_mutex; - } -private: - PTMutexInit& m_lock; - int m_status; -}; - -#endif /* _PTMUTEX_H_INCLUDED_ */ diff --git a/src/utils/rclionice.cpp b/src/utils/rclionice.cpp index 82a5ded3..baab1b88 100644 --- a/src/utils/rclionice.cpp +++ b/src/utils/rclionice.cpp @@ -21,7 +21,7 @@ #include "rclionice.h" #include "execmd.h" -#include "debuglog.h" +#include "log.h" using namespace std; @@ -30,7 +30,7 @@ bool rclionice(const string& clss, const string& cdata) string ionicexe; if (!ExecCmd::which("ionice", ionicexe)) { // ionice not found, bail out - LOGDEB0(("rclionice: ionice not found\n")); + LOGDEB0("rclionice: ionice not found\n" ); return false; } vector args; @@ -51,8 +51,9 @@ bool rclionice(const string& clss, const string& cdata) int status = cmd.doexec(ionicexe, args); if (status) { - LOGERR(("rclionice: failed, status 0x%x\n", status)); + LOGERR("rclionice: failed, status 0x" << (status) << "\n" ); return false; } return true; } + diff --git a/src/utils/rclutil.cpp b/src/utils/rclutil.cpp new file mode 100644 index 00000000..13cc1e54 --- /dev/null +++ b/src/utils/rclutil.cpp @@ -0,0 +1,485 @@ +/* Copyright (C) 2016 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef TEST_RCLUTIL +#include "autoconfig.h" + +#include +#include +#include +#include "safefcntl.h" +#include "safeunistd.h" +#include "dirent.h" +#include "cstr.h" +#ifdef _WIN32 +#include "safewindows.h" +#else +#include +#include +#include +#endif +#include +#include +#include +#include "safesysstat.h" + +#include + +#include "rclutil.h" +#include "pathut.h" +#include "wipedir.h" +#include "transcode.h" +#include "md5ut.h" + +using namespace std; + + +void map_ss_cp_noshr(const map s, map *d) +{ + for (map::const_iterator it = s.begin(); + it != s.end(); it++) { + d->insert( + pair(string(it->first.begin(), it->first.end()), + string(it->second.begin(), it->second.end()))); + } +} + +#ifdef _WIN32 +static bool path_hasdrive(const string& s) +{ + if (s.size() >= 2 && isalpha(s[0]) && s[1] == ':') { + return true; + } + return false; +} +static bool path_isdriveabs(const string& s) +{ + if (s.size() >= 3 && isalpha(s[0]) && s[1] == ':' && s[2] == '/') { + return true; + } + return false; +} + +#include +#pragma comment(lib, "shlwapi.lib") + +string path_tchartoutf8(TCHAR *text) +{ +#ifdef UNICODE + // Simple C + // const size_t size = ( wcslen(text) + 1 ) * sizeof(wchar_t); + // wcstombs(&buffer[0], text, size); + // std::vector buffer(size); + // Or: + // Windows API + std::vector buffer; + int size = WideCharToMultiByte(CP_UTF8, 0, text, -1, NULL, 0, NULL, NULL); + if (size > 0) { + buffer.resize(size); + WideCharToMultiByte(CP_UTF8, 0, text, -1, + &buffer[0], int(buffer.size()), NULL, NULL); + } else { + return string(); + } + return string(&buffer[0]); +#else + return text; +#endif +} +string path_thisexecpath() +{ + TCHAR text[MAX_PATH]; + GetModuleFileName(NULL, text, MAX_PATH); +#ifdef NTDDI_WIN8_future + PathCchRemoveFileSpec(text, MAX_PATH); +#else + PathRemoveFileSpec(text); +#endif + string path = path_tchartoutf8(text); + if (path.empty()) { + path = "c:/"; + } + + return path; +} +string path_wingettempfilename(TCHAR *pref) +{ + TCHAR buf[(MAX_PATH + 1)*sizeof(TCHAR)]; + TCHAR dbuf[(MAX_PATH + 1)*sizeof(TCHAR)]; + GetTempPath(MAX_PATH + 1, dbuf); + GetTempFileName(dbuf, pref, 0, buf); + // Windows will have created a temp file, we delete it. + string filename = path_tchartoutf8(buf); + unlink(filename.c_str()); + path_slashize(filename); + return filename; +} + +#endif // _WIN32 + +string path_defaultrecollconfsubdir() +{ +#ifdef _WIN32 + return "Recoll"; +#else + return ".recoll"; +#endif +} + +// Location for sample config, filters, etc. (e.g. /usr/share/recoll/) +const string& path_pkgdatadir() +{ + static string datadir; + if (datadir.empty()) { +#ifdef _WIN32 + datadir = path_cat(path_thisexecpath(), "Share"); +#else + const char *cdatadir = getenv("RECOLL_DATADIR"); + if (cdatadir == 0) { + // If not in environment, use the compiled-in constant. + datadir = RECOLL_DATADIR; + } else { + datadir = cdatadir; + } +#endif + } + return datadir; +} + +// Printable url: this is used to transcode from the system charset +// into either utf-8 if transcoding succeeds, or url-encoded +bool printableUrl(const string& fcharset, const string& in, string& out) +{ + int ecnt = 0; + if (!transcode(in, out, fcharset, "UTF-8", &ecnt) || ecnt) { + out = url_encode(in, 7); + } + return true; +} + +string url_gpathS(const string& url) +{ +#ifdef _WIN32 + string u = url_gpath(url); + string nu; + if (path_hasdrive(u)) { + nu.append(1, '/'); + nu.append(1, u[0]); + if (path_isdriveabs(u)) { + nu.append(u.substr(2)); + } else { + // This should be an error really + nu.append(1, '/'); + nu.append(u.substr(2)); + } + } + return nu; +#else + return url_gpath(url); +#endif +} + +const string& tmplocation() +{ + static string stmpdir; + if (stmpdir.empty()) { + const char *tmpdir = getenv("RECOLL_TMPDIR"); + if (tmpdir == 0) { + tmpdir = getenv("TMPDIR"); + } + if (tmpdir == 0) { + tmpdir = getenv("TMP"); + } + if (tmpdir == 0) { + tmpdir = getenv("TEMP"); + } + if (tmpdir == 0) { +#ifdef _WIN32 + TCHAR bufw[(MAX_PATH + 1)*sizeof(TCHAR)]; + GetTempPath(MAX_PATH + 1, bufw); + stmpdir = path_tchartoutf8(bufw); +#else + stmpdir = "/tmp"; +#endif + } else { + stmpdir = tmpdir; + } + stmpdir = path_canon(stmpdir); + } + + return stmpdir; +} + +bool maketmpdir(string& tdir, string& reason) +{ +#ifndef _WIN32 + tdir = path_cat(tmplocation(), "rcltmpXXXXXX"); + + char *cp = strdup(tdir.c_str()); + if (!cp) { + reason = "maketmpdir: out of memory (for file name !)\n"; + tdir.erase(); + return false; + } + + // There is a race condition between name computation and + // mkdir. try to make sure that we at least don't shoot ourselves + // in the foot +#if !defined(HAVE_MKDTEMP) || defined(_WIN32) + static std::mutex mmutex; + std::unique_lock lock(mmutex); +#endif + + if (! +#ifdef HAVE_MKDTEMP + mkdtemp(cp) +#else + mktemp(cp) +#endif // HAVE_MKDTEMP + ) { + free(cp); + reason = "maketmpdir: mktemp failed for [" + tdir + "] : " + + strerror(errno); + tdir.erase(); + return false; + } + tdir = cp; + free(cp); +#else // _WIN32 + // There is a race condition between name computation and + // mkdir. try to make sure that we at least don't shoot ourselves + // in the foot + static std::mutex mmutex; + std::unique_lock lock(mmutex); + tdir = path_wingettempfilename(TEXT("rcltmp")); +#endif + + // At this point the directory does not exist yet except if we used + // mkdtemp + +#if !defined(HAVE_MKDTEMP) || defined(_WIN32) + if (mkdir(tdir.c_str(), 0700) < 0) { + reason = string("maketmpdir: mkdir ") + tdir + " failed"; + tdir.erase(); + return false; + } +#endif + + return true; +} + +TempFileInternal::TempFileInternal(const string& suffix) + : m_noremove(false) +{ + // Because we need a specific suffix, can't use mkstemp + // well. There is a race condition between name computation and + // file creation. try to make sure that we at least don't shoot + // our own selves in the foot. maybe we'll use mkstemps one day. + static std::mutex mmutex; + std::unique_lock lock(mmutex); + +#ifndef _WIN32 + string filename = path_cat(tmplocation(), "rcltmpfXXXXXX"); + char *cp = strdup(filename.c_str()); + if (!cp) { + m_reason = "Out of memory (for file name !)\n"; + return; + } + + // Using mkstemp this way is awful (bot the suffix adding and + // using mkstemp() instead of mktemp just to avoid the warnings) + int fd; + if ((fd = mkstemp(cp)) < 0) { + free(cp); + m_reason = "TempFileInternal: mkstemp failed\n"; + return; + } + close(fd); + unlink(cp); + filename = cp; + free(cp); +#else + string filename = path_wingettempfilename(TEXT("recoll")); +#endif + + m_filename = filename + suffix; + if (close(open(m_filename.c_str(), O_CREAT | O_EXCL, 0600)) != 0) { + m_reason = string("Could not open/create") + m_filename; + m_filename.erase(); + } +} + +TempFileInternal::~TempFileInternal() +{ + if (!m_filename.empty() && !m_noremove) { + unlink(m_filename.c_str()); + } +} + +TempDir::TempDir() +{ + if (!maketmpdir(m_dirname, m_reason)) { + m_dirname.erase(); + return; + } +} + +TempDir::~TempDir() +{ + if (!m_dirname.empty()) { + (void)wipedir(m_dirname, true, true); + m_dirname.erase(); + } +} + +bool TempDir::wipe() +{ + if (m_dirname.empty()) { + m_reason = "TempDir::wipe: no directory !\n"; + return false; + } + if (wipedir(m_dirname, false, true)) { + m_reason = "TempDir::wipe: wipedir failed\n"; + return false; + } + return true; +} + +// Freedesktop standard paths for cache directory (thumbnails are now in there) +static const string& xdgcachedir() +{ + static string xdgcache; + if (xdgcache.empty()) { + const char *cp = getenv("XDG_CACHE_HOME"); + if (cp == 0) { + xdgcache = path_cat(path_home(), ".cache"); + } else { + xdgcache = string(cp); + } + } + return xdgcache; +} +static const string& thumbnailsdir() +{ + static string thumbnailsd; + if (thumbnailsd.empty()) { + thumbnailsd = path_cat(xdgcachedir(), "thumbnails"); + if (access(thumbnailsd.c_str(), 0) != 0) { + thumbnailsd = path_cat(path_home(), ".thumbnails"); + } + } + return thumbnailsd; +} + +// Place for 256x256 files +static const string thmbdirlarge = "large"; +// 128x128 +static const string thmbdirnormal = "normal"; + +static void thumbname(const string& url, string& name) +{ + string digest; + string l_url = url_encode(url); + MD5String(l_url, digest); + MD5HexPrint(digest, name); + name += ".png"; +} + +bool thumbPathForUrl(const string& url, int size, string& path) +{ + string name; + thumbname(url, name); + if (size <= 128) { + path = path_cat(thumbnailsdir(), thmbdirnormal); + path = path_cat(path, name); + if (access(path.c_str(), R_OK) == 0) { + return true; + } + } + path = path_cat(thumbnailsdir(), thmbdirlarge); + path = path_cat(path, name); + if (access(path.c_str(), R_OK) == 0) { + return true; + } + + // File does not exist. Path corresponds to the large version at this point, + // fix it if needed. + if (size <= 128) { + path = path_cat(path_home(), thmbdirnormal); + path = path_cat(path, name); + } + return false; +} + +void rclutil_init_mt() +{ + path_pkgdatadir(); + tmplocation(); + thumbnailsdir(); +} + +#else // TEST_RCLUTIL + +void path_to_thumb(const string& _input) +{ + string input(_input); + // Make absolute path if needed + if (input[0] != '/') { + input = path_absolute(input); + } + + input = string("file://") + path_canon(input); + + string path; + //path = url_encode(input, 7); + thumbPathForUrl(input, 7, path); + cout << path << endl; +} + +const char *thisprog; + +int main(int argc, const char **argv) +{ + thisprog = *argv++; + argc--; + + string s; + vector::const_iterator it; + +#if 0 + if (argc > 1) { + cerr << "Usage: thumbpath " << endl; + exit(1); + } + string input; + if (argc == 1) { + input = *argv++; + if (input.empty()) { + cerr << "Usage: thumbpath " << endl; + exit(1); + } + path_to_thumb(input); + } else { + while (getline(cin, input)) { + path_to_thumb(input); + } + } + exit(0); +#endif +} + +#endif // TEST_RCLUTIL + diff --git a/src/utils/rclutil.h b/src/utils/rclutil.h new file mode 100644 index 00000000..106d69e2 --- /dev/null +++ b/src/utils/rclutil.h @@ -0,0 +1,112 @@ +/* Copyright (C) 2016 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _RCLUTIL_H_INCLUDED_ +#define _RCLUTIL_H_INCLUDED_ +#include "autoconfig.h" + +// Misc stuff not generic enough to get into smallut or pathut + +#include +#include +#include + + +extern void rclutil_init_mt(); + +/// Sub-directory for default recoll config (e.g: .recoll) +extern std::string path_defaultrecollconfsubdir(); + +/// e.g. /usr/share/recoll. Depends on OS and config +extern const std::string& path_pkgdatadir(); + +/// Transcode to utf-8 if possible or url encoding, for display. +extern bool printableUrl(const std::string& fcharset, + const std::string& in, std::string& out); +/// Same but, in the case of a Windows local path, also turn "c:/" into +/// "/c/" This should be used only for splitting the path in rcldb. +extern std::string url_gpathS(const std::string& url); + +/// Retrieve the temp dir location: $RECOLL_TMPDIR else $TMPDIR else /tmp +extern const std::string& tmplocation(); + +/// Create temporary directory (inside the temp location) +extern bool maketmpdir(std::string& tdir, std::string& reason); + +/// Temporary file class +class TempFileInternal { +public: + TempFileInternal(const std::string& suffix); + ~TempFileInternal(); + const char *filename() { + return m_filename.c_str(); + } + const std::string& getreason() { + return m_reason; + } + void setnoremove(bool onoff) { + m_noremove = onoff; + } + bool ok() { + return !m_filename.empty(); + } +private: + std::string m_filename; + std::string m_reason; + bool m_noremove; +}; + +typedef std::shared_ptr TempFile; + +/// Temporary directory class. Recursively deleted by destructor. +class TempDir { +public: + TempDir(); + ~TempDir(); + const char *dirname() { + return m_dirname.c_str(); + } + const std::string& getreason() { + return m_reason; + } + bool ok() { + return !m_dirname.empty(); + } + /// Recursively delete contents but not self. + bool wipe(); +private: + std::string m_dirname; + std::string m_reason; + TempDir(const TempDir&) {} + TempDir& operator=(const TempDir&) { + return *this; + }; +}; + +// Freedesktop thumbnail standard path routine +// On return, path will have the appropriate value in all cases, +// returns true if the file already exists +extern bool thumbPathForUrl(const std::string& url, int size, + std::string& path); + +// Duplicate map while ensuring no shared string data (to pass +// to other thread): +void map_ss_cp_noshr(const std::map s, + std::map *d); + + +#endif /* _RCLUTIL_H_INCLUDED_ */ diff --git a/src/utils/readfile.cpp b/src/utils/readfile.cpp index 858a5155..513471b1 100644 --- a/src/utils/readfile.cpp +++ b/src/utils/readfile.cpp @@ -15,92 +15,96 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef TEST_READFILE +#ifdef BUILDING_RECOLL #include "autoconfig.h" - -#include -#include -#include -#include - -#ifndef O_STREAMING -#define O_STREAMING 0 +#else +#include "config.h" #endif + #include -#include -#include - +#include +#ifdef _WIN32 +#include "safefcntl.h" +#include "safesysstat.h" +#include "safeunistd.h" +#else +#define O_BINARY 0 +#include +#include +#include +#endif #include -#ifndef NO_NAMESPACES -using std::string; -#endif /* NO_NAMESPACES */ - #include "readfile.h" #include "smallut.h" -#ifndef MIN -#define MIN(A,B) ((A) < (B) ? (A) : (B)) -#endif +using std::string; class FileToString : public FileScanDo { public: FileToString(string& data) : m_data(data) {} string& m_data; bool init(size_t size, string *reason) { - if (size > 0) - m_data.reserve(size); - return true; + if (size > 0) { + m_data.reserve(size); + } + return true; } bool data(const char *buf, int cnt, string *reason) { - try { - m_data.append(buf, cnt); - } catch (...) { - catstrerror(reason, "append", errno); - return false; - } - return true; + try { + m_data.append(buf, cnt); + } catch (...) { + catstrerror(reason, "append", errno); + return false; + } + return true; } }; -bool file_to_string(const string &fn, string &data, string *reason) +bool file_to_string(const string& fn, string& data, string *reason) { return file_to_string(fn, data, 0, size_t(-1), reason); } -bool file_to_string(const string &fn, string &data, off_t offs, size_t cnt, +bool file_to_string(const string& fn, string& data, off_t offs, size_t cnt, string *reason) { FileToString accum(data); return file_scan(fn, &accum, offs, cnt, reason); } -bool file_scan(const string &fn, FileScanDo* doer, string *reason) +bool file_scan(const string& fn, FileScanDo* doer, string *reason) { return file_scan(fn, doer, 0, size_t(-1), reason); } -const int RDBUFSZ = 4096; +const int RDBUFSZ = 8192; // Note: the fstat() + reserve() (in init()) calls divide cpu usage almost by 2 // on both linux i586 and macosx (compared to just append()) // Also tried a version with mmap, but it's actually slower on the mac and not // faster on linux. -bool file_scan(const string &fn, FileScanDo* doer, off_t startoffs, +bool file_scan(const string& fn, FileScanDo* doer, off_t startoffs, size_t cnttoread, string *reason) { + if (startoffs < 0) { + *reason += " file_scan: negative startoffs not allowed"; + return false; + } + bool ret = false; bool noclosing = true; int fd = 0; struct stat st; - // Initialize st_size: if fn.empty() , the fstat() call won't happen. + // Initialize st_size: if fn.empty() , the fstat() call won't happen. st.st_size = 0; // If we have a file name, open it, else use stdin. if (!fn.empty()) { - fd = open(fn.c_str(), O_RDONLY|O_STREAMING); - if (fd < 0 || fstat(fd, &st) < 0) { - catstrerror(reason, "open/stat", errno); - return false; - } - noclosing = false; + fd = open(fn.c_str(), O_RDONLY | O_BINARY); + if (fd < 0 || fstat(fd, &st) < 0) { + catstrerror(reason, "open/stat", errno); + return false; + } + noclosing = false; } #if defined O_NOATIME && O_NOATIME != 0 @@ -109,12 +113,12 @@ bool file_scan(const string &fn, FileScanDo* doer, off_t startoffs, } #endif - if (cnttoread != (size_t)-1 && cnttoread) { - doer->init(cnttoread+1, reason); + if (cnttoread != (size_t) - 1 && cnttoread) { + doer->init(cnttoread + 1, reason); } else if (st.st_size > 0) { - doer->init(st.st_size+1, reason); + doer->init(size_t(st.st_size + 1), reason); } else { - doer->init(0, reason); + doer->init(0, reason); } off_t curoffs = 0; @@ -131,36 +135,40 @@ bool file_scan(const string &fn, FileScanDo* doer, off_t startoffs, for (;;) { size_t toread = RDBUFSZ; if (startoffs > 0 && curoffs < startoffs) { - toread = MIN(RDBUFSZ, startoffs - curoffs); + toread = size_t(MIN(RDBUFSZ, startoffs - curoffs)); } if (cnttoread != size_t(-1)) { toread = MIN(toread, cnttoread - totread); } - int n = read(fd, buf, toread); - if (n < 0) { - catstrerror(reason, "read", errno); - goto out; - } - if (n == 0) - break; + ssize_t n = static_cast(read(fd, buf, toread)); + if (n < 0) { + catstrerror(reason, "read", errno); + goto out; + } + if (n == 0) { + break; + } curoffs += n; - if (curoffs - n < startoffs) + if (curoffs - n < startoffs) { continue; - - if (!doer->data(buf, n, reason)) { - goto out; - } + } + + if (!doer->data(buf, n, reason)) { + goto out; + } totread += n; - if (cnttoread > 0 && totread >= cnttoread) + if (cnttoread > 0 && totread >= cnttoread) { break; + } } ret = true; - out: - if (fd >= 0 && !noclosing) - close(fd); +out: + if (fd >= 0 && !noclosing) { + close(fd); + } return ret; } @@ -168,10 +176,9 @@ bool file_scan(const string &fn, FileScanDo* doer, off_t startoffs, #include "autoconfig.h" #include -#include -#include -#include #include +#include "safesysstat.h" +#include #include #include @@ -183,29 +190,28 @@ using namespace std; using namespace std; class myCB : public FsTreeWalkerCB { - public: - FsTreeWalker::Status processone(const string &path, - const struct stat *st, - FsTreeWalker::CbFlag flg) - { - if (flg == FsTreeWalker::FtwDirEnter) { - //cout << "[Entering " << path << "]" << endl; - } else if (flg == FsTreeWalker::FtwDirReturn) { - //cout << "[Returning to " << path << "]" << endl; - } else if (flg == FsTreeWalker::FtwRegular) { - //cout << path << endl; - string s, reason; - if (!file_to_string(path, s, &reason)) { - cerr << "Failed: " << reason << " : " << path << endl; - } else { - //cout << - //"================================================" << endl; - cout << path << endl; - // cout << s; - } - reason.clear(); - } - return FsTreeWalker::FtwOk; +public: + FsTreeWalker::Status processone(const string& path, + const struct stat *st, + FsTreeWalker::CbFlag flg) { + if (flg == FsTreeWalker::FtwDirEnter) { + //cout << "[Entering " << path << "]" << endl; + } else if (flg == FsTreeWalker::FtwDirReturn) { + //cout << "[Returning to " << path << "]" << endl; + } else if (flg == FsTreeWalker::FtwRegular) { + //cout << path << endl; + string s, reason; + if (!file_to_string(path, s, &reason)) { + cerr << "Failed: " << reason << " : " << path << endl; + } else { + //cout << + //"================================================" << endl; + cout << path << endl; + // cout << s; + } + reason.clear(); + } + return FsTreeWalker::FtwOk; } }; @@ -216,8 +222,8 @@ static int op_flags; static const char *thisprog; static char usage [] = -"trreadfile [-o offs] [-c cnt] topdirorfile\n\n" -; + "trreadfile [-o offs] [-c cnt] topdirorfile\n\n" + ; static void Usage(void) { @@ -230,51 +236,71 @@ int main(int argc, const char **argv) off_t offs = 0; size_t cnt = size_t(-1); thisprog = argv[0]; - argc--; argv++; + argc--; + argv++; - while (argc > 0 && **argv == '-') { - (*argv)++; - if (!(**argv)) - /* Cas du "adb - core" */ - Usage(); - while (**argv) - switch (*(*argv)++) { - case 'c': op_flags |= OPT_c; if (argc < 2) Usage(); - cnt = atoll(*(++argv)); argc--; - goto b1; - case 'o': op_flags |= OPT_o; if (argc < 2) Usage(); - offs = strtoull(*(++argv), 0, 0); argc--; - goto b1; - default: Usage(); break; - } - b1: argc--; argv++; - } + while (argc > 0 && **argv == '-') { + (*argv)++; + if (!(**argv)) + /* Cas du "adb - core" */ + { + Usage(); + } + while (**argv) + switch (*(*argv)++) { + case 'c': + op_flags |= OPT_c; + if (argc < 2) { + Usage(); + } + cnt = atoll(*(++argv)); + argc--; + goto b1; + case 'o': + op_flags |= OPT_o; + if (argc < 2) { + Usage(); + } + offs = strtoull(*(++argv), 0, 0); + argc--; + goto b1; + default: + Usage(); + break; + } +b1: + argc--; + argv++; + } - if (argc != 1) - Usage(); - string top = *argv++;argc--; - cerr << "filename " << top << " offs " << offs << " cnt " << cnt << endl; + if (argc != 1) { + Usage(); + } + string top = *argv++; + argc--; + cerr << "filename " << top << " offs " << offs << " cnt " << cnt << endl; - struct stat st; - if (!top.empty() && stat(top.c_str(), &st) < 0) { - perror("stat"); - exit(1); - } - if (!top.empty() && S_ISDIR(st.st_mode)) { - FsTreeWalker walker; - myCB cb; - walker.walk(top, cb); - if (walker.getErrCnt() > 0) - cout << walker.getReason(); - } else { - string s, reason; - if (!file_to_string(top, s, offs, cnt, &reason)) { - cerr << reason << endl; - exit(1); - } else { - cout << s; - } - } - exit(0); + struct stat st; + if (!top.empty() && stat(top.c_str(), &st) < 0) { + perror("stat"); + exit(1); + } + if (!top.empty() && S_ISDIR(st.st_mode)) { + FsTreeWalker walker; + myCB cb; + walker.walk(top, cb); + if (walker.getErrCnt() > 0) { + cout << walker.getReason(); + } + } else { + string s, reason; + if (!file_to_string(top, s, offs, cnt, &reason)) { + cerr << reason << endl; + exit(1); + } else { + cout << s; + } + } + exit(0); } #endif //TEST_READFILE diff --git a/src/utils/readfile.h b/src/utils/readfile.h index f0ce5e75..aa578b5c 100644 --- a/src/utils/readfile.h +++ b/src/utils/readfile.h @@ -20,32 +20,31 @@ #include #include -using std::string; -/** - * Read file in chunks, calling an accumulator for each chunk. Can be used +/** + * Read file in chunks, calling an accumulator for each chunk. Can be used * for reading in a file, computing an md5... */ class FileScanDo { public: virtual ~FileScanDo() {} - virtual bool init(size_t size, string *reason) = 0; - virtual bool data(const char *buf, int cnt, string* reason) = 0; + virtual bool init(size_t size, std::string *reason) = 0; + virtual bool data(const char *buf, int cnt, std::string* reason) = 0; }; -bool file_scan(const string &filename, FileScanDo* doer, string *reason = 0); -/* Same but only process count cnt from offset offs. Set cnt to size_t(-1) +bool file_scan(const std::string& filename, FileScanDo* doer, std::string *reason = 0); +/* Same but only process count cnt from offset offs. Set cnt to size_t(-1) * for no limit */ -bool file_scan(const string &fn, FileScanDo* doer, off_t offs, size_t cnt, - string *reason = 0); +bool file_scan(const std::string& fn, FileScanDo* doer, off_t offs, size_t cnt, + std::string *reason = 0); /** * Read file into string. * @return true for ok, false else */ -bool file_to_string(const string &filename, string &data, string *reason = 0); +bool file_to_string(const std::string& filename, std::string& data, std::string *reason = 0); /** Read file chunk into string. Set cnt to size_t(-1) for whole file */ -bool file_to_string(const string &filename, string &data, - off_t offs, size_t cnt, string *reason = 0); +bool file_to_string(const std::string& filename, std::string& data, + off_t offs, size_t cnt, std::string *reason = 0); #endif /* _READFILE_H_INCLUDED_ */ diff --git a/src/utils/refcntr.h b/src/utils/refcntr.h index 003fcab1..ce075d6c 100644 --- a/src/utils/refcntr.h +++ b/src/utils/refcntr.h @@ -1,7 +1,24 @@ #ifndef _REFCNTR_H_ #define _REFCNTR_H_ +/* Copyright (C) 2014 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ // See Stroustrup C++ 3rd ed, p. 783 +// This is only used if std::shared_ptr is not available template class RefCntr { X *rep; int *pcount; @@ -32,8 +49,7 @@ public: (*pcount)++; return *this; } - void release() - { + void reset() { if (pcount && --(*pcount) == 0) { delete rep; delete pcount; @@ -41,22 +57,14 @@ public: rep = 0; pcount = 0; } - void reset() { - release(); - } ~RefCntr() { - release(); + reset(); } X *operator->() {return rep;} - X *getptr() const {return rep;} X *get() const {return rep;} - const X *getconstptr() const {return rep;} - int getcnt() const {return pcount ? *pcount : 0;} - bool isNull() const {return rep == 0;} - bool isNotNull() const {return rep != 0;} + int use_count() const {return pcount ? *pcount : 0;} operator bool() const {return rep != 0;} }; - #endif /*_REFCNTR_H_ */ diff --git a/src/utils/smallut.cpp b/src/utils/smallut.cpp index 6d84e999..5f260c87 100644 --- a/src/utils/smallut.cpp +++ b/src/utils/smallut.cpp @@ -1,84 +1,78 @@ -/* Copyright (C) 2004 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 - * (at your option) any later version. +/* Copyright (C) 2006-2016 J.F.Dockes * - * 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 General Public License for more details. + * This library 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. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the - * Free Software Foundation, Inc., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * This library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA */ - -#ifndef TEST_SMALLUT -#ifdef HAVE_CONFIG_H -#include "autoconfig.h" -#endif #include #include #include #include -#include #include #include -#include -#include #include #include +// Older compilers don't support stdc++ regex, but Windows does not +// have the Linux one. Have a simple class to solve the simple cases. +#if defined(_WIN32) +#define USE_STD_REGEX +#include +#else +#define USE_LINUX_REGEX +#include +#endif + #include #include #include -#include "unordered_defs.h" -using namespace std; +#include +#include #include "smallut.h" -#include "utf8iter.h" -#include "hldata.h" -#include "cstr.h" -void map_ss_cp_noshr(const map s, map *d) -{ - for (map::const_iterator it= s.begin(); - it != s.end(); it++) { - d->insert( - pair(string(it->first.begin(), it->first.end()), - string(it->second.begin(), it->second.end()))); - } -} +using namespace std; -int stringicmp(const string & s1, const string& s2) +int stringicmp(const string& s1, const string& s2) { string::const_iterator it1 = s1.begin(); string::const_iterator it2 = s2.begin(); - int size1 = s1.length(), size2 = s2.length(); + string::size_type size1 = s1.length(), size2 = s2.length(); char c1, c2; - if (size1 > size2) { - while (it1 != s1.end()) { - c1 = ::toupper(*it1); - c2 = ::toupper(*it2); - if (c1 != c2) { - return c1 > c2 ? 1 : -1; - } - ++it1; ++it2; - } - return size1 == size2 ? 0 : 1; + if (size1 < size2) { + while (it1 != s1.end()) { + c1 = ::toupper(*it1); + c2 = ::toupper(*it2); + if (c1 != c2) { + return c1 > c2 ? 1 : -1; + } + ++it1; + ++it2; + } + return size1 == size2 ? 0 : -1; } else { - while (it2 != s2.end()) { - c1 = ::toupper(*it1); - c2 = ::toupper(*it2); - if (c1 != c2) { - return c1 > c2 ? 1 : -1; - } - ++it1; ++it2; - } - return size1 == size2 ? 0 : -1; + while (it2 != s2.end()) { + c1 = ::toupper(*it1); + c2 = ::toupper(*it2); + if (c1 != c2) { + return c1 > c2 ? 1 : -1; + } + ++it1; + ++it2; + } + return size1 == size2 ? 0 : 1; } } void stringtolower(string& io) @@ -86,8 +80,8 @@ void stringtolower(string& io) string::iterator it = io.begin(); string::iterator ite = io.end(); while (it != ite) { - *it = ::tolower(*it); - it++; + *it = ::tolower(*it); + it++; } } string stringtolower(const string& i) @@ -96,98 +90,128 @@ string stringtolower(const string& i) stringtolower(o); return o; } + +void stringtoupper(string& io) +{ + string::iterator it = io.begin(); + string::iterator ite = io.end(); + while (it != ite) { + *it = ::toupper(*it); + it++; + } +} +string stringtoupper(const string& i) +{ + string o = i; + stringtoupper(o); + return o; +} + extern int stringisuffcmp(const string& s1, const string& s2) { string::const_reverse_iterator r1 = s1.rbegin(), re1 = s1.rend(), - r2 = s2.rbegin(), re2 = s2.rend(); + r2 = s2.rbegin(), re2 = s2.rend(); while (r1 != re1 && r2 != re2) { - char c1 = ::toupper(*r1); - char c2 = ::toupper(*r2); - if (c1 != c2) { - return c1 > c2 ? 1 : -1; - } - ++r1; ++r2; + char c1 = ::toupper(*r1); + char c2 = ::toupper(*r2); + if (c1 != c2) { + return c1 > c2 ? 1 : -1; + } + ++r1; + ++r2; } return 0; } // s1 is already lowercase -int stringlowercmp(const string & s1, const string& s2) +int stringlowercmp(const string& s1, const string& s2) { string::const_iterator it1 = s1.begin(); string::const_iterator it2 = s2.begin(); - int size1 = s1.length(), size2 = s2.length(); + string::size_type size1 = s1.length(), size2 = s2.length(); char c2; - if (size1 > size2) { - while (it1 != s1.end()) { - c2 = ::tolower(*it2); - if (*it1 != c2) { - return *it1 > c2 ? 1 : -1; - } - ++it1; ++it2; - } - return size1 == size2 ? 0 : 1; + if (size1 < size2) { + while (it1 != s1.end()) { + c2 = ::tolower(*it2); + if (*it1 != c2) { + return *it1 > c2 ? 1 : -1; + } + ++it1; + ++it2; + } + return size1 == size2 ? 0 : -1; } else { - while (it2 != s2.end()) { - c2 = ::tolower(*it2); - if (*it1 != c2) { - return *it1 > c2 ? 1 : -1; - } - ++it1; ++it2; - } - return size1 == size2 ? 0 : -1; + while (it2 != s2.end()) { + c2 = ::tolower(*it2); + if (*it1 != c2) { + return *it1 > c2 ? 1 : -1; + } + ++it1; + ++it2; + } + return size1 == size2 ? 0 : 1; } } // s1 is already uppercase -int stringuppercmp(const string & s1, const string& s2) +int stringuppercmp(const string& s1, const string& s2) { string::const_iterator it1 = s1.begin(); string::const_iterator it2 = s2.begin(); - int size1 = s1.length(), size2 = s2.length(); + string::size_type size1 = s1.length(), size2 = s2.length(); char c2; - if (size1 > size2) { - while (it1 != s1.end()) { - c2 = ::toupper(*it2); - if (*it1 != c2) { - return *it1 > c2 ? 1 : -1; - } - ++it1; ++it2; - } - return size1 == size2 ? 0 : 1; + if (size1 < size2) { + while (it1 != s1.end()) { + c2 = ::toupper(*it2); + if (*it1 != c2) { + return *it1 > c2 ? 1 : -1; + } + ++it1; + ++it2; + } + return size1 == size2 ? 0 : -1; } else { - while (it2 != s2.end()) { - c2 = ::toupper(*it2); - if (*it1 != c2) { - return *it1 > c2 ? 1 : -1; - } - ++it1; ++it2; - } - return size1 == size2 ? 0 : -1; + while (it2 != s2.end()) { + c2 = ::toupper(*it2); + if (*it1 != c2) { + return *it1 > c2 ? 1 : -1; + } + ++it1; + ++it2; + } + return size1 == size2 ? 0 : 1; } } +bool beginswith(const std::string& big, const std::string& small) +{ + if (big.compare(0, small.size(), small)) { + return false; + } + return true; +} + // Compare charset names, removing the more common spelling variations -bool samecharset(const string &cs1, const string &cs2) +bool samecharset(const string& cs1, const string& cs2) { string mcs1, mcs2; // Remove all - and _, turn to lowecase - for (unsigned int i = 0; i < cs1.length();i++) { - if (cs1[i] != '_' && cs1[i] != '-') { - mcs1 += ::tolower(cs1[i]); - } + for (unsigned int i = 0; i < cs1.length(); i++) { + if (cs1[i] != '_' && cs1[i] != '-') { + mcs1 += ::tolower(cs1[i]); + } } - for (unsigned int i = 0; i < cs2.length();i++) { - if (cs2[i] != '_' && cs2[i] != '-') { - mcs2 += ::tolower(cs2[i]); - } + for (unsigned int i = 0; i < cs2.length(); i++) { + if (cs2[i] != '_' && cs2[i] != '-') { + mcs2 += ::tolower(cs2[i]); + } } return mcs1 == mcs2; } -template bool stringToStrings(const string &s, T &tokens, +template bool stringToStrings(const string& s, T& tokens, const string& addseps) { string current; @@ -195,350 +219,376 @@ template bool stringToStrings(const string &s, T &tokens, enum states {SPACE, TOKEN, INQUOTE, ESCAPE}; states state = SPACE; for (unsigned int i = 0; i < s.length(); i++) { - switch (s[i]) { - case '"': - switch(state) { - case SPACE: - state=INQUOTE; continue; - case TOKEN: - current += '"'; - continue; - case INQUOTE: + switch (s[i]) { + case '"': + switch (state) { + case SPACE: + state = INQUOTE; + continue; + case TOKEN: + current += '"'; + continue; + case INQUOTE: tokens.insert(tokens.end(), current); - current.clear(); - state = SPACE; - continue; + current.clear(); + state = SPACE; + continue; case ESCAPE: - current += '"'; - state = INQUOTE; + current += '"'; + state = INQUOTE; continue; - } - break; - case '\\': - switch(state) { - case SPACE: - case TOKEN: + } + break; + case '\\': + switch (state) { + case SPACE: + case TOKEN: current += '\\'; - state=TOKEN; + state = TOKEN; continue; - case INQUOTE: + case INQUOTE: state = ESCAPE; continue; case ESCAPE: current += '\\'; state = INQUOTE; continue; - } - break; + } + break; - case ' ': - case '\t': - case '\n': - case '\r': - switch(state) { - case SPACE: + case ' ': + case '\t': + case '\n': + case '\r': + switch (state) { + case SPACE: continue; - case TOKEN: - tokens.insert(tokens.end(), current); - current.clear(); - state = SPACE; - continue; - case INQUOTE: + case TOKEN: + tokens.insert(tokens.end(), current); + current.clear(); + state = SPACE; + continue; + case INQUOTE: case ESCAPE: current += s[i]; continue; - } - break; + } + break; default: if (!addseps.empty() && addseps.find(s[i]) != string::npos) { - switch(state) { + switch (state) { case ESCAPE: state = INQUOTE; break; - case INQUOTE: + case INQUOTE: break; - case SPACE: + case SPACE: tokens.insert(tokens.end(), string(1, s[i])); continue; - case TOKEN: + case TOKEN: tokens.insert(tokens.end(), current); current.erase(); tokens.insert(tokens.end(), string(1, s[i])); state = SPACE; continue; } - } else switch(state) { + } else switch (state) { case ESCAPE: state = INQUOTE; break; - case SPACE: + case SPACE: state = TOKEN; break; - case TOKEN: - case INQUOTE: + case TOKEN: + case INQUOTE: break; } - current += s[i]; - } + current += s[i]; + } } - switch(state) { - case SPACE: - break; - case TOKEN: - tokens.insert(tokens.end(), current); - break; - case INQUOTE: + switch (state) { + case SPACE: + break; + case TOKEN: + tokens.insert(tokens.end(), current); + break; + case INQUOTE: case ESCAPE: - return false; + return false; } return true; } -template bool stringToStrings >(const string &, - list &, const string&); -template bool stringToStrings >(const string &, - vector &,const string&); -template bool stringToStrings >(const string &, - set &, const string&); -template bool stringToStrings > -(const string &, STD_UNORDERED_SET &, const string&); +template bool stringToStrings >(const string&, + list&, const string&); +template bool stringToStrings >(const string&, + vector&, const string&); +template bool stringToStrings >(const string&, + set&, const string&); +template bool stringToStrings > +(const string&, std::unordered_set&, const string&); -template void stringsToString(const T &tokens, string &s) +template void stringsToString(const T& tokens, string& s) { for (typename T::const_iterator it = tokens.begin(); - it != tokens.end(); it++) { - bool hasblanks = false; - if (it->find_first_of(" \t\n") != string::npos) - hasblanks = true; - if (it != tokens.begin()) - s.append(1, ' '); - if (hasblanks) - s.append(1, '"'); - for (unsigned int i = 0; i < it->length(); i++) { - char car = it->at(i); - if (car == '"') { - s.append(1, '\\'); - s.append(1, car); - } else { - s.append(1, car); - } - } - if (hasblanks) - s.append(1, '"'); + it != tokens.end(); it++) { + bool hasblanks = false; + if (it->find_first_of(" \t\n") != string::npos) { + hasblanks = true; + } + if (it != tokens.begin()) { + s.append(1, ' '); + } + if (hasblanks) { + s.append(1, '"'); + } + for (unsigned int i = 0; i < it->length(); i++) { + char car = it->at(i); + if (car == '"') { + s.append(1, '\\'); + s.append(1, car); + } else { + s.append(1, car); + } + } + if (hasblanks) { + s.append(1, '"'); + } } } -template void stringsToString >(const list &, string &); -template void stringsToString >(const vector &,string &); -template void stringsToString >(const set &, string &); -template string stringsToString(const T &tokens) +template void stringsToString >(const list&, string&); +template void stringsToString >(const vector&, string&); +template void stringsToString >(const set&, string&); +template string stringsToString(const T& tokens) { string out; stringsToString(tokens, out); return out; } -template string stringsToString >(const list &); -template string stringsToString >(const vector &); -template string stringsToString >(const set &); +template string stringsToString >(const list&); +template string stringsToString >(const vector&); +template string stringsToString >(const set&); -template void stringsToCSV(const T &tokens, string &s, - char sep) +template void stringsToCSV(const T& tokens, string& s, + char sep) { s.erase(); for (typename T::const_iterator it = tokens.begin(); - it != tokens.end(); it++) { - bool needquotes = false; - if (it->empty() || - it->find_first_of(string(1, sep) + "\"\n") != string::npos) - needquotes = true; - if (it != tokens.begin()) - s.append(1, sep); - if (needquotes) - s.append(1, '"'); - for (unsigned int i = 0; i < it->length(); i++) { - char car = it->at(i); - if (car == '"') { - s.append(2, '"'); - } else { - s.append(1, car); - } - } - if (needquotes) - s.append(1, '"'); + it != tokens.end(); it++) { + bool needquotes = false; + if (it->empty() || + it->find_first_of(string(1, sep) + "\"\n") != string::npos) { + needquotes = true; + } + if (it != tokens.begin()) { + s.append(1, sep); + } + if (needquotes) { + s.append(1, '"'); + } + for (unsigned int i = 0; i < it->length(); i++) { + char car = it->at(i); + if (car == '"') { + s.append(2, '"'); + } else { + s.append(1, car); + } + } + if (needquotes) { + s.append(1, '"'); + } } } -template void stringsToCSV >(const list &, string &, char); -template void stringsToCSV >(const vector &,string &, - char); +template void stringsToCSV >(const list&, string&, char); +template void stringsToCSV >(const vector&, string&, + char); void stringToTokens(const string& str, vector& tokens, - const string& delims, bool skipinit) + const string& delims, bool skipinit) { string::size_type startPos = 0, pos; // Skip initial delims, return empty if this eats all. - if (skipinit && - (startPos = str.find_first_not_of(delims, 0)) == string::npos) { - return; + if (skipinit && + (startPos = str.find_first_not_of(delims, 0)) == string::npos) { + return; } - while (startPos < str.size()) { + while (startPos < str.size()) { // Find next delimiter or end of string (end of token) pos = str.find_first_of(delims, startPos); // Add token to the vector and adjust start - if (pos == string::npos) { - tokens.push_back(str.substr(startPos)); - break; - } else if (pos == startPos) { - // Dont' push empty tokens after first - if (tokens.empty()) - tokens.push_back(string()); - startPos = ++pos; - } else { - tokens.push_back(str.substr(startPos, pos - startPos)); - startPos = ++pos; - } + if (pos == string::npos) { + tokens.push_back(str.substr(startPos)); + break; + } else if (pos == startPos) { + // Dont' push empty tokens after first + if (tokens.empty()) { + tokens.push_back(string()); + } + startPos = ++pos; + } else { + tokens.push_back(str.substr(startPos, pos - startPos)); + startPos = ++pos; + } } } -bool stringToBool(const string &s) +bool stringToBool(const string& s) { - if (s.empty()) - return false; - if (isdigit(s[0])) { - int val = atoi(s.c_str()); - return val ? true : false; + if (s.empty()) { + return false; + } + if (isdigit(s[0])) { + int val = atoi(s.c_str()); + return val ? true : false; + } + if (s.find_first_of("yYtT") == 0) { + return true; } - if (s.find_first_of("yYtT") == 0) - return true; return false; } -void trimstring(string &s, const char *ws) +void trimstring(string& s, const char *ws) { string::size_type pos = s.find_first_not_of(ws); if (pos == string::npos) { - s.clear(); - return; + s.clear(); + return; } s.replace(0, pos, string()); pos = s.find_last_not_of(ws); - if (pos != string::npos && pos != s.length()-1) - s.replace(pos+1, string::npos, string()); + if (pos != string::npos && pos != s.length() - 1) { + s.replace(pos + 1, string::npos, string()); + } } // Remove some chars and replace them with spaces -string neutchars(const string &str, const string &chars) +string neutchars(const string& str, const string& chars) { string out; neutchars(str, out, chars); return out; } -void neutchars(const string &str, string &out, const string& chars) +void neutchars(const string& str, string& out, const string& chars) { string::size_type startPos, pos; - for (pos = 0;;) { + for (pos = 0;;) { // Skip initial chars, break if this eats all. - if ((startPos = str.find_first_not_of(chars, pos)) == string::npos) - break; + if ((startPos = str.find_first_not_of(chars, pos)) == string::npos) { + break; + } // Find next delimiter or end of string (end of token) pos = str.find_first_of(chars, startPos); // Add token to the output. Note: token cant be empty here - if (pos == string::npos) { - out += str.substr(startPos); - } else { - out += str.substr(startPos, pos - startPos) + " "; - } + if (pos == string::npos) { + out += str.substr(startPos); + } else { + out += str.substr(startPos, pos - startPos) + " "; + } } } /* Truncate a string to a given maxlength, avoiding cutting off midword * if reasonably possible. Note: we could also use textsplit, stopping when - * we have enough, this would be cleanly utf8-aware but would remove + * we have enough, this would be cleanly utf8-aware but would remove * punctuation */ static const string cstr_SEPAR = " \t\n\r-:.;,/[]{}"; -string truncate_to_word(const string &input, string::size_type maxlen) +string truncate_to_word(const string& input, string::size_type maxlen) { string output; if (input.length() <= maxlen) { - output = input; + output = input; } else { - output = input.substr(0, maxlen); - string::size_type space = output.find_last_of(cstr_SEPAR); - // Original version only truncated at space if space was found after - // maxlen/2. But we HAVE to truncate at space, else we'd need to do - // utf8 stuff to avoid truncating at multibyte char. In any case, - // not finding space means that the text probably has no value. - // Except probably for Asian languages, so we may want to fix this - // one day - if (space == string::npos) { - output.erase(); - } else { - output.erase(space); - } + output = input.substr(0, maxlen); + string::size_type space = output.find_last_of(cstr_SEPAR); + // Original version only truncated at space if space was found after + // maxlen/2. But we HAVE to truncate at space, else we'd need to do + // utf8 stuff to avoid truncating at multibyte char. In any case, + // not finding space means that the text probably has no value. + // Except probably for Asian languages, so we may want to fix this + // one day + if (space == string::npos) { + output.erase(); + } else { + output.erase(space); + } } return output; } -void utf8truncate(string &s, int maxlen) -{ - if (s.size() <= string::size_type(maxlen)) - return; - Utf8Iter iter(s); - int pos = 0; - while (iter++ != string::npos) - if (iter.getBpos() < string::size_type(maxlen)) - pos = iter.getBpos(); - - s.erase(pos); -} - // Escape things that would look like markup -string escapeHtml(const string &in) +string escapeHtml(const string& in) { string out; for (string::size_type pos = 0; pos < in.length(); pos++) { switch(in.at(pos)) { - case '<': - out += "<"; - break; - case '&': - out += "&"; - break; - default: - out += in.at(pos); + case '<': out += "<"; break; + case '>': out += ">"; break; + case '&': out += "&"; break; + case '"': out += """; break; + default: out += in.at(pos); break; } } return out; } -string escapeShell(const string &in) +string escapeShell(const string& in) { string out; out += "\""; for (string::size_type pos = 0; pos < in.length(); pos++) { - switch(in.at(pos)) { - case '$': - out += "\\$"; - break; - case '`': - out += "\\`"; - break; - case '"': - out += "\\\""; - break; - case '\n': - out += "\\\n"; - break; - case '\\': - out += "\\\\"; - break; - default: - out += in.at(pos); - } + switch (in.at(pos)) { + case '$': + out += "\\$"; + break; + case '`': + out += "\\`"; + break; + case '"': + out += "\\\""; + break; + case '\n': + out += "\\\n"; + break; + case '\\': + out += "\\\\"; + break; + default: + out += in.at(pos); + } + } + out += "\""; + return out; +} + +// Escape value to be suitable as C++ source double-quoted string (for +// generating a c++ program +string makeCString(const string& in) +{ + string out; + out += "\""; + for (string::size_type pos = 0; pos < in.length(); pos++) { + switch (in.at(pos)) { + case '"': + out += "\\\""; + break; + case '\n': + out += "\\n"; + break; + case '\r': + out += "\\r"; + break; + case '\\': + out += "\\\\"; + break; + default: + out += in.at(pos); + } } out += "\""; return out; @@ -549,26 +599,26 @@ string escapeShell(const string &in) bool pcSubst(const string& in, string& out, const map& subs) { string::const_iterator it; - for (it = in.begin(); it != in.end();it++) { - if (*it == '%') { - if (++it == in.end()) { - out += '%'; - break; - } - if (*it == '%') { - out += '%'; - continue; - } - map::const_iterator tr; - if ((tr = subs.find(*it)) != subs.end()) { - out += tr->second; - } else { - // We used to do "out += *it;" here but this does not make + for (it = in.begin(); it != in.end(); it++) { + if (*it == '%') { + if (++it == in.end()) { + out += '%'; + break; + } + if (*it == '%') { + out += '%'; + continue; + } + map::const_iterator tr; + if ((tr = subs.find(*it)) != subs.end()) { + out += tr->second; + } else { + // We used to do "out += *it;" here but this does not make // sense - } - } else { - out += *it; - } + } + } else { + out += *it; + } } return true; } @@ -578,15 +628,15 @@ bool pcSubst(const string& in, string& out, const map& subs) out.erase(); string::size_type i; for (i = 0; i < in.size(); i++) { - if (in[i] == '%') { - if (++i == in.size()) { - out += '%'; - break; - } - if (in[i] == '%') { - out += '%'; - continue; - } + if (in[i] == '%') { + if (++i == in.size()) { + out += '%'; + break; + } + if (in[i] == '%') { + out += '%'; + continue; + } string key = ""; if (in[i] == '(') { if (++i == in.size()) { @@ -596,231 +646,200 @@ bool pcSubst(const string& in, string& out, const map& subs) string::size_type j = in.find_first_of(")", i); if (j == string::npos) { // ??concatenate remaining part and stop - out += in.substr(i-2); + out += in.substr(i - 2); break; } - key = in.substr(i, j-i); + key = in.substr(i, j - i); i = j; } else { key = in[i]; } - map::const_iterator tr; - if ((tr = subs.find(key)) != subs.end()) { - out += tr->second; - } else { + map::const_iterator tr; + if ((tr = subs.find(key)) != subs.end()) { + out += tr->second; + } else { // Substitute to nothing, that's the reasonable thing to do // instead of keeping the %(key) // out += key.size()==1? key : string("(") + key + string(")"); - } - } else { - out += in[i]; - } + } + } else { + out += in[i]; + } } return true; } +inline static int ulltorbuf(unsigned long long val, char *rbuf) +{ + int idx; + for (idx = 0; val; idx++) { + rbuf[idx] = '0' + val % 10; + val /= 10; + } + while (val); + rbuf[idx] = 0; + return idx; +} + +inline static void ullcopyreverse(const char *rbuf, string& buf, int idx) +{ + buf.reserve(idx + 1); + for (int i = idx - 1; i >= 0; i--) { + buf.push_back(rbuf[i]); + } +} + +void ulltodecstr(unsigned long long val, string& buf) +{ + buf.clear(); + if (val == 0) { + buf = "0"; + return; + } + + char rbuf[30]; + int idx = ulltorbuf(val, rbuf); + + ullcopyreverse(rbuf, buf, idx); + return; +} + +void lltodecstr(long long val, string& buf) +{ + buf.clear(); + if (val == 0) { + buf = "0"; + return; + } + + bool neg = val < 0; + if (neg) { + val = -val; + } + + char rbuf[30]; + int idx = ulltorbuf(val, rbuf); + + if (neg) { + rbuf[idx++] = '-'; + } + rbuf[idx] = 0; + + ullcopyreverse(rbuf, buf, idx); + return; +} + +string lltodecstr(long long val) +{ + string buf; + lltodecstr(val, buf); + return buf; +} + +string ulltodecstr(unsigned long long val) +{ + string buf; + ulltodecstr(val, buf); + return buf; +} // Convert byte count into unit (KB/MB...) appropriate for display string displayableBytes(off_t size) { - char sizebuf[50]; const char *unit; - + double roundable = 0; if (size < 1000) { - unit = " B "; - roundable = double(size); + unit = " B "; + roundable = double(size); } else if (size < 1E6) { - unit = " KB "; - roundable = double(size) / 1E3; + unit = " KB "; + roundable = double(size) / 1E3; } else if (size < 1E9) { - unit = " MB "; - roundable = double(size) / 1E6; + unit = " MB "; + roundable = double(size) / 1E6; } else { - unit = " GB "; - roundable = double(size) / 1E9; + unit = " GB "; + roundable = double(size) / 1E9; } - size = round(roundable); - sprintf(sizebuf, "%lld" "%s", (long long)size, unit); - return string(sizebuf); + size = off_t(round(roundable)); + return lltodecstr(size).append(unit); } -string breakIntoLines(const string& in, unsigned int ll, - unsigned int maxlines) +string breakIntoLines(const string& in, unsigned int ll, + unsigned int maxlines) { string query = in; string oq; unsigned int nlines = 0; while (query.length() > 0) { - string ss = query.substr(0, ll); - if (ss.length() == ll) { - string::size_type pos = ss.find_last_of(" "); - if (pos == string::npos) { - pos = query.find_first_of(" "); - if (pos != string::npos) - ss = query.substr(0, pos+1); - else - ss = query; - } else { - ss = ss.substr(0, pos+1); - } - } - // This cant happen, but anyway. Be very sure to avoid an infinite loop - if (ss.length() == 0) { - oq = query; - break; - } - oq += ss + "\n"; - if (nlines++ >= maxlines) { - oq += " ... \n"; - break; - } - query= query.substr(ss.length()); + string ss = query.substr(0, ll); + if (ss.length() == ll) { + string::size_type pos = ss.find_last_of(" "); + if (pos == string::npos) { + pos = query.find_first_of(" "); + if (pos != string::npos) { + ss = query.substr(0, pos + 1); + } else { + ss = query; + } + } else { + ss = ss.substr(0, pos + 1); + } + } + // This cant happen, but anyway. Be very sure to avoid an infinite loop + if (ss.length() == 0) { + oq = query; + break; + } + oq += ss + "\n"; + if (nlines++ >= maxlines) { + oq += " ... \n"; + break; + } + query = query.substr(ss.length()); } return oq; } -//////////////////// - -#if 0 -// Internal redefinition of system time interface to help with dependancies -struct m_timespec { - time_t tv_sec; - long tv_nsec; -}; -#endif - -#ifndef CLOCK_REALTIME -typedef int clockid_t; -#define CLOCK_REALTIME 1 -#endif - -#define MILLIS(TV) ( (long)(((TV).tv_sec - m_secs) * 1000L + \ - ((TV).tv_nsec - m_nsecs) / 1000000)) -#define MICROS(TV) ( (long)(((TV).tv_sec - m_secs) * 1000000L + \ - ((TV).tv_nsec - m_nsecs) / 1000)) -#define NANOS(TV) ( (long long)(((TV).tv_sec - m_secs) * 1000000000LL + \ - ((TV).tv_nsec - m_nsecs))) - -// Using clock_gettime() is nice because it gives us ns res and it helps with -// computing threads work times, but it's also a pita because it forces linking -// with -lrt. So keep it optional. And not on the mac anyway -// #define USE_CLOCK_GETTIME - -#ifdef __APPLE__ -#undef USE_CLOCK_GETTIME -#endif - -#ifndef USE_CLOCK_GETTIME -#include -#endif - -static void gettime(clockid_t clk_id, struct timespec *ts) -{ -#ifndef USE_CLOCK_GETTIME - struct timeval tv; - gettimeofday(&tv, 0); - ts->tv_sec = tv.tv_sec; - ts->tv_nsec = tv.tv_usec * 1000; -#else - clock_gettime(clk_id, ts); -#endif -} -///// End system interface - -// Note: this not protected against multithread access and not reentrant, but -// this is mostly debug code, and it won't crash, just show bad results. Also -// the frozen thing is not used that much -static timespec frozen_tv; -void Chrono::refnow() -{ - gettime(CLOCK_REALTIME, &frozen_tv); -} - -Chrono::Chrono() -{ - restart(); -} - -// Reset and return value before rest in milliseconds -long Chrono::restart() -{ - struct timespec tv; - gettime(CLOCK_REALTIME, &tv); - long ret = MILLIS(tv); - m_secs = tv.tv_sec; - m_nsecs = tv.tv_nsec; - return ret; -} - -// Get current timer value, milliseconds -long Chrono::millis(int frozen) -{ - return nanos() / 1000000; -} - -// -long Chrono::micros(int frozen) -{ - return nanos() / 1000; -} - -long long Chrono::nanos(int frozen) -{ - if (frozen) { - return NANOS(frozen_tv); - } else { - struct timespec tv; - gettime(CLOCK_REALTIME, &tv); - return NANOS(tv); - } -} - -float Chrono::secs(int frozen) -{ - struct timespec tv; - gettime(CLOCK_REALTIME, &tv); - float secs = (float)(frozen?frozen_tv.tv_sec:tv.tv_sec - m_secs); - float nsecs = (float)(frozen?frozen_tv.tv_nsec:tv.tv_nsec - m_nsecs); - return secs + nsecs * 1e-9; -} - // Date is Y[-M[-D]] -static bool parsedate(vector::const_iterator& it, - vector::const_iterator end, DateInterval *dip) +static bool parsedate(vector::const_iterator& it, + vector::const_iterator end, DateInterval *dip) { dip->y1 = dip->m1 = dip->d1 = dip->y2 = dip->m2 = dip->d2 = 0; - if (it->length() > 4 || !it->length() || - it->find_first_not_of("0123456789") != string::npos) { + if (it->length() > 4 || !it->length() || + it->find_first_not_of("0123456789") != string::npos) { return false; } if (it == end || sscanf(it++->c_str(), "%d", &dip->y1) != 1) { return false; } - if (it == end || *it == "/") + if (it == end || *it == "/") { return true; + } if (*it++ != "-") { return false; } - if (it->length() > 2 || !it->length() || - it->find_first_not_of("0123456789") != string::npos) { + if (it->length() > 2 || !it->length() || + it->find_first_not_of("0123456789") != string::npos) { return false; } if (it == end || sscanf(it++->c_str(), "%d", &dip->m1) != 1) { return false; } - if (it == end || *it == "/") + if (it == end || *it == "/") { return true; + } if (*it++ != "-") { return false; } - if (it->length() > 2 || !it->length() || - it->find_first_not_of("0123456789") != string::npos) { + if (it->length() > 2 || !it->length() || + it->find_first_not_of("0123456789") != string::npos) { return false; } if (it == end || sscanf(it++->c_str(), "%d", &dip->d1) != 1) { - return -1; + return false; } return true; @@ -829,7 +848,7 @@ static bool parsedate(vector::const_iterator& it, // Called with the 'P' already processed. Period ends at end of string // or at '/'. We dont' do a lot effort at validation and will happily // accept 10Y1Y4Y (the last wins) -static bool parseperiod(vector::const_iterator& it, +static bool parseperiod(vector::const_iterator& it, vector::const_iterator end, DateInterval *dip) { dip->y1 = dip->m1 = dip->d1 = dip->y2 = dip->m2 = dip->d2 = 0; @@ -841,17 +860,29 @@ static bool parseperiod(vector::const_iterator& it, if (sscanf(it++->c_str(), "%d", &value) != 1) { return false; } - if (it == end || it->empty()) + if (it == end || it->empty()) { return false; + } switch (it->at(0)) { - case 'Y': case 'y': dip->y1 = value;break; - case 'M': case 'm': dip->m1 = value;break; - case 'D': case 'd': dip->d1 = value;break; - default: return false; + case 'Y': + case 'y': + dip->y1 = value; + break; + case 'M': + case 'm': + dip->m1 = value; + break; + case 'D': + case 'd': + dip->d1 = value; + break; + default: + return false; } it++; - if (it == end) + if (it == end) { return true; + } if (*it == "/") { return true; } @@ -859,14 +890,51 @@ static bool parseperiod(vector::const_iterator& it, return true; } +#ifdef _WIN32 +int setenv(const char *name, const char *value, int overwrite) +{ + if (!overwrite) { + const char *cp = getenv(name); + if (cp) { + return -1; + } + } + return _putenv_s(name, value); +} +void unsetenv(const char *name) +{ + _putenv_s(name, ""); +} +#endif + +time_t portable_timegm(struct tm *tm) +{ + time_t ret; + char *tz; + + tz = getenv("TZ"); + setenv("TZ", "", 1); + tzset(); + ret = mktime(tm); + if (tz) { + setenv("TZ", tz, 1); + } else { + unsetenv("TZ"); + } + tzset(); + return ret; +} + +#if 0 static void cerrdip(const string& s, DateInterval *dip) { cerr << s << dip->y1 << "-" << dip->m1 << "-" << dip->d1 << "/" - << dip->y2 << "-" << dip->m2 << "-" << dip->d2 + << dip->y2 << "-" << dip->m2 << "-" << dip->d2 << endl; } +#endif -// Compute date + period. Won't work out of the unix era. +// Compute date + period. Won't work out of the unix era. // or pre-1970 dates. Just convert everything to unixtime and // seconds (with average durations for months/years), add and convert // back @@ -877,15 +945,10 @@ static bool addperiod(DateInterval *dp, DateInterval *pp) // timegm sort it out memset(&tm, 0, sizeof(tm)); tm.tm_year = dp->y1 - 1900 + pp->y1; - tm.tm_mon = dp->m1 + pp->m1 -1; + tm.tm_mon = dp->m1 + pp->m1 - 1; tm.tm_mday = dp->d1 + pp->d1; -#ifdef sun time_t tres = mktime(&tm); localtime_r(&tres, &tm); -#else - time_t tres = timegm(&tm); - gmtime_r(&tres, &tm); -#endif dp->y1 = tm.tm_year + 1900; dp->m1 = tm.tm_mon + 1; dp->d1 = tm.tm_mday; @@ -895,10 +958,19 @@ static bool addperiod(DateInterval *dp, DateInterval *pp) int monthdays(int mon, int year) { switch (mon) { - // We are returning a few two many 29 days februaries, no problem - case 2: return (year % 4) == 0 ? 29 : 28; - case 1:case 3:case 5:case 7: case 8:case 10:case 12: return 31; - default: return 30; + // We are returning a few too many 29 days februaries, no problem + case 2: + return (year % 4) == 0 ? 29 : 28; + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + return 31; + default: + return 30; } } bool parsedateinterval(const string& s, DateInterval *dip) @@ -907,14 +979,15 @@ bool parsedateinterval(const string& s, DateInterval *dip) dip->y1 = dip->m1 = dip->d1 = dip->y2 = dip->m2 = dip->d2 = 0; DateInterval p1, p2, d1, d2; p1 = p2 = d1 = d2 = *dip; - bool hasp1 = false, hasp2 = false, hasd1 = false, hasd2 = false, - hasslash = false; + bool hasp1 = false, hasp2 = false, hasd1 = false, hasd2 = false, + hasslash = false; if (!stringToStrings(s, vs, "PYMDpymd-/")) { return false; } - if (vs.empty()) + if (vs.empty()) { return false; + } vector::const_iterator it = vs.begin(); if (*it == "P" || *it == "p") { @@ -952,7 +1025,7 @@ secondelt: if (!parseperiod(it, vs.end(), &p2)) { return false; } - hasp2 = true; + hasp2 = true; } else { if (!parsedate(it, vs.end(), &d2)) { return false; @@ -992,7 +1065,7 @@ secondelt: // If there is no explicit period, an incomplete date indicates a // period of the size of the uncompleted elements. Ex: 1999 // actually means 1999/P12M - // + // // If there is a period, the incomplete date should be extended // to the beginning or end of the unspecified portion. Ex: 1999/ // means 1999-01-01/ and /1999 means /1999-12-31 @@ -1051,10 +1124,12 @@ secondelt: void catstrerror(string *reason, const char *what, int _errno) { - if (!reason) - return; - if (what) - reason->append(what); + if (!reason) { + return; + } + if (what) { + reason->append(what); + } reason->append(": errno: "); @@ -1064,83 +1139,32 @@ void catstrerror(string *reason, const char *what, int _errno) reason->append(" : "); -#ifdef sun +#if defined(sun) || defined(_WIN32) // Note: sun strerror is noted mt-safe ?? reason->append(strerror(_errno)); #else -#define ERRBUFSZ 200 +#define ERRBUFSZ 200 char errbuf[ERRBUFSZ]; - // There are 2 versions of strerror_r. + // There are 2 versions of strerror_r. // - The GNU one returns a pointer to the message (maybe // static storage or supplied buffer). // - The POSIX one always stores in supplied buffer and // returns 0 on success. As the possibility of error and // error code are not specified, we're basically doomed // cause we can't use a test on the 0 value to know if we - // were returned a pointer... + // were returned a pointer... // Also couldn't find an easy way to disable the gnu version without // changing the cxxflags globally, so forget it. Recent gnu lib versions // normally default to the posix version. // At worse we get no message at all here. errbuf[0] = 0; - strerror_r(_errno, errbuf, ERRBUFSZ); + // We don't use ret, it's there to silence a cc warning + char *ret = (char *)strerror_r(_errno, errbuf, ERRBUFSZ); + (void)ret; reason->append(errbuf); #endif } -void HighlightData::toString(std::string& out) -{ - out.append("\nUser terms (orthograph): "); - for (std::set::const_iterator it = uterms.begin(); - it != uterms.end(); it++) { - out.append(" [").append(*it).append("]"); - } - out.append("\nUser terms to Query terms:"); - for (map::const_iterator it = terms.begin(); - it != terms.end(); it++) { - out.append("[").append(it->first).append("]->["); - out.append(it->second).append("] "); - } - out.append("\nGroups: "); - char cbuf[200]; - sprintf(cbuf, "Groups size %d grpsugidx size %d ugroups size %d", - int(groups.size()), int(grpsugidx.size()), int(ugroups.size())); - out.append(cbuf); - - unsigned int ugidx = (unsigned int)-1; - for (unsigned int i = 0; i < groups.size(); i++) { - if (ugidx != grpsugidx[i]) { - ugidx = grpsugidx[i]; - out.append("\n("); - for (unsigned int j = 0; j < ugroups[ugidx].size(); j++) { - out.append("[").append(ugroups[ugidx][j]).append("] "); - } - out.append(") ->"); - } - out.append(" {"); - for (unsigned int j = 0; j < groups[i].size(); j++) { - out.append("[").append(groups[i][j]).append("]"); - } - sprintf(cbuf, "%d", slacks[i]); - out.append("}").append(cbuf); - } - out.append("\n"); -} - -void HighlightData::append(const HighlightData& hl) -{ - uterms.insert(hl.uterms.begin(), hl.uterms.end()); - terms.insert(hl.terms.begin(), hl.terms.end()); - size_t ugsz0 = ugroups.size(); - ugroups.insert(ugroups.end(), hl.ugroups.begin(), hl.ugroups.end()); - - groups.insert(groups.end(), hl.groups.begin(), hl.groups.end()); - slacks.insert(slacks.end(), hl.slacks.begin(), hl.slacks.end()); - for (std::vector::const_iterator it = hl.grpsugidx.begin(); - it != hl.grpsugidx.end(); it++) { - grpsugidx.push_back(*it + ugsz0); - } -} static const char *vlang_to_code[] = { "be", "cp1251", @@ -1167,21 +1191,24 @@ static const char *vlang_to_code[] = { "uk", "koi8-u", }; +static const string cstr_cp1252("CP1252"); + string langtocode(const string& lang) { - static STD_UNORDERED_MAP lang_to_code; + static std::unordered_map lang_to_code; if (lang_to_code.empty()) { - for (unsigned int i = 0; - i < sizeof(vlang_to_code) / sizeof(char *); i += 2) { - lang_to_code[vlang_to_code[i]] = vlang_to_code[i+1]; - } + for (unsigned int i = 0; + i < sizeof(vlang_to_code) / sizeof(char *); i += 2) { + lang_to_code[vlang_to_code[i]] = vlang_to_code[i + 1]; + } } - STD_UNORDERED_MAP::const_iterator it = - lang_to_code.find(lang); + std::unordered_map::const_iterator it = + lang_to_code.find(lang); // Use cp1252 by default... - if (it == lang_to_code.end()) - return cstr_cp1252; + if (it == lang_to_code.end()) { + return cstr_cp1252; + } return it->second; } @@ -1190,195 +1217,176 @@ string localelang() { const char *lang = getenv("LANG"); - if (lang == 0 || *lang == 0 || !strcmp(lang, "C") || !strcmp(lang, "POSIX")) - return "en"; + if (lang == 0 || *lang == 0 || !strcmp(lang, "C") || + !strcmp(lang, "POSIX")) { + return "en"; + } string locale(lang); string::size_type under = locale.find_first_of("_"); - if (under == string::npos) - return locale; + if (under == string::npos) { + return locale; + } return locale.substr(0, under); } -// Initialization for static stuff to be called from main thread before going +#ifdef USE_STD_REGEX + +class SimpleRegexp::Internal { +public: + Internal(const string& exp, int flags, int nm) + : expr(exp, + basic_regex::flag_type(regex_constants::extended | + ((flags&SRE_ICASE) ? regex_constants::icase : 0) | + ((flags&SRE_NOSUB) ? regex_constants::nosubs : 0) + )), ok(true), nmatch(nm) { + } + std::regex expr; + std::smatch res; + bool ok; + int nmatch; +}; + +bool SimpleRegexp::simpleMatch(const string& val) const +{ + if (!ok()) + return false; + return regex_match(val, m->res, m->expr); +} + +string SimpleRegexp::getMatch(const string& val, int i) const +{ + return m->res.str(i); +} + +#else // -> !WIN32 + +class SimpleRegexp::Internal { +public: + Internal(const string& exp, int flags, int nm) : nmatch(nm) { + if (regcomp(&expr, exp.c_str(), REG_EXTENDED | + ((flags&SRE_ICASE) ? REG_ICASE : 0) | + ((flags&SRE_NOSUB) ? REG_NOSUB : 0)) == 0) { + ok = true; + } else { + ok = false; + } + matches.reserve(nmatch+1); + } + ~Internal() { + regfree(&expr); + } + bool ok; + regex_t expr; + int nmatch; + vector matches; +}; + +bool SimpleRegexp::simpleMatch(const string& val) const +{ + if (!ok()) + return false; + if (regexec(&m->expr, val.c_str(), m->nmatch+1, &m->matches[0], 0) == 0) { + return true; + } else { + return false; + } +} + +string SimpleRegexp::getMatch(const string& val, int i) const +{ + if (i > m->nmatch) { + return string(); + } + return val.substr(m->matches[i].rm_so, + m->matches[i].rm_eo - m->matches[i].rm_so); +} + +#endif // win/notwinf + +SimpleRegexp::SimpleRegexp(const string& exp, int flags, int nmatch) + : m(new Internal(exp, flags, nmatch)) +{ +} + +SimpleRegexp::~SimpleRegexp() +{ + delete m; +} + +bool SimpleRegexp::ok() const +{ + return m->ok; +} + +bool SimpleRegexp::operator() (const string& val) const +{ + return simpleMatch(val); +} + +string flagsToString(const vector& flags, unsigned int val) +{ + const char *s; + string out; + for (auto& flag : flags) { + if ((val & flag.value) == flag.value) { + s = flag.yesname; + } else { + s = flag.noname; + } + if (s && *s) { + /* We have something to write */ + if (out.length()) { + // If not first, add '|' separator + out.append("|"); + } + out.append(s); + } + } + return out; +} + +string valToString(const vector& flags, unsigned int val) +{ + string out; + for (auto& flag : flags) { + if (flag.value == val) { + out = flag.yesname; + return out; + } + } + { + char mybuf[100]; + sprintf(mybuf, "Unknown Value 0x%x", val); + out = mybuf; + } + return out; +} + +unsigned int stringToFlags(const vector& flags, + const string& input, const char *sep) +{ + unsigned int out = 0; + + vector toks; + stringToTokens(input, toks, sep); + for (auto& tok: toks) { + trimstring(tok); + for (auto& flag : flags) { + if (!tok.compare(flag.yesname)) { + /* Note: we don't break: the same name could conceivably + set several flags. */ + out |= flag.value; + } + } + } + return out; +} + + +// Initialization for static stuff to be called from main thread before going // multiple void smallut_init_mt() { // Init langtocode() static table langtocode(""); } - -#else // TEST_SMALLUT - -#include -using namespace std; -#include - -#include "smallut.h" - -struct spair { - const char *s1; - const char *s2; -}; -struct spair pairs[] = { - {"", ""}, - {"", "a"}, - {"a", ""}, - {"a", "a"}, - {"A", "a"}, - {"a", "A"}, - {"A", "A"}, - {"12", "12"}, - {"a", "ab"}, - {"ab", "a"}, - {"A", "Ab"}, - {"a", "Ab"}, -}; -int npairs = sizeof(pairs) / sizeof(struct spair); -struct spair suffpairs[] = { - {"", ""}, - {"", "a"}, - {"a", ""}, - {"a", "a"}, - {"toto.txt", ".txt"}, - {"TXT", "toto.txt"}, - {"toto.txt", ".txt1"}, - {"toto.txt1", ".txt"}, -}; -int nsuffpairs = sizeof(suffpairs) / sizeof(struct spair); - - -// Periods test strings -const char* periods[] = { - "2001", // Year 2001 - "2001/", // 2001 or later - "2001/P3Y", // 2001 -> 2004 or 2005, ambiguous - "2001-01-01/P3Y", // 01-2001 -> 01 2004 - "2001-03-03/2001-05-01", // Explicit one - "P3M/", // 3 months ago to now - "P1Y1M/2001-03-01", // 2000-02-01/2001-03-01 - "/2001", // From the epoch to the end of 2001 -}; -const int nperiods = sizeof(periods) / sizeof(char*); - -const char *thisprog; -static void cerrdip(const string& s, DateInterval *dip) -{ - cerr << s << dip->y1 << "-" << dip->m1 << "-" << dip->d1 << "/" - << dip->y2 << "-" << dip->m2 << "-" << dip->d2 - << endl; -} - -int main(int argc, char **argv) -{ - thisprog = *argv++;argc--; - -#if 1 - if (argc <=0 ) { - cerr << "Usage: smallut " << endl; - exit(1); - } - string s = *argv++;argc--; - vector vs; - if (!stringToStrings(s, vs, ":-()")) { - cerr << "Bad entry" << endl; - exit(1); - } - for (vector::const_iterator it = vs.begin(); it != vs.end(); it++) - cerr << "[" << *it << "] "; - cerr << endl; - exit(0); -#elif 0 - if (argc <=0 ) { - cerr << "Usage: smallut " << endl; - exit(1); - } - string s = *argv++;argc--; - DateInterval di; - if (!parsedateinterval(s, &di)) { - cerr << "Parse failed" << endl; - exit(1); - } - cerrdip("", &di); - exit(0); -#elif 0 - DateInterval di; - for (int i = 0; i < nperiods; i++) { - if (!parsedateinterval(periods[i], &di)) { - cerr << "Parsing failed for [" << periods[i] << "]" << endl; - } else { - cerrdip(string(periods[i]).append(" : "), &di); - } - } - exit(0); -#elif 0 - for (int i = 0; i < npairs; i++) { - { - int c = stringicmp(pairs[i].s1, pairs[i].s2); - printf("'%s' %s '%s' ", pairs[i].s1, - c == 0 ? "==" : c < 0 ? "<" : ">", pairs[i].s2); - } - { - int cl = stringlowercmp(pairs[i].s1, pairs[i].s2); - printf("L '%s' %s '%s' ", pairs[i].s1, - cl == 0 ? "==" : cl < 0 ? "<" : ">", pairs[i].s2); - } - { - int cu = stringuppercmp(pairs[i].s1, pairs[i].s2); - printf("U '%s' %s '%s' ", pairs[i].s1, - cu == 0 ? "==" : cu < 0 ? "<" : ">", pairs[i].s2); - } - printf("\n"); - } -#elif 0 - for (int i = 0; i < nsuffpairs; i++) { - int c = stringisuffcmp(suffpairs[i].s1, suffpairs[i].s2); - printf("[%s] %s [%s] \n", suffpairs[i].s1, - c == 0 ? "matches" : c < 0 ? "<" : ">", suffpairs[i].s2); - } -#elif 0 - std::string testit("\303\251l\303\251gant"); - for (int sz = 10; sz >= 0; sz--) { - utf8truncate(testit, sz); - cout << testit << endl; - } -#elif 0 - std::string testit("ligne\ndeuxieme ligne\r3eme ligne\r\n"); - cout << "[" << neutchars(testit, "\r\n") << "]" << endl; - string i, o; - cout << "neutchars(null) is [" << neutchars(i, "\r\n") << "]" << endl; -#elif 0 - map substs; - substs["a"] = "A_SUBST"; - substs["title"] = "TITLE_SUBST"; - string in = "a: %a title: %(title) pcpc: %% %"; - string out; - pcSubst(in, out, substs); - cout << in << " => " << out << endl; - - in = "unfinished: %(unfinished"; - pcSubst(in, out, substs); - cout << in << " => " << out << endl; - in = "unfinished: %("; - pcSubst(in, out, substs); - cout << in << " => " << out << endl; - in = "empty: %()"; - pcSubst(in, out, substs); - cout << in << " => " << out << endl; - substs.clear(); - in = "a: %a title: %(title) pcpc: %% %"; - pcSubst(in, out, substs); - cout << "After map clear: " << in << " => " << out << endl; -#elif 0 - list tokens; - tokens.push_back(""); - tokens.push_back("a,b"); - tokens.push_back("simple value"); - tokens.push_back("with \"quotes\""); - string out; - stringsToCSV(tokens, out); - cout << "CSV line: [" << out << "]" << endl; -#endif - -} - -#endif diff --git a/src/utils/smallut.h b/src/utils/smallut.h index 6a5b1193..1281a6f9 100644 --- a/src/utils/smallut.h +++ b/src/utils/smallut.h @@ -1,256 +1,37 @@ -/* Copyright (C) 2004 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 - * (at your option) any later version. +/* Copyright (C) 2006-2016 J.F.Dockes * - * 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 General Public License for more details. + * This library 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. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the - * Free Software Foundation, Inc., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * This library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA */ #ifndef _SMALLUT_H_INCLUDED_ #define _SMALLUT_H_INCLUDED_ -#include +#include #include #include #include #include -using std::string; -using std::vector; -using std::map; -using std::set; +// Miscellaneous mostly string-oriented small utilities +// Note that none of the following code knows about utf-8. -// Note these are all ascii routines -extern int stringicmp(const string& s1, const string& s2); -// For find_if etc. -struct StringIcmpPred { - StringIcmpPred(const string& s1) - : m_s1(s1) - {} - bool operator()(const string& s2) { - return stringicmp(m_s1, s2) == 0; - } - const string& m_s1; -}; - -extern int stringlowercmp(const string& alreadylower, const string& s2); -extern int stringuppercmp(const string& alreadyupper, const string& s2); - -extern void stringtolower(string& io); -extern string stringtolower(const string& io); - -// Is one string the end part of the other ? -extern int stringisuffcmp(const string& s1, const string& s2); - -// Divine language from locale -extern std::string localelang(); -// Divine 8bit charset from language -extern std::string langtocode(const string& lang); - -// Compare charset names, removing the more common spelling variations -extern bool samecharset(const string &cs1, const string &cs2); - -// Parse date interval specifier into pair of y,m,d dates. The format -// for the time interval is based on a subset of iso 8601 with -// the addition of open intervals, and removal of all time indications. -// 'P' is the Period indicator, it's followed by a length in -// years/months/days (or any subset thereof) -// Dates: YYYY-MM-DD YYYY-MM YYYY -// Periods: P[nY][nM][nD] where n is an integer value. -// At least one of YMD must be specified -// The separator for the interval is /. Interval examples -// YYYY/ (from YYYY) YYYY-MM-DD/P3Y (3 years after date) etc. -// This returns a pair of y,m,d dates. -struct DateInterval { - int y1;int m1;int d1; int y2;int m2;int d2; -}; -extern bool parsedateinterval(const string&s, DateInterval *di); -extern int monthdays(int mon, int year); - -/** - * Parse input string into list of strings. - * - * Token delimiter is " \t\n" except inside dquotes. dquote inside - * dquotes can be escaped with \ etc... - * Input is handled a byte at a time, things will work as long as space tab etc. - * have the ascii values and can't appear as part of a multibyte char. utf-8 ok - * but so are the iso-8859-x and surely others. addseps do have to be - * single-bytes - */ -template bool stringToStrings(const string& s, T &tokens, - const string& addseps = ""); - -/** - * Inverse operation: - */ -template void stringsToString(const T &tokens, string &s); -template std::string stringsToString(const T &tokens); - -/** - * Strings to CSV string. tokens containing the separator are quoted (") - * " inside tokens is escaped as "" ([word "quote"] =>["word ""quote"""] - */ -template void stringsToCSV(const T &tokens, string &s, - char sep = ','); - -/** - * Split input string. No handling of quoting - */ -extern void stringToTokens(const string &s, vector &tokens, - const string &delims = " \t", bool skipinit=true); - -/** Convert string to boolean */ -extern bool stringToBool(const string &s); - -/** Remove instances of characters belonging to set (default {space, - tab}) at beginning and end of input string */ -extern void trimstring(string &s, const char *ws = " \t"); - -/** Escape things like < or & by turning them into entities */ -extern string escapeHtml(const string &in); - -/** Replace some chars with spaces (ie: newline chars). This is not utf8-aware - * so chars should only contain ascii */ -extern string neutchars(const string &str, const string &chars); -extern void neutchars(const string &str, string& out, const string &chars); - -/** Turn string into something that won't be expanded by a shell. In practise - * quote with double-quotes and escape $`\ */ -extern string escapeShell(const string &str); - -/** Truncate a string to a given maxlength, avoiding cutting off midword - * if reasonably possible. */ -extern string truncate_to_word(const string &input, string::size_type maxlen); - -/** Truncate in place in an utf8-legal way */ -extern void utf8truncate(string &s, int maxlen); - -/** Convert byte count into unit (KB/MB...) appropriate for display */ -string displayableBytes(off_t size); - -/** Break big string into lines */ -string breakIntoLines(const string& in, unsigned int ll = 100, - unsigned int maxlines= 50); -/** Small utility to substitute printf-like percents cmds in a string */ -bool pcSubst(const string& in, string& out, const map& subs); -/** Substitute printf-like percents and also %(key) */ -bool pcSubst(const string& in, string& out, const map& subs); - -/** Append system error message */ -void catstrerror(string *reason, const char *what, int _errno); - -/** Compute times to help with perf issues */ -class Chrono { - public: - Chrono(); - /** Reset origin */ - long restart(); - /** Snapshot current time */ - static void refnow(); - /** Get current elapsed since creation or restart - * - * @param frozen give time since the last refnow call (this is to - * allow for using one actual system call to get values from many - * chrono objects, like when examining timeouts in a queue) - */ - long millis(int frozen = 0); - long ms() {return millis();} - long micros(int frozen = 0); - long long nanos(int frozen = 0); - float secs(int frozen = 0); - private: - long m_secs; - long m_nsecs; -}; - -/** Temp buffer with automatic deallocation */ -struct TempBuf { - TempBuf() - : m_buf(0) - {} - TempBuf(int n) - { - m_buf = (char *)malloc(n); - } - ~TempBuf() - { - if (m_buf) - free(m_buf); - } - char *setsize(int n) { return (m_buf = (char *)realloc(m_buf, n)); } - char *buf() {return m_buf;} - char *m_buf; -}; - -inline void leftzeropad(string& s, unsigned len) -{ - if (s.length() && s.length() < len) - s = s.insert(0, len - s.length(), '0'); -} - -// Duplicate map while ensuring no shared string data (to pass -// to other thread): -void map_ss_cp_noshr(const std::map s, - std::map *d); - -// Code for static initialization of an stl map. Somewhat like Boost.assign. -// Ref: http://stackoverflow.com/questions/138600/initializing-a-static-stdmapint-int-in-c -// Example use: map m = create_map (1,2) (3,4) (5,6) (7,8); - -template -class create_map -{ -private: - std::map m_map; -public: - create_map(const T& key, const U& val) - { - m_map[key] = val; - } - - create_map& operator()(const T& key, const U& val) - { - m_map[key] = val; - return *this; - } - - operator std::map() - { - return m_map; - } -}; -template -class create_vector -{ -private: - std::vector m_vector; -public: - create_vector(const T& val) - { - m_vector.push_back(val); - } - - create_vector& operator()(const T& val) - { - m_vector.push_back(val); - return *this; - } - - operator std::vector() - { - return m_vector; - } -}; +// Call this before going multithread. +void smallut_init_mt(); +#ifndef SMALLUT_DISABLE_MACROS #ifndef MIN #define MIN(A,B) (((A)<(B)) ? (A) : (B)) #endif @@ -260,7 +41,208 @@ public: #ifndef deleteZ #define deleteZ(X) {delete X;X = 0;} #endif +#endif /* SMALLUT_DISABLE_MACROS */ -void smallut_init_mt(); +// Case-insensitive compare. ASCII ONLY ! +extern int stringicmp(const std::string& s1, const std::string& s2); + +// For find_if etc. +struct StringIcmpPred { + StringIcmpPred(const std::string& s1) + : m_s1(s1) { + } + bool operator()(const std::string& s2) { + return stringicmp(m_s1, s2) == 0; + } + const std::string& m_s1; +}; + +extern int stringlowercmp(const std::string& alreadylower, + const std::string& s2); +extern int stringuppercmp(const std::string& alreadyupper, + const std::string& s2); + +extern void stringtolower(std::string& io); +extern std::string stringtolower(const std::string& io); +extern void stringtoupper(std::string& io); +extern std::string stringtoupper(const std::string& io); +extern bool beginswith(const std::string& big, const std::string& small); + +// Is one string the end part of the other ? +extern int stringisuffcmp(const std::string& s1, const std::string& s2); + +// Divine language from locale +extern std::string localelang(); +// Divine 8bit charset from language +extern std::string langtocode(const std::string& lang); + +// Compare charset names, removing the more common spelling variations +extern bool samecharset(const std::string& cs1, const std::string& cs2); + +// Parse date interval specifier into pair of y,m,d dates. The format +// for the time interval is based on a subset of iso 8601 with +// the addition of open intervals, and removal of all time indications. +// 'P' is the Period indicator, it's followed by a length in +// years/months/days (or any subset thereof) +// Dates: YYYY-MM-DD YYYY-MM YYYY +// Periods: P[nY][nM][nD] where n is an integer value. +// At least one of YMD must be specified +// The separator for the interval is /. Interval examples +// YYYY/ (from YYYY) YYYY-MM-DD/P3Y (3 years after date) etc. +// This returns a pair of y,m,d dates. +struct DateInterval { + int y1; + int m1; + int d1; + int y2; + int m2; + int d2; +}; +extern bool parsedateinterval(const std::string& s, DateInterval *di); +extern int monthdays(int mon, int year); + +/** + * Parse input string into list of strings. + * + * Token delimiter is " \t\n" except inside dquotes. dquote inside + * dquotes can be escaped with \ etc... + * Input is handled a byte at a time, things will work as long as + * space tab etc. have the ascii values and can't appear as part of a + * multibyte char. utf-8 ok but so are the iso-8859-x and surely + * others. addseps do have to be single-bytes + */ +template bool stringToStrings(const std::string& s, T& tokens, + const std::string& addseps = ""); + +/** + * Inverse operation: + */ +template void stringsToString(const T& tokens, std::string& s); +template std::string stringsToString(const T& tokens); + +/** + * Strings to CSV string. tokens containing the separator are quoted (") + * " inside tokens is escaped as "" ([word "quote"] =>["word ""quote"""] + */ +template void stringsToCSV(const T& tokens, std::string& s, + char sep = ','); + +/** + * Split input string. No handling of quoting + */ +extern void stringToTokens(const std::string& s, + std::vector& tokens, + const std::string& delims = " \t", + bool skipinit = true); + +/** Convert string to boolean */ +extern bool stringToBool(const std::string& s); + +/** Remove instances of characters belonging to set (default {space, + tab}) at beginning and end of input string */ +extern void trimstring(std::string& s, const char *ws = " \t"); + +/** Escape things like < or & by turning them into entities */ +extern std::string escapeHtml(const std::string& in); + +/** Double-quote and escape to produce C source code string (prog generation) */ +extern std::string makeCString(const std::string& in); + +/** Replace some chars with spaces (ie: newline chars). */ +extern std::string neutchars(const std::string& str, const std::string& chars); +extern void neutchars(const std::string& str, std::string& out, + const std::string& chars); + +/** Turn string into something that won't be expanded by a shell. In practise + * quote with double-quotes and escape $`\ */ +extern std::string escapeShell(const std::string& str); + +/** Truncate a string to a given maxlength, avoiding cutting off midword + * if reasonably possible. */ +extern std::string truncate_to_word(const std::string& input, + std::string::size_type maxlen); + +void ulltodecstr(unsigned long long val, std::string& buf); +void lltodecstr(long long val, std::string& buf); +std::string lltodecstr(long long val); +std::string ulltodecstr(unsigned long long val); + +/** Convert byte count into unit (KB/MB...) appropriate for display */ +std::string displayableBytes(off_t size); + +/** Break big string into lines */ +std::string breakIntoLines(const std::string& in, unsigned int ll = 100, + unsigned int maxlines = 50); + +/** Small utility to substitute printf-like percents cmds in a string */ +bool pcSubst(const std::string& in, std::string& out, + const std::map& subs); +/** Substitute printf-like percents and also %(key) */ +bool pcSubst(const std::string& in, std::string& out, + const std::map& subs); + +/** Append system error message */ +void catstrerror(std::string *reason, const char *what, int _errno); + +/** Portable timegm. MS C has _mkgmtime, but there is a bug in Gminw which + * makes it inaccessible */ +struct tm; +time_t portable_timegm(struct tm *tm); + +inline void leftzeropad(std::string& s, unsigned len) +{ + if (s.length() && s.length() < len) { + s = s.insert(0, len - s.length(), '0'); + } +} + +// A class to solve platorm/compiler issues for simple regex +// matches. Uses the appropriate native lib under the hood. +// This always uses extended regexp syntax. +class SimpleRegexp { +public: + enum Flags {SRE_NONE = 0, SRE_ICASE = 1, SRE_NOSUB = 2}; + /// @param nmatch must be >= the number of parenthesed subexp in exp + SimpleRegexp(const std::string& exp, int flags, int nmatch = 0); + ~SimpleRegexp(); + /// Match input against exp, return true if matches + bool simpleMatch(const std::string& val) const; + /// After simpleMatch success, get nth submatch, 0 is the whole + /// match, 1 first parentheses, etc. + std::string getMatch(const std::string& val, int matchidx) const; + /// Calls simpleMatch() + bool operator() (const std::string& val) const; + /// Check after construction + bool ok() const; + + class Internal; +private: + Internal *m; +}; + +/// Utilities for printing names for defined values (Ex: O_RDONLY->"O_RDONLY") + +/// Entries for the descriptive table +struct CharFlags { + unsigned int value; // Flag or value + const char *yesname;// String to print if flag set or equal + const char *noname; // String to print if flag not set (unused for values) +}; + +/// Helper macro for the common case where we want to print the +/// flag/value defined name +#define CHARFLAGENTRY(NM) {NM, #NM} + +/// Translate a bitfield into string description +extern std::string flagsToString(const std::vector&, + unsigned int flags); + +/// Translate a value into a name +extern std::string valToString(const std::vector&, unsigned int val); + +/// Reverse operation: translate string into bitfield +extern unsigned int +stringToFlags(const std::vector&, const std::string& input, + const char *sep = "|"); #endif /* _SMALLUT_H_INCLUDED_ */ diff --git a/src/utils/strmatcher.cpp b/src/utils/strmatcher.cpp index dcd3aa1b..7f33704a 100644 --- a/src/utils/strmatcher.cpp +++ b/src/utils/strmatcher.cpp @@ -19,29 +19,31 @@ #include #include +#ifdef _WIN32 +#include +#else #include +#endif #include #include -using std::string; #include "cstr.h" -#include "debuglog.h" -#include "strmatcher.h" +#include "log.h" #include "pathut.h" +#include "strmatcher.h" + +using namespace std; bool StrWildMatcher::match(const string& val) const { - LOGDEB2(("StrWildMatcher::match: [%s] against [%s]\n", - m_sexp.c_str(), val.c_str())); + LOGDEB2("StrWildMatcher::match: [" << (m_sexp) << "] against [" << (val) << "]\n" ); int ret = fnmatch(m_sexp.c_str(), val.c_str(), FNM_NOESCAPE); switch (ret) { case 0: return true; case FNM_NOMATCH: return false; default: - LOGINFO(("StrWildMatcher::match:err: e [%s] s [%s] (%s) ret %d\n", - m_sexp.c_str(), val.c_str(), - url_encode(val).c_str(), ret)); + LOGINFO("StrWildMatcher::match:err: e [" << (m_sexp) << "] s [" << (val) << "] (" << (url_encode(val)) << ") ret " << (ret) << "\n" ); return false; } } @@ -60,9 +62,25 @@ StrRegexpMatcher::StrRegexpMatcher(const string& exp) bool StrRegexpMatcher::setExp(const string& exp) { if (m_compiled) { +#ifdef _WIN32 + delete (regex*)m_compiled; +#else regfree((regex_t*)m_compiled); delete (regex_t*)m_compiled; +#endif } + m_compiled = 0; + +#ifdef _WIN32 + try { + m_compiled = new regex(exp, std::regex_constants::nosubs | + std::regex_constants::extended); + } catch (...) { + m_reason = string("StrRegexpMatcher:regcomp failed for ") + + exp + string("syntax error ?"); + return false; + } +#else m_compiled = new regex_t; if ((m_errcode = regcomp((regex_t*)m_compiled, exp.c_str(), REG_EXTENDED|REG_NOSUB))) { @@ -72,6 +90,7 @@ bool StrRegexpMatcher::setExp(const string& exp) + exp + string(errbuf); return false; } +#endif m_sexp = exp; return true; } @@ -79,8 +98,12 @@ bool StrRegexpMatcher::setExp(const string& exp) StrRegexpMatcher::~StrRegexpMatcher() { if (m_compiled) { +#ifdef _WIN32 + delete (regex *)m_compiled; +#else regfree((regex_t*)m_compiled); delete (regex_t*)m_compiled; +#endif } } @@ -88,7 +111,11 @@ bool StrRegexpMatcher::match(const string& val) const { if (m_errcode) return false; +#ifdef _WIN32 + return regex_match(val, *((regex *)m_compiled)); +#else return regexec((regex_t*)m_compiled, val.c_str(), 0, 0, 0) != REG_NOMATCH; +#endif } string::size_type StrRegexpMatcher::baseprefixlen() const @@ -100,3 +127,4 @@ bool StrRegexpMatcher::ok() const { return !m_errcode; } + diff --git a/src/utils/transcode.cpp b/src/utils/transcode.cpp index aec847d7..3453b056 100644 --- a/src/utils/transcode.cpp +++ b/src/utils/transcode.cpp @@ -20,16 +20,15 @@ #include #include -#ifndef NO_NAMESPACES +#include using std::string; -#endif /* NO_NAMESPACES */ #include #include #include "transcode.h" -#include "debuglog.h" -#include "ptmutex.h" +#include "log.h" + #ifdef RCL_ICONV_INBUF_CONST #define ICV_P2_TYPE const char** #else @@ -49,13 +48,13 @@ using std::string; bool transcode(const string &in, string &out, const string &icode, const string &ocode, int *ecnt) { - LOGDEB2(("Transcode: %s -> %s\n", icode.c_str(), ocode.c_str())); + LOGDEB2("Transcode: " << (icode) << " -> " << (ocode) << "\n" ); #ifdef ICONV_CACHE_OPEN static iconv_t ic = (iconv_t)-1; static string cachedicode; static string cachedocode; - static PTMutexInit o_cachediconv_mutex; - PTMutexLocker locker(o_cachediconv_mutex); + static std::mutex o_cachediconv_mutex; + std::unique_lock lock(o_cachediconv_mutex); #else iconv_t ic; #endif @@ -107,9 +106,8 @@ bool transcode(const string &in, string &out, const string &icode, " : " + strerror(errno); #endif if (errno == EILSEQ) { - LOGDEB1(("transcode:iconv: bad input seq.: shift, retry\n")); - LOGDEB1((" Input consumed %d output produced %d\n", - ip - in.c_str(), out.length() + OBSIZ - osiz)); + LOGDEB1("transcode:iconv: bad input seq.: shift, retry\n" ); + LOGDEB1(" Input consumed " << (ip - in) << " output produced " << (out.length() + OBSIZ - osiz) << "\n" ); out.append(obuf, OBSIZ - osiz); out += "?"; mecnt++; @@ -152,8 +150,7 @@ error: } if (mecnt) - LOGDEB(("transcode: [%s]->[%s] %d errors\n", - icode.c_str(), ocode.c_str(), mecnt)); + LOGDEB("transcode: [" << (icode) << "]->[" << (ocode) << "] " << (mecnt) << " errors\n" ); if (ecnt) *ecnt = mecnt; return ret; @@ -165,7 +162,6 @@ error: #include #include #include -#include #include #include @@ -219,16 +215,17 @@ int main(int argc, char **argv) cerr << out << endl; exit(1); } - int fd = open(ofilename.c_str(), O_CREAT|O_TRUNC|O_WRONLY, 0666); - if (fd < 0) { + FILE *fp = fopen(ofilename.c_str(), "wb"); + if (fp == 0) { perror("Open/create output"); exit(1); } - if (write(fd, out.c_str(), out.length()) != (int)out.length()) { - perror("write"); + if (fwrite(out.c_str(), 1, out.length(), fp) != (int)out.length()) { + perror("fwrite"); exit(1); } - close(fd); + fclose(fp); exit(0); } #endif + diff --git a/src/utils/trexecmd.cpp b/src/utils/trexecmd.cpp new file mode 100644 index 00000000..94813d38 --- /dev/null +++ b/src/utils/trexecmd.cpp @@ -0,0 +1,384 @@ +#include "autoconfig.h" + +#include "execmd.h" + +#include +#include +#include "safeunistd.h" +#include +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include + +#include "log.h" +#include "cancelcheck.h" +#include "execmd.h" +#include "smallut.h" + +using namespace std; + +// Testing the rclexecm protocol outside of recoll. Here we use the +// rcldoc.py filter, you can try with rclaudio too, adjust the file +// arg accordingly. This simplified driver only really works with +// single-doc files (else it extracts only the first doc, usually the +// empty self-doc). +bool exercise_mhexecm(const string& cmdstr, const string& mimetype, + vector& files) +{ + if (files.empty()) + return false; + + ExecCmd cmd; + vector myparams; + +#ifdef _WIN32 + // Hack for windows: the command is always "Python somescript" + myparams.push_back(files[0]); + files.erase(files.begin()); +#endif + + if (cmd.startExec(cmdstr, myparams, 1, 1) < 0) { + cerr << "startExec " << cmdstr << " failed. Missing command?\n"; + return false; + } + + for (vector::const_iterator it = files.begin(); + it != files.end(); it++) { + // Build request message + ostringstream obuf; + obuf << "Filename: " << (*it).length() << "\n" << (*it); + obuf << "Mimetype: " << mimetype.length() << "\n" << mimetype; + // Bogus parameter should be skipped by filter + obuf << "BogusParam: " << string("bogus").length() << "\n" << "bogus"; + obuf << "\n"; + cerr << "SENDING: [" << obuf.str() << "]\n"; + // Send it + if (cmd.send(obuf.str()) < 0) { + // The real code calls zapchild here, but we don't need it as + // this will be handled by ~ExecCmd + //cmd.zapChild(); + cerr << "send error\n"; + return false; + } + + // Read answer + for (int loop=0;;loop++) { + string name, data; + + // Code from mh_execm.cpp: readDataElement + string ibuf; + // Read name and length + if (cmd.getline(ibuf) <= 0) { + cerr << "getline error\n"; + return false; + } + // Empty line (end of message) + if (!ibuf.compare("\n")) { + cerr << "Got empty line\n"; + name.clear(); + break; + } + + // Filters will sometimes abort before entering the real + // protocol, ie if a module can't be loaded. Check the + // special filter error first word: + if (ibuf.find("RECFILTERROR ") == 0) { + cerr << "Got RECFILTERROR\n"; + return false; + } + + // We're expecting something like Name: len\n + vector tokens; + stringToTokens(ibuf, tokens); + if (tokens.size() != 2) { + cerr << "bad line in filter output: [" << ibuf << "]\n"; + return false; + } + vector::iterator it = tokens.begin(); + name = *it++; + string& slen = *it; + int len; + if (sscanf(slen.c_str(), "%d", &len) != 1) { + cerr << "bad line in filter output (no len): [" << + ibuf << "]\n"; + return false; + } + // Read element data + data.erase(); + if (len > 0 && cmd.receive(data, len) != len) { + cerr << "MHExecMultiple: expected " << len << + " bytes of data, got " << data.length() << endl; + return false; + } + + // Empty element: end of message + if (name.empty()) + break; + cerr << "Got name: [" << name << "] data [" << data << "]\n"; + } + } + return true; +} + +static char *thisprog; +static char usage [] = +"trexecmd [-c -r -i -o] [-e ] cmd [arg1 arg2 ...]\n" +" -c : test cancellation (ie: trexecmd -c sleep 1000)\n" +" -r : run reexec. Must be separate option.\n" +" -i : command takes input\n" +" -o : command produces output\n" +" -e : send stderr to file named fn (will truncate it)\n" +" If -i is set, we send /etc/group contents to whatever command is run\n" +" If -o is set, we print whatever comes out\n" +"trexecmd -f bogus filter for testing. Uses same options\n" +"trexecmd -m [file ...]: test execm:\n" +" should be the path to an execm filter\n" +" the type of the file parameters\n" +"trexecmd -w cmd : do the 'which' thing\n" + ; + +static void Usage(FILE *fp = stderr) +{ + fprintf(fp, "%s: usage:\n%s", thisprog, usage); + exit(1); +} + +static int op_flags; +#define OPT_MOINS 0x1 +#define OPT_i 0x4 +#define OPT_w 0x8 +#define OPT_c 0x10 +#define OPT_r 0x20 +#define OPT_m 0x40 +#define OPT_o 0x80 +#define OPT_e 0x100 +#define OPT_f 0x200 + +void childfilter() +{ + const int bs = 1024; + char buf[bs]; + if (op_flags & OPT_c) + sleep(2000); + if (op_flags& OPT_i) { + while (read(0, buf, bs) > 0); + } + if (op_flags& OPT_o) { + for (int i = 0; i < 10; i++) { + printf("This is DATA 1 2 3\n"); + } + } + exit(0); +} + +// Data sink for data coming out of the command. We also use it to set +// a cancellation after a moment. +class MEAdv : public ExecCmdAdvise { +public: + void newData(int cnt) { + cerr << "newData(" << cnt << ")" << endl; + if (op_flags & OPT_c) { + static int callcnt; + if (callcnt++ == 5) { + // Just sets the cancellation flag + CancelCheck::instance().setCancel(); + // Would be called from somewhere else and throws an + // exception. We call it here for simplicity + cerr << "newData: should throw !\n"; + CancelCheck::instance().checkCancel(); + } + } + } +}; + +// Data provider, used if the -i flag is set +class MEPv : public ExecCmdProvide { +public: + string *m_input; + int m_cnt; + MEPv(string *i) + : m_input(i), m_cnt(0) { + } + ~MEPv() { + } + void newData() { + if (m_cnt++ < 10) { + char num[30]; + sprintf(num, "%d", m_cnt); + *m_input = string("This is an input chunk ") + string(num) + + string("\n"); + } else { + m_input->erase(); + } + } + void reset() { + m_cnt = 0; + } +}; + + + +ReExec reexec; +int main(int argc, char *argv[]) +{ +#ifndef _WIN32 + reexec.init(argc, argv); + + if (0) { + // Disabled: For testing reexec arg handling + vector newargs; + newargs.push_back("newarg"); + newargs.push_back("newarg1"); + newargs.push_back("newarg2"); + newargs.push_back("newarg3"); + newargs.push_back("newarg4"); + reexec.insertArgs(newargs, 2); + } +#endif + + string stderrFile; + thisprog = argv[0]; + argc--; argv++; + + while (argc > 0 && **argv == '-') { + (*argv)++; + if (!(**argv)) + /* Cas du "adb - core" */ + Usage(); + while (**argv) + switch (*(*argv)++) { + case 'c': op_flags |= OPT_c; break; + case 'e': + op_flags |= OPT_e; + if (argc < 2) { + Usage(); + } + stderrFile = *(++argv); argc--; + goto b1; + + case 'f': op_flags |= OPT_f; break; + case 'h': + for (int i = 0; i < 10; i++) { + cout << "MESSAGE " << i << " FROM TREXECMD\n"; + cout.flush(); + //sleep(1); + } + return 0; + case 'i': op_flags |= OPT_i; break; + case 'o': op_flags |= OPT_o; break; + case 'm': op_flags |= OPT_m; break; + case 'r': op_flags |= OPT_r; break; + case 'w': op_flags |= OPT_w; break; + default: Usage(); break; + } + b1: argc--; argv++; + } + + if (op_flags & OPT_f) { + childfilter(); + } + + if (argc < 1) + Usage(); + + string arg1 = *argv++; argc--; + vector l; + while (argc > 0) { + l.push_back(*argv++); argc--; + } + + DebugLog::getdbl()->setloglevel(DEBDEB1); + DebugLog::setfilename("stderr"); +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); + + if (op_flags & OPT_r) { + // Test reexec. Normally only once, next time we fall through + // because we remove the -r option (only works if it was + // isolated, not like -rc + chdir("/"); + argv[0] = strdup(""); + sleep(1); + cerr << "Calling reexec\n"; + // We remove the -r arg from list, otherwise we are going to + // loop (which you can try by commenting out the following + // line) + reexec.removeArg("-r"); + reexec.reexec(); + } +#endif + + + if (op_flags & OPT_w) { + // Test "which" method + string path; + if (ExecCmd::which(arg1, path)) { + cout << path << endl; + return 0; + } + return 1; + } else if (op_flags & OPT_m) { + if (l.size() < 2) + Usage(); + string mimetype = l[0]; + l.erase(l.begin()); + return exercise_mhexecm(arg1, mimetype, l) ? 0 : 1; + } else { + // Default: execute command line arguments + ExecCmd mexec; + + // Set callback to be called whenever there is new data + // available and at a periodic interval, to check for + // cancellation + MEAdv adv; + mexec.setAdvise(&adv); + //mexec.setTimeout(5); + // Stderr output goes there + if (!stderrFile.empty()) + mexec.setStderr(stderrFile); + + // A few environment variables. Check with trexecmd env + mexec.putenv("TESTVARIABLE1=TESTVALUE1"); + mexec.putenv("TESTVARIABLE2=TESTVALUE2"); + mexec.putenv("TESTVARIABLE3=TESTVALUE3"); + + string input, output; + MEPv pv(&input); + + string *ip = 0; + if (op_flags & OPT_i) { + ip = &input; + mexec.setProvide(&pv); + } + string *op = 0; + if (op_flags & OPT_o) { + op = &output; + } + + int status = -1; + for (int i = 0; i < 10; i++) { + output.clear(); + pv.reset(); + try { + status = mexec.doexec(arg1, l, ip, op); + } catch (CancelExcept) { + cerr << "CANCELLED" << endl; + } + //fprintf(stderr, "Status: 0x%x\n", status); + if (op_flags & OPT_o) { + cout << "data received: [" << output << "]\n"; + cerr << "iter " << i << " status " << + status << " bytes received " << output.size() << endl; + } + if (status) + break; + } + return status >> 8; + } +} + diff --git a/src/utils/utf8iter.cpp b/src/utils/utf8iter.cpp index 8a2d17f5..68f5132b 100644 --- a/src/utils/utf8iter.cpp +++ b/src/utils/utf8iter.cpp @@ -22,7 +22,7 @@ #include -#include "debuglog.h" +#include "log.h" #include "transcode.h" #ifndef NO_NAMESPACES @@ -32,6 +32,15 @@ using namespace std; #define UTF8ITER_CHECK #include "utf8iter.h" #include "readfile.h" +#include "textsplit.h" + +void tryempty() +{ + Utf8Iter it(""); + cout << "EOF ? " << it.eof() << endl; + TextSplit::isCJK(*it); + exit(0); +} const char *thisprog; static char usage [] = @@ -173,3 +182,4 @@ int main(int argc, char **argv) } exit(0); } + diff --git a/src/utils/utf8iter.h b/src/utils/utf8iter.h index 4e8894a1..fd2ee9cb 100644 --- a/src/utils/utf8iter.h +++ b/src/utils/utf8iter.h @@ -50,7 +50,7 @@ public: /** "Direct" access. Awfully inefficient as we skip from start or current * position at best. This can only be useful for a lookahead from the * current position */ - unsigned int operator[](unsigned int charpos) const + unsigned int operator[](std::string::size_type charpos) const { std::string::size_type mypos = 0; unsigned int mycp = 0; diff --git a/src/utils/utmkdefs.mk b/src/utils/utmkdefs.mk new file mode 100644 index 00000000..bf1b8a19 --- /dev/null +++ b/src/utils/utmkdefs.mk @@ -0,0 +1,13 @@ +ALL_CXXFLAGS = -std=c++11 \ + -I../aspell \ + -I../bincimapmime \ + -I../common \ + -I../index \ + -I../internfile \ + -I../rcldb \ + -I../unac \ + -I../utils +LIBRECOLL = -L../.libs -lrecoll -Wl,-rpath=$(shell pwd)/../.libs + +clean: + rm -f $(PROGS) *.o diff --git a/src/utils/wipedir.cpp b/src/utils/wipedir.cpp index 847d705f..d5196d9e 100644 --- a/src/utils/wipedir.cpp +++ b/src/utils/wipedir.cpp @@ -19,22 +19,22 @@ #ifndef TEST_WIPEDIR #include "autoconfig.h" -#include -#include -#include -#include #include +#include "safefcntl.h" +#include +#include "safesysstat.h" +#include "safeunistd.h" +#include #include #include -#ifndef NO_NAMESPACES -using namespace std; -#endif /* NO_NAMESPACES */ -#include "debuglog.h" +#include "log.h" #include "pathut.h" #include "wipedir.h" +using namespace std; + int wipedir(const string& dir, bool selfalso, bool recurse) { struct stat st; @@ -43,22 +43,22 @@ int wipedir(const string& dir, bool selfalso, bool recurse) statret = lstat(dir.c_str(), &st); if (statret == -1) { - LOGERR(("wipedir: cant stat %s, errno %d\n", dir.c_str(), errno)); + LOGERR("wipedir: cant stat " << (dir) << ", errno " << (errno) << "\n" ); return -1; } if (!S_ISDIR(st.st_mode)) { - LOGERR(("wipedir: %s not a directory\n", dir.c_str())); + LOGERR("wipedir: " << (dir) << " not a directory\n" ); return -1; } if (access(dir.c_str(), R_OK|W_OK|X_OK) < 0) { - LOGERR(("wipedir: no write access to %s\n", dir.c_str())); + LOGERR("wipedir: no write access to " << (dir) << "\n" ); return -1; } DIR *d = opendir(dir.c_str()); if (d == 0) { - LOGERR(("wipedir: cant opendir %s, errno %d\n", dir.c_str(), errno)); + LOGERR("wipedir: cant opendir " << (dir) << ", errno " << (errno) << "\n" ); return -1; } int remaining = 0; @@ -72,7 +72,7 @@ int wipedir(const string& dir, bool selfalso, bool recurse) struct stat st; int statret = lstat(fn.c_str(), &st); if (statret == -1) { - LOGERR(("wipedir: cant stat %s, errno %d\n", fn.c_str(), errno)); + LOGERR("wipedir: cant stat " << (fn) << ", errno " << (errno) << "\n" ); goto out; } if (S_ISDIR(st.st_mode)) { @@ -87,8 +87,7 @@ int wipedir(const string& dir, bool selfalso, bool recurse) } } else { if (unlink(fn.c_str()) < 0) { - LOGERR(("wipedir: cant unlink %s, errno %d\n", - fn.c_str(), errno)); + LOGERR("wipedir: cant unlink " << (fn) << ", errno " << (errno) << "\n" ); goto out; } } @@ -97,8 +96,7 @@ int wipedir(const string& dir, bool selfalso, bool recurse) ret = remaining; if (selfalso && ret == 0) { if (rmdir(dir.c_str()) < 0) { - LOGERR(("wipedir: rmdir(%s) failed, errno %d\n", - dir.c_str(), errno)); + LOGERR("wipedir: rmdir(" << (dir) << ") failed, errno " << (errno) << "\n" ); ret = -1; } } @@ -114,7 +112,6 @@ int wipedir(const string& dir, bool selfalso, bool recurse) #include #include -#include #include "wipedir.h" @@ -168,3 +165,4 @@ int main(int argc, const char **argv) } #endif + diff --git a/src/utils/workqueue.cpp b/src/utils/workqueue.cpp index b3d6a2f7..1ba38188 100644 --- a/src/utils/workqueue.cpp +++ b/src/utils/workqueue.cpp @@ -1,9 +1,28 @@ +/* Copyright (C) 2014 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +// Test program for the workqueue module + #include #include -#include #include #include +#include "safeunistd.h" + #include "workqueue.h" static char *thisprog; @@ -80,9 +99,9 @@ int main(int argc, char **argv) if (argc != 0) Usage(); - WorkQueue wq(10); + WorkQueue wq("testwq", 10); - if (!wq.start(&worker, &wq)) { + if (!wq.start(2, &worker, &wq)) { fprintf(stderr, "Start failed\n"); exit(1); } diff --git a/src/utils/workqueue.h b/src/utils/workqueue.h index 164bac12..216876ac 100644 --- a/src/utils/workqueue.h +++ b/src/utils/workqueue.h @@ -1,31 +1,34 @@ -/* Copyright (C) 2012 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 - * (at your option) any later version. +/* Copyright (C) 2006-2016 J.F.Dockes * - * 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 General Public License for more details. + * This library 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. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the - * Free Software Foundation, Inc., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * This library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA */ #ifndef _WORKQUEUE_H_INCLUDED_ #define _WORKQUEUE_H_INCLUDED_ -#include -#include - +#include +#if HAVE_STD_FUTURE +#include +#endif #include #include #include +#include +#include -#include "debuglog.h" -#include "ptmutex.h" +#include "log.h" /** * A WorkQueue manages the synchronisation around a queue of work items, @@ -39,52 +42,52 @@ * the client or worker sets an end condition on the queue. A second * queue could conceivably be used for returning individual task * status. + * + * The strange thread functions argument and return values + * comes from compatibility with an earlier pthread-based + * implementation. */ template class WorkQueue { public: /** Create a WorkQueue * @param name for message printing - * @param hi number of tasks on queue before clients blocks. Default 0 + * @param hi number of tasks on queue before clients blocks. Default 0 * meaning no limit. hi == -1 means that the queue is disabled. * @param lo minimum count of tasks before worker starts. Default 1. */ - WorkQueue(const string& name, size_t hi = 0, size_t lo = 1) - : m_name(name), m_high(hi), m_low(lo), - m_workers_exited(0), m_clients_waiting(0), m_workers_waiting(0), - m_tottasks(0), m_nowake(0), m_workersleeps(0), m_clientsleeps(0) - { - m_ok = (pthread_cond_init(&m_ccond, 0) == 0) && - (pthread_cond_init(&m_wcond, 0) == 0); + WorkQueue(const std::string& name, size_t hi = 0, size_t lo = 1) + : m_name(name), m_high(hi), m_low(lo), m_workers_exited(0), + m_ok(true), m_clients_waiting(0), m_workers_waiting(0), + m_tottasks(0), m_nowake(0), m_workersleeps(0), m_clientsleeps(0) { } - ~WorkQueue() - { - LOGDEB2(("WorkQueue::~WorkQueue:%s\n", m_name.c_str())); - if (!m_worker_threads.empty()) + ~WorkQueue() { + if (!m_worker_threads.empty()) { setTerminateAndWait(); + } } - /** Start the worker threads. + /** Start the worker threads. * * @param nworkers number of threads copies to start. * @param start_routine thread function. It should loop - * taking (QueueWorker::take()) and executing tasks. + * taking (QueueWorker::take()) and executing tasks. * @param arg initial parameter to thread function. * @return true if ok. */ - bool start(int nworkers, void *(*start_routine)(void *), void *arg) - { - PTMutexLocker lock(m_mutex); - for (int i = 0; i < nworkers; i++) { - int err; - pthread_t thr; - if ((err = pthread_create(&thr, 0, start_routine, arg))) { - LOGERR(("WorkQueue:%s: pthread_create failed, err %d\n", - m_name.c_str(), err)); - return false; - } - m_worker_threads.push_back(thr); + bool start(int nworkers, void *(workproc)(void *), void *arg) { + std::unique_lock lock(m_mutex); + for (int i = 0; i < nworkers; i++) { + Worker w; +#if HAVE_STD_FUTURE + std::packaged_task task(workproc); + w.res = task.get_future(); + w.thr = std::thread(std::move(task), arg); +#else + w.thr = std::thread(workproc, arg); +#endif + m_worker_threads.push_back(std::move(w)); } return true; } @@ -93,33 +96,37 @@ public: * * Sleeps if there are already too many. */ - bool put(T t) - { - PTMutexLocker lock(m_mutex); - if (!lock.ok() || !ok()) { - LOGERR(("WorkQueue::put:%s: !ok or mutex_lock failed\n", - m_name.c_str())); + bool put(T t, bool flushprevious = false) { + std::unique_lock lock(m_mutex); + if (!ok()) { + LOGERR("WorkQueue::put:" << m_name << ": !ok\n"); return false; - } + } while (ok() && m_high > 0 && m_queue.size() >= m_high) { - m_clientsleeps++; + m_clientsleeps++; // Keep the order: we test ok() AFTER the sleep... - m_clients_waiting++; - if (pthread_cond_wait(&m_ccond, lock.getMutex()) || !ok()) { - m_clients_waiting--; + m_clients_waiting++; + m_ccond.wait(lock); + if (!ok()) { + m_clients_waiting--; return false; } - m_clients_waiting--; + m_clients_waiting--; + } + if (flushprevious) { + while (!m_queue.empty()) { + m_queue.pop(); + } } m_queue.push(t); - if (m_workers_waiting > 0) { - // Just wake one worker, there is only one new task. - pthread_cond_signal(&m_wcond); - } else { - m_nowake++; - } + if (m_workers_waiting > 0) { + // Just wake one worker, there is only one new task. + m_wcond.notify_one(); + } else { + m_nowake++; + } return true; } @@ -135,133 +142,142 @@ public: * (which can control the task flow), else there could be * tasks in the intermediate queues. * To rephrase: there is no warranty on return that the queue is actually - * idle EXCEPT if the caller knows that no jobs are still being created. + * idle EXCEPT if the caller knows that no jobs are still being created. * It would be possible to transform this into a safe call if some kind - * of suspend condition was set on the queue by waitIdle(), to be reset by + * of suspend condition was set on the queue by waitIdle(), to be reset by * some kind of "resume" call. Not currently the case. */ - bool waitIdle() - { - PTMutexLocker lock(m_mutex); - if (!lock.ok() || !ok()) { - LOGERR(("WorkQueue::waitIdle:%s: not ok or can't lock\n", - m_name.c_str())); + bool waitIdle() { + std::unique_lock lock(m_mutex); + if (!ok()) { + LOGERR("WorkQueue::waitIdle:" << m_name << ": not ok\n"); return false; } // We're done when the queue is empty AND all workers are back // waiting for a task. - while (ok() && (m_queue.size() > 0 || + while (ok() && (m_queue.size() > 0 || m_workers_waiting != m_worker_threads.size())) { - m_clients_waiting++; - if (pthread_cond_wait(&m_ccond, lock.getMutex())) { - m_clients_waiting--; - m_ok = false; - LOGERR(("WorkQueue::waitIdle:%s: cond_wait failed\n", - m_name.c_str())); - return false; - } - m_clients_waiting--; + m_clients_waiting++; + m_ccond.wait(lock); + m_clients_waiting--; } return ok(); } - - /** Tell the workers to exit, and wait for them. + /** Tell the workers to exit, and wait for them. * * Does not bother about tasks possibly remaining on the queue, so * should be called after waitIdle() for an orderly shutdown. */ - void* setTerminateAndWait() - { - PTMutexLocker lock(m_mutex); - LOGDEB(("setTerminateAndWait:%s\n", m_name.c_str())); + void *setTerminateAndWait() { + std::unique_lock lock(m_mutex); + LOGDEB("setTerminateAndWait:" << m_name << "\n"); - if (m_worker_threads.empty()) { - // Already called ? - return (void*)0; - } + if (m_worker_threads.empty()) { + // Already called ? + return (void*)0; + } - // Wait for all worker threads to have called workerExit() + // Wait for all worker threads to have called workerExit() m_ok = false; while (m_workers_exited < m_worker_threads.size()) { - pthread_cond_broadcast(&m_wcond); - m_clients_waiting++; - if (pthread_cond_wait(&m_ccond, lock.getMutex())) { - LOGERR(("WorkQueue::setTerminate:%s: cond_wait failed\n", - m_name.c_str())); - m_clients_waiting--; - return (void*)0; - } - m_clients_waiting--; + m_wcond.notify_all(); + m_clients_waiting++; + m_ccond.wait(lock); + m_clients_waiting--; } - LOGINFO(("%s: tasks %u nowakes %u wsleeps %u csleeps %u\n", - m_name.c_str(), m_tottasks, m_nowake, m_workersleeps, - m_clientsleeps)); - // Perform the thread joins and compute overall status + LOGINFO("" << m_name << ": tasks " << m_tottasks << " nowakes " << + m_nowake << " wsleeps " << m_workersleeps << " csleeps " << + m_clientsleeps << "\n"); + // Perform the thread joins and compute overall status // Workers return (void*)1 if ok void *statusall = (void*)1; - std::list::iterator it; while (!m_worker_threads.empty()) { - void *status; - it = m_worker_threads.begin(); - pthread_join(*it, &status); - if (status == (void *)0) +#if HAVE_STD_FUTURE + void *status = m_worker_threads.front().res.get(); +#else + void *status = (void*) 1; +#endif + m_worker_threads.front().thr.join(); + if (status == (void *)0) { statusall = status; - m_worker_threads.erase(it); + } + m_worker_threads.pop_front(); } - // Reset to start state. - m_workers_exited = m_clients_waiting = m_workers_waiting = - m_tottasks = m_nowake = m_workersleeps = m_clientsleeps = 0; + // Reset to start state. + m_workers_exited = m_clients_waiting = m_workers_waiting = + m_tottasks = m_nowake = m_workersleeps = m_clientsleeps = 0; m_ok = true; - LOGDEB(("setTerminateAndWait:%s done\n", m_name.c_str())); + LOGDEB("setTerminateAndWait:" << m_name << " done\n"); return statusall; } /** Take task from queue. Called from worker. - * - * Sleeps if there are not enough. Signal if we go to sleep on empty + * + * Sleeps if there are not enough. Signal if we go to sleep on empty * queue: client may be waiting for our going idle. */ - bool take(T* tp, size_t *szp = 0) - { - PTMutexLocker lock(m_mutex); - if (!lock.ok() || !ok()) { - LOGDEB(("WorkQueue::take:%s: not ok\n", m_name.c_str())); + bool take(T* tp, size_t *szp = 0) { + std::unique_lock lock(m_mutex); + if (!ok()) { + LOGDEB("WorkQueue::take:" << m_name << ": not ok\n"); return false; - } + } while (ok() && m_queue.size() < m_low) { - m_workersleeps++; + m_workersleeps++; m_workers_waiting++; - if (m_queue.empty()) - pthread_cond_broadcast(&m_ccond); - if (pthread_cond_wait(&m_wcond, lock.getMutex()) || !ok()) { - // !ok is a normal condition when shutting down - if (ok()) - LOGERR(("WorkQueue::take:%s: cond_wait failed or !ok\n", - m_name.c_str())); + if (m_queue.empty()) { + m_ccond.notify_all(); + } + m_wcond.wait(lock); + if (!ok()) { + // !ok is a normal condition when shutting down m_workers_waiting--; return false; } m_workers_waiting--; } - m_tottasks++; + m_tottasks++; *tp = m_queue.front(); - if (szp) - *szp = m_queue.size(); + if (szp) { + *szp = m_queue.size(); + } m_queue.pop(); - if (m_clients_waiting > 0) { - // No reason to wake up more than one client thread - pthread_cond_signal(&m_ccond); - } else { - m_nowake++; - } + if (m_clients_waiting > 0) { + // No reason to wake up more than one client thread + m_ccond.notify_one(); + } else { + m_nowake++; + } + return true; + } + + bool waitminsz(size_t sz) { + std::unique_lock lock(m_mutex); + if (!ok()) { + return false; + } + + while (ok() && m_queue.size() < sz) { + m_workersleeps++; + m_workers_waiting++; + if (m_queue.empty()) { + m_ccond.notify_all(); + } + m_wcond.wait(lock); + if (!ok()) { + m_workers_waiting--; + return false; + } + m_workers_waiting--; + } return true; } @@ -273,60 +289,59 @@ public: * false by the shutdown code anyway). The thread must return/exit * immediately after calling this. */ - void workerExit() - { - LOGDEB(("workerExit:%s\n", m_name.c_str())); - PTMutexLocker lock(m_mutex); + void workerExit() { + LOGDEB("workerExit:" << m_name << "\n"); + std::unique_lock lock(m_mutex); m_workers_exited++; m_ok = false; - pthread_cond_broadcast(&m_ccond); + m_ccond.notify_all(); } - size_t qsize() - { - PTMutexLocker lock(m_mutex); - size_t sz = m_queue.size(); - return sz; + size_t qsize() { + std::unique_lock lock(m_mutex); + return m_queue.size(); } private: - bool ok() - { - bool isok = m_ok && m_workers_exited == 0 && !m_worker_threads.empty(); - if (!isok) { - LOGDEB(("WorkQueue:ok:%s: not ok m_ok %d m_workers_exited %d " - "m_worker_threads size %d\n", m_name.c_str(), - m_ok, m_workers_exited, int(m_worker_threads.size()))); - } + bool ok() { + bool isok = m_ok && m_workers_exited == 0 && !m_worker_threads.empty(); + if (!isok) { + LOGDEB("WorkQueue:ok:" << m_name << ": not ok m_ok " << m_ok << + " m_workers_exited " << m_workers_exited << + " m_worker_threads size " << m_worker_threads.size() << + "\n"); + } return isok; } - long long nanodiff(const struct timespec& older, - const struct timespec& newer) - { - return (newer.tv_sec - older.tv_sec) * 1000000000LL - + newer.tv_nsec - older.tv_nsec; - } - + struct Worker { + std::thread thr; +#if HAVE_STD_FUTURE + std::future res; +#endif + }; + // Configuration - string m_name; + std::string m_name; size_t m_high; - size_t m_low; + size_t m_low; - // Status - // Worker threads having called exit + // Worker threads having called exit. Used to decide when we're done unsigned int m_workers_exited; + // Status bool m_ok; - // Per-thread data. The data is not used currently, this could be - // a set - std::list m_worker_threads; + // Our threads. + std::list m_worker_threads; - // Synchronization + // Jobs input queue std::queue m_queue; - pthread_cond_t m_ccond; - pthread_cond_t m_wcond; - PTMutexInit m_mutex; + + // Synchronization + std::condition_variable m_ccond; + std::condition_variable m_wcond; + std::mutex m_mutex; + // Client/Worker threads currently waiting for a job unsigned int m_clients_waiting; unsigned int m_workers_waiting; @@ -339,3 +354,4 @@ private: }; #endif /* _WORKQUEUE_H_INCLUDED_ */ + diff --git a/src/windows/SolutionSettings/SolutionSettings.props b/src/windows/SolutionSettings/SolutionSettings.props new file mode 100644 index 00000000..2e9d8144 --- /dev/null +++ b/src/windows/SolutionSettings/SolutionSettings.props @@ -0,0 +1,30 @@ + + + + + C:\recolldeps\pthreads-w32\Pre-built.2 + c:\recolldeps\iconv64 + C:\recolldeps\xapian-core-1.2.21 + C:\recolldeps\zlib + + + + + + $(pthreads) + true + + + $(libiconv) + true + + + $(xapian) + true + + + $(zlib) + true + + + \ No newline at end of file diff --git a/src/windows/SolutionSettings/SolutionSettings.vcxproj b/src/windows/SolutionSettings/SolutionSettings.vcxproj new file mode 100644 index 00000000..4bc9c1ef --- /dev/null +++ b/src/windows/SolutionSettings/SolutionSettings.vcxproj @@ -0,0 +1,129 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {7713CDF1-27B3-4F9B-B207-C7CEB801C24B} + SolutionSettings + 8.1 + + + + Application + true + v140 + MultiByte + + + Application + false + v140 + true + MultiByte + + + Application + true + v140 + MultiByte + + + Application + false + v140 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + + + true + + + + + Level3 + Disabled + true + + + true + + + + + Level3 + MaxSpeed + true + true + true + + + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + + + true + true + true + + + + + + + + \ No newline at end of file diff --git a/src/windows/SolutionSettings/SolutionSettings.vcxproj.filters b/src/windows/SolutionSettings/SolutionSettings.vcxproj.filters new file mode 100644 index 00000000..f81daba0 --- /dev/null +++ b/src/windows/SolutionSettings/SolutionSettings.vcxproj.filters @@ -0,0 +1,17 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + \ No newline at end of file diff --git a/src/windows/Win32ProjectRecoll.sln b/src/windows/Win32ProjectRecoll.sln new file mode 100644 index 00000000..0a059964 --- /dev/null +++ b/src/windows/Win32ProjectRecoll.sln @@ -0,0 +1,69 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.23107.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Win32ProjectRecoll", "Win32ProjectRecoll.vcxproj", "{23FF40E1-BA87-4E5F-9B22-2EB760FF403D}" + ProjectSection(ProjectDependencies) = postProject + {7713CDF1-27B3-4F9B-B207-C7CEB801C24B} = {7713CDF1-27B3-4F9B-B207-C7CEB801C24B} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "recollindex", "recollindex\recollindex.vcxproj", "{A513D65F-798C-4166-B66E-49F69ADA4F59}" + ProjectSection(ProjectDependencies) = postProject + {23FF40E1-BA87-4E5F-9B22-2EB760FF403D} = {23FF40E1-BA87-4E5F-9B22-2EB760FF403D} + {7713CDF1-27B3-4F9B-B207-C7CEB801C24B} = {7713CDF1-27B3-4F9B-B207-C7CEB801C24B} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "recollq", "recollq\recollq.vcxproj", "{C1D0CCD2-0015-44AC-A606-AC48BB80C133}" + ProjectSection(ProjectDependencies) = postProject + {23FF40E1-BA87-4E5F-9B22-2EB760FF403D} = {23FF40E1-BA87-4E5F-9B22-2EB760FF403D} + {7713CDF1-27B3-4F9B-B207-C7CEB801C24B} = {7713CDF1-27B3-4F9B-B207-C7CEB801C24B} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SolutionSettings", "SolutionSettings\SolutionSettings.vcxproj", "{7713CDF1-27B3-4F9B-B207-C7CEB801C24B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {23FF40E1-BA87-4E5F-9B22-2EB760FF403D}.Debug|x64.ActiveCfg = Debug|x64 + {23FF40E1-BA87-4E5F-9B22-2EB760FF403D}.Debug|x64.Build.0 = Debug|x64 + {23FF40E1-BA87-4E5F-9B22-2EB760FF403D}.Debug|x86.ActiveCfg = Debug|Win32 + {23FF40E1-BA87-4E5F-9B22-2EB760FF403D}.Debug|x86.Build.0 = Debug|Win32 + {23FF40E1-BA87-4E5F-9B22-2EB760FF403D}.Release|x64.ActiveCfg = Release|x64 + {23FF40E1-BA87-4E5F-9B22-2EB760FF403D}.Release|x64.Build.0 = Release|x64 + {23FF40E1-BA87-4E5F-9B22-2EB760FF403D}.Release|x86.ActiveCfg = Release|Win32 + {23FF40E1-BA87-4E5F-9B22-2EB760FF403D}.Release|x86.Build.0 = Release|Win32 + {A513D65F-798C-4166-B66E-49F69ADA4F59}.Debug|x64.ActiveCfg = Debug|x64 + {A513D65F-798C-4166-B66E-49F69ADA4F59}.Debug|x64.Build.0 = Debug|x64 + {A513D65F-798C-4166-B66E-49F69ADA4F59}.Debug|x86.ActiveCfg = Debug|Win32 + {A513D65F-798C-4166-B66E-49F69ADA4F59}.Debug|x86.Build.0 = Debug|Win32 + {A513D65F-798C-4166-B66E-49F69ADA4F59}.Release|x64.ActiveCfg = Release|x64 + {A513D65F-798C-4166-B66E-49F69ADA4F59}.Release|x64.Build.0 = Release|x64 + {A513D65F-798C-4166-B66E-49F69ADA4F59}.Release|x86.ActiveCfg = Release|Win32 + {A513D65F-798C-4166-B66E-49F69ADA4F59}.Release|x86.Build.0 = Release|Win32 + {C1D0CCD2-0015-44AC-A606-AC48BB80C133}.Debug|x64.ActiveCfg = Debug|x64 + {C1D0CCD2-0015-44AC-A606-AC48BB80C133}.Debug|x64.Build.0 = Debug|x64 + {C1D0CCD2-0015-44AC-A606-AC48BB80C133}.Debug|x86.ActiveCfg = Debug|Win32 + {C1D0CCD2-0015-44AC-A606-AC48BB80C133}.Debug|x86.Build.0 = Debug|Win32 + {C1D0CCD2-0015-44AC-A606-AC48BB80C133}.Release|x64.ActiveCfg = Release|x64 + {C1D0CCD2-0015-44AC-A606-AC48BB80C133}.Release|x64.Build.0 = Release|x64 + {C1D0CCD2-0015-44AC-A606-AC48BB80C133}.Release|x86.ActiveCfg = Release|Win32 + {C1D0CCD2-0015-44AC-A606-AC48BB80C133}.Release|x86.Build.0 = Release|Win32 + {7713CDF1-27B3-4F9B-B207-C7CEB801C24B}.Debug|x64.ActiveCfg = Debug|x64 + {7713CDF1-27B3-4F9B-B207-C7CEB801C24B}.Debug|x64.Build.0 = Debug|x64 + {7713CDF1-27B3-4F9B-B207-C7CEB801C24B}.Debug|x86.ActiveCfg = Debug|Win32 + {7713CDF1-27B3-4F9B-B207-C7CEB801C24B}.Debug|x86.Build.0 = Debug|Win32 + {7713CDF1-27B3-4F9B-B207-C7CEB801C24B}.Release|x64.ActiveCfg = Release|x64 + {7713CDF1-27B3-4F9B-B207-C7CEB801C24B}.Release|x64.Build.0 = Release|x64 + {7713CDF1-27B3-4F9B-B207-C7CEB801C24B}.Release|x86.ActiveCfg = Release|Win32 + {7713CDF1-27B3-4F9B-B207-C7CEB801C24B}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/windows/Win32ProjectRecoll.vcxproj b/src/windows/Win32ProjectRecoll.vcxproj new file mode 100644 index 00000000..5cbf1f95 --- /dev/null +++ b/src/windows/Win32ProjectRecoll.vcxproj @@ -0,0 +1,257 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {23FF40E1-BA87-4E5F-9B22-2EB760FF403D} + Win32Proj + Win32ProjectRecoll + 8.1 + + + + StaticLibrary + true + v140 + Unicode + + + StaticLibrary + false + v140 + true + Unicode + + + StaticLibrary + true + v140 + MultiByte + + + StaticLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + + NotUsing + Level3 + Disabled + BUILDING_RECOLL;WIN32;__WIN32__;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + false + $(libiconv)\include;$(xapian)\include;$(solutiondir)..\internfile;$(solutiondir)..\rcldb;$(solutiondir)..\index;$(solutiondir)..\bincimapmime;$(solutiondir)..\unac;$(solutiondir)..\windows;$(zlib);$(pthreads)\include;$(solutiondir)..\xaposix;$(solutiondir)..\common;$(solutiondir)..\utils;%(AdditionalIncludeDirectories) + 4800;4996 + + + Windows + true + + + + + NotUsing + Level3 + Disabled + BUILDING_RECOLL;__WIN32__;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + false + $(libiconv)\include;$(xapian)\include;$(solutiondir)..\internfile;$(solutiondir)..\rcldb;$(solutiondir)..\index;$(solutiondir)..\bincimapmime;$(solutiondir)..\unac;$(solutiondir)..\windows;$(zlib);$(pthreads)\include;$(solutiondir)..\xaposix;$(solutiondir)..\common;$(solutiondir)..\utils;%(AdditionalIncludeDirectories) + 4800;4996 + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/windows/Win32ProjectRecoll.vcxproj.filters b/src/windows/Win32ProjectRecoll.vcxproj.filters new file mode 100644 index 00000000..aa2cb2ee --- /dev/null +++ b/src/windows/Win32ProjectRecoll.vcxproj.filters @@ -0,0 +1,327 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + \ No newline at end of file diff --git a/src/windows/Win32ProjectRecoll.vcxproj.user b/src/windows/Win32ProjectRecoll.vcxproj.user new file mode 100644 index 00000000..6fb136bf --- /dev/null +++ b/src/windows/Win32ProjectRecoll.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/windows/dirent.c b/src/windows/dirent.c new file mode 100644 index 00000000..070f7d03 --- /dev/null +++ b/src/windows/dirent.c @@ -0,0 +1,154 @@ +/* + + Implementation of POSIX directory browsing functions and types for Win32. + + Author: Kevlin Henney (kevlin@acm.org, kevlin@curbralan.com) + History: Created March 1997. Updated June 2003 and July 2012. + Rights: See end of file. + +*/ + +#include +#include +#include /* _findfirst and _findnext set errno iff they return -1 */ +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef ptrdiff_t handle_type; /* C99's intptr_t not sufficiently portable */ + +struct DIR +{ + handle_type handle; /* -1 for failed rewind */ + struct _finddata_t info; + struct dirent result; /* d_name null iff first time */ + char *name; /* null-terminated char string */ +}; + +DIR *opendir(const char *name) +{ + DIR *dir = 0; + + if(name && name[0]) + { + size_t base_length = strlen(name); + const char *all = /* search pattern must end with suitable wildcard */ + strchr("/\\", name[base_length - 1]) ? "*" : "/*"; + + if((dir = (DIR *) malloc(sizeof *dir)) != 0 && + (dir->name = (char *) malloc(base_length + strlen(all) + 1)) != 0) + { + strcat(strcpy(dir->name, name), all); + + if((dir->handle = + (handle_type) _findfirst(dir->name, &dir->info)) != -1) + { + dir->result.d_name = 0; + } + else /* rollback */ + { + free(dir->name); + free(dir); + dir = 0; + } + } + else /* rollback */ + { + free(dir); + dir = 0; + errno = ENOMEM; + } + } + else + { + errno = EINVAL; + } + + return dir; +} + +int closedir(DIR *dir) +{ + int result = -1; + + if(dir) + { + if(dir->handle != -1) + { + result = _findclose(dir->handle); + } + + free(dir->name); + free(dir); + } + + if(result == -1) /* map all errors to EBADF */ + { + errno = EBADF; + } + + return result; +} + +struct dirent *readdir(DIR *dir) +{ + struct dirent *result = 0; + + if(dir && dir->handle != -1) + { + if(!dir->result.d_name || _findnext(dir->handle, &dir->info) != -1) + { + result = &dir->result; + result->d_mtime = dir->info.time_write; + result->d_size = dir->info.size; + result->d_name = dir->info.name; + if (dir->info.attrib & _A_SUBDIR) + result->d_mode = S_IFDIR; + else + result->d_mode = S_IFREG; + } + } + else + { + errno = EBADF; + } + + return result; +} + +void rewinddir(DIR *dir) +{ + if(dir && dir->handle != -1) + { + _findclose(dir->handle); + dir->handle = (handle_type) _findfirst(dir->name, &dir->info); + dir->result.d_name = 0; + } + else + { + errno = EBADF; + } +} + +#ifdef __cplusplus +} +#endif + +/* + + Copyright Kevlin Henney, 1997, 2003, 2012. All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose is hereby granted without fee, provided + that this copyright and permissions notice appear in all copies and + derivatives. + + This software is supplied "as is" without express or implied warranty. + + But that said, if there are any problems please get in touch. + +*/ diff --git a/src/windows/dirent.h b/src/windows/dirent.h new file mode 100644 index 00000000..16bb0865 --- /dev/null +++ b/src/windows/dirent.h @@ -0,0 +1,57 @@ +#ifndef DIRENT_INCLUDED +#define DIRENT_INCLUDED + +/* + + Declaration of POSIX directory browsing functions and types for Win32. + + Author: Kevlin Henney (kevlin@acm.org, kevlin@curbralan.com) + History: Created March 1997. Updated June 2003. + Rights: See end of file. + +*/ +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct DIR DIR; + +struct dirent +{ + char *d_name; + // The native call we use, findfirst/next return file attributes at once, + // no need for a separate stat() call in most cases + // Note that ctime is actually creation time. No use for posix. + time_t d_mtime; + off_t d_size; + int d_mode; // S_IFREG or S_IFDIR only +}; + +DIR *opendir(const char *); +int closedir(DIR *); +struct dirent *readdir(DIR *); +void rewinddir(DIR *); + +/* + + Copyright Kevlin Henney, 1997, 2003. All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose is hereby granted without fee, provided + that this copyright and permissions notice appear in all copies and + derivatives. + + This software is supplied "as is" without express or implied warranty. + + But that said, if there are any problems please get in touch. + +*/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/windows/execmd_w.cpp b/src/windows/execmd_w.cpp new file mode 100644 index 00000000..c7092bef --- /dev/null +++ b/src/windows/execmd_w.cpp @@ -0,0 +1,1131 @@ +/* Copyright (C) 2014 J.F.Dockes + * Based on ideas and code contributions from Christian Motz + * + * 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include "autoconfig.h" + +#include "execmd.h" + +#include +#include +#include +#include + +#include "log.h" +#include "safesysstat.h" +#include "safeunistd.h" +#include "safewindows.h" +#include +#include "smallut.h" +#include "pathut.h" + +using namespace std; + +////////////////////////////////////////////// +// Helper routines + +static void printError(const string& text) +{ + DWORD err = GetLastError(); + LOGERR(text << " : err: " << err << "\n"); +} + +/** + Append the given argument to a command line such that + CommandLineToArgvW will return the argument string unchanged. + Arguments in a command line should be separated by spaces; this + function does not add these spaces. The caller must append spaces + between calls. + + @param arg Supplies the argument to encode. + @param cmdLine Supplies the command line to which we append + the encoded argument string. + @param force Supplies an indication of whether we should quote + the argument even if it does not contain any characters that + would ordinarily require quoting. +*/ +static void argQuote(const string& arg, string& cmdLine, bool force = false) +{ + // Don't quote unless we actually need to do so + if (!force && !arg.empty() && + arg.find_first_of(" \t\n\v\"") == arg.npos) { + cmdLine.append(arg); + } else { + cmdLine.push_back ('"'); + + for (auto It = arg.begin () ; ; ++It) { + unsigned NumberBackslashes = 0; + + while (It != arg.end () && *It == '\\') { + ++It; + ++NumberBackslashes; + } + + if (It == arg.end()) { + // Escape all backslashes, but let the terminating + // double quotation mark we add below be interpreted + // as a metacharacter. + cmdLine.append (NumberBackslashes * 2, '\\'); + break; + } else if (*It == L'"') { + // Escape all backslashes and the following + // double quotation mark. + cmdLine.append (NumberBackslashes * 2 + 1, '\\'); + cmdLine.push_back (*It); + } else { + // Backslashes aren't special here. + cmdLine.append (NumberBackslashes, '\\'); + cmdLine.push_back (*It); + } + } + cmdLine.push_back ('"'); + } +} + +static string argvToCmdLine(const string& cmd, const vector& args) +{ + string cmdline; + argQuote(cmd, cmdline); + for (auto it = args.begin(); it != args.end(); it++) { + cmdline.append(" "); + argQuote(*it, cmdline); + } + return cmdline; +} + +// Merge the father environment with the variable specified in m_env +static char *mergeEnvironment(const std::unordered_map& addenv) +{ + // Parse existing environment. + char *envir = GetEnvironmentStrings(); + char *cp0 = envir; + std::unordered_map envirmap; + + string name, value; + for (char *cp1 = cp0;;cp1++) { + if (*cp1 == '=') { + name = string(cp0, cp1 - cp0); + cp0 = cp1 + 1; + } else if (*cp1 == 0) { + value = string(cp0, cp1 - cp0); + envirmap[name] = value; + LOGDEB1("mergeEnvir: [" << (name) << "] = [" << (value) << "]\n" ); + cp0 = cp1 + 1; + if (*cp0 == 0) + break; + } + } + + FreeEnvironmentStrings(envir); + + // Merge our values + for (auto it = addenv.begin(); it != addenv.end(); it++) { + envirmap[it->first] = it->second; + } + + // Create environment block + size_t sz = 0; + for (auto it = envirmap.begin(); it != envirmap.end(); it++) { + sz += it->first.size() + it->second.size() + 2; // =, 0 + } + sz++; // final 0 + char *nenvir = (char *)malloc(sz); + if (nenvir == 0) + return nenvir; + char *cp = nenvir; + for (auto it = envirmap.begin(); it != envirmap.end(); it++) { + memcpy(cp, it->first.c_str(), it->first.size()); + cp += it->first.size(); + *cp++ = '='; + memcpy(cp, it->second.c_str(), it->second.size()); + cp += it->second.size(); + *cp++ = 0; + } + // Final double-zero + *cp++ = 0; + + return nenvir; +} + +static bool is_exe(const string& path) +{ + struct stat st; + if (access(path.c_str(), X_OK) == 0 && stat(path.c_str(), &st) == 0 && + S_ISREG(st.st_mode)) { + return true; + } + return false; +} + +// In mt programs the static vector computations below needs a call +// from main before going mt. This is done by rclinit and saves having +// to take a lock on every call + +// Try appending executable suffixes to the base name and see if we +// have something +static bool is_exe_base(const string& path) +{ + static vector exts; + if (exts.empty()) { + const char *ep = getenv("PATHEXT"); + if (!ep || !*ep) { + ep = ".com;.exe;.bat;.cmd"; + } + string eps(ep); + trimstring(eps, ";"); + stringToTokens(eps, exts, ";"); + } + + if (is_exe(path)) + return true; + for (auto it = exts.begin(); it != exts.end(); it++) { + if (is_exe(path + *it)) + return true; + } + return false; +} + +// Parse a PATH spec into a vector +static void make_path_vec(const char *ep, vector& vec) +{ + if (ep && *ep) { + string eps(ep); + trimstring(eps, ";"); + stringToTokens(eps, vec, ";"); + } + vec.insert(vec.begin(), ".\\"); +} + +static std::string pipeUniqueName(std::string nClass, std::string prefix) +{ + std::stringstream uName; + + long currCnt; + // PID + multi-thread-protected static counter to be unique + { + static long cnt = 0; + currCnt = InterlockedIncrement(&cnt); + } + DWORD pid = GetCurrentProcessId(); + + // naming convention + uName << "\\\\.\\" << nClass << "\\"; + uName << "pid-" << pid << "-cnt-" << currCnt << "-"; + uName << prefix; + + return uName.str(); +} + +enum WaitResult { + Ok, Quit, Timeout +}; + +static WaitResult Wait(HANDLE hdl, int timeout) +{ + //HANDLE hdls[2] = { hdl, eQuit }; + HANDLE hdls[1] = { hdl}; + LOGDEB1("Wait()\n" ); + DWORD res = WaitForMultipleObjects(1, hdls, FALSE, timeout); + if (res == WAIT_OBJECT_0) { + LOGDEB1("Wait: returning Ok\n" ); + return Ok; + } else if (res == (WAIT_OBJECT_0 + 1)) { + LOGDEB0("Wait: returning Quit\n" ); + return Quit; + } else if (res == WAIT_TIMEOUT) { + LOGDEB0("Wait: returning Timeout\n" ); + return Timeout; + } + printError("Wait: WaitForMultipleObjects: unknown, returning Timout\n"); + return Timeout; +} + +static int getVMMBytes(HANDLE hProcess) +{ + PROCESS_MEMORY_COUNTERS pmc; + const int MB = 1024 * 1024; + if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) { + LOGDEB2("ExecCmd: getVMMBytes paged Kbs " << (int(pmc.QuotaPagedPoolUsage/1024)) << " non paged " << (int(pmc.QuotaNonPagedPoolUsage/1024)) << " Kbs\n" ); + return int(pmc.QuotaPagedPoolUsage /MB + + pmc.QuotaNonPagedPoolUsage / MB); + } + return -1; +} + +//////////////////////////////////////////////////////// +// ExecCmd: + +class ExecCmd::Internal { +public: + Internal(int flags) + : m_advise(0), m_provide(0), m_timeoutMs(1000), m_rlimit_as_mbytes(0), + m_flags(flags) { + reset(); + } + + std::unordered_map m_env; + ExecCmdAdvise *m_advise; + ExecCmdProvide *m_provide; + int m_timeoutMs; + int m_rlimit_as_mbytes; + int m_flags; + + // We need buffered I/O for getline. The Unix version uses netcon's + string m_buf; // Buffer. Only used when doing getline()s + size_t m_bufoffs; // Pointer to current 1st byte of useful data + bool m_killRequest; + string m_stderrFile; + HANDLE m_hOutputRead; + HANDLE m_hInputWrite; + OVERLAPPED m_oOutputRead; + OVERLAPPED m_oInputWrite; + PROCESS_INFORMATION m_piProcInfo; + + + // Reset internal state indicators. Any resources should have been + // previously freed. + void reset() { + m_buf.resize(0); + m_bufoffs = 0; + m_stderrFile.erase(); + m_killRequest = false; + m_hOutputRead = NULL; + m_hInputWrite = NULL; + memset(&m_oOutputRead, 0, sizeof(m_oOutputRead)); + memset(&m_oInputWrite, 0, sizeof(m_oInputWrite)); + ZeroMemory(&m_piProcInfo, sizeof(PROCESS_INFORMATION)); + } + bool preparePipes(bool has_input, HANDLE *hChildInput, + bool has_output, HANDLE *hChildOutput, + HANDLE *hChildError); + bool tooBig(); +}; + +// Sending a break to a subprocess is only possible if we share the +// same console We temporarily ignore breaks, attach to the Console, +// send the break, and restore normal break processing +static bool sendIntr(int pid) +{ + LOGDEB("execmd_w: sendIntr -> " << (pid) << "\n" ); + bool needDetach = false; + if (GetConsoleWindow() == NULL) { + needDetach = true; + LOGDEB("execmd_w: sendIntr attaching console\n" ); + if (!AttachConsole((unsigned int) pid)) { + int err = GetLastError(); + LOGERR("execmd_w: sendIntr: AttachConsole failed: " << (err) << "\n" ); + return false; + } + } + +#if 0 + // Would need to do this for sending to all processes on this console + // Disable Ctrl-C handling for our program + if (!SetConsoleCtrlHandler(NULL, true)) { + int err = GetLastError(); + LOGERR("execmd_w:sendIntr:SetCons.Ctl.Hndlr.(NULL, true) failed: " << (err) << "\n" ); + return false; + } +#endif + + // Note: things don't work with CTRL_C (process not advised, sometimes + // stays apparently blocked), no idea why + bool ret = GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid); + if (!ret) { + int err = GetLastError(); + LOGERR("execmd_w:sendIntr:Gen.Cons.CtrlEvent failed: " << (err) << "\n" ); + } + +#if 0 + // Restore Ctrl-C handling for our program + SetConsoleCtrlHandler(NULL, false); +#endif + + if (needDetach) { + LOGDEB("execmd_w: sendIntr detaching console\n" ); + if (!FreeConsole()) { + int err = GetLastError(); + LOGERR("execmd_w: sendIntr: FreeConsole failed: " << (err) << "\n" ); + } + } + + return ret; +} + +// ExecCmd resource releaser class. Using a separate object makes it +// easier that resources are released under all circumstances, +// esp. exceptions +class ExecCmdRsrc { +public: + ExecCmdRsrc(ExecCmd::Internal *parent) + : m_parent(parent), m_active(true) { + } + void inactivate() { + m_active = false; + } + ~ExecCmdRsrc() { + if (!m_active || !m_parent) + return; + LOGDEB1("~ExecCmdRsrc: working. mypid: " << ((int)getpid()) << "\n" ); + if (m_parent->m_hOutputRead) + CloseHandle(m_parent->m_hOutputRead); + if (m_parent->m_hInputWrite) + CloseHandle(m_parent->m_hInputWrite); + if (m_parent->m_oOutputRead.hEvent) + CloseHandle(m_parent->m_oOutputRead.hEvent); + if (m_parent->m_oInputWrite.hEvent) + CloseHandle(m_parent->m_oInputWrite.hEvent); + + if (m_parent->m_piProcInfo.hProcess) { + BOOL bSuccess = sendIntr(m_parent->m_piProcInfo.dwProcessId); + if (bSuccess) { + // Give it a chance, then terminate + for (int i = 0; i < 3; i++) { + WaitResult res = Wait(m_parent->m_piProcInfo.hProcess, + i == 0 ? 5 : (i == 1 ? 100 : 2000)); + switch (res) { + case Ok: + case Quit: + goto breakloop; + case Timeout: + if (i == 2) { + TerminateProcess(m_parent->m_piProcInfo.hProcess, + 0xffff); + } + } + } + } else { + TerminateProcess(m_parent->m_piProcInfo.hProcess, + 0xffff); + } + breakloop: + CloseHandle(m_parent->m_piProcInfo.hProcess); + } + if (m_parent->m_piProcInfo.hThread) + CloseHandle(m_parent->m_piProcInfo.hThread); + m_parent->reset(); + } +private: + ExecCmd::Internal *m_parent; + bool m_active; +}; + +ExecCmd::ExecCmd(int flags) +{ + m = new Internal(flags); + if (m) { + m->reset(); + } +} + +ExecCmd::~ExecCmd() +{ + if (m) { + ExecCmdRsrc(this->m); + delete m; + } +} + +// This does nothing under windows, but defining it avoids ifdefs in multiple +// places +void ExecCmd::useVfork(bool) +{ +} + +bool ExecCmd::which(const string& cmd, string& exe, const char* path) +{ + static vector s_pathelts; + vector pathelts; + vector *pep; + + if (path) { + make_path_vec(path, pathelts); + pep = &pathelts; + } else { + if (s_pathelts.empty()) { + const char *ep = getenv("PATH"); + make_path_vec(ep, s_pathelts); + } + pep = &s_pathelts; + } + + if (path_isabsolute(cmd)) { + if (is_exe_base(cmd)) { + exe = cmd; + return true; + } + exe.clear(); + return false; + } + + for (auto it = pep->begin(); it != pep->end(); it++) { + exe = path_cat(*it, cmd); + if (is_exe_base(exe)) { + return true; + } + } + exe.clear(); + return false; +} + +void ExecCmd::setAdvise(ExecCmdAdvise *adv) +{ + m->m_advise = adv; +} +void ExecCmd::setProvide(ExecCmdProvide *p) +{ + m->m_provide = p; +} +void ExecCmd::setTimeout(int mS) +{ + if (mS > 30) { + m->m_timeoutMs = mS; + } +} +void ExecCmd::setStderr(const std::string& stderrFile) +{ + m->m_stderrFile = stderrFile; +} + +pid_t ExecCmd::getChildPid() +{ + return m->m_piProcInfo.dwProcessId; +} + +void ExecCmd::setKill() +{ + m->m_killRequest = true; +} +void ExecCmd::zapChild() +{ + setKill(); + (void)wait(); +} + +bool ExecCmd::requestChildExit() +{ + if (m->m_piProcInfo.hProcess) { + return sendIntr(m->m_piProcInfo.dwProcessId); + } + return false; +} + +void ExecCmd::putenv(const string &envassign) +{ + vector v; + stringToTokens(envassign, v, "="); + if (v.size() == 2) { + m->m_env[v[0]] = v[1]; + } +} +void ExecCmd::putenv(const string &name, const string& value) +{ + m->m_env[name] = value; +} + +void ExecCmd::setrlimit_as(int mbytes) +{ + m->m_rlimit_as_mbytes = mbytes; +} + +bool ExecCmd::Internal::tooBig() +{ + if (m_rlimit_as_mbytes <= 0) + return false; + int mbytes = getVMMBytes(m_piProcInfo.hProcess); + if (mbytes > m_rlimit_as_mbytes) { + LOGINFO("ExecCmd:: process mbytes " << (mbytes) << " > set limit " << (m_rlimit_as_mbytes) << "\n" ); + m_killRequest = true; + return true; + } + return false; +} + +bool ExecCmd::Internal::preparePipes(bool has_input,HANDLE *hChildInput, + bool has_output, HANDLE *hChildOutput, + HANDLE *hChildError) +{ + // Note: our caller is responsible for ensuring that we start with + // a clean state, and for freeing class resources in case of + // error. We just manage the local ones. + HANDLE hOutputWrite = NULL; + HANDLE hErrorWrite = NULL; + HANDLE hInputRead = NULL; + + // manual reset event + m_oOutputRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (m_oOutputRead.hEvent == INVALID_HANDLE_VALUE) { + LOGERR("ExecCmd::preparePipes: CreateEvent failed\n" ); + goto errout; + } + + // manual reset event + m_oInputWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (m_oInputWrite.hEvent == INVALID_HANDLE_VALUE) { + LOGERR("ExecCmd::preparePipes: CreateEvent failed\n" ); + goto errout; + } + + SECURITY_ATTRIBUTES sa; + // Set up the security attributes struct. + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; // this is the critical bit + sa.lpSecurityDescriptor = NULL; + + if (has_output) { + // src for this code: https://www.daniweb.com/software-development/cpp/threads/295780/using-named-pipes-with-asynchronous-io-redirection-to-winapi + // ONLY IMPORTANT CHANGE + // set inheritance flag to TRUE in CreateProcess + // you need this for the client to inherit the handles + + // Create the child output named pipe. + // This creates a non-inheritable, one-way handle for the server to read + string pipeName = pipeUniqueName("pipe", "output"); + sa.bInheritHandle = FALSE; + m_hOutputRead = CreateNamedPipeA( + pipeName.c_str(), + PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, + PIPE_WAIT, + 1, 4096, 4096, 0, &sa); + if (m_hOutputRead == INVALID_HANDLE_VALUE) { + printError("preparePipes: CreateNamedPipe(outputR)"); + goto errout; + } + + // However, the client can not use an inbound server handle. + // Client needs a write-only, outgoing handle. + // So, you create another handle to the same named pipe, only + // write-only. Again, must be created with the inheritable + // attribute, and the options are important. + // use CreateFile to open a new handle to the existing pipe... + sa.bInheritHandle = TRUE; + hOutputWrite = CreateFile( + pipeName.c_str(), + FILE_WRITE_DATA | SYNCHRONIZE, + 0, &sa, OPEN_EXISTING, // very important flag! + FILE_ATTRIBUTE_NORMAL, 0 // no template file for OPEN_EXISTING + ); + if (hOutputWrite == INVALID_HANDLE_VALUE) { + printError("preparePipes: CreateFile(outputWrite)"); + goto errout; + } + } else { + // Not using child output. Let the child have our standard output. + HANDLE hstd = GetStdHandle(STD_OUTPUT_HANDLE); + if (hstd == INVALID_HANDLE_VALUE) { + printError("preparePipes: GetStdHandle(stdout)"); + goto errout; + } + if (!DuplicateHandle(GetCurrentProcess(), hstd, + GetCurrentProcess(), &hOutputWrite, + 0, TRUE, + DUPLICATE_SAME_ACCESS)) { + printError("preparePipes: DuplicateHandle(stdout)"); + // This occurs when the parent process is a GUI app. Ignoring the error works, but + // not too sure this is the right approach. Maybe look a bit more at: + // https://support.microsoft.com/en-us/kb/190351 + //goto errout; + } + } + + if (has_input) { + // now same procedure for input pipe + + sa.bInheritHandle = FALSE; + string pipeName = pipeUniqueName("pipe", "input"); + m_hInputWrite = CreateNamedPipeA( + pipeName.c_str(), + PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, + PIPE_WAIT, + 1, 4096, 4096, 0, &sa); + if (m_hInputWrite == INVALID_HANDLE_VALUE) { + printError("preparePipes: CreateNamedPipe(inputW)"); + goto errout; + } + + sa.bInheritHandle = TRUE; + hInputRead = CreateFile( + pipeName.c_str(), + FILE_READ_DATA | SYNCHRONIZE, + 0, &sa, OPEN_EXISTING, // very important flag! + FILE_ATTRIBUTE_NORMAL, 0 // no template file for OPEN_EXISTING + ); + if (hInputRead == INVALID_HANDLE_VALUE) { + printError("preparePipes: CreateFile(inputRead)"); + goto errout; + } + } else { + // Let the child inherit our standard input + HANDLE hstd = GetStdHandle(STD_INPUT_HANDLE); + if (hstd == INVALID_HANDLE_VALUE) { + printError("preparePipes: GetStdHandle(stdin)"); + goto errout; + } + if (!DuplicateHandle(GetCurrentProcess(), hstd, + GetCurrentProcess(), &hInputRead, + 0, TRUE, + DUPLICATE_SAME_ACCESS)) { + printError("preparePipes: DuplicateHandle(stdin)"); + //goto errout; + } + } + + // Stderr: output to file or inherit. We don't support the file thing + // for the moment + if (false && !m_stderrFile.empty()) { + // Open the file set up the child handle: TBD + printError("preparePipes: m_stderrFile not empty"); + } else { + // Let the child inherit our standard input + HANDLE hstd = GetStdHandle(STD_ERROR_HANDLE); + if (hstd == INVALID_HANDLE_VALUE) { + printError("preparePipes: GetStdHandle(stderr)"); + goto errout; + } + if (!DuplicateHandle(GetCurrentProcess(), hstd, + GetCurrentProcess(), &hErrorWrite, + 0, TRUE, + DUPLICATE_SAME_ACCESS)) { + printError("preparePipes: DuplicateHandle(stderr)"); + //goto errout; + } + } + + *hChildInput = hInputRead; + *hChildOutput = hOutputWrite; + *hChildError = hErrorWrite; + return true; + +errout: + if (hOutputWrite) + CloseHandle(hOutputWrite); + if (hInputRead) + CloseHandle(hInputRead); + if (hErrorWrite) + CloseHandle(hErrorWrite); + return false; +} + +// Create a child process +int ExecCmd::startExec(const string &cmd, const vector& args, + bool has_input, bool has_output) +{ + { // Debug and logging + string command = cmd + " "; + for (vector::const_iterator it = args.begin(); + it != args.end(); it++) { + command += "{" + *it + "} "; + } + LOGDEB("ExecCmd::startExec: (" << (has_input) << "|" << (has_output) << ") " << (command) << "\n" ); + } + + // What if we're called twice ? First make sure we're clean + { + ExecCmdRsrc(this->m); + } + + // Arm clean up. This will be disabled before a success return. + ExecCmdRsrc cleaner(this->m); + + string cmdline = argvToCmdLine(cmd, args); + + HANDLE hInputRead; + HANDLE hOutputWrite; + HANDLE hErrorWrite; + if (!m->preparePipes(has_input, &hInputRead, has_output, + &hOutputWrite, &hErrorWrite)) { + LOGERR("ExecCmd::startExec: preparePipes failed\n" ); + return false; + } + + STARTUPINFO siStartInfo; + BOOL bSuccess = FALSE; + + // Set up members of the PROCESS_INFORMATION structure. + ZeroMemory(&m->m_piProcInfo, sizeof(PROCESS_INFORMATION)); + + // Set up members of the STARTUPINFO structure. + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + if (m->m_flags & EXF_SHOWWINDOW) { + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + if (m->m_flags & EXF_MAXIMIZED) { + siStartInfo.dwFlags |= STARTF_USESHOWWINDOW; + siStartInfo.wShowWindow = SW_SHOWMAXIMIZED; + } + } else { + siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + // This is to hide the console when starting a cmd line command from + // the GUI. Also note STARTF_USESHOWWINDOW above + siStartInfo.wShowWindow = SW_HIDE; + } + siStartInfo.hStdOutput = hOutputWrite; + siStartInfo.hStdInput = hInputRead; + siStartInfo.hStdError = hErrorWrite; + + char *envir = mergeEnvironment(m->m_env); + + // Create the child process. + // Need a writable buffer for the command line, for some reason. + LOGDEB1("ExecCmd:startExec: cmdline [" << (cmdline) << "]\n" ); + LPSTR buf = (LPSTR)malloc(cmdline.size() + 1); + memcpy(buf, cmdline.c_str(), cmdline.size()); + buf[cmdline.size()] = 0; + bSuccess = CreateProcess(NULL, + buf, // command line + NULL, // process security attributes + NULL, // primary thread security attrs + TRUE, // handles are inherited + CREATE_NEW_PROCESS_GROUP, // creation flags + envir, // Merged environment + NULL, // use parent's current directory + &siStartInfo, // STARTUPINFO pointer + &m->m_piProcInfo); // PROCESS_INFORMATION + if (!bSuccess) { + printError("ExecCmd::doexec: CreateProcess"); + } + free(envir); + free(buf); + // Close child-side handles else we'll never see eofs + if (!CloseHandle(hOutputWrite)) + printError("CloseHandle"); + if (!CloseHandle(hInputRead)) + printError("CloseHandle"); + if (!CloseHandle(hErrorWrite)) + printError("CloseHandle"); + + if (bSuccess) { + cleaner.inactivate(); + } + return bSuccess; +} + +// Send data to the child. +int ExecCmd::send(const string& data) +{ + LOGDEB2("ExecCmd::send: cnt " << (int(data.size())) << "\n" ); + BOOL bSuccess = WriteFile(m->m_hInputWrite, data.c_str(), + (DWORD)data.size(), NULL, &m->m_oInputWrite); + DWORD err = GetLastError(); + + // TODO: some more decision, either the operation completes immediately + // and we get success, or it is started (which is indicated by no success) + // and ERROR_IO_PENDING + // in the first case bytes read/written parameter can be used directly + if (!bSuccess && err != ERROR_IO_PENDING) { + LOGERR("ExecCmd::send: WriteFile: got err " << (err) << "\n" ); + return -1; + } + + WaitResult waitRes = Wait(m->m_oInputWrite.hEvent, m->m_timeoutMs); + DWORD dwWritten; + if (waitRes == Ok) { + if (!GetOverlappedResult(m->m_hInputWrite, + &m->m_oInputWrite, &dwWritten, TRUE)) { + err = GetLastError(); + LOGERR("ExecCmd::send: GetOverLappedResult: err " << (err) << "\n" ); + return -1; + } + } else if (waitRes == Quit) { + printError("ExecCmd::send: got Quit"); + if (!CancelIo(m->m_hInputWrite)) { + printError("CancelIo"); + } + return -1; + } else if (waitRes == Timeout) { + printError("ExecCmd::send: got Timeout"); + if (!CancelIo(m->m_hInputWrite)) { + printError("CancelIo"); + } + return -1; + } + LOGDEB2("ExecCmd::send: returning " << (int(dwWritten)) << "\n" ); + return dwWritten; +} + +#ifndef MIN +#define MIN(A,B) ((A)<(B)?(A):(B)) +#endif + +// Read output from the child process's pipe for STDOUT +// and write to cout in this programme +// Stop when there is no more data. +// @arg cnt count to read, -1 means read to end of data. +// 0 means read whatever comes back on the first read; +int ExecCmd::receive(string& data, int cnt) +{ + LOGDEB1("ExecCmd::receive: cnt " << (cnt) << "\n" ); + + int totread = 0; + + // If there is buffered data, use it (remains from a previous getline()) + if (m->m_bufoffs < m->m_buf.size()) { + int bufcnt = int(m->m_buf.size() - m->m_bufoffs); + int toread = (cnt > 0) ? MIN(cnt, bufcnt) : bufcnt; + data.append(m->m_buf, m->m_bufoffs, toread); + m->m_bufoffs += toread; + totread += toread; + if (cnt == 0 || (cnt > 0 && totread == cnt)) { + return cnt; + } + } + while (true) { + const int BUFSIZE = 4096; + CHAR chBuf[BUFSIZE]; + int toread = cnt > 0 ? MIN(cnt - totread, BUFSIZE) : BUFSIZE; + BOOL bSuccess = ReadFile(m->m_hOutputRead, chBuf, toread, + NULL, &m->m_oOutputRead); + DWORD err = GetLastError(); + LOGDEB1("receive: ReadFile: success " << (int(bSuccess)) << " err " << (int(err)) << "\n" ); + if (!bSuccess && err != ERROR_IO_PENDING) { + if (err != ERROR_BROKEN_PIPE) + LOGERR("ExecCmd::receive: ReadFile error: " << (int(err)) << "\n" ); + break; + } + + waitagain: + WaitResult waitRes = Wait(m->m_oOutputRead.hEvent, m->m_timeoutMs); + if (waitRes == Ok) { + DWORD dwRead; + if (!GetOverlappedResult(m->m_hOutputRead, &m->m_oOutputRead, + &dwRead, TRUE)) { + err = GetLastError(); + if (err && err != ERROR_BROKEN_PIPE) { + LOGERR("ExecCmd::recv:GetOverlappedResult: err " << (err) << "\n" ); + return -1; + } + } + if (dwRead > 0) { + totread += dwRead; + data.append(chBuf, dwRead); + if (m->m_advise) + m->m_advise->newData(dwRead); + LOGDEB1("ExecCmd::recv: ReadFile: " << (int(dwRead)) << " bytes\n" ); + } + } else if (waitRes == Quit) { + if (!CancelIo(m->m_hOutputRead)) { + printError("CancelIo"); + } + break; + } else if (waitRes == Timeout) { + LOGDEB0("ExecCmd::receive: timeout (" << (m->m_timeoutMs) << " mS)\n" ); + if (m->tooBig()) { + if (!CancelIo(m->m_hOutputRead)) { + printError("CancelIo"); + } + return -1; + } + // We only want to cancel if m_advise says so here. + if (m->m_advise) { + try { + m->m_advise->newData(0); + } catch (...) { + if (!CancelIo(m->m_hOutputRead)) { + printError("CancelIo"); + } + throw; + } + } + if (m->m_killRequest) { + LOGINFO("ExecCmd::doexec: cancel request\n" ); + if (!CancelIo(m->m_hOutputRead)) { + printError("CancelIo"); + } + break; + } + goto waitagain; + } + if ((cnt == 0 && totread > 0) || (cnt > 0 && totread == cnt)) + break; + } + LOGDEB1("ExecCmd::receive: returning " << (totread) << " bytes\n" ); + return totread; +} + +int ExecCmd::getline(string& data) +{ + LOGDEB2("ExecCmd::getline: cnt " << (cnt) << ", timeo " << (timeo) << "\n" ); + data.erase(); + if (m->m_buf.empty()) { + m->m_buf.reserve(4096); + m->m_bufoffs = 0; + } + + for (;;) { + // Transfer from buffer. Have to take a lot of care to keep counts and + // pointers consistant in all end cases + int nn = int(m->m_buf.size() - m->m_bufoffs); + bool foundnl = false; + for (; nn > 0;) { + nn--; + char c = m->m_buf[m->m_bufoffs++]; + if (c == '\r') + continue; + data += c; + if (c == '\n') { + foundnl = true; + break; + } + } + + if (foundnl) { + LOGDEB2("ExecCmd::getline: ret: [" << (data) << "]\n" ); + return int(data.size()); + } + + // Read more + m->m_buf.erase(); + if (receive(m->m_buf, 0) < 0) { + return -1; + } + if (m->m_buf.empty()) { + LOGDEB("ExecCmd::getline: eof? ret: [" << (data) << "]\n" ); + return int(data.size()); + } + m->m_bufoffs = 0; + } + //?? + return -1; +} + +int ExecCmd::wait() +{ + // If killRequest was set, we don't perform the normal + // wait. cleaner will kill the child. + ExecCmdRsrc cleaner(this->m); + + DWORD exit_code = -1; + if (!m->m_killRequest && m->m_piProcInfo.hProcess) { + // Wait until child process exits. + while (WaitForSingleObject(m->m_piProcInfo.hProcess, m->m_timeoutMs) + == WAIT_TIMEOUT) { + LOGDEB("ExecCmd::wait: timeout (ok)\n" ); + if (m->m_advise) { + m->m_advise->newData(0); + } + if (m->tooBig()) { + // Let cleaner work to kill the child + m->m_killRequest = true; + return -1; + } + } + + exit_code = 0; + GetExitCodeProcess(m->m_piProcInfo.hProcess, &exit_code); + // Clean up, here to avoid cleaner trying to kill the now + // inexistant process. + CloseHandle(m->m_piProcInfo.hProcess); + m->m_piProcInfo.hProcess = NULL; + if (m->m_piProcInfo.hThread) { + CloseHandle(m->m_piProcInfo.hThread); + m->m_piProcInfo.hThread = NULL; + } + } + return (int)exit_code; +} + +bool ExecCmd::maybereap(int *status) +{ + ExecCmdRsrc e(this->m); + *status = -1; + + if (m->m_piProcInfo.hProcess == NULL) { + // Already waited for ?? + return true; + } + + WaitResult res = Wait(m->m_piProcInfo.hProcess, 1); + DWORD exit_code = -1; + switch (res) { + case Ok: + exit_code = 0; + GetExitCodeProcess(m->m_piProcInfo.hProcess, &exit_code); + *status = (int)exit_code; + CloseHandle(m->m_piProcInfo.hProcess); + m->m_piProcInfo.hProcess = NULL; + if (m->m_piProcInfo.hThread) { + CloseHandle(m->m_piProcInfo.hThread); + m->m_piProcInfo.hThread = NULL; + } + return true; + default: + e.inactivate(); + return false; + } +} + +// Static +bool ExecCmd::backtick(const vector cmd, string& out) +{ + vector::const_iterator it = cmd.begin(); + it++; + vector args(it, cmd.end()); + ExecCmd mexec; + int status = mexec.doexec(*cmd.begin(), args, 0, &out); + return status == 0; +} + +int ExecCmd::doexec(const string &cmd, const vector& args, + const string *input, string *output) +{ + if (input && output) { + LOGERR("ExecCmd::doexec: can't do both input and output\n" ); + return -1; + } + + // The assumption is that the state is clean when this method + // returns. Have a cleaner ready in case we return without + // calling wait() e.g. if an exception is raised in receive() + ExecCmdRsrc cleaner(this->m); + + if (startExec(cmd, args, input != 0, output != 0) < 0) { + return -1; + } + + if (input) { + if (!input->empty()) { + if (send(*input) != (int)input->size()) { + LOGERR("ExecCmd::doexec: send failed\n" ); + CloseHandle(m->m_hInputWrite); + m->m_hInputWrite = NULL; + return -1; + } + } + if (m->m_provide) { + for (;;) { + m->m_provide->newData(); + if (input->empty()) { + CloseHandle(m->m_hInputWrite); + m->m_hInputWrite = NULL; + break; + } + if (send(*input) != (int)input->size()) { + LOGERR("ExecCmd::doexec: send failed\n" ); + CloseHandle(m->m_hInputWrite); + m->m_hInputWrite = NULL; + break; + } + } + } + } else if (output) { + receive(*output); + } + cleaner.inactivate(); + return wait(); +} + diff --git a/src/windows/fnmatch.c b/src/windows/fnmatch.c new file mode 100644 index 00000000..6846d221 --- /dev/null +++ b/src/windows/fnmatch.c @@ -0,0 +1,186 @@ +/* Copyright (C) 1992 Free Software Foundation, Inc. +This file is part of the GNU C Library. + +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +The GNU C Library 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 +Library General Public License for more details. */ + +/* Modified slightly by Brian Berliner and + Jim Blandy for CVS use */ +/* Modified slightly by j.f. dockes for recoll use */ + +#include "autoconfig.h" +#ifdef _WIN32 +#include +static inline int fold_fn_char(int c) +{ + /* Only ASCII for now... */ + if (c == '\\') + return '/'; + if (c > 0 && c <= 127) + return tolower(c); + return c; +} +#define FOLD_FN_CHAR(c) fold_fn_char(c) +#endif /* _WIN32 */ + +/* Some file systems are case-insensitive. If FOLD_FN_CHAR is + #defined, it maps the character C onto its "canonical" form. In a + case-insensitive system, it would map all alphanumeric characters + to lower case. Under Windows NT, / and \ are both path component + separators, so FOLD_FN_CHAR would map them both to /. */ +#ifndef FOLD_FN_CHAR +#define FOLD_FN_CHAR(c) (c) +#endif + +#include +#include "fnmatch.h" + + +/* Match STRING against the filename pattern PATTERN, returning zero if + it matches, nonzero if not. */ +int fnmatch (const char *pattern, const char *string, int flags) +{ + register const char *p = pattern, *n = string; + register char c; + + if ((flags & ~__FNM_FLAGS) != 0) + { + errno = EINVAL; + return -1; + } + + while ((c = *p++) != '\0') + { + switch (c) + { + case '?': + if (*n == '\0') + return FNM_NOMATCH; + else if ((flags & FNM_PATHNAME) && *n == '/') + return FNM_NOMATCH; + else if ((flags & FNM_PERIOD) && *n == '.' && + (n == string || ((flags & FNM_PATHNAME) && n[-1] == '/'))) + return FNM_NOMATCH; + break; + + case '\\': + if (!(flags & FNM_NOESCAPE)) + c = *p++; + if (*n != c) + return FNM_NOMATCH; + break; + + case '*': + if ((flags & FNM_PERIOD) && *n == '.' && + (n == string || ((flags & FNM_PATHNAME) && n[-1] == '/'))) + return FNM_NOMATCH; + + for (c = *p++; c == '?' || c == '*'; c = *p++, ++n) + if (((flags & FNM_PATHNAME) && *n == '/') || + (c == '?' && *n == '\0')) + return FNM_NOMATCH; + + if (c == '\0') + return 0; + + { + char c1 = (!(flags & FNM_NOESCAPE) && c == '\\') ? *p : c; + for (--p; *n != '\0'; ++n) + if ((c == '[' || *n == c1) && + fnmatch(p, n, flags & ~FNM_PERIOD) == 0) + return 0; + return FNM_NOMATCH; + } + + case '[': + { + /* Nonzero if the sense of the character class is inverted. */ + register int not; + + if (*n == '\0') + return FNM_NOMATCH; + + if ((flags & FNM_PERIOD) && *n == '.' && + (n == string || ((flags & FNM_PATHNAME) && n[-1] == '/'))) + return FNM_NOMATCH; + + not = (*p == '!' || *p == '^'); + if (not) + ++p; + + c = *p++; + for (;;) + { + register char cstart = c, cend = c; + + if (!(flags & FNM_NOESCAPE) && c == '\\') + cstart = cend = *p++; + + if (c == '\0') + /* [ (unterminated) loses. */ + return FNM_NOMATCH; + + c = *p++; + + if ((flags & FNM_PATHNAME) && c == '/') + /* [/] can never match. */ + return FNM_NOMATCH; + + if (c == '-' && *p != ']') + { + cend = *p++; + if (!(flags & FNM_NOESCAPE) && cend == '\\') + cend = *p++; + if (cend == '\0') + return FNM_NOMATCH; + c = *p++; + } + + if (*n >= cstart && *n <= cend) + goto matched; + + if (c == ']') + break; + } + if (!not) + return FNM_NOMATCH; + break; + + matched:; + /* Skip the rest of the [...] that already matched. */ + while (c != ']') + { + if (c == '\0') + /* [... (unterminated) loses. */ + return FNM_NOMATCH; + + c = *p++; + if (!(flags & FNM_NOESCAPE) && c == '\\') + /* 1003.2d11 is unclear if this is right. %%% */ + ++p; + } + if (not) + return FNM_NOMATCH; + } + break; + + default: + if (FOLD_FN_CHAR (c) != FOLD_FN_CHAR (*n)) + return FNM_NOMATCH; + } + + ++n; + } + + if (*n == '\0') + return 0; + + return FNM_NOMATCH; +} diff --git a/src/windows/fnmatch.h b/src/windows/fnmatch.h new file mode 100644 index 00000000..9c628693 --- /dev/null +++ b/src/windows/fnmatch.h @@ -0,0 +1,44 @@ +/* Copyright (C) 1992 Free Software Foundation, Inc. +This file is part of the GNU C Library. + +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +The GNU C Library 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 +Library General Public License for more details. */ + +#ifndef _FNMATCH_H + +#define _FNMATCH_H 1 + +/* Bits set in the FLAGS argument to `fnmatch'. */ +#undef FNM_PATHNAME +#define FNM_PATHNAME (1 << 0)/* No wildcard can ever match `/'. */ +#undef FNM_NOESCAPE +#define FNM_NOESCAPE (1 << 1)/* Backslashes don't quote special chars. */ +#undef FNM_PERIOD +#define FNM_PERIOD (1 << 2)/* Leading `.' is matched only explicitly. */ +#undef __FNM_FLAGS +#define __FNM_FLAGS (FNM_PATHNAME|FNM_NOESCAPE|FNM_PERIOD) + +/* Value returned by `fnmatch' if STRING does not match PATTERN. */ +#undef FNM_NOMATCH +#define FNM_NOMATCH 1 + +/* Match STRING against the filename pattern PATTERN, + returning zero if it matches, FNM_NOMATCH if not. */ +#ifdef __cplusplus +extern "C" { +#endif +extern int fnmatch (const char *pattern, const char *str, int flags); +#ifdef __cplusplus +} +#endif + + +#endif /* fnmatch.h */ + diff --git a/src/windows/mimeconf b/src/windows/mimeconf new file mode 100644 index 00000000..66d29bc7 --- /dev/null +++ b/src/windows/mimeconf @@ -0,0 +1,394 @@ +# (C) 2015 J.F.Dockes + +# This file contains most of the data which determines how we +# handle the different mime types (also see the "mimeview" file). +# +# This is the version specific to MS-WINDOWS +# +# Sections: +# top-level: Decompression parameters. Should not be at top-level, historical. +# [index] : Associations of mime types to the filters that translate them +# to plain text or html. +# [icons] : Associations of mime types to result list icons (GUI) +# [categories] : groupings of mime types (media, text, message etc.) +# [guifilters] : defines the filtering checkboxes in the GUI. Uses the +# above categories by default + +## ####################################### +# Decompression: these types need a first pass to create a temp file to +# work with. We use a script because uncompress utilities usually work in +# place, which is not suitable. +# +# Obviously this should be in a [decompress] section or such, but it was once +# forgotten and remained global for compatibility... +# +# The %t parameter will be substituted to the name of a temporary directory +# by recoll. This directory is guaranteed empty when calling the filter +# +# The %f parameter will be substituted with the input file. +# +# The script (ie: rcluncomp) must output the uncompressed file name on +# stdout. Note that the windows version will always use 7z, and ignore +# the decompressor parameter in the following lines +application/gzip = uncompress python rcluncomp.py 7z %f %t +application/x-gzip = uncompress python rcluncomp.py 7z %f %t +application/x-compress = uncompress python rcluncomp.py 7z %f %t +application/x-bzip2 = uncompress python rcluncomp.py 7z %f %t +application/x-xz = uncompress python rcluncomp.py 7z %f %t +application/x-lzma = uncompress python rcluncomp.py 7z %f %t + + +## ################################### +# Filters for indexing and internal preview. +# The "internal" filters are hardwired in the c++ code. +# The external "exec" filters are typically scripts. By default, they output the +# document in simple html format, have a look at the scripts. +# A different format (ie text/plain), and a character set can be defined for +# each filter, see the exemples below (ie: msword) +[index] +application/msword = execm python rcldoc.py +application/pdf = execm python rclpdf.py +application/vnd.ms-excel = execm python rclxls.py +application/vnd.ms-powerpoint = execm python rclppt.py +application/vnd.openxmlformats-officedocument.wordprocessingml.document = \ + execm python rclopxml.py +application/vnd.openxmlformats-officedocument.wordprocessingml.template = \ + execm python rclopxml.py +application/vnd.openxmlformats-officedocument.presentationml.template = \ + execm python rclopxml.py +application/vnd.openxmlformats-officedocument.presentationml.presentation = \ + execm python rclopxml.py +application/vnd.openxmlformats-officedocument.spreadsheetml.sheet = \ + execm python rclopxml.py +application/vnd.openxmlformats-officedocument.spreadsheetml.template =\ + execm python rclopxml.py + +application/vnd.oasis.opendocument.text = execm python rclsoff.py +application/vnd.oasis.opendocument.text-template = execm python rclsoff.py +application/vnd.oasis.opendocument.presentation = execm python rclsoff.py +application/vnd.oasis.opendocument.spreadsheet = execm python rclsoff.py +application/vnd.oasis.opendocument.graphics = execm python rclsoff.py +application/vnd.sun.xml.calc = execm python rclsoff.py +application/vnd.sun.xml.calc.template = execm python rclsoff.py +application/vnd.sun.xml.draw = execm python rclsoff.py +application/vnd.sun.xml.draw.template = execm python rclsoff.py +application/vnd.sun.xml.impress = execm python rclsoff.py +application/vnd.sun.xml.impress.template = execm python rclsoff.py +application/vnd.sun.xml.math = execm python rclsoff.py +application/vnd.sun.xml.writer = execm python rclsoff.py +application/vnd.sun.xml.writer.global = execm python rclsoff.py +application/vnd.sun.xml.writer.template = execm python rclsoff.py + +application/vnd.wordperfect = exec wpd/wpd2html;mimetype=text/html + +application/vnd.openxmlformats-officedocument.wordprocessingml.document = \ + execm python rclopxml.py +application/vnd.openxmlformats-officedocument.wordprocessingml.template = \ + execm python rclopxml.py +application/vnd.openxmlformats-officedocument.presentationml.template = \ + execm python rclopxml.py +application/vnd.openxmlformats-officedocument.presentationml.presentation = \ + execm python rclopxml.py +application/vnd.openxmlformats-officedocument.spreadsheetml.sheet = \ + execm python rclopxml.py +application/vnd.openxmlformats-officedocument.spreadsheetml.template =\ + execm python rclopxml.py + +application/epub+zip = execm python rclepub +# Returned by xdg-mime for .js. Future-proofing +application/javascript = internal text/plain + +# Also Handle the mime type returned by "file -i" for a suffix-less word +# file. This could probably just as well be an excel file, but we have to +# chose one. +application/vnd.ms-office = execm python rcldoc.py + +application/ogg = execm python rclaudio + +application/x-awk = internal text/plain +application/x-chm = execm python rclchm +application/x-dia-diagram = execm python rcldia;mimetype=text/plain +application/x-flac = execm python rclaudio +application/x-gnote = execm python rclxml.py +application/x-gnuinfo = execm python rclinfo +application/x-mimehtml = internal message/rfc822 +application/x-perl = internal text/plain +application/x-php = internal text/plain +application/x-rar = execm python rclrar;charset=default +application/x-shellscript = internal text/plain +#application/x-tar = execm python rcltar +application/x-webarchive = execm python rclwar +application/x-7z-compressed = execm python rcl7z +audio/mpeg = execm python rclaudio +audio/mp4 = execm python rclaudio +audio/aac = execm python rclaudio +audio/x-karaoke = execm python rclkar +image/gif = execm python rclimg.py +image/jp2 = execm python rclimg.py +image/jpeg = execm python rclimg.py +image/png = execm python rclimg.py +image/tiff = execm python rclimg.py +image/svg+xml = execm python rclsvg.py +#image/x-xcf = execm perl rclimg +inode/symlink = internal +application/x-zerosize = internal +inode/x-empty = internal application/x-zerosize +message/rfc822 = internal +text/calendar = execm python rclics;mimetype=text/plain +text/html = internal +text/plain = internal +text/rtf = exec unrtf --nopict --html;mimetype=text/html +#text/rtf = execm python rclrtf.py +text/x-c = internal +text/x-c++ = internal +text/x-c+ = internal +text/x-csharp = internal text/plain +text/css = internal text/plain +application/javascript = internal text/plain +text/x-csv = internal text/plain +text/x-chm-html = internal text/html +text/x-ini = internal text/plain +text/x-mail = internal +text/x-perl = internal text/plain +text/x-python = exec python rclpython +text/x-shellscript = internal text/plain +text/x-srt = internal text/plain + +# Generic XML is best indexed as text, else it generates too many errors +# All parameter and tag names, attribute values etc, are indexed as +# text. rclxml.py tries to just index the text content. +#application/xml = execm rclxml.py +#text/xml = execm rclxml.py +application/xml = internal text/plain +text/xml = internal text/plain + +## ############################################# +# Icons to be used in the result list if required by gui config +[icons] +application/epub+zip = book +application/javascript = source +application/msword = wordprocessing +application/ogg = sownd +application/pdf = pdf +application/postscript = postscript +application/vnd.ms-excel = spreadsheet +application/vnd.ms-powerpoint = presentation +application/vnd.oasis.opendocument.presentation = presentation +application/vnd.oasis.opendocument.spreadsheet = spreadsheet +application/vnd.oasis.opendocument.text = wordprocessing +application/vnd.openxmlformats-officedocument.presentationml.presentation = presentation +application/vnd.openxmlformats-officedocument.presentationml.template = presentation +application/vnd.openxmlformats-officedocument.spreadsheetml.sheet = spreadsheet +application/vnd.openxmlformats-officedocument.spreadsheetml.template = spreadsheet +application/vnd.openxmlformats-officedocument.wordprocessingml.document = wordprocessing +application/vnd.openxmlformats-officedocument.wordprocessingml.template = wordprocessing +application/vnd.sun.xml.calc = spreadsheet +application/vnd.sun.xml.calc.template = spreadsheet +application/vnd.sun.xml.draw = drawing +application/vnd.sun.xml.draw.template = drawing +application/vnd.sun.xml.impress = presentation +application/vnd.sun.xml.impress.template = presentation +application/vnd.sun.xml.math = wordprocessing +application/vnd.sun.xml.writer = wordprocessing +application/vnd.sun.xml.writer.global = wordprocessing +application/vnd.sun.xml.writer.template = wordprocessing +application/vnd.wordperfect = wordprocessing +application/x-abiword = wordprocessing +application/x-awk = source +application/x-chm = book +application/x-dia-diagram = drawing +application/x-dvi = document +application/x-flac = sownd +application/x-fsdirectory = folder +application/x-gnote = document +application/x-gnuinfo = book +application/x-gnumeric = spreadsheet +application/x-kword = wordprocessing +application/x-lyx = wordprocessing +application/x-mimehtml = message +application/x-mobipocket-ebook = document +application/x-okular-notes = document +application/x-perl = source +application/x-php = source +application/x-rar = archive +application/x-scribus = document +application/x-scribus = wordprocessing +application/x-shellscript = source +application/x-tar = archive +application/x-tex = wordprocessing +application/x-webarchive = archive +application/xml = document +application/zip = archive +application/x-7z-compressed = archive +audio/mpeg = sownd +audio/x-karaoke = sownd +image/bmp = image +image/gif = image +image/jp2 = image +image/jpeg = image +image/png = image +image/svg+xml = drawing +image/tiff = image +image/vnd.djvu = document +image/x-xcf = image +image/x-xpmi = image +inode/directory = folder +inode/symlink = emblem-symbolic-link +message/rfc822 = message +text/html = html +text/html|chm = bookchap +text/html|epub = bookchap +text/html|gnuinfo = bookchap +text/plain = txt +text/rtf = wordprocessing +text/x-c = source +text/x-c+ = source +text/x-c++ = source +text/x-csv = txt +text/x-fictionbook = document +text/x-html-aptosid-man = aptosid-book +text/x-html-sidux-man = sidux-book +text/x-ini = txt +text/x-mail = message +text/x-man = document +text/x-perl = source +text/x-purple-html-log = pidgin +text/x-purple-log = pidgin +text/x-python = text-x-python +text/x-shellscript = source +text/x-tex = wordprocessing +text/xml = document +video/3gpp = video +video/mp2p = video +video/mp2t = video +video/mp4 = video +video/mpeg = video +video/quicktime = video +video/x-matroska = video +video/x-ms-asf = video +video/x-msvideo = video + +[categories] +# Categories group mime types by "kind". They can be used from the query +# language as an "rclcat" clause. This is fully dynamic, you can change the +# names and groups as you wish, only the mime types are stored in the index. +# +# If you add/remove categories, you may also want to change the +# "guifilters" section below. +text = \ + application/epub+zip \ + application/msword \ + application/pdf \ + application/postscript \ + application/vnd.oasis.opendocument.text \ + application/vnd.openxmlformats-officedocument.wordprocessingml.document \ + application/vnd.openxmlformats-officedocument.wordprocessingml.template \ + application/vnd.sun.xml.writer \ + application/vnd.sun.xml.writer.global \ + application/vnd.sun.xml.writer.template \ + application/vnd.wordperfect \ + application/x-abiword \ + application/x-awk \ + application/x-chm \ + application/x-dvi \ + application/x-gnote \ + application/x-gnuinfo \ + application/x-kword \ + application/x-lyx \ + application/x-mobipocket-ebook \ + application/x-okular-notes \ + application/x-perl \ + application/x-scribus \ + application/x-shellscript \ + application/x-tex \ + application/xml \ + text/xml \ + text/x-csv \ + text/x-tex \ + image/vnd.djvu \ + text/calendar \ + text/html \ + text/plain \ + text/rtf \ + text/x-c \ + text/x-c++ \ + text/x-c+ \ + text/x-fictionbook \ + text/x-html-aptosid-man \ + text/x-html-sidux-man \ + text/x-ini \ + text/x-man \ + text/x-perl \ + text/x-python \ + text/x-shellscript + +spreadsheet = \ + application/vnd.ms-excel \ + application/vnd.oasis.opendocument.spreadsheet \ + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet \ + application/vnd.openxmlformats-officedocument.spreadsheetml.template \ + application/vnd.sun.xml.calc \ + application/vnd.sun.xml.calc.template \ + application/x-gnumeric + +presentation = \ + application/vnd.ms-powerpoint \ + application/vnd.oasis.opendocument.presentation \ + application/vnd.openxmlformats-officedocument.presentationml.presentation \ + application/vnd.openxmlformats-officedocument.presentationml.template \ + application/vnd.sun.xml.impress \ + application/vnd.sun.xml.impress.template + +media = \ + application/ogg \ + application/x-flac \ + audio/* \ + image/* \ + video/* \ + +message = message/rfc822 \ + text/x-gaim-log \ + text/x-mail \ + text/x-purple-log \ + text/x-purple-html-log \ + +other = application/vnd.sun.xml.draw \ + application/vnd.sun.xml.draw.template \ + application/vnd.sun.xml.math \ + application/x-dia-diagram \ + application/x-fsdirectory \ + application/x-mimehtml \ + application/x-rar \ + application/x-tar \ + application/x-webarchive \ + application/zip \ + application/x-7z-compressed \ + inode/directory \ + inode/symlink \ + +[guifilters] +# This defines the top level filters in the GUI (accessed by the the +# radiobuttons above the results area, or a toolbar combobox). +# Each entry defines a label and a query language fragment that will be +# applied to filter the current query if the option is activated. +# +# This does not really belong in mimeconf, but it does belong in the index +# config (not the GUI one), because it's not necessarily the same in all +# configs, it has to go somewhere, and it's not worth a separate config +# file... +# +# By default this filters by document category (see above), but any +# language fragment should be ok. Be aware though that the "document +# history" queries only know about simple "rclcat" filtering. +# +# If you don't want the filter names to be displayed in alphabetic order, +# you can define them with a colon. The part before the colon is not +# displayed but used for ordering, ie: a:zzbutshouldbefirst b:aacomeslast +# +text = rclcat:text +spreadsheet = rclcat:spreadsheet +presentation = rclcat:presentation +media = rclcat:media +message = rclcat:message +other = rclcat:other + diff --git a/src/windows/mimeview b/src/windows/mimeview new file mode 100644 index 00000000..b335054d --- /dev/null +++ b/src/windows/mimeview @@ -0,0 +1,192 @@ +## ########################################## +# External viewers, launched by the recoll GUI when you click on a result +# 'edit' link +# +# MS WINDOWS VERSION +# +# Mime types which we should not uncompress if they are found gzipped or +# bzipped because the native viewer knows how to handle. These would be +# exceptions and the list is normally empty +#nouncompforviewmts = + +# For releases 1.18 and later: exceptions when using the x-all entry: these +# types will use their local definition. This is useful, e.g.: +# +# - for pdf, where we can pass additional parameters like page to open and +# search string +# - For pages of CHM and EPUB documents where we can choose to open the +# parent document instead of a temporary html file. +xallexcepts = \ + text/html|epub \ + application/x-fsdirectory|parentopen inode/directory|parentopen + +[view] +# Pseudo entry used if the 'use desktop' preference is set in the GUI +# Could not get the cmd-based ones to work (tried quoting "start %u" too) +#application/x-all = c:/Windows/System32/cmd /c start %u +#application/x-all = cmd /c start %u +application/x-all = rclstartw %f + +####### Special ones +# Open the parent epub document for epub parts instead of opening them as +# html documents. This is almost always what we want. +text/html|epub = rclstartw %F;ignoreipath=1 +# Parent open for fs file. +application/x-fsdirectory|parentopen = rclstartw %f +inode/directory|parentopen = rclstartw %f + +###### The following are not used at all on windows, but the types need to +###### be listed for an "Open" link to appear in the result list +application/epub+zip = ebook-viewer %f + +application/x-gnote = gnote %f + +application/x-mobipocket-ebook = ebook-viewer %f + +application/x-kword = kword %f +application/x-abiword = abiword %f + +application/pdf = evince --page-index=%p --find=%s %f +# Or: +#application/pdf = qpdfview --search %s %f#%p + +application/postscript = evince --page-index=%p --find=%s %f +application/x-dvi = evince --page-index=%p --find=%s %f + +application/x-lyx = lyx %f +application/x-scribus = scribus %f + +#application/msword = libreoffice %f +application/msword = \ + "C:/Program Files (x86)/LibreOffice 5/program/soffice.exe" %f + +application/vnd.ms-excel = libreoffice %f +application/vnd.ms-powerpoint = libreoffice %f + +application/vnd.oasis.opendocument.text = libreoffice %f +application/vnd.oasis.opendocument.presentation = libreoffice %f +application/vnd.oasis.opendocument.spreadsheet = libreoffice %f + +application/vnd.openxmlformats-officedocument.wordprocessingml.document = \ + libreoffice %f +application/vnd.openxmlformats-officedocument.wordprocessingml.template = \ + libreoffice %f +application/vnd.openxmlformats-officedocument.presentationml.template = \ + libreoffice %f +application/vnd.openxmlformats-officedocument.presentationml.presentation = \ + libreoffice %f +application/vnd.openxmlformats-officedocument.spreadsheetml.sheet = \ + libreoffice %f +application/vnd.openxmlformats-officedocument.spreadsheetml.template =\ + libreoffice %f +application/vnd.sun.xml.calc = libreoffice %f +application/vnd.sun.xml.calc.template = libreoffice %f +application/vnd.sun.xml.draw = libreoffice %f +application/vnd.sun.xml.draw.template = libreoffice %f +application/vnd.sun.xml.impress = libreoffice %f +application/vnd.sun.xml.impress.template = libreoffice %f +application/vnd.sun.xml.math = libreoffice %f +application/vnd.sun.xml.writer = libreoffice %f +application/vnd.sun.xml.writer.global = libreoffice %f +application/vnd.sun.xml.writer.template = libreoffice %f +application/vnd.wordperfect = libreoffice %f +text/rtf = libreoffice %f + +application/x-dia-diagram = dia %f + +application/x-fsdirectory = dolphin %f +inode/directory = dolphin %f + +application/x-gnuinfo = xterm -e "info -f %f" +application/x-gnumeric = gnumeric %f + +application/x-flac = rhythmbox %f +audio/mpeg = rhythmbox %f +application/ogg = rhythmbox %f +audio/x-karaoke = kmid %f + +image/jpeg = gwenview %f +image/png = gwenview %f +image/tiff = gwenview %f +image/gif = gwenview %f +image/svg+xml = inkview %f +image/vnd.djvu = djview %f +image/x-xcf = gimp %f +image/bmp = gwenview %f +image/x-ms-bmp = gwenview %f +image/x-xpmi = gwenview %f + +# Opening mail messages not always works. +# - Thunderbird will only open a single-message file if it has an .emf +# extension +# - "sylpheed %f" seems to work ok as of version 3.3 +# - "kmail --view %u" works +message/rfc822 = thunderbird -file %f +text/x-mail = thunderbird -file %f +application/x-mimehtml = thunderbird -file %f + +text/calendar = evolution %f + +application/x-okular-notes = okular %f + +application/x-rar = ark %f +application/x-tar = ark %f +application/zip = ark %f +application/x-7z-compressed = ark %f + +application/x-awk = emacsclient --no-wait %f +application/x-perl = emacsclient --no-wait %f +text/x-perl = emacsclient --no-wait %f +application/x-shellscript = emacsclient --no-wait %f +text/x-shellscript = emacsclient --no-wait %f +text/x-srt = emacsclient --no-wait %f + +# Or firefox -remote "openFile(%u)" +text/html = firefox %u + +# gnu info nodes are translated to html with a "gnuinfo" +# rclaptg. rclshowinfo knows how to start the info command on the right +# node +text/html|gnuinfo = rclshowinfo %F %(title);ignoreipath=1 + +application/x-webarchive = konqueror %f +text/x-fictionbook = ebook-viewer %f +application/x-tex = emacsclient --no-wait %f +application/xml = emacsclient --no-wait %f +text/xml = emacsclient --no-wait %f +text/x-tex = emacsclient --no-wait %f +text/plain = emacsclient --no-wait %f +text/x-awk = emacsclient --no-wait %f +text/x-c = emacsclient --no-wait %f +text/x-c+ = emacsclient --no-wait %f +text/x-c++ = emacsclient --no-wait %f +text/x-csv = libreoffice %f +text/x-html-sidux-man = konqueror %f +text/x-html-aptosid-man = iceweasel %f + +application/x-chm = kchmviewer %f +# Html pages inside a chm have a chm rclaptg set by the filter. Kchmviewer +# knows how to use the ipath (which is the internal chm path) to open the +# file at the right place +text/html|chm = kchmviewer --url %i %F + +text/x-ini = emacsclient --no-wait %f +text/x-man = xterm -u8 -e "groff -T ascii -man %f | more" +text/x-python = idle %f +text/x-gaim-log = emacsclient --no-wait %f +text/x-purple-html-log = emacsclient --no-wait %f +text/x-purple-log = emacsclient --no-wait %f + +# The video types will usually be handled by the desktop default, but they +# need entries here to get an "Open" link +video/3gpp = vlc %f +video/mp2p = vlc %f +video/mp2t = vlc %f +video/mp4 = vlc %f +video/mpeg = vlc %f +video/quicktime = vlc %f +video/x-matroska = vlc %f +video/x-ms-asf = vlc %f +video/x-msvideo = vlc %f + + diff --git a/src/windows/mkinstdir.sh b/src/windows/mkinstdir.sh new file mode 100644 index 00000000..8bd6a9d9 --- /dev/null +++ b/src/windows/mkinstdir.sh @@ -0,0 +1,245 @@ +#!/bin/sh + +fatal() +{ + echo $* + exit 1 +} +Usage() +{ + fatal mkinstdir.sh targetdir +} + +test $# -eq 1 || Usage + +DESTDIR=$1 + +test -d $DESTDIR || mkdir $DESTDIR || fatal cant create $DESTDIR + +# Script to make a prototype recoll install directory from locally compiled +# software. *** Needs msys or cygwin *** + +################################ +# Local values (to be adjusted) + +# Recoll src tree +RCL=c:/recoll/src/ + +ReleaseBuild=y + +# Note: unrtf not under recolldeps because it's a clone from the +# original mercurial repository +UNRTF=c:/unrtf +# antiword is under recolldeps: it's not really maintained any more +# and has no public repository +ANTIWORD=c:/recolldeps/antiword +PYXSLT=C:/recolldeps/pyxslt +PYEXIV2=C:/recolldeps/pyexiv2 +#LIBXAPIAN=c:/temp/xapian-core-1.2.21/.libs/libxapian-22.dll +LIBXAPIAN=c:/temp/xapian-core-1.4.1/.libs/libxapian-30.dll +MUTAGEN=C:/temp/mutagen-1.32/ +EPUB=C:/temp/epub-0.5.2 +ZLIB=c:/temp/zlib-1.2.8 +POPPLER=c:/temp/poppler-0.36/ +LIBWPD=c:/temp/libwpd/libwpd-0.10.0/ +LIBREVENGE=c:/temp/libwpd/librevenge-0.0.1.jfd/ +CHM=c:/recolldeps/pychm + +# Where to find libgcc_s_dw2-1.dll for progs which need it copied +gccpath=`which gcc` +MINGWBIN=`dirname $gccpath` + +# Where to copy the Qt Dlls from: +QTBIN=C:/Qt/5.5/mingw492_32/bin + +# Qt arch +QTA=Desktop_Qt_5_5_0_MinGW_32bit + +RCLW=$RCL/windows/ + +if test X$ReleaseBuild = X'y'; then + qtsdir=release +else + qtsdir=debug +fi +LIBR=$RCLW/build-librecoll-${QTA}-${qtsdir}/${qtsdir}/librecoll.dll +GUIBIN=$RCL/build-recoll-win-${QTA}-${qtsdir}/${qtsdir}/recoll.exe +RCLIDX=$RCLW/build-recollindex-${QTA}-${qtsdir}/${qtsdir}/recollindex.exe +RCLQ=$RCLW/build-recollq-${QTA}-${qtsdir}/${qtsdir}/recollq.exe +RCLS=$RCLW/build-rclstartw-${QTA}-${qtsdir}/${qtsdir}/rclstartw.exe + + +# Needed for a VS build (which we did not ever complete because of +# missing Qt VS2015 support). +#CONFIGURATION=Release +#PLATFORM=Win32 + +################ +# Script: + +FILTERS=$DESTDIR/Share/filters + +# checkcopy. +chkcp() +{ + echo "Copying $@" + cp $@ || fatal cp $@ failed +} + +# Note: can't build static recoll as there is no static qtwebkit (ref: 5.5.0) +copyqt() +{ + cd $DESTDIR + PATH=$QTBIN:$PATH + export PATH + $QTBIN/windeployqt recoll.exe + chkcp $QTBIN/libwinpthread-1.dll $DESTDIR + chkcp $QTBIN/libstdc++-6.dll $DESTDIR +} + +copyxapian() +{ + chkcp $LIBXAPIAN $DESTDIR +} + +copyzlib() +{ + chkcp $ZLIB/zlib1.dll $DESTDIR +} + +copyrecoll() +{ +# bindir=$RCL/windows/$PLATFORM/$CONFIGURATION/ +# chkcp $bindir/recollindex.exe $DESTDIR +# chkcp $bindir/recollq.exe $DESTDIR +# chkcp $bindir/pthreadVC2.dll $DESTDIR + chkcp $LIBR $DESTDIR + chkcp $GUIBIN $DESTDIR + chkcp $RCLIDX $DESTDIR + chkcp $RCLQ $DESTDIR + chkcp $RCLS $DESTDIR + + chkcp $RCL/COPYING $DESTDIR/COPYING.txt + chkcp $RCL/doc/user/usermanual.html $DESTDIR/Share/doc + chkcp $RCL/doc/user/docbook-xsl.css $DESTDIR/Share/doc + mkdir -p $DESTDIR/Share/doc/webhelp + cp -rp $RCL/doc/user/webhelp/docs/* $DESTDIR/Share/doc/webhelp + chkcp $RCL/sampleconf/fields $DESTDIR/Share/examples + chkcp $RCL/sampleconf/fragbuts.xml $DESTDIR/Share/examples + chkcp $RCL/windows/mimeconf $DESTDIR/Share/examples + chkcp $RCL/sampleconf/mimemap $DESTDIR/Share/examples + chkcp $RCL/windows/mimeview $DESTDIR/Share/examples + chkcp $RCL/sampleconf/recoll.conf $DESTDIR/Share/examples + chkcp $RCL/sampleconf/recoll.qss $DESTDIR/Share/examples + + chkcp $RCL/python/recoll/recoll/rclconfig.py $FILTERS + chkcp $RCL/filters/* $FILTERS + chkcp $RCL/qtgui/mtpics/* $DESTDIR/Share/images + chkcp $RCL/qtgui/i18n/*.qm $DESTDIR/Share/translations +} + +copyantiword() +{ + # MS VS + #bindir=$ANTIWORD/Win32-only/$PLATFORM/$CONFIGURATION + # MINGW + bindir=$ANTIWORD/ + + test -d $Filters/Resources || mkdir -p $FILTERS/Resources || exit 1 + chkcp $bindir/antiword.exe $FILTERS + chkcp $ANTIWORD/Resources/* $FILTERS/Resources +} + +copyunrtf() +{ +# bindir=$UNRTF/Windows/$PLATFORM/$CONFIGURATION + bindir=$UNRTF/Windows/ + + test -d $FILTERS/Share || mkdir -p $FILTERS/Share || exit 1 + chkcp $bindir/unrtf.exe $FILTERS + chkcp $UNRTF/outputs/*.conf $FILTERS/Share + chkcp $UNRTF/outputs/SYMBOL.charmap $FILTERS/Share + # libiconv2 is not present in qt, get it from mingw direct. is C, should + # be compatible + chkcp c:/MinGW/bin/libiconv-2.dll $FILTERS +} + +copymutagen() +{ + cp -rp $MUTAGEN/build/lib/mutagen $FILTERS + # chkcp to check that mutagen is where we think it is + chkcp $MUTAGEN/build/lib/mutagen/mp3.py $FILTERS/mutagen +} + +copyepub() +{ + cp -rp $EPUB/build/lib/epub $FILTERS + # chkcp to check that epub is where we think it is + chkcp $EPUB/build/lib/epub/opf.py $FILTERS/epub +} + +copypyexiv2() +{ + cp -rp $PYEXIV2/pyexiv2 $FILTERS + chkcp $PYEXIV2/libexiv2python.pyd $FILTERS/ +} + +copyxslt() +{ + chkcp $PYXSLT/libxslt.py $FILTERS/ + cp -rp $PYXSLT/* $FILTERS +} + +copypoppler() +{ + test -d $FILTERS/poppler || mkdir $FILTERS/poppler || \ + fatal cant create poppler dir + for f in pdftotext.exe libpoppler.dll freetype6.dll jpeg62.dll \ + libpng16-16.dll zlib1.dll libtiff3.dll \ + libgcc_s_dw2-1.dll libstdc++-6.dll; do + chkcp $POPPLER/bin/$f $FILTERS/poppler + done +} + +copywpd() +{ + DEST=$FILTERS/wpd + test -d $DEST || mkdir $DEST || fatal cant create poppler dir $DEST + + for f in librevenge-0.0.dll librevenge-generators-0.0.dll \ + librevenge-stream-0.0.dll; do + chkcp $LIBREVENGE/src/lib/.libs/$f $DEST + done + chkcp $LIBWPD/src/lib/.libs/libwpd-0.10.dll $DEST + chkcp $LIBWPD/src/conv/html/.libs/wpd2html.exe $DEST + chkcp $MINGWBIN/libgcc_s_dw2-1.dll $DEST + chkcp $MINGWBIN/libstdc++-6.dll $DEST + chkcp $ZLIB/zlib1.dll $DEST + chkcp $QTBIN/libwinpthread-1.dll $DEST +} + +copychm() +{ + DEST=$FILTERS + cp -rp $CHM/chm $DEST || fatal "can't copy pychm" +} + +for d in doc examples filters images translations; do + test -d $DESTDIR/Share/$d || mkdir -p $DESTDIR/Share/$d || \ + fatal mkdir $d failed +done + +# copyrecoll must stay before copyqt so that windeployqt can do its thing +copyrecoll +copyqt +copyxapian +copyzlib +copypoppler +copyantiword +copyunrtf +copyxslt +copymutagen +copyepub +copypyexiv2 +copywpd +copychm diff --git a/src/windows/qmkrecoll/librecoll.pro b/src/windows/qmkrecoll/librecoll.pro new file mode 100644 index 00000000..693f0041 --- /dev/null +++ b/src/windows/qmkrecoll/librecoll.pro @@ -0,0 +1,136 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2015-10-03T09:04:49 +# +#------------------------------------------------- + +QT -= core gui + +TARGET = librecoll +TEMPLATE = lib + +DEFINES += LIBRECOLL_LIBRARY BUILDING_RECOLL +DEFINES -= UNICODE +DEFINES -= _UNICODE +DEFINES += _MBCS +DEFINES += PSAPI_VERSION=1 + +SOURCES += \ +../../aspell/rclaspell.cpp \ +../../bincimapmime/convert.cc \ +../../bincimapmime/mime-parsefull.cc \ +../../bincimapmime/mime-parseonlyheader.cc \ +../../bincimapmime/mime-printbody.cc \ +../../bincimapmime/mime.cc \ +../../common/beaglequeuecache.cpp \ +../../common/cstr.cpp \ +../../common/rclconfig.cpp \ +../../common/rclinit.cpp \ +../../common/syngroups.cpp \ +../../common/textsplit.cpp \ +../../common/unacpp.cpp \ +../../common/utf8fn.cpp \ +../../index/beaglequeue.cpp \ +../../index/bglfetcher.cpp \ +../../index/checkretryfailed.cpp \ +../../index/fetcher.cpp \ +../../index/exefetcher.cpp \ +../../index/fsfetcher.cpp \ +../../index/fsindexer.cpp \ +../../index/indexer.cpp \ +../../index/mimetype.cpp \ +../../index/subtreelist.cpp \ +../../internfile/extrameta.cpp \ +../../internfile/htmlparse.cpp \ +../../internfile/internfile.cpp \ +../../internfile/mh_exec.cpp \ +../../internfile/mh_execm.cpp \ +../../internfile/mh_html.cpp \ +../../internfile/mh_mail.cpp \ +../../internfile/mh_mbox.cpp \ +../../internfile/mh_text.cpp \ +../../internfile/mimehandler.cpp \ +../../internfile/myhtmlparse.cpp \ +../../internfile/txtdcode.cpp \ +../../internfile/uncomp.cpp \ +../../query/docseq.cpp \ +../../query/docseqdb.cpp \ +../../query/docseqhist.cpp \ +../../query/dynconf.cpp \ +../../query/filtseq.cpp \ +../../query/plaintorich.cpp \ +../../query/recollq.cpp \ +../../query/reslistpager.cpp \ +../../query/sortseq.cpp \ +../../query/wasaparse.cpp \ +../../query/wasaparseaux.cpp \ +../../rcldb/daterange.cpp \ +../../rcldb/expansiondbs.cpp \ +../../rcldb/rclabstract.cpp \ +../../rcldb/rcldb.cpp \ +../../rcldb/rcldoc.cpp \ +../../rcldb/rcldups.cpp \ +../../rcldb/rclquery.cpp \ +../../rcldb/rclterms.cpp \ +../../rcldb/searchdata.cpp \ +../../rcldb/searchdatatox.cpp \ +../../rcldb/searchdataxml.cpp \ +../../rcldb/stemdb.cpp \ +../../rcldb/stoplist.cpp \ +../../rcldb/synfamily.cpp \ +../../unac/unac.cpp \ +../../utils/appformime.cpp \ +../../utils/base64.cpp \ +../../utils/cancelcheck.cpp \ +../../utils/chrono.cpp \ +../../utils/circache.cpp \ +../../utils/conftree.cpp \ +../../utils/copyfile.cpp \ +../../utils/cpuconf.cpp \ +../../utils/ecrontab.cpp \ +../../windows/execmd_w.cpp \ +../../windows/fnmatch.c \ +../../windows/wincodepages.cpp \ +../../utils/fileudi.cpp \ +../../utils/fstreewalk.cpp \ +../../utils/hldata.cpp \ +../../utils/idfile.cpp \ +../../utils/log.cpp \ +../../utils/md5.cpp \ +../../utils/md5ut.cpp \ +../../utils/mimeparse.cpp \ +../../utils/pathut.cpp \ +../../utils/pxattr.cpp \ +../../utils/rclionice.cpp \ +../../utils/rclutil.cpp \ +../../utils/readfile.cpp \ +../../utils/smallut.cpp \ +../../utils/strmatcher.cpp \ +../../utils/transcode.cpp \ +../../utils/wipedir.cpp \ +../../windows/strptime.cpp \ +../../windows/dirent.c + +INCLUDEPATH += ../../common ../../index ../../internfile ../../query \ + ../../unac ../../utils ../../aspell ../../rcldb ../../qtgui \ + ../../xaposix ../../confgui ../../bincimapmime + +windows { + contains(QMAKE_CC, gcc){ + # MingW + QMAKE_CXXFLAGS += -std=c++11 -pthread -Wno-unused-parameter + } + contains(QMAKE_CC, cl){ + # Visual Studio + } + LIBS += c:/temp/xapian-core-1.4.1/.libs/libxapian-30.dll \ + c:/temp/zlib-1.2.8/zlib1.dll -liconv -lshlwapi -lpsapi -lkernel32 + + INCLUDEPATH += ../../windows \ + C:/temp/xapian-core-1.4.1/include +} + +unix { + target.path = /usr/lib + INSTALLS += target +} diff --git a/src/windows/qmkrecoll/rclstartw.pro b/src/windows/qmkrecoll/rclstartw.pro new file mode 100644 index 00000000..5af52100 --- /dev/null +++ b/src/windows/qmkrecoll/rclstartw.pro @@ -0,0 +1,33 @@ + +QT -= core gui + +TARGET = rclstartw +TEMPLATE = app + +DEFINES += BUILDING_RECOLL +DEFINES -= UNICODE +DEFINES -= _UNICODE +DEFINES += _MBCS +DEFINES += PSAPI_VERSION=1 + + +SOURCES += \ +../rclstartw.cpp + +INCLUDEPATH += ../../common ../../index ../../internfile ../../query \ + ../../unac ../../utils ../../aspell ../../rcldb ../../qtgui \ + ../../xaposix ../../confgui ../../bincimapmime + +windows { + contains(QMAKE_CC, gcc){ + # MingW + QMAKE_CXXFLAGS += -std=c++11 -Wno-unused-parameter + } + contains(QMAKE_CC, cl){ + # Visual Studio + } + LIBS += \ + -liconv -lshlwapi -lpsapi -lkernel32 + + INCLUDEPATH += ../../windows +} diff --git a/src/windows/qmkrecoll/recollindex.pro b/src/windows/qmkrecoll/recollindex.pro new file mode 100644 index 00000000..3f9e2994 --- /dev/null +++ b/src/windows/qmkrecoll/recollindex.pro @@ -0,0 +1,38 @@ + +QT -= core gui + +TARGET = recollindex +CONFIG += console +CONFIG -= app_bundle +TEMPLATE = app + +DEFINES += BUILDING_RECOLL +DEFINES -= UNICODE +DEFINES -= _UNICODE +DEFINES += _MBCS +DEFINES += PSAPI_VERSION=1 +DEFINES += RCL_MONITOR + +SOURCES += \ +../../index/recollindex.cpp \ +../../index/rclmonprc.cpp \ +../../index/rclmonrcv.cpp + +INCLUDEPATH += ../../common ../../index ../../internfile ../../query \ + ../../unac ../../utils ../../aspell ../../rcldb ../../qtgui \ + ../../xaposix ../../confgui ../../bincimapmime + +windows { + contains(QMAKE_CC, gcc){ + # MingW + QMAKE_CXXFLAGS += -std=c++11 -pthread -Wno-unused-parameter + } + contains(QMAKE_CC, cl){ + # Visual Studio + } + LIBS += \ + C:/recoll/src/windows/build-librecoll-Desktop_Qt_5_5_0_MinGW_32bit-Release/release/librecoll.dll \ + -lshlwapi -lpsapi -lkernel32 + + INCLUDEPATH += ../../windows +} diff --git a/src/windows/qmkrecoll/recollq.pro b/src/windows/qmkrecoll/recollq.pro new file mode 100644 index 00000000..c012abf1 --- /dev/null +++ b/src/windows/qmkrecoll/recollq.pro @@ -0,0 +1,36 @@ + +QT -= core gui + +TARGET = recollq +CONFIG += console +CONFIG -= app_bundle +TEMPLATE = app + +DEFINES += BUILDING_RECOLL +DEFINES -= UNICODE +DEFINES -= _UNICODE +DEFINES += _MBCS +DEFINES += PSAPI_VERSION=1 + + +SOURCES += \ +../../query/recollqmain.cpp + +INCLUDEPATH += ../../common ../../index ../../internfile ../../query \ + ../../unac ../../utils ../../aspell ../../rcldb ../../qtgui \ + ../../xaposix ../../confgui ../../bincimapmime + +windows { + contains(QMAKE_CC, gcc){ + # MingW + QMAKE_CXXFLAGS += -std=c++11 -Wno-unused-parameter + } + contains(QMAKE_CC, cl){ + # Visual Studio + } + LIBS += \ + C:/recoll/src/windows/build-librecoll-Desktop_Qt_5_5_0_MinGW_32bit-Release/release/librecoll.dll \ + -lshlwapi -lpsapi -lkernel32 + + INCLUDEPATH += ../../windows +} diff --git a/src/windows/rclstartw.cpp b/src/windows/rclstartw.cpp new file mode 100644 index 00000000..c2511829 --- /dev/null +++ b/src/windows/rclstartw.cpp @@ -0,0 +1,81 @@ +/* Copyright (C) 2015 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 + * (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include "autoconfig.h" + +#include +#include +#include + +using namespace std; + +// This exists only because I could not find any way to get "cmd /c +// start fn" to work when executed from the recoll GUI. Otoh, +// cygstart, which uses ShellExecute() did work... So this is just a +// simpler cygstart +static char *thisprog; +static char usage [] ="rclstartw [-m] \n" + " Will use ShellExecute to open the arg with the default app\n" + " -m 1 start maximized\n"; + +static void Usage(FILE *fp = stderr) +{ + fprintf(fp, "%s: usage:\n%s", thisprog, usage); + exit(1); +} +int op_flags; +#define OPT_m 0x1 + +int main(int argc, char *argv[]) +{ + thisprog = argv[0]; + argc--; argv++; + int imode = 0; + while (argc > 0 && **argv == '-') { + (*argv)++; + if (!(**argv)) + Usage(); + while (**argv) + switch (*(*argv)++) { + case 'm': op_flags |= OPT_m; if (argc < 2) Usage(); + if ((sscanf(*(++argv), "%d", &imode)) != 1) + Usage(); + argc--; goto b1; + default: Usage(); break; + } + b1: argc--; argv++; + } + + if (argc != 1) { + Usage(); + } + char *fn = strdup(argv[0]); + // Do we need this ? + //https://msdn.microsoft.com/en-us/library/windows/desktop/bb762153%28v=vs.85%29.aspx + //CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + + int wmode = SW_SHOWNORMAL; + switch (imode) { + case 1: wmode = SW_SHOWMAXIMIZED;break; + default: wmode = SW_SHOWNORMAL; break; + } + + int ret = (int)ShellExecute(NULL, "open", fn, NULL, NULL, wmode); + if (ret) { + fprintf(stderr, "ShellExecute returned %d\n", ret); + } + return ret; +} diff --git a/src/windows/recoll-setup.iss b/src/windows/recoll-setup.iss new file mode 100644 index 00000000..e3f2b49f --- /dev/null +++ b/src/windows/recoll-setup.iss @@ -0,0 +1,52 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +#define MyAppName "Recoll" +#define MyAppVersion "1.23.0-9c5e32-20161216" +#define MyAppPublisher "Recoll.org" +#define MyAppURL "http://www.recoll.org" +#define MyAppExeName "recoll.exe" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. +; Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{E9BC39EC-0E3D-4DDA-8DA0-FDB8ED16DC8D} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +;AppVerName={#MyAppName} {#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultGroupName={#MyAppName} +LicenseFile=C:\install\recoll\COPYING.txt +OutputDir=C:\Temp +OutputBaseFilename=recoll-setup-{#MyAppVersion} +SetupIconFile=C:\recoll\src\desktop\recoll.ico +Compression=lzma +SolidCompression=yes +;; Use either prv=adm and defaultdir={pf} or prv=lowest and defaultdir=userpf +;PrivilegesRequired=lowest +;DefaultDirName={userpf}\{#MyAppName} +PrivilegesRequired=admin +DefaultDirName={pf}\{#MyAppName} + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Files] +Source: "C:\install\recoll\recoll.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "C:\install\recoll\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Icons] +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" +Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon + +[Run] +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent + diff --git a/src/windows/recollindex/recollindex.vcxproj b/src/windows/recollindex/recollindex.vcxproj new file mode 100644 index 00000000..c1e8d4d7 --- /dev/null +++ b/src/windows/recollindex/recollindex.vcxproj @@ -0,0 +1,163 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {A513D65F-798C-4166-B66E-49F69ADA4F59} + Win32Proj + recollindex + 8.1 + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + true + + + false + + + false + + + + + + Level3 + Disabled + _CRT_SECURE_NO_WARNINGS;__WIN32__;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(pthreads)\include;$(solutiondir)..\xaposix;$(solutiondir)..\utils;$(solutiondir)..\unac;$(solutiondir)..\rcldb;$(solutiondir)..\internfile;$(solutiondir)..\index;$(solutiondir)..\common;%(AdditionalIncludeDirectories) + + + Console + true + $(libiconv)\libiconv\$(Platform)\$(Configuration)\;$(zlib)\$(PLatform)\$(Configuration)\;$(xapian)\win32\$(PLatform)\$(Configuration)\;$(solutiondir)$(PLatform)\$(Configuration)\;$(pthreads)\lib\x86\;%(AdditionalLibraryDirectories) + Win32ProjectRecoll.lib;xapian-core.lib;libiconv.lib;zlib.lib;Ws2_32.lib;Rpcrt4.lib;pthreadVC2.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + _CRT_SECURE_NO_WARNINGS;__WIN32__;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(pthreads)\include;$(solutiondir)..\xaposix;$(solutiondir)..\utils;$(solutiondir)..\unac;$(solutiondir)..\rcldb;$(solutiondir)..\internfile;$(solutiondir)..\index;$(solutiondir)..\common;$(solutiondir)..\windows;%(AdditionalIncludeDirectories) + 4800 + + + Console + true + $(libiconv)\libiconv\$(Platform)\$(Configuration);$(zlib)\$(PLatform)\$(Configuration)\;$(xapian)\win32\$(PLatform)\$(Configuration)\;$(solutiondir)$(PLatform)\$(Configuration)\;$(pthreads)\lib\x64;%(AdditionalLibraryDirectories) + Win32ProjectRecoll.lib;zlib.lib;xapian-core.lib;libiconv.lib;Ws2_32.lib;Rpcrt4.lib;pthreadVC2.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + \ No newline at end of file diff --git a/src/windows/recollindex/recollindex.vcxproj.filters b/src/windows/recollindex/recollindex.vcxproj.filters new file mode 100644 index 00000000..7888c808 --- /dev/null +++ b/src/windows/recollindex/recollindex.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/src/windows/recollindex/recollindex.vcxproj.user b/src/windows/recollindex/recollindex.vcxproj.user new file mode 100644 index 00000000..6fb136bf --- /dev/null +++ b/src/windows/recollindex/recollindex.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/windows/recollq/recollq.vcxproj b/src/windows/recollq/recollq.vcxproj new file mode 100644 index 00000000..dd6fdc2a --- /dev/null +++ b/src/windows/recollq/recollq.vcxproj @@ -0,0 +1,190 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {C1D0CCD2-0015-44AC-A606-AC48BB80C133} + Win32Proj + recollq + 8.1 + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\ + $(PLatform)\$(Configuration)\ + + + true + + + false + + + false + + + + + + Level3 + Disabled + _CRT_SECURE_NO_WARNINGS;__WIN32__;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(pthreads)\include;$(solutiondir)..\xaposix;$(solutiondir)..\utils;$(solutiondir)..\unac;$(solutiondir)..\rcldb;$(solutiondir)..\internfile;$(solutiondir)..\index;$(solutiondir)..\common;%(AdditionalIncludeDirectories) + 4800 + + + Console + true + $(libiconv)\libiconv\$(Platform)\$(Configuration)\;$(zlib)\$(PLatform)\$(Configuration)\;$(xapian)\win32\$(PLatform)\$(Configuration)\;$(solutiondir)$(PLatform)\$(Configuration)\;$(pthreads)\lib\x86;%(AdditionalLibraryDirectories) + Win32ProjectRecoll.lib;zlib.lib;xapian-core.lib;libiconv.lib;Ws2_32.lib;Rpcrt4.lib;pthreadVC2.lib;uuid.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + _CRT_SECURE_NO_WARNINGS;__WIN32__;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(pthreads)\include;$(solutiondir)..\xaposix;$(solutiondir)..\utils;$(solutiondir)..\unac;$(solutiondir)..\rcldb;$(solutiondir)..\internfile;$(solutiondir)..\index;$(solutiondir)..\common;%(AdditionalIncludeDirectories) + 4800 + + + Console + true + $(libiconv)\libiconv\$(Platform)\$(Configuration);$(zlib)\$(PLatform)\$(Configuration)\;$(xapian)\win32\$(PLatform)\$(Configuration)\;$(solutiondir)$(PLatform)\$(Configuration)\;$(pthreads)\lib\x64;%(AdditionalLibraryDirectories) + Win32ProjectRecoll.lib;zlib.lib;xapian-core.lib;libiconv.lib;Ws2_32.lib;Rpcrt4.lib;pthreadVC2.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/windows/recollq/recollq.vcxproj.filters b/src/windows/recollq/recollq.vcxproj.filters new file mode 100644 index 00000000..f8e3ba7c --- /dev/null +++ b/src/windows/recollq/recollq.vcxproj.filters @@ -0,0 +1,96 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/src/windows/strptime.cpp b/src/windows/strptime.cpp new file mode 100644 index 00000000..09cb5ffc --- /dev/null +++ b/src/windows/strptime.cpp @@ -0,0 +1,253 @@ +#include +#include +#include +#include +#include + +const char * strp_weekdays[] = +{ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" }; +const char * strp_monthnames[] = +{ "january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december" }; +bool strp_atoi(const char * & s, int & result, int low, int high, int offset) +{ + bool worked = false; + char * end; + unsigned long num = strtoul(s, &end, 10); + if (num >= (unsigned long)low && num <= (unsigned long)high) + { + result = (int)(num + offset); + s = end; + worked = true; + } + return worked; +} +char * strptime(const char *s, const char *format, struct tm *tm) +{ + bool working = true; + while (working && *format && *s) + { + switch (*format) + { + case '%': + { + ++format; + switch (*format) + { + case 'a': + case 'A': // weekday name + tm->tm_wday = -1; + working = false; + for (int i = 0; i < 7; ++i) + { + size_t len = strlen(strp_weekdays[i]); + if (!strnicmp(strp_weekdays[i], s, len)) + { + tm->tm_wday = i; + s += len; + working = true; + break; + } + else if (!strnicmp(strp_weekdays[i], s, 3)) + { + tm->tm_wday = i; + s += 3; + working = true; + break; + } + } + break; + case 'b': + case 'B': + case 'h': // month name + tm->tm_mon = -1; + working = false; + for (int i = 0; i < 12; ++i) + { + size_t len = strlen(strp_monthnames[i]); + if (!strnicmp(strp_monthnames[i], s, len)) + { + tm->tm_mon = i; + s += len; + working = true; + break; + } + else if (!strnicmp(strp_monthnames[i], s, 3)) + { + tm->tm_mon = i; + s += 3; + working = true; + break; + } + } + break; + case 'd': + case 'e': // day of month number + working = strp_atoi(s, tm->tm_mday, 1, 31, 0); + break; + case 'D': // %m/%d/%y + { + const char * s_save = s; + working = strp_atoi(s, tm->tm_mon, 1, 12, -1); + if (working && *s == '/') + { + ++s; + working = strp_atoi(s, tm->tm_mday, 1, 31, 0); + if (working && *s == '/') + { + ++s; + working = strp_atoi(s, tm->tm_year, 0, 99, 0); + if (working && tm->tm_year < 69) + tm->tm_year += 100; + } + } + if (!working) + s = s_save; + } + break; + case 'H': // hour + working = strp_atoi(s, tm->tm_hour, 0, 23, 0); + break; + case 'I': // hour 12-hour clock + working = strp_atoi(s, tm->tm_hour, 1, 12, 0); + break; + case 'j': // day number of year + working = strp_atoi(s, tm->tm_yday, 1, 366, -1); + break; + case 'm': // month number + working = strp_atoi(s, tm->tm_mon, 1, 12, -1); + break; + case 'M': // minute + working = strp_atoi(s, tm->tm_min, 0, 59, 0); + break; + case 'n': // arbitrary whitespace + case 't': + while (isspace((int)*s)) + ++s; + break; + case 'p': // am / pm + if (!strnicmp(s, "am", 2)) + { // the hour will be 1 -> 12 maps to 12 am, 1 am .. 11 am, 12 noon 12 pm .. 11 pm + if (tm->tm_hour == 12) // 12 am == 00 hours + tm->tm_hour = 0; + } + else if (!strnicmp(s, "pm", 2)) + { + if (tm->tm_hour < 12) // 12 pm == 12 hours + tm->tm_hour += 12; // 1 pm -> 13 hours, 11 pm -> 23 hours + } + else + working = false; + break; + case 'r': // 12 hour clock %I:%M:%S %p + { + const char * s_save = s; + working = strp_atoi(s, tm->tm_hour, 1, 12, 0); + if (working && *s == ':') + { + ++s; + working = strp_atoi(s, tm->tm_min, 0, 59, 0); + if (working && *s == ':') + { + ++s; + working = strp_atoi(s, tm->tm_sec, 0, 60, 0); + if (working && isspace((int)*s)) + { + ++s; + while (isspace((int)*s)) + ++s; + if (!strnicmp(s, "am", 2)) + { // the hour will be 1 -> 12 maps to 12 am, 1 am .. 11 am, 12 noon 12 pm .. 11 pm + if (tm->tm_hour == 12) // 12 am == 00 hours + tm->tm_hour = 0; + } + else if (!strnicmp(s, "pm", 2)) + { + if (tm->tm_hour < 12) // 12 pm == 12 hours + tm->tm_hour += 12; // 1 pm -> 13 hours, 11 pm -> 23 hours + } + else + working = false; + } + } + } + if (!working) + s = s_save; + } + break; + case 'R': // %H:%M + { + const char * s_save = s; + working = strp_atoi(s, tm->tm_hour, 0, 23, 0); + if (working && *s == ':') + { + ++s; + working = strp_atoi(s, tm->tm_min, 0, 59, 0); + } + if (!working) + s = s_save; + } + break; + case 'S': // seconds + working = strp_atoi(s, tm->tm_sec, 0, 60, 0); + break; + case 'T': // %H:%M:%S + { + const char * s_save = s; + working = strp_atoi(s, tm->tm_hour, 0, 23, 0); + if (working && *s == ':') + { + ++s; + working = strp_atoi(s, tm->tm_min, 0, 59, 0); + if (working && *s == ':') + { + ++s; + working = strp_atoi(s, tm->tm_sec, 0, 60, 0); + } + } + if (!working) + s = s_save; + } + break; + case 'w': // weekday number 0->6 sunday->saturday + working = strp_atoi(s, tm->tm_wday, 0, 6, 0); + break; + case 'Y': // year + working = strp_atoi(s, tm->tm_year, 1900, 65535, -1900); + break; + case 'y': // 2-digit year + working = strp_atoi(s, tm->tm_year, 0, 99, 0); + if (working && tm->tm_year < 69) + tm->tm_year += 100; + break; + case '%': // escaped + if (*s != '%') + working = false; + ++s; + break; + default: + working = false; + } + } + break; + case ' ': + case '\t': + case '\r': + case '\n': + case '\f': + case '\v': + // zero or more whitespaces: + while (isspace((int)*s)) + ++s; + break; + default: + // match character + if (*s != *format) + working = false; + else + ++s; + break; + } + ++format; + } + return (working ? (char *)s : 0); +} diff --git a/src/windows/strptime.h b/src/windows/strptime.h new file mode 100644 index 00000000..6bf30110 --- /dev/null +++ b/src/windows/strptime.h @@ -0,0 +1,3 @@ + +extern char * strptime(const char *s, const char *format, struct tm *tm); + diff --git a/src/windows/targetver.h b/src/windows/targetver.h new file mode 100644 index 00000000..90e767bf --- /dev/null +++ b/src/windows/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/src/windows/trexe/trexecmd/trexecmd.sln b/src/windows/trexe/trexecmd/trexecmd.sln new file mode 100644 index 00000000..02263f11 --- /dev/null +++ b/src/windows/trexe/trexecmd/trexecmd.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.23107.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "trexecmd", "trexecmd.vcxproj", "{AA325A63-79BC-41A5-BEDD-A64879D2A0A0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AA325A63-79BC-41A5-BEDD-A64879D2A0A0}.Debug|x64.ActiveCfg = Debug|x64 + {AA325A63-79BC-41A5-BEDD-A64879D2A0A0}.Debug|x64.Build.0 = Debug|x64 + {AA325A63-79BC-41A5-BEDD-A64879D2A0A0}.Debug|x86.ActiveCfg = Debug|Win32 + {AA325A63-79BC-41A5-BEDD-A64879D2A0A0}.Debug|x86.Build.0 = Debug|Win32 + {AA325A63-79BC-41A5-BEDD-A64879D2A0A0}.Release|x64.ActiveCfg = Release|x64 + {AA325A63-79BC-41A5-BEDD-A64879D2A0A0}.Release|x64.Build.0 = Release|x64 + {AA325A63-79BC-41A5-BEDD-A64879D2A0A0}.Release|x86.ActiveCfg = Release|Win32 + {AA325A63-79BC-41A5-BEDD-A64879D2A0A0}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/windows/trexe/trexecmd/trexecmd.vcxproj b/src/windows/trexe/trexecmd/trexecmd.vcxproj new file mode 100644 index 00000000..0953e5d0 --- /dev/null +++ b/src/windows/trexe/trexecmd/trexecmd.vcxproj @@ -0,0 +1,168 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + {AA325A63-79BC-41A5-BEDD-A64879D2A0A0} + Win32Proj + trexecmd + 8.1 + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + true + + + false + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(Solutiondir)..\..\..\xaposix;$(Solutiondir)..\..\;$(Solutiondir)..\..\..\common;$(Solutiondir)..\..\..\utils;%(AdditionalIncludeDirectories) + + + Console + true + $(Solutiondir)..\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + librecoll.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + __WIN32__;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(Solutiondir)..\..\..\xaposix;$(Solutiondir)..\..\;$(Solutiondir)..\..\..\common;$(Solutiondir)..\..\..\utils;%(AdditionalIncludeDirectories) + + + Console + true + $(libiconv)\libiconv\$(Platform)\$(Configuration);$(zlib)\$(PLatform)\$(Configuration)\;$(xapian)\win32\$(PLatform)\$(Configuration)\;$(solutiondir)..\..\$(PLatform)\$(Configuration)\;$(pthreads)\lib\x64;%(AdditionalLibraryDirectories) + Win32ProjectRecoll.lib;zlib.lib;xapian-core.lib;libiconv.lib;Ws2_32.lib;Rpcrt4.lib;pthreadVC2.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(Solutiondir)..\..\..\xaposix;$(Solutiondir)..\..\;$(Solutiondir)..\..\..\common;$(Solutiondir)..\..\..\utils;%(AdditionalIncludeDirectories) + + + Console + true + true + true + $(Solutiondir)..\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + librecoll.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(Solutiondir)..\..\..\xaposix;$(Solutiondir)..\..\;$(Solutiondir)..\..\..\common;$(Solutiondir)..\..\..\utils;%(AdditionalIncludeDirectories) + + + Console + true + true + true + $(Solutiondir)..\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + librecoll.lib;%(AdditionalDependencies) + + + + + + \ No newline at end of file diff --git a/src/windows/trexe/trexecmd/trexecmd.vcxproj.filters b/src/windows/trexe/trexecmd/trexecmd.vcxproj.filters new file mode 100644 index 00000000..fd552249 --- /dev/null +++ b/src/windows/trexe/trexecmd/trexecmd.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/src/windows/wincodepages.cpp b/src/windows/wincodepages.cpp new file mode 100644 index 00000000..bc2cc355 --- /dev/null +++ b/src/windows/wincodepages.cpp @@ -0,0 +1,180 @@ +#include +#include + +#include "safewindows.h" +#include "wincodepages.h" + +using namespace std; + +struct WinCpDef { + string cpname; + string cpcomment; +}; + +static unordered_map cpdefs { + {037, {"IBM037", "IBM EBCDIC US-Canada"}}, + {437, {"IBM437", "OEM United States"}}, + {500, {"IBM500", "IBM EBCDIC International"}}, + {708, {"ASMO-708", "Arabic (ASMO 708)"}}, + {709, {"", "Arabic (ASMO-449+, BCON V4)"}}, + {710, {"", "Arabic - Transparent Arabic"}}, + {720, {"DOS-720", "Arabic (Transparent ASMO); Arabic (DOS)"}}, + {737, {"ibm737", "OEM Greek (formerly 437G); Greek (DOS)"}}, + {775, {"ibm775", "OEM Baltic; Baltic (DOS)"}}, + {850, {"ibm850", "OEM Multilingual Latin 1; Western European (DOS)"}}, + {852, {"ibm852", "OEM Latin 2; Central European (DOS)"}}, + {855, {"IBM855", "OEM Cyrillic (primarily Russian)"}}, + {857, {"ibm857", "OEM Turkish; Turkish (DOS)"}}, + {858, {"IBM00858", "OEM Multilingual Latin 1 + Euro symbol"}}, + {860, {"IBM860", "OEM Portuguese; Portuguese (DOS)"}}, + {861, {"ibm861", "OEM Icelandic; Icelandic (DOS)"}}, + {862, {"DOS-862", "OEM Hebrew; Hebrew (DOS)"}}, + {863, {"IBM863", "OEM French Canadian; French Canadian (DOS)"}}, + {864, {"IBM864", "OEM Arabic; Arabic (864)"}}, + {865, {"IBM865", "OEM Nordic; Nordic (DOS)"}}, + {866, {"cp866", "OEM Russian; Cyrillic (DOS)"}}, + {869, {"ibm869", "OEM Modern Greek; Greek, Modern (DOS)"}}, + {870, {"IBM870", "IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2"}}, + {874, {"windows-874", "ANSI/OEM Thai (ISO 8859-11); Thai (Windows)"}}, + {875, {"cp875", "IBM EBCDIC Greek Modern"}}, + {932, {"shift_jis", "ANSI/OEM Japanese; Japanese (Shift-JIS)"}}, + {936, {"gb2312", "ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312)"}}, + {949, {"ks_c_5601-1987", "ANSI/OEM Korean (Unified Hangul Code)"}}, + {950, {"big5", "ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5)"}}, + {1026, {"IBM1026", "IBM EBCDIC Turkish (Latin 5)"}}, + {1047, {"IBM01047", "IBM EBCDIC Latin 1/Open System"}}, + {1140, {"IBM01140", "IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro)"}}, + {1141, {"IBM01141", "IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro)"}}, + {1142, {"IBM01142", "IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro)"}}, + {1143, {"IBM01143", "IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro)"}}, + {1144, {"IBM01144", "IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro)"}}, + {1145, {"IBM01145", "IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro)"}}, + {1146, {"IBM01146", "IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro)"}}, + {1147, {"IBM01147", "IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro)"}}, + {1148, {"IBM01148", "IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro)"}}, + {1149, {"IBM01149", "IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro)"}}, + {1200, {"utf-16", "Unicode UTF-16, little endian byte order (BMP of ISO 10646); available only to managed applications"}}, + {1201, {"unicodeFFFE", "Unicode UTF-16, big endian byte order; available only to managed applications"}}, + {1250, {"windows-1250", "ANSI Central European; Central European (Windows)"}}, + {1251, {"windows-1251", "ANSI Cyrillic; Cyrillic (Windows)"}}, + {1252, {"windows-1252", "ANSI Latin 1; Western European (Windows)"}}, + {1253, {"windows-1253", "ANSI Greek; Greek (Windows)"}}, + {1254, {"windows-1254", "ANSI Turkish; Turkish (Windows)"}}, + {1255, {"windows-1255", "ANSI Hebrew; Hebrew (Windows)"}}, + {1256, {"windows-1256", "ANSI Arabic; Arabic (Windows)"}}, + {1257, {"windows-1257", "ANSI Baltic; Baltic (Windows)"}}, + {1258, {"windows-1258", "ANSI/OEM Vietnamese; Vietnamese (Windows)"}}, + {1361, {"Johab", "Korean (Johab)"}}, + {10000, {"macintosh", "MAC Roman; Western European (Mac)"}}, + {10001, {"x-mac-japanese", "Japanese (Mac)"}}, + {10002, {"x-mac-chinesetrad", "MAC Traditional Chinese (Big5); Chinese Traditional (Mac)"}}, + {10003, {"x-mac-korean", "Korean (Mac)"}}, + {10004, {"x-mac-arabic", "Arabic (Mac)"}}, + {10005, {"x-mac-hebrew", "Hebrew (Mac)"}}, + {10006, {"x-mac-greek", "Greek (Mac)"}}, + {10007, {"x-mac-cyrillic", "Cyrillic (Mac)"}}, + {10008, {"x-mac-chinesesimp", "MAC Simplified Chinese (GB 2312); Chinese Simplified (Mac)"}}, + {10010, {"x-mac-romanian", "Romanian (Mac)"}}, + {10017, {"x-mac-ukrainian", "Ukrainian (Mac)"}}, + {10021, {"x-mac-thai", "Thai (Mac)"}}, + {10029, {"x-mac-ce", "MAC Latin 2; Central European (Mac)"}}, + {10079, {"x-mac-icelandic", "Icelandic (Mac)"}}, + {10081, {"x-mac-turkish", "Turkish (Mac)"}}, + {10082, {"x-mac-croatian", "Croatian (Mac)"}}, + {12000, {"utf-32", "Unicode UTF-32, little endian byte order; available only to managed applications"}}, + {12001, {"utf-32BE", "Unicode UTF-32, big endian byte order; available only to managed applications"}}, + {20000, {"x-Chinese_CNS", "CNS Taiwan; Chinese Traditional (CNS)"}}, + {20001, {"x-cp20001", "TCA Taiwan"}}, + {20002, {"x_Chinese-Eten", "Eten Taiwan; Chinese Traditional (Eten)"}}, + {20003, {"x-cp20003", "IBM5550 Taiwan"}}, + {20004, {"x-cp20004", "TeleText Taiwan"}}, + {20005, {"x-cp20005", "Wang Taiwan"}}, + {20105, {"x-IA5", "IA5 (IRV International Alphabet No. 5, 7-bit); Western European (IA5)"}}, + {20106, {"x-IA5-German", "IA5 German (7-bit)"}}, + {20107, {"x-IA5-Swedish", "IA5 Swedish (7-bit)"}}, + {20108, {"x-IA5-Norwegian", "IA5 Norwegian (7-bit)"}}, + {20127, {"us-ascii", "US-ASCII (7-bit)"}}, + {20261, {"x-cp20261", "T.61"}}, + {20269, {"x-cp20269", "ISO 6937 Non-Spacing Accent"}}, + {20273, {"IBM273", "IBM EBCDIC Germany"}}, + {20277, {"IBM277", "IBM EBCDIC Denmark-Norway"}}, + {20278, {"IBM278", "IBM EBCDIC Finland-Sweden"}}, + {20280, {"IBM280", "IBM EBCDIC Italy"}}, + {20284, {"IBM284", "IBM EBCDIC Latin America-Spain"}}, + {20285, {"IBM285", "IBM EBCDIC United Kingdom"}}, + {20290, {"IBM290", "IBM EBCDIC Japanese Katakana Extended"}}, + {20297, {"IBM297", "IBM EBCDIC France"}}, + {20420, {"IBM420", "IBM EBCDIC Arabic"}}, + {20423, {"IBM423", "IBM EBCDIC Greek"}}, + {20424, {"IBM424", "IBM EBCDIC Hebrew"}}, + {20833, {"x-EBCDIC-KoreanExtended", "IBM EBCDIC Korean Extended"}}, + {20838, {"IBM-Thai", "IBM EBCDIC Thai"}}, + {20866, {"koi8-r", "Russian (KOI8-R); Cyrillic (KOI8-R)"}}, + {20871, {"IBM871", "IBM EBCDIC Icelandic"}}, + {20880, {"IBM880", "IBM EBCDIC Cyrillic Russian"}}, + {20905, {"IBM905", "IBM EBCDIC Turkish"}}, + {20924, {"IBM00924", "IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)"}}, + {20932, {"EUC-JP", "Japanese (JIS 0208-1990 and 0212-1990)"}}, + {20936, {"x-cp20936", "Simplified Chinese (GB2312); Chinese Simplified (GB2312-80)"}}, + {20949, {"x-cp20949", "Korean Wansung"}}, + {21025, {"cp1025", "IBM EBCDIC Cyrillic Serbian-Bulgarian"}}, + {21027, {"", "(deprecated)"}}, + {21866, {"koi8-u", "Ukrainian (KOI8-U); Cyrillic (KOI8-U)"}}, + {28591, {"iso-8859-1", "ISO 8859-1 Latin 1; Western European (ISO)"}}, + {28592, {"iso-8859-2", "ISO 8859-2 Central European; Central European (ISO)"}}, + {28593, {"iso-8859-3", "ISO 8859-3 Latin 3"}}, + {28594, {"iso-8859-4", "ISO 8859-4 Baltic"}}, + {28595, {"iso-8859-5", "ISO 8859-5 Cyrillic"}}, + {28596, {"iso-8859-6", "ISO 8859-6 Arabic"}}, + {28597, {"iso-8859-7", "ISO 8859-7 Greek"}}, + {28598, {"iso-8859-8", "ISO 8859-8 Hebrew; Hebrew (ISO-Visual)"}}, + {28599, {"iso-8859-9", "ISO 8859-9 Turkish"}}, + {28603, {"iso-8859-13", "ISO 8859-13 Estonian"}}, + {28605, {"iso-8859-15", "ISO 8859-15 Latin 9"}}, + {29001, {"x-Europa", "Europa 3"}}, + {38598, {"iso-8859-8-i", "ISO 8859-8 Hebrew; Hebrew (ISO-Logical)"}}, + {50220, {"iso-2022-jp", "ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS)"}}, + {50221, {"csISO2022JP", "ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana)"}}, + {50222, {"iso-2022-jp", "ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI)"}}, + {50225, {"iso-2022-kr", "ISO 2022 Korean"}}, + {50227, {"x-cp50227", "ISO 2022 Simplified Chinese; Chinese Simplified (ISO 2022)"}}, + {50229, {"", "ISO 2022 Traditional Chinese"}}, + {50930, {"", "EBCDIC Japanese (Katakana) Extended"}}, + {50931, {"", "EBCDIC US-Canada and Japanese"}}, + {50933, {"", "EBCDIC Korean Extended and Korean"}}, + {50935, {"", "EBCDIC Simplified Chinese Extended and Simplified Chinese"}}, + {50936, {"", "EBCDIC Simplified Chinese"}}, + {50937, {"", "EBCDIC US-Canada and Traditional Chinese"}}, + {50939, {"", "EBCDIC Japanese (Latin) Extended and Japanese"}}, + {51932, {"euc-jp", "EUC Japanese"}}, + {51936, {"EUC-CN", "EUC Simplified Chinese; Chinese Simplified (EUC)"}}, + {51949, {"euc-kr", "EUC Korean"}}, + {51950, {"", "EUC Traditional Chinese"}}, + {52936, {"hz-gb-2312", "HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ)"}}, + {54936, {"GB18030", "Windows XP and later: GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030)"}}, + {57002, {"x-iscii-de", "ISCII Devanagari"}}, + {57003, {"x-iscii-be", "ISCII Bangla"}}, + {57004, {"x-iscii-ta", "ISCII Tamil"}}, + {57005, {"x-iscii-te", "ISCII Telugu"}}, + {57006, {"x-iscii-as", "ISCII Assamese"}}, + {57007, {"x-iscii-or", "ISCII Odia"}}, + {57008, {"x-iscii-ka", "ISCII Kannada"}}, + {57009, {"x-iscii-ma", "ISCII Malayalam"}}, + {57010, {"x-iscii-gu", "ISCII Gujarati"}}, + {57011, {"x-iscii-pa", "ISCII Punjabi"}}, + {65000, {"utf-7", "Unicode (UTF-7)"}}, + {65001, {"utf-8", "Unicode (UTF-8)"}}, + }; + +static const string cp1252("CP1252"); + +const string& winACPName() +{ + unsigned int acp = GetACP(); + auto it = cpdefs.find(acp); + if (it == cpdefs.end()) { + return cp1252; + } else { + return it->second.cpname; + } +} diff --git a/src/windows/wincodepages.h b/src/windows/wincodepages.h new file mode 100644 index 00000000..bb244832 --- /dev/null +++ b/src/windows/wincodepages.h @@ -0,0 +1,8 @@ +#ifndef WINCODEPAGES_H_ +#define WINCODEPAGES_H_ + +#include + +extern const std::string& winACPName(); + +#endif // WINCODEPAGES_H_ diff --git a/src/xaposix/safe.cc b/src/xaposix/safe.cc new file mode 100644 index 00000000..33ffc2f8 --- /dev/null +++ b/src/xaposix/safe.cc @@ -0,0 +1,31 @@ +/** @file safe.cc + * @brief Helper functions for safe*.h + */ +/* Copyright (C) 2007 Olly Betts + * + * 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 (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "safewindows.h" + +// Used by safeunistd.h: +void +xapian_sleep_milliseconds(unsigned int millisecs) +{ + Sleep(millisecs); +} + + diff --git a/src/xaposix/safefcntl.h b/src/xaposix/safefcntl.h new file mode 100644 index 00000000..6248351e --- /dev/null +++ b/src/xaposix/safefcntl.h @@ -0,0 +1,71 @@ +/* safefcntl.h: #include , but working around broken platforms. + * + * Copyright (C) 2006,2007 Olly Betts + * + * 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 (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#ifndef XAPIAN_INCLUDED_SAFEFCNTL_H +#define XAPIAN_INCLUDED_SAFEFCNTL_H + +#include + +#if defined __cplusplus && defined open + +// On some versions of Solaris, fcntl.h pollutes the namespace by #define-ing +// "open" to "open64" when largefile support is enabled. This causes problems +// if you have a method called "open" (other symbols are also #define-d +// e.g. "creat" to "creat64", but only "open" is a problem for Xapian so +// that's the only one we currently fix). + +#ifdef _MSC_VER +// MSVC #define-s open but also defines a function called open, so just undef +// the macro. +# undef open +#else + +inline int fcntl_open_(const char *filename, int flags, mode_t mode) { + return open(filename, flags, mode); +} + +inline int fcntl_open_(const char *filename, int flags) { + return open(filename, flags); +} + +#undef open + +inline int open(const char *filename, int flags, mode_t mode) { + return fcntl_open_(filename, flags, mode); +} + +inline int open(const char *filename, int flags) { + return fcntl_open_(filename, flags); +} + +#endif + +#endif + +// O_BINARY is only useful for platforms like Windows which distinguish between +// text and binary files, but it's cleaner to define it to 0 here for other +// platforms so we can avoid #ifdef where we need to use it in the code. +#ifndef __WIN32__ +# ifndef O_BINARY +# define O_BINARY 0 +# endif +#endif + +#endif /* XAPIAN_INCLUDED_SAFEFCNTL_H */ diff --git a/src/xaposix/safesysstat.h b/src/xaposix/safesysstat.h new file mode 100644 index 00000000..83179b7d --- /dev/null +++ b/src/xaposix/safesysstat.h @@ -0,0 +1,91 @@ +/* safesysstat.h: #include , but enabling large file support. + * + * Copyright (C) 2007,2012 Olly Betts + * + * 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 (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#ifndef XAPIAN_INCLUDED_SAFESYSSTAT_H +#define XAPIAN_INCLUDED_SAFESYSSTAT_H + +#include + +// For most platforms, AC_SYS_LARGEFILE enables support for large files at +// configure time, but MSVC doesn't use configure so we have to put the +// magic somewhere else - i.e. here! + +#ifdef _MSC_VER +// MSVC needs to call _stati64() instead of stat() and the struct which holds +// the information is "struct _stati64" instead of "struct stat" so we just +// use #define to replace both in one go. We also want to use _fstati64() +// instead of fstat() but in this case we can use a function-like macro. +// +// This hack is a problem is we ever want a method called "stat", or one called +// fstat which takes 2 parameters, but we can probably live with these +// limitations. + +#ifdef stat +# undef stat +#endif + +#ifdef fstat +# undef fstat +#endif + +// NB: _stati64 not _stat64 (the latter just returns a 64 bit timestamp). +#define stat _stati64 +#define fstat(FD, BUF) _fstati64(FD,BUF) + +#endif + +#ifdef __WIN32__ + +// MSVC lacks these POSIX macros and other compilers may too: +#ifndef S_ISDIR +# define S_ISDIR(ST_MODE) (((ST_MODE) & _S_IFMT) == _S_IFDIR) +#endif +#ifndef S_ISREG +# define S_ISREG(ST_MODE) (((ST_MODE) & _S_IFMT) == _S_IFREG) +#endif + +// On UNIX, mkdir() is prototyped in but on Windows it's in +// , so just include that from here to avoid build failures on +// MSVC just because of some new use of mkdir(). This also reduces the +// number of conditionalised #include statements we need in the sources. +#include + +// Add overloaded version of mkdir which takes an (ignored) mode argument +// to allow source code to just specify a mode argument unconditionally. +// +// The () around mkdir are in case it's defined as a macro. +inline int (mkdir)(const char *pathname, mode_t /*mode*/) { + return _mkdir(pathname); +} + +#else + +// These were specified by POSIX.1-1996, so most platforms should have +// these by now: +#ifndef S_ISDIR +# define S_ISDIR(ST_MODE) (((ST_MODE) & S_IFMT) == S_IFDIR) +#endif +#ifndef S_ISREG +# define S_ISREG(ST_MODE) (((ST_MODE) & S_IFMT) == S_IFREG) +#endif + +#endif + +#endif /* XAPIAN_INCLUDED_SAFESYSSTAT_H */ diff --git a/src/xaposix/safesyswait.h b/src/xaposix/safesyswait.h new file mode 100644 index 00000000..776d9282 --- /dev/null +++ b/src/xaposix/safesyswait.h @@ -0,0 +1,39 @@ +/** @file safesyswait.h + * @brief #include , with portability stuff. + */ +/* Copyright (C) 2010 Olly Betts + * + * 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 (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#ifndef XAPIAN_INCLUDED_SAFESYSWAIT_H +#define XAPIAN_INCLUDED_SAFESYSWAIT_H + +#ifndef __WIN32__ +# include +#else +// We don't try to replace waitpid(), etc - they're only useful for us when +// we can fork(). But it's handy to be able to use WIFEXITED() and +// WEXITSTATUS(). +# ifndef WIFEXITED +# define WIFEXITED(STATUS) (STATUS != -1) +# endif +# ifndef WEXITSTATUS +# define WEXITSTATUS(STATUS) (STATUS) +# endif +#endif + +#endif /* XAPIAN_INCLUDED_SAFESYSWAIT_H */ diff --git a/src/xaposix/safeunistd.h b/src/xaposix/safeunistd.h new file mode 100644 index 00000000..ee5971ff --- /dev/null +++ b/src/xaposix/safeunistd.h @@ -0,0 +1,79 @@ +/* safeunistd.h: , but with compat. and large file support for MSVC. + * + * Copyright (C) 2007 Olly Betts + * + * 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 (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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#ifndef XAPIAN_INCLUDED_SAFEUNISTD_H +#define XAPIAN_INCLUDED_SAFEUNISTD_H + +#ifndef _MSC_VER +# include +#else + +// sys/types.h has a typedef for off_t so make sure we've seen that before +// we hide it behind a #define. +# include + +// MSVC doesn't even HAVE unistd.h - io.h seems the nearest equivalent. +// We also need to do some renaming of functions to get versions which +// work on large files. +# include + +# ifdef lseek +# undef lseek +# endif + +# ifdef off_t +# undef off_t +# endif + +# define lseek(FD, OFF, WHENCE) _lseeki64(FD, OFF, WHENCE) +# define off_t __int64 + +// process.h is needed for getpid(). +# include + +#endif + +#ifdef __WIN32__ +#ifdef _MSC_VER +/* Recent MinGW versions define this */ +inline unsigned int +sleep(unsigned int seconds) +{ + // Use our own little helper function to avoid pulling in . + extern void xapian_sleep_milliseconds(unsigned int millisecs); + + // Sleep takes a time interval in milliseconds, whereas POSIX sleep takes + // a time interval in seconds, so we need to multiply 'seconds' by 1000. + // + // But make sure the multiplication won't overflow! 4294967 seconds is + // nearly 50 days, so just sleep for that long and return the number of + // seconds left to sleep for. The common case of sleep(CONSTANT) should + // optimise to just xapian_sleep_milliseconds(CONSTANT). + if (seconds > 4294967u) { + xapian_sleep_milliseconds(4294967000u); + return seconds - 4294967u; + } + xapian_sleep_milliseconds(seconds * 1000u); + return 0; +} +#endif /* _MSC_VER*/ +#endif /* __WIN32__ */ + +#endif /* XAPIAN_INCLUDED_SAFEUNISTD_H */ diff --git a/src/xaposix/safewindows.h b/src/xaposix/safewindows.h new file mode 100644 index 00000000..745190e9 --- /dev/null +++ b/src/xaposix/safewindows.h @@ -0,0 +1,45 @@ +/* safewindows.h: #include without all the bloat and damage. + * + * Copyright (C) 2005,2007 Olly Betts + * + * 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 (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 General Public License for more details. + * + * You should have received a copy of the GNU 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 + */ + +#ifndef XAPIAN_INCLUDED_SAFEWINDOWS_H +#define XAPIAN_INCLUDED_SAFEWINDOWS_H + +#if !defined __CYGWIN__ && !defined __WIN32__ +# error Including safewindows.h, but neither __CYGWIN__ nor __WIN32__ defined! +#endif + +// Prevent windows.h from defining min and max macros. +#ifndef NOMINMAX +# define NOMINMAX +#endif + +// Prevent windows.h from including lots of obscure win32 api headers +// which we don't care about and will just slow down compilation and +// increase the risk of symbol collisions. +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include + +// FOF_NOERRORUI isn't defined by older versions of the mingw headers. +#ifndef FOF_NOERRORUI +# define FOF_NOERRORUI 1024 +#endif + +#endif // XAPIAN_INCLUDED_SAFEWINDOWS_H diff --git a/src/ylwrap b/src/ylwrap new file mode 100755 index 00000000..ee0c0f9f --- /dev/null +++ b/src/ylwrap @@ -0,0 +1,57 @@ +#! /bin/sh + +# ylwrap - wrapper for lex/yacc invocations. Local version, the +# autotools scriptversion=2015-08-06.06; # UTC doesnt work for us +# because it does not move location.hh position.hh stack.hh into the +# appropriate directory (which is a bug, but it's simpler to rewrite a +# simple version for our needs than to fix the original). + +fatal() { + echo $* 1>&2 + exit 1 +} +usage() { + fatal "Usage: ylwrap query/wasaparse.y" +} + +test $# -ge 1 || usage + +toptmpdir=/tmp/rclylwrap$$ +tmpdir=${toptmpdir}/tmp +mkdir -p "${tmpdir}" + +cleanup() { + rm -rf "${toptmpdir}"/tmp/* + rmdir "${tmpdir}" + rmdir "${toptmpdir}" +} + +trap cleanup 0 2 15 + +# First arg is the input file + +input=$1 +inputdir=`dirname $1` +curdir=`pwd` || exit 1 +absinput="${curdir}/${input}" + +(cd "${tmpdir}"; bison -d -y $absinput) +ls $tmpdir + +for f in location.hh position.hh stack.hh; do + cmp -s "${tmpdir}"/$f "${inputdir}"/$f || cp -p "${tmpdir}"/$f "${inputdir}" +done + +# Note that we'd prefer to use wasaparse.h instead of wasaparse.hpp, +# but automake generates a dist list with wasaparse.hpp, so no choice. + +# Fix the include line in y.tab.c (it wants to include y.tab.h, but we already +# include it as wasaparse.hpp +(cd "${tmpdir}"; \ + sed -e 's/#include "y.tab.h"//' < y.tab.c > toto; \ + mv -f toto y.tab.c) + +cmp -s "${tmpdir}"/y.tab.c "${inputdir}"/wasaparse.cpp || \ + cp -p "${tmpdir}"/y.tab.c "${inputdir}"/wasaparse.cpp +cmp -s "${tmpdir}"/y.tab.h "${inputdir}"/wasaparse.hpp || \ + cp -p "${tmpdir}"/y.tab.h "${inputdir}"/wasaparse.hpp diff --git a/tests/config/recoll.conf b/tests/config/recoll.conf index 4e66ddb2..b1f775be 100644 --- a/tests/config/recoll.conf +++ b/tests/config/recoll.conf @@ -4,15 +4,20 @@ logfilename = /tmp/logrcltst daemloglevel = 6 daemlogfilename = /tmp/rclmontrace +systemfilecommand = xdg-mime query filetype + indexStripChars = 1 detectxattronly = 1 topdirs = /home/dockes/projets/fulltext/testrecoll/ +# Comics_12 causes rclppt to loop. We keep it around for general testing +# but it takes too much time when running the test-set skippedPaths = \ /home/dockes/projets/fulltext/testrecoll/.hg \ /home/dockes/projets/fulltext/testrecoll/skipped/real* \ - /home/dockes/projets/fulltext/testrecoll/config + /home/dockes/projets/fulltext/testrecoll/config \ + /home/dockes/projets/fulltext/testrecoll/ppt/Comics_12.pps daemSkippedPaths = \ /home/dockes/projets/fulltext/testrecoll/.hg \ /home/dockes/projets/fulltext/testrecoll/skipped/real* \ diff --git a/tests/info/info.sh b/tests/info/info.sh index fb2c3397..c7f396f3 100755 --- a/tests/info/info.sh +++ b/tests/info/info.sh @@ -5,6 +5,14 @@ topdir=`dirname $0`/.. initvariables $0 +# Note: file -i returns application/octet-stream for +# somefile.info-1. xdg-mime query filetype returns text/plain. Recoll +# 1.22 and later return several results for the following query, one +# for the info file, and others in the somefile.info-xx files, as +# text/plain files. Previous versions only returned the info file, +# which was better, but I'm not sure that we can do something about +# it. + recollq '"GPGME is compiled with largefile support by default"' 2> $mystderr | egrep -v '^Recoll query: ' > $mystdout diff --git a/tests/info/info.txt b/tests/info/info.txt index 02e011dc..d9c2ea46 100644 --- a/tests/info/info.txt +++ b/tests/info/info.txt @@ -1,2 +1,3 @@ -1 results +2 results text/html [file:///home/dockes/projets/fulltext/testrecoll/info/gpgme.info] [gpgme.info / Preparation / Largefile Support (LFS)] 4225 bytes +text/plain [file:///home/dockes/projets/fulltext/testrecoll/info/gpgme.info-1] [gpgme.info-1] 275877 bytes diff --git a/tests/langparser1/langparser1.sh b/tests/langparser1/langparser1.sh new file mode 100755 index 00000000..62ce5144 --- /dev/null +++ b/tests/langparser1/langparser1.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +# langparser actually test queries. We only test the language parser, the tested reference is the Xapian query. + +topdir=`dirname $0`/.. +. $topdir/shared.sh + +initvariables $0 +xrun() +{ + echo $* + $* +} + +( + for Q in \ + 'A' \ + 'A B' \ + 'A AND B' \ + 'A OR B' \ + 'A OR B AND C' \ + 'A AND B OR C' \ + '(A AND B) OR (C AND D)' \ + '(A OR B) AND (C OR D)' \ + '-the B' \ + 'A -B' \ + 'mime:text/plain' \ + 'size>10k' \ + 'date:3000-01-01' \ + 'mime:text/plain A OR B mime:text/html' \ + 'mime:text/plain A AND B mime:text/html' \ + 'mime:text/plain mime:text/html (A B) ' \ + 'mime:text/plain OR mime:text/html OR (A B) ' \ + 'rclcat:media A' \ + 'rclcat:media rclcat:message A' \ + 'A size>10k' \ + 'size>10k A' \ + 'date:3000-01-01 A' \ + 'A OR B date:3000-01-01' \ + 'A OR B AND date:3000-01-01' \ + 'title:A B' \ + 'title:A -B' \ + 'A -title:B' \ + ; do + # The " $Q" is there to avoid issue with a query beginning with - + # (recollq does not grok --) + printf "%60s" "Query: $Q -> ";recollq -Q -q " $Q" + done +) 2> $mystderr | egrep -v 'results|^Query setup took' > $mystdout + +diff -w ${myname}.txt $mystdout > $mydiffs 2>&1 + +checkresult diff --git a/tests/langparser1/langparser1.txt b/tests/langparser1/langparser1.txt new file mode 100644 index 00000000..18335d11 --- /dev/null +++ b/tests/langparser1/langparser1.txt @@ -0,0 +1,27 @@ + Query: A -> Recoll query: (a:(wqf=11)) + Query: A B -> Recoll query: ((a:(wqf=11) AND b:(wqf=11))) + Query: A AND B -> Recoll query: ((a:(wqf=11) AND b:(wqf=11))) + Query: A OR B -> Recoll query: ((a:(wqf=11) OR b:(wqf=11))) + Query: A OR B AND C -> Recoll query: (((a:(wqf=11) OR b:(wqf=11)) AND c:(wqf=11))) + Query: A AND B OR C -> Recoll query: ((a:(wqf=11) AND (b:(wqf=11) OR c:(wqf=11)))) + Query: (A AND B) OR (C AND D) -> Recoll query: (((a:(wqf=11) AND b:(wqf=11)) OR (c:(wqf=11) AND d:(wqf=11)))) + Query: (A OR B) AND (C OR D) -> Recoll query: (((a:(wqf=11) OR b:(wqf=11)) AND (c:(wqf=11) OR d:(wqf=11)))) + Query: -the B -> Recoll query: ((( AND_NOT the:(wqf=11)) AND b:(wqf=11))) + Query: A -B -> Recoll query: ((a:(wqf=11) AND ( AND_NOT b:(wqf=11)))) + Query: mime:text/plain -> Recoll query: (( FILTER Ttext/plain)) + Query: size>10k -> Recoll query: (( FILTER VALUE_GE 2 000000010000)) + Query: date:3000-01-01 -> Recoll query: (( FILTER D30000101)) + Query: mime:text/plain A OR B mime:text/html -> Recoll query: (((a:(wqf=11) OR b:(wqf=11)) FILTER (Ttext/html OR Ttext/plain))) + Query: mime:text/plain A AND B mime:text/html -> Recoll query: (((a:(wqf=11) AND b:(wqf=11)) FILTER (Ttext/html OR Ttext/plain))) + Query: mime:text/plain mime:text/html (A B) -> Recoll query: (((a:(wqf=11) AND b:(wqf=11)) FILTER (Ttext/html OR Ttext/plain))) + Query: mime:text/plain OR mime:text/html OR (A B) -> Recoll query: (((a:(wqf=11) AND b:(wqf=11)) FILTER (Ttext/html OR Ttext/plain))) + Query: rclcat:media A -> Recoll query: ((a:(wqf=11) FILTER (Tapplication/ogg OR Tapplication/x-flac OR Taudio/mpeg OR Taudio/x-karaoke OR Timage/bmp OR Timage/gif OR Timage/jpeg OR Timage/png OR Timage/svg+xml OR Timage/tiff OR Timage/vnd.djvu OR Timage/x-icon OR Timage/x-xcf OR Timage/x-xpmi OR Tvideo/*))) + Query: rclcat:media rclcat:message A -> Recoll query: ((a:(wqf=11) FILTER (Tapplication/ogg OR Tapplication/x-flac OR Taudio/mpeg OR Taudio/x-karaoke OR Timage/bmp OR Timage/gif OR Timage/jpeg OR Timage/png OR Timage/svg+xml OR Timage/tiff OR Timage/vnd.djvu OR Timage/x-icon OR Timage/x-xcf OR Timage/x-xpmi OR Tmessage/rfc822 OR Ttext/x-gaim-log OR Ttext/x-mail OR Ttext/x-purple-html-log OR Ttext/x-purple-log OR Tvideo/*))) + Query: A size>10k -> Recoll query: ((a:(wqf=11) FILTER VALUE_GE 2 000000010000)) + Query: size>10k A -> Recoll query: ((a:(wqf=11) FILTER VALUE_GE 2 000000010000)) + Query: date:3000-01-01 A -> Recoll query: ((a:(wqf=11) FILTER D30000101)) + Query: A OR B date:3000-01-01 -> Recoll query: (((a:(wqf=11) OR b:(wqf=11)) FILTER D30000101)) + Query: A OR B AND date:3000-01-01 -> Recoll query: (((a:(wqf=11) OR b:(wqf=11)) FILTER D30000101)) + Query: title:A B -> Recoll query: ((Sa:(wqf=11) AND b:(wqf=11))) + Query: title:A -B -> Recoll query: ((Sa:(wqf=11) AND ( AND_NOT b:(wqf=11)))) + Query: A -title:B -> Recoll query: ((a:(wqf=11) AND ( AND_NOT Sb:(wqf=11)))) diff --git a/tests/msword/msword.txt b/tests/msword/msword.txt index a7e19251..4bc3312b 100644 --- a/tests/msword/msword.txt +++ b/tests/msword/msword.txt @@ -1,5 +1,5 @@ 2 results application/msword [file:///home/dockes/projets/fulltext/testrecoll/msword/programme.doc] [programme.doc] 58880 bytes -application/msword [file:///home/dockes/projets/fulltext/testrecoll/zip/misc.zip] [misc.zip] 58880 bytes +application/msword [file:///home/dockes/projets/fulltext/testrecoll/zip/misc.zip] [programme.doc] 58880 bytes 1 results application/msword [file:///home/dockes/projets/fulltext/testrecoll/msword/IAmActuallyAnRTF.DOC] [DOC PROGRAMMEUR PCX9] 85381 bytes diff --git a/tests/program/program.txt b/tests/program/program.txt index 8afd1a9c..e621bf3b 100644 --- a/tests/program/program.txt +++ b/tests/program/program.txt @@ -1,6 +1,6 @@ 1 results -text/x-perl [file:///home/dockes/projets/fulltext/testrecoll/program/rclimg] [rclimg] 5294 bytes +application/x-perl [file:///home/dockes/projets/fulltext/testrecoll/program/rclimg] [rclimg] 5294 bytes 1 results application/x-shellscript [file:///home/dockes/projets/fulltext/testrecoll/program/run_program_with_counters.sh] [run_program_with_counters.sh] 2632 bytes 1 results -text/x-shellscript [file:///home/dockes/projets/fulltext/testrecoll/program/nosuffixshell] [nosuffixshell] 34 bytes +application/x-shellscript [file:///home/dockes/projets/fulltext/testrecoll/program/nosuffixshell] [nosuffixshell] 34 bytes diff --git a/tests/txt/txt.txt b/tests/txt/txt.txt index fea813de..b049305b 100644 --- a/tests/txt/txt.txt +++ b/tests/txt/txt.txt @@ -1,6 +1,6 @@ 3 results text/plain [file:///home/dockes/projets/fulltext/testrecoll/txt/liste1.txt] [liste1.txt] 893 bytes text/plain [file:///home/dockes/projets/fulltext/testrecoll/txt/liste.txt] [liste.txt] 1182 bytes -text/plain [file:///home/dockes/projets/fulltext/testrecoll/zip/misc.zip] [misc.zip] 1181 bytes +text/plain [file:///home/dockes/projets/fulltext/testrecoll/zip/misc.zip] [liste.txt] 1181 bytes 1 results text/x-ini [file:///home/dockes/projets/fulltext/testrecoll/txt/docutils.ini] [docutils.ini] 177 bytes diff --git a/tests/utfInPath/utfInPath.txt b/tests/utfInPath/utfInPath.txt index 704b4617..5a276243 100644 --- a/tests/utfInPath/utfInPath.txt +++ b/tests/utfInPath/utfInPath.txt @@ -2,5 +2,5 @@ image/vnd.djvu [file:///home/dockes/projets/fulltext/testrecoll/utfInPath/accentueÌ/1999_Hu_Shu.OCR.djvu] [1999_Hu_Shu.OCR.djvu] 753409 bytes image/vnd.djvu [file:///home/dockes/projets/fulltext/testrecoll/utfInPath/accentué/1999_Hu_Shu.OCR.djvu] [1999_Hu_Shu.OCR.djvu] 753409 bytes 2 results -text/plain [file:///home/dockes/projets/fulltext/testrecoll/utfInPath/accentueÌ/utf8path.txt] [utf8path.txt] 49 bytes text/plain [file:///home/dockes/projets/fulltext/testrecoll/utfInPath/accentué/utf8path.txt] [utf8path.txt] 49 bytes +text/plain [file:///home/dockes/projets/fulltext/testrecoll/utfInPath/accentueÌ/utf8path.txt] [utf8path.txt] 49 bytes diff --git a/tests/zip/zip.txt b/tests/zip/zip.txt index 91e34348..9b12303e 100644 --- a/tests/zip/zip.txt +++ b/tests/zip/zip.txt @@ -1,6 +1,6 @@ 3 results -text/plain [file:///home/dockes/projets/fulltext/testrecoll/zip/with blanks.zip] [with blanks.zip] 84 bytes -text/plain [file:///home/dockes/projets/fulltext/testrecoll/zip/manysmall.zip] [manysmall.zip] 12 bytes -text/plain [file:///home/dockes/projets/fulltext/testrecoll/zip/misc.zip] [misc.zip] 12 bytes +text/plain [file:///home/dockes/projets/fulltext/testrecoll/zip/with blanks.zip] [motd.txt] 84 bytes +text/plain [file:///home/dockes/projets/fulltext/testrecoll/zip/manysmall.zip] [49.txt] 12 bytes +text/plain [file:///home/dockes/projets/fulltext/testrecoll/zip/misc.zip] [zipuniq.txt] 12 bytes 1 results message/rfc822 [file:///home/dockes/projets/fulltext/testrecoll/zip/maildir.zip] [[FreeBSD-Announce] ZIPPEDMAILDIR_UNIQUEXXX FreeBSD Security Advisory FreeBSD-SA-04:17.procfs] 10765 bytes diff --git a/unac/unac.c b/unac/unac.c index 8356e6ae..60b91098 100644 --- a/unac/unac.c +++ b/unac/unac.c @@ -16,23 +16,25 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifdef HAVE_CONFIG_H -#ifdef RECOLL_DATADIR +#ifdef BUILDING_RECOLL #include "autoconfig.h" #else #include "config.h" #endif /* RECOLL */ -#endif /* HAVE_CONFIG_H */ -#ifdef RECOLL_DATADIR +#ifdef BUILDING_RECOLL /* Yes, recoll unac is actually c++, lets face modernity, I will not be caught writing another binary search */ #include #include #include #include -#include "unordered_defs.h" +#include +#include +#include + using std::string; +using std::vector; #include "smallut.h" @@ -42,17 +44,16 @@ using std::string; instead according to some local rule. There will usually be very few of them, but they must be looked up for every translated char. */ -STD_UNORDERED_MAP except_trans; +std::unordered_map except_trans; static inline bool is_except_char(unsigned short c, string& trans) { - STD_UNORDERED_MAP::const_iterator it - = except_trans.find(c); + auto it = except_trans.find(c); if (it == except_trans.end()) return false; trans = it->second; return true; } -#endif /* RECOLL_DATADIR */ +#endif /* BUILDING_RECOLL*/ /* * If configure.in has not defined this symbol, assume const. It @@ -74,7 +75,6 @@ static inline bool is_except_char(unsigned short c, string& trans) #include #include #endif /* HAVE_VSNPRINTF */ -#include #include "unac.h" #include "unac_version.h" @@ -14170,9 +14170,9 @@ int unacmaybefold_string_utf16(const char* in, size_t in_length, char** outp, size_t* out_lengthp, int what) { char* out; - int out_size; - int out_length; - unsigned int i; + size_t out_size; + size_t out_length; + size_t i; out_size = in_length > 0 ? in_length : 1024; @@ -14190,13 +14190,13 @@ int unacmaybefold_string_utf16(const char* in, size_t in_length, for(i = 0; i < in_length; i += 2) { unsigned short c; unsigned short* p; - int l; - int k; + size_t l; + size_t k; c = (in[i] << 8) | (in[i + 1] & 0xff); /* * Lookup the tables for decomposition information */ -#ifdef RECOLL_DATADIR +#ifdef BUILDING_RECOLL // Exception unac/fold values set by user. There should be 3 arrays for // unac/fold/unac+fold. For now there is only one array, which used to // be set for unac+fold, and is mostly or only used to prevent diacritics @@ -14219,11 +14219,11 @@ int unacmaybefold_string_utf16(const char* in, size_t in_length, l = trans.size() / 2; } } else { -#endif /* RECOLL_DATADIR */ +#endif /* BUILDING_RECOLL */ unac_uf_char_utf16_(c, p, l, what) -#ifdef RECOLL_DATADIR +#ifdef BUILDING_RECOLL } -#endif /* RECOLL_DATADIR */ +#endif /* BUILDING_RECOLL */ /* * Explain what's done in great detail @@ -14236,7 +14236,7 @@ int unacmaybefold_string_utf16(const char* in, size_t in_length, if(l == 0) { DEBUG_APPEND("untouched\n"); } else { - int i; + size_t i; for(i = 0; i < l; i++) DEBUG_APPEND("0x%04x ", p[i]); DEBUG_APPEND("\n"); @@ -14312,14 +14312,7 @@ int fold_string_utf16(const char* in, size_t in_length, static const char *utf16be = "UTF-16BE"; static iconv_t u8tou16_cd = (iconv_t)-1; static iconv_t u16tou8_cd = (iconv_t)-1; -static pthread_mutex_t o_unac_mutex; -static int unac_mutex_is_init; -// Call this or take your chances with the auto init. -void unac_init_mt() -{ - pthread_mutex_init(&o_unac_mutex, 0); - unac_mutex_is_init = 1; -} +static std::mutex o_unac_mutex; /* * Convert buffer containing string encoded in charset into @@ -14341,14 +14334,7 @@ static int convert(const char* from, const char* to, int from_utf16, from_utf8, to_utf16, to_utf8, u8tou16, u16tou8; const char space[] = { 0x00, 0x20 }; - /* Note: better call explicit unac_init_mt() before starting threads than - rely on this. - */ - if (unac_mutex_is_init == 0) { - pthread_mutex_init(&o_unac_mutex, 0); - unac_mutex_is_init = 1; - } - pthread_mutex_lock(&o_unac_mutex); + std::unique_lock lock(o_unac_mutex); if (!strcmp(utf16be, from)) { from_utf8 = 0; @@ -14436,10 +14422,11 @@ static int convert(const char* from, const char* to, const char* tmp = space; size_t tmp_length = 2; if(iconv(cd, (ICONV_CONST char **) &tmp, &tmp_length, &out, &out_remain) == (size_t)-1) { - if(errno == E2BIG) + if(errno == E2BIG) { /* fall thru to the E2BIG case below */; - else - goto out; + } else { + goto out; + } } else { /* The offending character was replaced by a SPACE, skip it. */ in += 2; @@ -14455,7 +14442,7 @@ static int convert(const char* from, const char* to, /* * The output does not fit in the current out buffer, enlarge it. */ - int length = out - out_base; + size_t length = out - out_base; out_size *= 2; { char *saved = out_base; @@ -14491,7 +14478,6 @@ static int convert(const char* from, const char* to, ret = 0; out: - pthread_mutex_unlock(&o_unac_mutex); return ret; } @@ -14561,7 +14547,7 @@ const char* unac_version(void) return UNAC_VERSION; } -#ifdef RECOLL_DATADIR +#ifdef BUILDING_RECOLL void unac_set_except_translations(const char *spectrans) { except_trans.clear(); @@ -14614,4 +14600,4 @@ void unac_set_except_translations(const char *spectrans) free(out); } } -#endif /* RECOLL_DATADIR */ +#endif /* BUILDING_RECOLL */ diff --git a/unac/unac.h b/unac/unac.h index cfc2cfd3..3b39f489 100644 --- a/unac/unac.h +++ b/unac/unac.h @@ -111,10 +111,7 @@ int fold_string(const char* charset, const char* in, size_t in_length, char** out, size_t* out_length); -/* To be called before starting threads in mt programs */ -void unac_init_mt(); - -#ifdef RECOLL_DATADIR +#ifdef BUILDING_RECOLL #include /** * Set exceptions for unaccenting, for characters which should not be @@ -128,7 +125,7 @@ void unac_init_mt(); * can't be an exception character, deal with it... */ void unac_set_except_translations(const char *spectrans); -#endif /* RECOLL_DATADIR */ +#endif /* BUILDING_RECOLL */ /* * Return unac version number. diff --git a/website/BUGS.html b/website/BUGS.html index d8b287f2..19677ec0 100644 --- a/website/BUGS.html +++ b/website/BUGS.html @@ -29,9 +29,51 @@ later versions. Bugs listed in the topmost section may also exist in older versions.

          -

          recoll 1.20.2

          +

          recoll 1.21.7, 1.22.3

            +
          • There have been multiple occurrences of an indexing problem + resulting in a damaged index. The cause is not completely determined, + but there is a suspicion that a Xapian bug may be involved at least + in some cases. The index is damaged in such a way that some search + results are missing, and that many document up-to-date checks can + fail. This makes partial/incremental indexing very expensive because + many documents are reindexed (for nothing, the index data is lost + anyway).
            + + You can see a partial description in Bitbucket Recoll issue #257, but + part of the discussion happened on the Xapian mailing list.
            + + The problem would signal itself by the following kind of message in + the indexer log:
            + +
            
            +:2:../rcldb/rcldb.cpp:1818:Db::needUpdate: get_document error: Document XX not found
            +   
            + + Also, messages about Xapian being unable to read some blocks. More + generally, any error message (beginning with :2:) originating in the rcldb + module is highly suspicious.
            + + If you get this message, the index is damaged, only deleting it and + reindexing can recover it.
            + + Olly Betts, the Xapian index developper, thinks that the origin may + be a problem which was fixed in Xapian 1.2.21, so, updating Xapian + may help. All Xapian 1.2.x versions are binary-compatible (you can + just drop them on Recoll), and there are backports repository for + several common Linux versions, get in touch if you have a + problem.
            +
          • + +
          • For indexes with case and diacritics sensitivity (not the + default), the autocasesens and autodiacsens configuration variable do + not work as described in the manual (they have no effect).
          • + +
          • The GUI must be restarted after changing the path translation + values (ptrans), even when they are changed from the GUI + preferences.
          • +
          • On old systems such as Debian Squeeze which use Evince version 2.x (not 3.x) as PDF viewer, the default "Open" command for PDF files will not @@ -83,6 +125,70 @@ versions.

            recoll.
          +

          recoll 1.22.2

          +
            +
          • The Python module limits result fetches to the Xapian result + count, which is estimated and usually smaller than the actual + number of results which can be read.
          • +
          • A bug in the text splitter made it impossible to match, for + example, filename:doc$ for files with names containing a space or + other word-breaking character before the '.'
          • +
          • The change from 'file' to 'xdg-mime' as file type identifier + command caused problem for some types of files (.java, .sql), because + of new and different returned types. Fixed by a mimemap/mimeconf + adjustment.
          • +
          + +

          recoll 1.22.0

          + + +

          recoll 1.21.6

          +
            +
          • The GUI dumps core on exit on Fedora23 + qt5 (maybe on other + platforms too). This has no real consequences apart from an + ennoying system pop up).
          • +
          • Starting the advanced search tool may crash the GUI if the saved + configuration has more than the default count of search clauses + (five).
          • +
          + +

          recoll 1.21.4

          +
            +
          • The query language parser interprets incorrectly queries having + multiple MIME type or category specifications, with missing + results as a consequence. This affects all 1.21 versions up to + 1.21.5 where it is fixed.
          • +
          + +

          recoll 1.21.2

          +
            +
          • Indexed file paths have a limit around 1010 after which the + results can't be properly displayed in the GUI (the files are + indexed and can be found, and displayed by a command line search, + but the GUI display is garbled).
          • +
          • A bug in the verification of configuration file path variables + generates spurious warnings from recollindex + when the skippedPaths variable contains elements with + wildcards. This has no consequence except for the spurious error + message.
          • +
          • Web cache: the GUI config tool capped the cache size at + 1 GB, and actually reset a bigger size + utility.
          • +
          • The directory filter for advanced search in "Any Clause" mode: + would not filter but add an ORed clause.
          • +
          • Parentheses around phrases would trigger a syntax error.
          • +
          • Fixed a few boundary conditions detected by VC++
          • +
          • External filters had no memory usage limit.
          • +
          +

          recoll 1.20.1

          • The web history queue is not processed by the real time indexer
          • diff --git a/website/CHANGES.html b/website/CHANGES.html index b5c18ca3..5d8af53b 100644 --- a/website/CHANGES.html +++ b/website/CHANGES.html @@ -70,7 +70,7 @@ bugs. Release 1.14.2 fixes the help browser which was broken by 1.14.2. Sigh ...

              -
            • +
            • date selection in queries.
            • Pure negative queries (ie: -someterm date:P10D/.
            • Autosuffs: option to automatically turn words into ext: @@ -381,7 +381,7 @@ Recoll when the specification is stabilized, and could be useful for other things, such as indexing contents from an RDBMS (see - + the manual for details). Restructured and cleaned up internal Recoll interfaces.
            • diff --git a/website/copydocs b/website/copydocs index 0f1d4ade..9e1ad754 100755 --- a/website/copydocs +++ b/website/copydocs @@ -1,7 +1,6 @@ #!/bin/sh set -x docdir=/home/dockes/projets/fulltext/recoll/src/doc/user/ -#docdir=/Users/dockes/projets/fulltext/recoll/src/doc/user/ #(cd $docdir;make) || exit 1 @@ -9,11 +8,10 @@ test -d usermanual || mkdir usermanual || exit 1 cd usermanual thisdir=`pwd` -(cd $docdir; find . -name '*.html' -print | cpio -vudp $thisdir) -cp $docdir/docbook.css . -cp $docdir/docbook-xsl.css . -cp $docdir/usermanual.pdf recoll_user_manual.pdf +(cd $docdir; find . -name templates -prune -o -print | cpio -vudp $thisdir) + +mv usermanual.pdf recoll_user_manual.pdf # The freebsd tool chain generates a link to book.html in the index. Too # lazy to check if this can be changed -cp usermanual.html book.html +cp -p usermanual.html book.html #cp usermanual.html index.html diff --git a/website/credits.html b/website/credits.html index 5359375c..cae4dc04 100644 --- a/website/credits.html +++ b/website/credits.html @@ -25,7 +25,7 @@
            • Home
            • Screenshots
            • Downloads
            • -
            • User manual
            • +
            • User manual
            • Support
            diff --git a/website/custom.html b/website/custom.html index fe572ccc..35bce226 100644 --- a/website/custom.html +++ b/website/custom.html @@ -71,7 +71,7 @@ a:hover .PZ3cap { padding:3px 5px; }
          • Home
          • Screenshots
          • Downloads
          • -
          • User manual
          • +
          • User manual
          • Support
        @@ -88,7 +88,7 @@ a:hover .PZ3cap { padding:3px 5px; } "almost full" support for HTML capabilities, with a few restrictions due to the Qt QTextBrowser object. The details are described in the - + Recoll manual.

        As of Recoll 1.17, the result list is a WebKit object by diff --git a/website/devel.html b/website/devel.html index d720b3eb..22a9be9a 100644 --- a/website/devel.html +++ b/website/devel.html @@ -29,9 +29,18 @@

        +

        Contributing to Recoll developement and availability

        + +

        If you are not a software developer, or have no time + available for testing the application of thinking about how it + could be improved, there is always the possibility of + contributing a donation, which will be much appreciated !
        + + +

        +

        If you wish to become involved in the development of Recoll, you are very much welcome, - please send me an Recoll, please send me an email.

        Translation

        diff --git a/website/doc.html b/website/doc.html index b0e6e0d9..d52c3097 100644 --- a/website/doc.html +++ b/website/doc.html @@ -33,7 +33,8 @@

        Recoll user manual

          -
        • English, HTML, many pages
        • +
        • English, HTML, many + pages, nicer format (needs javascript).
        • English, HTML, one page
        • 中文,HTML
        • diff --git a/website/download.html b/website/download.html index 027ddc75..208a1dc1 100644 --- a/website/download.html +++ b/website/download.html @@ -1,402 +1,445 @@ - - Recoll download - - - - - - - + + Recoll download + + + + + + + - + - + - + - - -
          -

          Recoll downloads

          - - - -
          -

          General information

          - -

          The current version is 1.20.6. Release - notes.

          - -

          Recoll Installation / building -manual.

          - -

          The indexing filters used for some document types may need external -packages not installed on your system by default, and not installed -automatically with Recoll: take a -look at the list and decide what you need to install.

          - -

          The Recoll term explorer tool in phonetic mode (marginally useful and -optional) uses the aspell package, version 0.60 (utf-8 support) or -newer.

          - -

          If you find problems with this page, the package or its installation, -please report them.

          - -

          What do the release numbers mean?

          - -

          The Recoll releases are numbered X.Y.Z. The X would only change for really -major modifications like a big change in the index format, and possibly won't -ever reach 2.

          - -

          Y is for functional modifications. These may bring bugs, so if you don't -need the new features, you may want to wait a little, and especially skip the -first release (X.Y.0), at least for a few weeks.

          - -

          Z changes for bug fixes only, and moving from X.Y.Z -to X.Y.Z+u should in general involve little risk of regression. But, -any change can bring problems, if you are not affected by the -corrected bugs (check the release file), there -is probably no necessity to upgrade anyway.

          -
          - -
          -

          Known bugs

          -

          There is a history of known bugs, sorted - by fix release. Also see - the - issue tracker on Bitbucket. -

          -
          - -
          -

          Source

          - -

          Current release distribution: 1.20.6:

          - - -

          recoll-1.20.6.tar.gz.

          - -

          Release 1.21.0

          - -

          Not the right choice if you are after complete stability: -recoll-1.21.0.tar.gz. See what's -new in the release notes.

          - - - -

          Ubuntu Unity Lens and Scope

          - -

          You will probably get these from the PPA, but - here are the source files. These are not included in the main tar file - any more. For recoll 1.19 and 1.20 installations (choose on the - Ubuntu version, not the Recoll one): - -

          - - recoll-lens-1.19.10.3543.tar.gz (Recoll 0.19 or 0.20, -Ubuntu up to 13.04 Raring)
          - - - unity-scope-recoll-1.20.2.4.tar.gz (Recoll 0.19 or 0.20, Ubuntu - 13.10 and later).
          - -
          - -For Recoll 1.18: -recoll-lens-1.18.1.2997.tar.gz
          -For Recoll 1.17: -recoll-lens-1.17.2.2697.tar.gz - -

          - -

          Prerequisites for building from source:

          -
            -
          • C++ compiler. Its absence sometimes manifests itself by strange messages - about iconv_open (fixed after 1.13.04).
          • -
          • Xapian core development libraries. Most Linux distributions carry them - in their package repository. Or you will find source and binary packages on - the Xapian download page. -
            - Recoll should still work with Xapian 1.0, but it is highly recommended to - use a Xapian 1.2 version.

            -

            Note on building Xapian for older CPUs: The build - configurations for Xapian releases 1.0.21 and 1.2.1 or newer enable the use - of SSE2 floating point instructions. These instructions are not available - in CPUs older than Intel Pentium 4 or AMD Athlon 64. When building for such - a CPU, you need to add the --disable-sse flag to the Xapian library - configure command. If this is not done, the problem signals itself by - "Illegal instruction" crashes (SIGILL) in recollindex and recoll.

            -
          • -
          • X11 development files.
          • -
          • zlib development files.
          • -
          • Qt development files: Qt 4.4 or newer. The Recoll GUI will not build - with Qt releases older than 4.4.

            -
          • -
          • Qt webkit development: Qt WebKit is quite often distributed apart from - the main Qt lib. It is possible to configure Recoll not to use Qt WebKit - (see configure --help).

            -
          • -
          • Python development package: you can avoid needing this by configuring - with --disable-python-module.
          • -
          - -

          Source repository:

          - -

          The Recoll source repository is hosted on -bitbucket.org. The -trunk is usually a bit on the bleeding edge, but there is always a maintenance -branch for the current production version.

          - -

          Instructions for building

          - -

          Normally, it's just ./configure; make; make install. If a bit - more detail is needed, - - there is some in the manual. - -

          - -
          -

          Packages

          - -

          Packages or ports for Recoll are available in the standard repositories for -many distributions.

          - -

          However they are often a bit older or built with older Xapian releases. Here - follow some pointers to find newer packages for some - distributions. In most cases, you will just need to use an - alternate repository.

          - -

          I sometimes build binary packages when no appropriate repository - exists. Any binary package directly linked from this page need a Qt - 4 (4.4 at least) runtime environment. To make things easier, on - systems where Xapian is not available from the standard package - repositories, the Recoll package will have a static link to Xapian - so that you do not need to build/install it separately.

          - -

          Debian

          - -

          The Debian Recoll packages are usually fairly up to date (at least in -testing), just use the appropriate Debian repository.

          - -

          Except they're not at the moment (2015-05).... So - I am maintaining a repository for packages built for Debian - Wheezy. To add the repository to your sources:

          - -
            -
          • Create and edit - /etc/apt/sources.list.d/recoll.list - and add the following lines:
            -
            -deb http://www.lesbonscomptes.com/recoll/debian/ unstable main -deb-src http://www.lesbonscomptes.com/recoll/debian/ unstable main + -
          • Then: -
            + +
            +

            Recoll downloads

            + + + +
            +

            General information

            + +

            The current version is 1.22.4. Release + notes.

            + +

            Recoll Installation / building + manual.

            + +

            The indexing filters used for some document types may need external + packages not installed on your system by default, and not installed + automatically with Recoll: take a + look at the list and decide what you need to install.

            + +

            The Recoll term explorer tool in phonetic mode (marginally useful and + optional) uses the aspell package, version 0.60 (utf-8 support) or + newer.

            + +

            If you find problems with this page, the package or its installation, + please report them.

            + +

            What do the release numbers mean?

            + +

            The Recoll releases are numbered X.Y.Z. The X would only change for really + major modifications like a big change in the index format, and possibly won't + ever reach 2.

            + +

            Y is for functional modifications. These may bring bugs, so if you don't + need the new features, you may want to wait a little, and especially skip the + first release (X.Y.0), at least for a few weeks.

            + +

            Z changes for bug fixes only, and moving from X.Y.Z + to X.Y.Z+u should in general involve little risk of regression. But, + any change can bring problems, if you are not affected by the + corrected bugs (check the release file), there + is probably no necessity to upgrade anyway.

            +
            + +
            +

            Known bugs

            +

            There is a history of known bugs, sorted + by fix release. Also see + the + issue tracker on Bitbucket. +

            +
            + +
            +

            Source

            + +

            Current release distribution: 1.22.4:

            + + +

            recoll-1.22.4.tar.gz.

            +

            The release notes.

            + +

            Previous production release 1.21.7:

            + +

            recoll-1.21.7.tar.gz.

            +

            The release notes.

            + + + + +

            Ubuntu Unity Lens and Scope

            + +

            You will probably get these from the PPA, but + here are the source files. These are not included in the main tar file + any more. For any Recoll version after 1.19 (choose on the + Ubuntu version, not the Recoll one): + +

            + + recoll-lens-1.19.10.3543.tar.gz (Ubuntu up to 13.04 + Raring)
            + + + unity-scope-recoll-1.20.2.4.tar.gz (Ubuntu 13.10 and + later).
            + +
            + + For Recoll 1.18: + + recoll-lens-1.18.1.2997.tar.gz
            + For Recoll 1.17: + + recoll-lens-1.17.2.2697.tar.gz + +

            + +

            Prerequisites for building from source:

            +
              +
            • C++ compiler. Be aware that its absence sometimes + manifests itself by quite cryptic messages.
            • + +
            • Xapian core development libraries. Most Linux + distributions carry them in their package repository. Or + you will find source and binary packages on + the Xapian + download page. +
              +

              Note on building Xapian for older CPUs: The build + configurations for Xapian releases 1.0.21 and 1.2.1 or + newer enable the use of SSE2 floating point + instructions. These instructions are not available in + CPUs older than Intel Pentium 4 or AMD Athlon 64. When + building for such a CPU, you need to add the + --disable-sse flag to the Xapian library configure + command. If this is not done, the problem signals itself + by "Illegal instruction" crashes (SIGILL) in recollindex + and recoll.

              +
            • +
            • Qt development files: Qt 4.4, 5.3 or newer (5.2 not ok).
            • +
            • Qt WebKit development files: these are quite often + distributed apart from the main Qt libraries. It is + possible to configure Recoll not to use Qt WebKit (see + configure --help).
            • +
            • zlib development files.
            • +
            • X11 development files.
            • +
            • Python development package: you can avoid needing this + by configuring with --disable-python-module.
            • +
            + +

            Source repository:

            + +

            The Recoll source + repository is hosted + on + bitbucket.org. The trunk is usually a bit on the + bleeding edge, but there is always a maintenance branch for + the current production version.

            + +

            Instructions for building

            + +

            Normally, it's just:

            +
            ./configure; make; make install
            +

            If a bit more detail is needed, + + there is some in the manual. + +

            + +
            +

            Packages

            + +

            Packages or ports for Recoll are available in the standard + repositories for many distributions.

            + +

            However they are often a bit older or built with older + Xapian releases. Here follow some pointers to find newer + packages for some distributions. In most cases, you will + just need to use an alternate repository.

            + +

            Debian

            + +

            The Debian Recoll packages are usually fairly up to date, just use + the appropriate Debian repository.

            + +

            Except they're not at the moment + (2016-09).... Debian stable has Recoll 1.17.3. Debian + testing has 1.22.3, and it may work on Jessie (or not...). + In any case, I am maintaining a repository for packages + built for Debian Wheezy, and Jessie. The repository + currently has recoll 1.22.x for Intel and 1.21 for armhf. To + add it to your sources:

            + +
              + +
            • Download the public key used to validate the repositories: +
              +wget -O - https://www.lesbonscomptes.com/key/jf@dockes.org.gpg.key | sudo apt-key add - +
              +
            • + +
            • Create and edit + /etc/apt/sources.list.d/recoll.list + and add the following lines:
              + for wheezy (debian 7.x):
              +
              +deb http://www.lesbonscomptes.com/recoll/debian/ wheezy main +deb-src http://www.lesbonscomptes.com/recoll/debian/ wheezy main +
              + for jessie (debian 8.x):
              +
              +deb http://www.lesbonscomptes.com/recoll/debian/ jessie main +deb-src http://www.lesbonscomptes.com/recoll/debian/ jessie main +
              +
            • Then: +
              +sudo apt-get update +sudo apt-get install recoll python-recoll python3-recoll +
              +
            • +
            + +

            If you prefer to manually install the packages, they are here: + + debian/pool/main/r/recoll/
            +

            + +

            Ubuntu

            + +

            There are Personal Package Archives on launchpad.net for + + Recoll, kio-recoll and recoll-lens. These were built + from the latest versions, for the current set of supported Ubuntu + versions. Procedure:

            +
            +sudo add-apt-repository ppa:recoll-backports/recoll-1.15-on sudo apt-get update sudo apt-get install recoll -
            -
          • -
          +
          -

          If you prefer to manually install the packages, they are here: -debian/pool/main/r/recoll/
          -

          +

          The packages in the PPA now have a separate package for the Python + extension, like the standard ones, so there should be no more + conflict issues while switching from the PPA to the normal + repositories and back.

          -

          The official Debian repository has 1.20.3 packages for Sid. I guess - that they should run on Jessie. Contact me if this does not work for you.

          +

          Linux Mint

          -

          Ubuntu

          +

          The Ubuntu PPA works perfectly for Mint 13 (and probably other releases + too). Just follow the instructions for Ubuntu.

          -

          There are Personal Package Archives on launchpad.net for Recoll, -kio-recoll and recoll-lens. These were built from the latest versions, for -a set of Ubuntu series. starting at Lucid. The installation is very simple:

          -
          
          -          sudo add-apt-repository ppa:recoll-backports/recoll-1.15-on
          -          sudo apt-get update
          -          sudo apt-get install recoll
          -
          +

          RPMS

          -

          The 1.19/1.20 packages in the PPA now have a separate package - for the Python extension, like the standard ones, so there should be - no more conflict issues while switching from the PPA to the normal - repositories and back.

          +

          You'll need to install the Xapian, Qt, Qt-Webkit and zlib development + packages if you want use the source rpms.

          -

          Linux Mint

          +

          Fedora

          -

          The Ubuntu PPA works perfectly for Mint 13 (and probably other releases -too). Just follow the instructions for Ubuntu.

          +

          Recoll is present in the standard Fedora package repositories starting from + F-12. Recoll packages in Fedora are usually fairly up to + date. Please get in touch if you have a need for a Recoll package + for Fedora.

          -

          RPMS

          +

          CentOS 7.1

          -

          You'll need to install the Xapian, Qt, Qt-Webkit and zlib development -packages if you want use the source rpms.

          - -

          Fedora

          - -

          Recoll is present in the standard Fedora package repositories starting from - F-12. Fedora 21 has up to date packages. Here are some packages - for Fedora 20. There are only x86_64 binaries - for now, use the source rpm for other archs.

          - -

          CentOS 7.1

          - -

          CentOS ships neither Xapian nor Recoll. Here are - some packages.. There are only x86_64 binaries - for now, use the source rpm for other archs. As far as I know, the - only specific issue is that CentOS does not seem to have the Qt - WebKit module. The Recoll build uses QTextBrowser instead of a - WebKit QWebView, so no Javascript or advanced CSS in the result list - or snippets window for you.

          +

          CentOS ships neither Xapian nor Recoll. Here are + some packages.. There are only x86_64 binaries + for now, use the source rpm for other archs. As far as I know, the + only specific issue is that CentOS does not seem to have the Qt + WebKit module. The Recoll build uses QTextBrowser instead of a + WebKit QWebView, so no Javascript or advanced CSS in the result list + or snippets window for you.

          -

          OpenSUSE

          +

          OpenSUSE

          -

          Recoll is in the KDE:Extra repository. You just need to add the - repository to your software - sources (Yast2->software->Software repositories).
          - - Repository list (supported Suse versions). - After adding the appropriate repository to your software sources, - you will be able to install recoll and kio_recoll from the software - management interface. The Xapian dependancy will also be satisfied - from the build service repository. Some of the older repositories do - not build antiword, just tell the software manager to "break" recoll - by installing anyway, and get antiword somewhere else.

          +

          Recoll is in the KDE:Extra repository. You just need to add the + repository to your software + sources (Yast2->software->Software repositories).
          + + Repository list (supported Suse versions). + After adding the appropriate repository to your software sources, + you will be able to install recoll and kio_recoll from the software + management interface. The Xapian dependancy will also be satisfied + from the build service repository. Some of the older repositories do + not build antiword, just tell the software manager to "break" recoll + by installing anyway, and get antiword somewhere else.

          - +

          Mageia version 2: mageia2/recoll-1.18.1-1.mga2.i586.rpm, + recoll-debug-1.18.1-1.mga2.i586.rpm. +
          + Source: recoll-1.18.1-1.mga2.src.rpm +

          + --> -
          -
          -

          Ports

          +
          -

          Mac port

          +
          +

          Microsoft Windows Setup Files

          -

          It seems that Recoll will sometimes find data that Spotlight misses -(especially inside pdfs apparently, which is probably more to the credit of -poppler than recoll itself).

          +

          The port of Recoll to Windows is still a bit experimental and + lacking things like real-time indexing or spelling + suggestions. However it works well enough to be useful. More info + and links to the setup + files here.

          +
          -

          Recoll is in MacPorts and really easy to install:

          -
            -
          1. Install - MacPorts.
          2. -
          3. Type "sudo port install recoll"
          4. -
          +
          +

          Ports

          -

          Recoll is then available from the command line and as an icon in the usual -MacPorts applications place.

          -
          +

          Mac port

          -
          -

          Updated filters

          -

          new or updated filters - sometimes become available after a release. As a rule, all - filters are compatible with all Recoll versions. Any - compatibility problem will be explicitely mentionned.

          -
          +

          It seems that Recoll will sometimes find data that Spotlight misses + (especially inside pdfs apparently, which is probably more to the credit of + poppler than recoll itself).

          -
          -

          Translations

          +

          Recoll is in MacPorts and really easy to install:

          +
            +
          1. Install + MacPorts.
          2. +
          3. Type "sudo port install recoll"
          4. +
          -

          Most of the translations for 1.20 are incomplete The source translation -files are included in the source release. If your language has some english -messages left and you want to take a shot at fixing the problem, you can send -the results to me and earn my gratefulness -(and your less multilingual compatriot's)...

          +

          Recoll is then available from the command line and as an icon in the usual + MacPorts applications place.

          +
          -

          You can use the .ts file to alter the translations if you wish (use -Qt's linguist tool to edit the source file, then lrelease to -produce the .qm file.). The .qmfile should be -copied to /usr/[local/]share/recoll/translations -

          +
          +

          Updated filters

          +

          new or updated filters + sometimes become available after a release. As a rule, all + filters are compatible with all Recoll versions. Any + compatibility problem will be explicitely mentionned.

          +
          -

          recoll_xx.ts is a blank -Recoll 1.20 message file, handy to work on a new translation. You can -also list the directory to see all the -translation files (same as those in the 1.20 source branch on -Bitbucket).

          +
          +

          Translations

          -

          Updated 1.20 translations that became available after the release:

          +

          Most of the translations for 1.22 are incomplete The source + translation files are included in the source release. If + your language has some english messages left and you want to + take a shot at fixing the problem, you can send the results + to me and earn my + gratefulness (and your less multilingual + compatriot's)...

          -

          A Danish translation by Morten Langlo: -recoll_da.ts -recoll_da.qm
          -

          +

          You can use the .ts file to alter the translations + if you wish (use Qt's linguist tool to edit the + source file, then lrelease to produce + the .qm file.). The .qm file should be copied + to /usr/[local/]share/recoll/translations +

          -

          Note that, if you are running an older release, you may find updated -messages by looking inside the appropriate maintenance branch on bitbucket.

          +

          recoll_xx.ts is a blank + Recoll 1.22 message file, handy to work on a new translation. You can + also list the directory to see all the + translation files (same as those in the maintenance source branch on + Bitbucket).

          -
          +

          Updated 1.22 translations that became available after the + release:

          -
        +

        Greek translation by Dimitrios Glentadakis: + recoll_el.ts + recoll_el.qm
        +

        +

        Dutch translation by Leslie Scheelings: + recoll_nl.ts + recoll_nl.qm
        +

        - +

        Danish translation by Morten Langlo: + recoll_da.ts + recoll_da.qm
        +

        + +

        Note that, if you are running an older release, you may find updated + messages by looking inside the appropriate maintenance + branch on + bitbucket.

        + + + + + diff --git a/website/features.html b/website/features.html index 0c888733..efbb6431 100644 --- a/website/features.html +++ b/website/features.html @@ -26,7 +26,7 @@
      • Downloads
      • -
      • User manual
      • +
      • Documentation
      • Support
      • @@ -56,9 +56,9 @@
      • Easy installation, few dependancies. No database daemon, web server, desktop environment or exotic language necessary.
      • Will run on most Unix-based - systems
      • + systems, and on MS-Windows too.
      • Qt 4 GUI, plus command line, Unity Lens, KIO and krunner - interfaces.
      • + interfaces.
      • Searches most common document types, emails and @@ -81,12 +81,12 @@

        Supported systems

        Recoll has been compiled and - tested on Linux, MacOS X and Solaris (initial versions Redhat 7, - Fedora Core 5, Suse 10, Gentoo, Debian 3.1, Solaris 8). It - should compile and run on all subsequent releases of these - systems and probably a few others too.

        + tested on Linux, MS-Windows 7-10, MacOS X and Solaris (initial + versions Redhat 7, Fedora Core 5, Suse 10, Gentoo, Debian 3.1, + Solaris 8). It should compile and run on all subsequent releases + of these systems and probably a few others too.

        -

        Qt versions from 3.1 to 4.8

        +

        Qt versions from 4.7 and later

        Document types

        @@ -97,16 +97,33 @@ text. Types that only need very common utilities (awk/sed/groff/Python etc.) are listed in the native section.

        +

        The MS-Windows installer includes the supporting application, + the only additional package you will need is the Python language + installation.

        + +

        Many formats are processed + by Python scripts. The Python + dependency will not always be mentionned. In general, Recoll + expects Python 2.x to be available (many, but not all, scripts + are compatible with Python 3). Formats which are processed + using Python and its standard + library are listed in the native section.

        +

        File types indexed natively

        • text.
        • html.
        • -
        • maildir and +
        • maildir, + mh, and mailbox ( Mozilla, Thunderbird and Evolution mail ok). + Evolution note: be sure to remove .cache from + the skippedNames list in the GUI Indexing + preferences/Local Parameters/ pane if you want to + index local copies of Imap mail.
        • gaim and @@ -122,8 +139,32 @@ and Powerpoint for Recoll versions 1.19.12 and later.
        • + +
        • Tar archives. Tar file + indexing is disabled by default (because tar archives don't + typically contain the kind of documents that people search + for), you will need to enable it explicitely, like with the + following in your + $HOME/.recoll/mimeconf file: +
          +[index]
          +application/x-tar = execm rcltar
          +
          +
        • + +
        • Zip archives.
        • +
        • Konqueror webarchive + format with Python (uses the tarfile standard + library module).
        • + +
        • Mimehtml web archive + format (support based on the mail + filter, which introduces some mild weirdness, but still + usable).
        + +

        File types indexed with external helpers

        Many document types need the iconv @@ -132,7 +173,9 @@

        The XML ones

        The following types need - xsltproc from the libxslt package. + xsltproc from the libxslt package for recoll + versions before 1.22, and in addition, python-libxslt1 and + python-libxml2 for 1.22 and newer. Quite a few also need unzip:

          @@ -201,7 +244,6 @@ class="literal">libwpd-tools or such, not the base libwpd package. -
        • Lyx files (needs Lyx to be installed).
        • @@ -224,29 +266,15 @@
        • EPUB files with Python and this - Python epub - decoding module.
        • + Python epub + decoding module, which is packaged on Fedora, but not Debian. -
        • Tar archives (needs Python). Tar file indexing is disabled - by default (because tar archives don't typically contain the - kind of documents that people search for), you will need to - enable it explicitely, like with the following in your - $HOME/.recoll/mimeconf file: -
          -[index]
          -application/x-tar = execm rcltar
          -
          -
        • - -
        • Zip archives (needs Python).
        • -
        • Rar archives (needs Python), the rarfile Python module and the unrar utility.
        • + href="http://www.rarlab.com/rar_add.htm">unrar + utility. The Python module is packaged by Fedora, not by Debian.
        • 7zip archives (needs Python and @@ -340,15 +368,11 @@ application/x-tar = execm rcltar notes here.
        • -
        • Konqueror webarchive - format with Python (uses the tarfile standard - library module).
        • - -
        • Mimehtml web archive - format (support based on the mail - filter, which introduces some mild weirdness, but still - usable).
        • - +
        • MediaWiki dump files: + Thomas Levine has written a handler for these, you will find + it here: + rclmwdump.
        • +

        Other features

        @@ -409,7 +433,7 @@ application/x-tar = execm rcltar Web UI lets you query a Recoll index from a web browser
      • Recoll also has - + Python and PHP modules which can allow easy integration with web or other applications.

        diff --git a/website/fr/features.html b/website/fr/features.html index a3450e6c..f8fd330c 100644 --- a/website/fr/features.html +++ b/website/fr/features.html @@ -64,7 +64,8 @@
      • Kword.
      • -
      • maildir et maildir, + mh et mailbox (Mozilla, Thunderbird, Downloads
      • -
      • User manual
      • +
      • User manual
      • Support
      • diff --git a/website/id3lib.html b/website/id3lib.html index a96ac4ff..13bb6eba 100644 --- a/website/id3lib.html +++ b/website/id3lib.html @@ -24,7 +24,7 @@
      • Features
      • Screenshots
      • Downloads
      • -
      • User manual
      • +
      • User manual
      • Support
      • Development
      • diff --git a/website/idxthreads/forkingRecoll.txt b/website/idxthreads/forkingRecoll.txt index 496d28c9..cd6b6633 100644 --- a/website/idxthreads/forkingRecoll.txt +++ b/website/idxthreads/forkingRecoll.txt @@ -7,12 +7,12 @@ == Introduction -Recoll is a big process which executes many others, mostly for extracting -text from documents. Some of the executed processes are quite short-lived, -and the time used by the process execution machinery can actually dominate -the time used to translate data. This document explores possible approaches -to improving performance without adding excessive complexity or damaging -reliability. +The Recoll indexer, *recollindex*, is a big process which executes many +others, mostly for extracting text from documents. Some of the executed +processes are quite short-lived, and the time used by the process execution +machinery can actually dominate the time used to translate data. This +document explores possible approaches to improving performance without +adding excessive complexity or damaging reliability. Studying fork/exec performance is not exactly a new venture, and there are many texts which address the subject. While researching, though, I found @@ -32,9 +32,10 @@ identical processes. space initialized from an executable file, inheriting some of the resources under various conditions. -As processes became bigger the copy-before-discard operation wasted -significant resources, and was optimized using two methods (at very -different points in time): +This was all fine with the small processes of the first Unix systems, but +as time progressed, processes became bigger and the copy-before-discard +operation was found to waste significant resources. It was optimized using +two methods (at very different points in time): - The first approach was to supplement +fork()+ with the +vfork()+ call, which is similar but does not duplicate the address space: the new process @@ -176,7 +177,7 @@ a single thread, and +fork()+ if it ran multiple ones. After another careful look at the code, I could see few issues with using +vfork()+ in the multithreaded indexer, so this was committed. -The only change necessary was to get rid on an implementation of the +The only change necessary was to get rid of an implementation of the lacking Linux +closefrom()+ call (used to close all open descriptors above a given value). The previous Recoll implementation listed the +/proc/self/fd+ directory to look for open descriptors but this was unsafe because of of @@ -200,13 +201,14 @@ same times as the +fork()+/+vfork()+ options. The tests were performed on an Intel Core i5 750 (4 cores, 4 threads). -The last line is just for the fun: *recollindex* 1.18 (single-threaded) -needed almost 6 times as long to process the same files... - It would be painful to play it safe and discard the 60% reduction in -execution time offered by using +vfork()+. +execution time offered by using +vfork()+, so this was adopted for Recoll +1.21. To this day, no problems were discovered, but, still crossing +fingers... -To this day, no problems were discovered, but, still crossing fingers... +The last line in the table is just for the fun: *recollindex* 1.18 +(single-threaded) needed almost 6 times as long to process the same +files... //// Objections to vfork: diff --git a/website/idxthreads/threadingRecoll.html b/website/idxthreads/threadingRecoll.html index ef927d8d..f6c6e45b 100644 --- a/website/idxthreads/threadingRecoll.html +++ b/website/idxthreads/threadingRecoll.html @@ -3,7 +3,7 @@ - + Converting Recoll indexing to multithreading + + comments powered by Disqus +++++ diff --git a/website/perfs.html b/website/perfs.html index ad7312c0..64d2c768 100644 --- a/website/perfs.html +++ b/website/perfs.html @@ -2,8 +2,7 @@ - RECOLL: a personal text search system for - Unix/Linux + RECOLL indexing performance and index sizes Recoll: Indexing performance and index sizes

        The time needed to index a given set of documents, and the - resulting index size depend of many factors, such as file size - and proportion of actual text content for the index size, cpu - speed, available memory, average file size and format for the - speed of indexing.

        + resulting index size depend of many factors. -

        We try here to give a number of reference points which can - be used to roughly estimate the resources needed to create and - store an index. Obviously, your data set will never fit one of - the samples, so the results cannot be exactly predicted.

        +

        The index size depends almost only on the size of the + uncompressed input text, and you can expect it to be roughly + of the same order of magnitude. Depending on the type of file, + the proportion of text to file size varies very widely, going + from close to 1 for pure text files to a very small factor + for, e.g., metadata tags in mp3 files.

        -

        The following data was obtained on a machine with a 1800 Mhz - AMD Duron CPU, 768Mb of Ram, and a 7200 RPM 160 GBytes IDE - disk, running Suse 10.1.

        +

        Estimating indexing time is a much more complicated issue, + depending on the type and size of input and on system + performance. There is no general way to determine what part of + the hardware should be optimized. Depending on the type of + input, performance may be bound by I/O read or write + performance, CPU single-processing speed, or combined + multi-processing speed.

        + +

        It should be noted that Recoll performance will not be an + issue for most people. The indexer can process 1000 typical + PDF files per minute, or 500 Wikipedia HTML pages per second + on medium-range hardware, meaning that the initial indexing of + a typical dataset will need a few dozen minutes at + most. Further incremental index updates will be much faster + because most files will not need to be processed again.

        + +

        However, there are Recoll installations with + terabyte-sized datasets, on which indexing can take days. For + such operations (or even much smaller ones), it is very + important to know what kind of performance can be expected, + and what aspects of the hardware should be optimized.

        + +

        In order to provide some reference points, I have run a + number of benchs on medium-sized datasets, using typical + mid-range desktop hardware, and varying the indexing + configuration parameters to show how they affect the results.

        + +

        The following may help you check that you are getting typical + performance for your indexing, and give some indications about + what to adjust to improve it.

        + +

        From time to time, I receive a report about a system becoming + unusable during indexing. As far as I know, with the default + Recoll configuration, and barring an exceptional issue (bug), + this is always due to a system problem (typically bad hardware + such as a disk doing retries). The tests below were mostly run + while I was using the desktop, which never became + unusable. However, some tests rendered it less responsive and + this is noted with the results.

        + +

        The following text refers to the indexing parameters without + further explanation. Here follow links to more explanation about the + processing + model and + configuration + parameters.

        + + +

        All text were run without generating the stemming database or + aspell dictionary. These phases are relatively short and there + is nothing which can be optimized about them.

        + +

        Hardware

        + +

        The tests were run on what could be considered a mid-range + desktop PC: +

          +
        • Intel Core I7-4770T CPU: 2.5 Ghz, 4 physical cores, and + hyper-threading for a total of 8 hardware threads
        • +
        • 8 GBytes of RAM
        • +
        • Asus H87I-Plus motherboard, Samsung 850 EVO SSD storage
        • +
        +

        + +

        This is usually a fanless PC, but I did run a fan on the + external case fins during some of the tests (esp. PDF + indexing), because the CPU was running a bit too hot.

        + + +

        Indexing PDF files

        + + +

        The tests were run on 18000 random PDFs harvested on + Google, with a total size of around 30 GB, using Recoll 1.22.3 + and Xapian 1.2.22. The resulting index size was 1.2 GB.

        + +

        PDF: storage

        + +

        Typical PDF files have a low text to file size ratio, and a + lot of data needs to be read for indexing. With the test + configuration, the indexer needs to read around 45 MBytes / S + from multiple files. This means that input storage makes a + difference and that you need an SSD or a fast array for + optimal performance.

        + + + + + + + + + + + + + + + + + + + + + + + +
        StorageidxflushmbthrTCountsReal Time
        NFS drive (gigabit)2006/4/124m40
        local SSD2006/4/111m40
        + + +

        PDF: threading

        + +

        Because PDF files are bulky and complicated to process, the + dominant step for indexing them is input processing. PDF text + extraction is performed by multiple instances + the pdftotext program, and parallelisation works very + well.

        + +

        The following table shows the indexing times with a variety + of threading parameters.

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        idxflushmbthrQSizesthrTCountsTime R/U/S
        2002/2/22/1/119m21
        2002/2/210/10/110m38
        2002/2/2100/10/111m
        + +

        10/10/1 was the best value for thrTCounts for this test. The + total CPU time was around 78 mn.

        + +

        The last line shows the effect of a ridiculously high thread + count value for the input step, which is not much. Using + sligthly lower values than the optimum has not much impact + either. The only thing which really degrades performance is + configuring less threads than available from the hardware.

        + +

        With the optimal parameters above, the peak recollindex + resident memory size is around 930 MB, to which we should add + ten instances of pdftotext (10MB typical), and of the + rclpdf.py Python input handler (around 15 MB each). This means + that the total resident memory used by indexing is around 1200 + MB, quite a modest value in 2016.

        + + +

        PDF: Xapian flushes

        + +

        idxflushmb has practically no influence on the indexing time + (tested from 40 to 1000), which is not too surprising because + the Xapian index size is very small relatively to the input + size, so that the cost of Xapian flushes to disk is not very + significant. The value of 200 used for the threading tests + could be lowered in practise, which would decrease memory + usage and not change the indexing time significantly.

        + +

        PDF: conclusion

        + +

        For indexing PDF files, you need many cores and a fast + input storage system. Neither single-thread performance nor + amount of memory will be critical aspects.

        + +

        Running the PDF indexing tests had no influence on the system + "feel", I could work on it just as if it were quiescent.

        + + +

        Indexing HTML files

        + +

        The tests were run on an (old) French Wikipedia dump: 2.9 + million HTML files stored in 42000 directories, for an + approximate total size of 41 GB (average file size + 14 KB). + +

        The files are stored on a local SSD. Just reading them with + find+cpio takes close to 8 mn.

        + +

        The resulting index has a size of around 30 GB.

        + +

        I was too lazy to extract 3 million entries tar file on a + spinning disk, so all tests were performed with the data + stored on a local SSD.

        + +

        For this test, the indexing time is dominated by the Xapian + index updates. As these are single threaded, only the flush + interval has a real influence.

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        idxflushmbthrQSizesthrTCountsTime R/U/S
        2002/2/22/1/188m
        2002/2/26/4/191m
        2002/2/21/1/196m
        1002/2/21/2/1120m
        1002/2/26/4/1121m
        402/2/21/2/1173m
        + + +

        The indexing process becomes quite big (resident size around + 4GB), and the combination of high I/O load and high memory + usage makes the system less responsive at times (but not + unusable). As this happens principally when switching + applications, my guess would be that some program pages + (e.g. from the window manager and X) get flushed out, and take + time being read in, during which time the display appears + frozen.

        + +

        For this kind of data, single-threaded CPU performance and + storage write speed can make a difference. Multithreading does + not help.

        + +

        Adjusting hardware to improve indexing performance

        + +

        I think that the following multi-step approach has a good + chance to improve performance: +

          +
        • Check that multithreading is enabled (it is, by default + with recent Recoll versions).
        • +
        • Increase the flush threshold until the machine begins to + have memory issues. Maybe add memory.
        • +
        • Store the index on an SSD. If possible, also store the + data on an SSD. Actually, when using many threads, it is + probably almost more important to have the data on an + SSD.
        • +
        • If you have many files which will need temporary copies + (email attachments, archive members, compressed files): use + a memory temporary directory. Add memory.
        • +
        • More CPUs...
        • +
        +

        + +

        At some point, the index updating and writing may become the + bottleneck (this depends on the data mix, very quickly with + HTML or text files). As far as I can think, the only possible + approach is then to partition the index. You can query the + multiple Xapian indices either by using the Recoll external + index capability, or by actually merging the results with + xapian-compact.

        + + + +
        Old benchmarks
        + +

        To provide a point of comparison for the evolution of + hardware and software...

        + +

        The following very old data was obtained (around 2007?) on a + machine with a 1800 Mhz AMD Duron CPU, 768Mb of Ram, and a + 7200 RPM 160 GBytes IDE disk, running Suse 10.1.

        recollindex (version 1.8.2 with xapian 1.0.0) is executed with the default flush threshold value. @@ -106,7 +409,6 @@ performance degradation. The resulting index is bigger though, the exact reason is not known to me, possibly because of additional fragmentation

        -

        diff --git a/website/release-1.16.html b/website/release-1.16.html index 2b67c884..b0bf9ce9 100644 --- a/website/release-1.16.html +++ b/website/release-1.16.html @@ -96,7 +96,7 @@ the text). This is useful for example in the very common case where the metadata for the author list was not created. More details about this feature are to be found in - the user + the user manual.
      • It is possible to configure the result list snippet diff --git a/website/release-1.21.html b/website/release-1.21.html index 3b20140b..ff8ddc95 100644 --- a/website/release-1.21.html +++ b/website/release-1.21.html @@ -55,22 +55,6 @@ see the manual). If you do so, you must then reset the index.

        -

        Minor releases

        -
          -
        • 1.21.1: -
            -
          • Force memory usage limits on external filters.
          • -
          • GUI: add Ctrl+l as a shortcut to return focus to the - search entry (compat with web browsers).
          • -
          • result list popup allows saving results from web cache - to files.
          • -
          • The web history indexer also processes non-html files - (e.g.: pdfs).
          • - -
          -
        • -
        -

        Changes in Recoll 1.21.0

          @@ -99,6 +83,76 @@ performed with incremental updates. Bumped max entries to 10000.
        + +

        Minor releases

        +
          +
        • 1.21.7: +
            +
          • Sidestep bus error in qt exit code by calling _exit() + instead of exit() in GUI exit code.
          • +
          • Avoid dependance of librecoll.so on libX11.
          • +
          • Hungarian translation.
          • +
          +
        • +
        • 1.21.6: +
            +
          • New version of the kio slave for KDE5.
          • +
          • Very minor other fixes.
          • +
          +
        • +
        • 1.21.5: +
          • Fixes a nasty bug affecting all previous 1.21 versions: + the query language parser processed incorrectly multiple + mime type or category specifications, with missing results + as a consequence.
          • +
          +
        • + +
        • 1.21.4: +
            +
          • Show confirmation dialog when opening a temporary file + (to warn the user about possible lost edits). The dialog + has a 'disable' checkbox.
          • +
          • Fixed bug which would crash the GUI when clicking an + Open link after modifying the indexing configuration + through the GUI tool.
          • +
          • Fix not showing results with paths over 1000 + characters.
          • +
          • Fix Show subdocs: too many docs were shown in some + cases, and the Preview button in the dialog was inactive.
          • +
          +
        • 1.21.3: +
            +
          • Web cache: fixed the GUI config to not cap the size at + 1 GB. added append function to test driver/maintenance + utility. +
          • +
          +
        • 1.21.2: +
            +
          • Added GUI dialog to perform partial indexing.
          • +
          • Avanced search in "Any Clause" mode: directory filter + would not filter but add an ORed clause.
          • +
          • Fix bogus syntax errors about parentheses around + phrases.
          • +
          • Fixed a few boundary conditions detected by VC++
          • +
          • Misc other small fixes, see commit log.
          • +
          +
        • +
        • 1.21.1: +
            +
          • Force memory usage limits on external filters.
          • +
          • GUI: add Ctrl+l as a shortcut to return focus to the + search entry (compat with web browsers).
          • +
          • result list popup allows saving results from web cache + to files.
          • +
          • The web history indexer also processes non-html files + (e.g.: pdfs).
          • + +
          +
        • +
        + diff --git a/website/release-1.22.html b/website/release-1.22.html new file mode 100644 index 00000000..69353e49 --- /dev/null +++ b/website/release-1.22.html @@ -0,0 +1,154 @@ + + + + Recoll 1.22 series release notes + + + + + + + + + + + + + +
        +

        Release notes for Recoll 1.22.x

        + +

        Caveats

        + +

        Some of the input handlers were converted from shell scripts + to Python programs, and some helper dependancies changed. For + example, you will need to install python-libxml2 and + python-libxslt1 in most cases (for replacing xsltproc).

        + +

        Installing over an older version: 1.19

        + +

        1.20-22 indexes are fully compatible. Installing 1.22 + over an 1.19 index is possible, but there have been small + changes in the way compound words (e.g. email addresses) are + indexed, so it will be best to reset the index. Still, in a + pinch, 1.22 search can mostly use an 1.19 index.

        + +

        Always reset the index if you do not know by which version it + was created (e.g.: you're not sure it's at least 1.18). The + best method is to quit all Recoll programs and delete the + index directory ( + rm -rf ~/.recoll/xapiandb), then start recoll + or recollindex.
        + + recollindex -z will do the same + in most, but not all, cases. It's better to use + the rm method, which will also ensure that no debris + from older releases remain (e.g.: old stemming files which are + not used any more).

        + +

        Case/diacritics sensitivity is off by default. It can be + turned on only by editing + recoll.conf ( + + see the manual). If you do so, you must then reset the + index.

        + +

        Changes in Recoll 1.22.0

        + +
          + +
        • The main "feature" in recoll 1.22 is that it has + a Microsoft Windows + version. This is has been tested on Windows 7 and + Windows 10, and it works mostly like the Unix version, with + the notable exceptions that it has no real-time mode (need to + start indexing by hand from the GUI, or arrange something with + the command-line recollindex.exe). Also there are a few very + Unix-y file types which are not processed on Windows, and the + indexer is single-threaded.
        • + +
        • It is now possible to define synonyms groups, used only at + query-time to expand the query terms to their defined + synonyms. + + More details.
        • + +
        • Many shell-script input handlers have been converted to + Python and are now persistent. Most are compatible with + Python3 (the only ones which are not are kept back by the + library they use). There are still a few shell handlers, + mostly for less used and Linux-only formats. And a single + Perl-based one (rclimg, which uses the excellent exiftool + Perl library).
        • + +
        • The Unix/Linux build system has been converted to use the + autotools in a fairly standard way. The Windows build is + based on Qt Creator and MinGW.
        • + +
        • Make dehyphenation (co-worker->coworker in addition to the + normal terms) optional, active by default.
        • + +
        • For people using the Firefox web page indexer add-on: a + new tool in the GUI to list and delete entries from the Web + cache.
        • + +
        • Improved index statistics in the GUI, and improved display + while the indexer is working.
        • + +
        + +

        Minor releases

        +
          +
        • 1.22.4: +
            +
          • Fix advanced search 'start search' button doing + nothing under qt5.
          • +
          • Fix html escaping with newer versions of + pdftotext.
          • +
          • New Danish and Dutch messages.
          • +
          +
        • +
        • 1.22.3: +
            +
          • Python module: do not limit result fetches to initial + Xapian result count, which is often underestimated.
          • +
          • Small bug fix in the text splitter: which resulted in + missing results when matching a file name extension + using, e.g. filename:doc$ instead of ext:doc.
          • +
          • Added suffix associations for .java and .sql, to fix + problems caused by the switch from 'file' to 'xdg-mime'.
          • +
          +
        • +
        • 1.22.2: +
            +
          • Small fixes for building the KIO
          • +
          • Fixed debian packaging issues.
          • +
          +
        • +
        • 1.22.1: +
            +
          • Sidestep bus error in qt exit code by calling _exit() + instead of exit() in GUI exit code.
          • +
          • Eliminate the dependance of librecoll.so on libX11.
          • +
          • Hungarian translation.
          • +
          • GUI: enable displaying the Xapian docid in the result + list with %x.
          • +
          • GUI, advanced search: fix crash which occurred when + restoring clause list bigger than the default size (6 + clauses).
          • +
          • The documentation (user manual and man page) for + recoll.conf is now generated from the structured + comments in the sample file.
          • +
          +
        • + +
        + + diff --git a/website/release-history.html b/website/release-history.html index 89c1c43e..83b218ec 100644 --- a/website/release-history.html +++ b/website/release-history.html @@ -29,7 +29,38 @@ came with them.

        -
        Release 1.21 (future): new + +
        Release 1.23: C++11
        +
        +
          +
        • Replace a bunch of locally grown hacks with c++11 + standard features, esp. std::thread, std::mutex etc. As + c++11 support is now mandatory, get rid of compatibility + code (e.g. switches to use c++11 smart pointers or the + local version).
        • +
        • Convert the logging facility to use c++ streams + instead of stdio.
        • +
        • Allow execm input handlers to set arbitrary data + fields. Previously, the only way for handlers to provide + metadata was through HTML meta fields.
        • +
        • fn and cfn queryaliases enabled by default.
        • +
        + + +
        Release 1.22: Windows + port, autotools-based Unix/Linux build system.
        +
        +
          +
        • A new autotools-based build system replaces the old + mostly homegrown one for Unix-like platforms.
        • +
        • Recoll was ported to Windows, where the build is based + on MinGW and Qt Creator.
        • +
        • New synonyms expansion feature.
        • +
        +
        + + +
        Release 1.21: new query parser