소스 검색

* First version of fpindexer engine

git-svn-id: trunk@20496 -
michael 13 년 전
부모
커밋
aa45ca8e0e

+ 20 - 0
.gitattributes

@@ -2783,6 +2783,26 @@ packages/fpgtk/src/fpgtk.pp svneol=native#text/plain
 packages/fpgtk/src/fpgtkext.pp svneol=native#text/plain
 packages/fpgtk/src/pgtk/pgtk.pp svneol=native#text/plain
 packages/fpgtk/src/pgtk/pgtk.ppr -text
+packages/fpindexer/Makefile svneol=native#text/plain
+packages/fpindexer/Makefile.fpc svneol=native#text/plain
+packages/fpindexer/examples/TestDBindexer.lpi svneol=native#text/plain
+packages/fpindexer/examples/TestDBindexer.pp svneol=native#text/plain
+packages/fpindexer/examples/TestIndexer.lpi svneol=native#text/plain
+packages/fpindexer/examples/TestIndexer.pp svneol=native#text/plain
+packages/fpindexer/examples/TestSearch.lpi svneol=native#text/plain
+packages/fpindexer/examples/TestSearch.pp svneol=native#text/plain
+packages/fpindexer/examples/english.txt svneol=native#text/plain
+packages/fpindexer/fpmake.pp svneol=native#text/plain
+packages/fpindexer/src/dbindexer.pp svneol=native#text/plain
+packages/fpindexer/src/fbindexdb.pp svneol=native#text/plain
+packages/fpindexer/src/fpindexer.pp svneol=native#text/plain
+packages/fpindexer/src/fpmasks.pp svneol=native#text/plain
+packages/fpindexer/src/ireaderhtml.pp svneol=native#text/plain
+packages/fpindexer/src/ireaderpas.pp svneol=native#text/plain
+packages/fpindexer/src/ireadertxt.pp svneol=native#text/plain
+packages/fpindexer/src/memindexdb.pp svneol=native#text/plain
+packages/fpindexer/src/sqldbindexdb.pp svneol=native#text/plain
+packages/fpindexer/src/sqliteindexdb.pp svneol=native#text/plain
 packages/fpmake.pp svneol=native#text/plain
 packages/fpmake_add.inc svneol=native#text/plain
 packages/fpmake_proc.inc svneol=native#text/plain

+ 2111 - 0
packages/fpindexer/Makefile

@@ -0,0 +1,2111 @@
+#
+# Don't edit, this file is generated by FPCMake Version 2.0.0 [2011/11/22]
+#
+default: all
+MAKEFILETARGETS=i386-linux i386-go32v2 i386-win32 i386-os2 i386-freebsd i386-beos i386-haiku i386-netbsd i386-solaris i386-qnx i386-netware i386-openbsd i386-wdosx i386-darwin i386-emx i386-watcom i386-netwlibc i386-wince i386-embedded i386-symbian i386-nativent i386-iphonesim m68k-linux m68k-freebsd m68k-netbsd m68k-amiga m68k-atari m68k-openbsd m68k-palmos m68k-embedded powerpc-linux powerpc-netbsd powerpc-amiga powerpc-macos powerpc-darwin powerpc-morphos powerpc-embedded powerpc-wii sparc-linux sparc-netbsd sparc-solaris sparc-embedded x86_64-linux x86_64-freebsd x86_64-solaris x86_64-darwin x86_64-win64 x86_64-embedded arm-linux arm-palmos arm-darwin arm-wince arm-gba arm-nds arm-embedded arm-symbian powerpc64-linux powerpc64-darwin powerpc64-embedded avr-embedded armeb-linux armeb-embedded mipsel-linux
+BSDs = freebsd netbsd openbsd darwin
+UNIXs = linux $(BSDs) solaris qnx haiku
+LIMIT83fs = go32v2 os2 emx watcom
+OSNeedsComspecToRunBatch = go32v2 watcom
+FORCE:
+.PHONY: FORCE
+override PATH:=$(patsubst %/,%,$(subst \,/,$(PATH)))
+ifneq ($(findstring darwin,$(OSTYPE)),)
+inUnix=1 #darwin
+SEARCHPATH:=$(filter-out .,$(subst :, ,$(PATH)))
+else
+ifeq ($(findstring ;,$(PATH)),)
+inUnix=1
+SEARCHPATH:=$(filter-out .,$(subst :, ,$(PATH)))
+else
+SEARCHPATH:=$(subst ;, ,$(PATH))
+endif
+endif
+SEARCHPATH+=$(patsubst %/,%,$(subst \,/,$(dir $(MAKE))))
+PWD:=$(strip $(wildcard $(addsuffix /pwd.exe,$(SEARCHPATH))))
+ifeq ($(PWD),)
+PWD:=$(strip $(wildcard $(addsuffix /pwd,$(SEARCHPATH))))
+ifeq ($(PWD),)
+$(error You need the GNU utils package to use this Makefile)
+else
+PWD:=$(firstword $(PWD))
+SRCEXEEXT=
+endif
+else
+PWD:=$(firstword $(PWD))
+SRCEXEEXT=.exe
+endif
+ifndef inUnix
+ifeq ($(OS),Windows_NT)
+inWinNT=1
+else
+ifdef OS2_SHELL
+inOS2=1
+endif
+endif
+else
+ifneq ($(findstring cygdrive,$(PATH)),)
+inCygWin=1
+endif
+endif
+ifdef inUnix
+SRCBATCHEXT=.sh
+else
+ifdef inOS2
+SRCBATCHEXT=.cmd
+else
+SRCBATCHEXT=.bat
+endif
+endif
+ifdef COMSPEC
+ifneq ($(findstring $(OS_SOURCE),$(OSNeedsComspecToRunBatch)),)
+ifndef RUNBATCH
+RUNBATCH=$(COMSPEC) /C
+endif
+endif
+endif
+ifdef inUnix
+PATHSEP=/
+else
+PATHSEP:=$(subst /,\,/)
+ifdef inCygWin
+PATHSEP=/
+endif
+endif
+ifdef PWD
+BASEDIR:=$(subst \,/,$(shell $(PWD)))
+ifdef inCygWin
+ifneq ($(findstring /cygdrive/,$(BASEDIR)),)
+BASENODIR:=$(patsubst /cygdrive%,%,$(BASEDIR))
+BASEDRIVE:=$(firstword $(subst /, ,$(BASENODIR)))
+BASEDIR:=$(subst /cygdrive/$(BASEDRIVE)/,$(BASEDRIVE):/,$(BASEDIR))
+endif
+endif
+else
+BASEDIR=.
+endif
+ifdef inOS2
+ifndef ECHO
+ECHO:=$(strip $(wildcard $(addsuffix /gecho$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(ECHO),)
+ECHO:=$(strip $(wildcard $(addsuffix /echo$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(ECHO),)
+ECHO=echo
+else
+ECHO:=$(firstword $(ECHO))
+endif
+else
+ECHO:=$(firstword $(ECHO))
+endif
+endif
+export ECHO
+endif
+override DEFAULT_FPCDIR=../..
+ifndef FPC
+ifdef PP
+FPC=$(PP)
+endif
+endif
+ifndef FPC
+FPCPROG:=$(strip $(wildcard $(addsuffix /fpc$(SRCEXEEXT),$(SEARCHPATH))))
+ifneq ($(FPCPROG),)
+FPCPROG:=$(firstword $(FPCPROG))
+ifneq ($(CPU_TARGET),)
+FPC:=$(shell $(FPCPROG) -P$(CPU_TARGET) -PB)
+else
+FPC:=$(shell $(FPCPROG) -PB)
+endif
+ifneq ($(findstring Error,$(FPC)),)
+override FPC=$(firstword $(strip $(wildcard $(addsuffix /ppc386$(SRCEXEEXT),$(SEARCHPATH)))))
+else
+ifeq ($(strip $(wildcard $(FPC))),)
+FPC:=$(firstword $(FPCPROG))
+endif
+endif
+else
+override FPC=$(firstword $(strip $(wildcard $(addsuffix /ppc386$(SRCEXEEXT),$(SEARCHPATH)))))
+endif
+endif
+override FPC:=$(subst $(SRCEXEEXT),,$(FPC))
+override FPC:=$(subst \,/,$(FPC))$(SRCEXEEXT)
+FOUNDFPC:=$(strip $(wildcard $(FPC)))
+ifeq ($(FOUNDFPC),)
+FOUNDFPC=$(strip $(wildcard $(addsuffix /$(FPC),$(SEARCHPATH))))
+ifeq ($(FOUNDFPC),)
+$(error Compiler $(FPC) not found)
+endif
+endif
+ifndef FPC_COMPILERINFO
+FPC_COMPILERINFO:=$(shell $(FPC) -iVSPTPSOTO)
+endif
+ifndef FPC_VERSION
+FPC_VERSION:=$(word 1,$(FPC_COMPILERINFO))
+endif
+export FPC FPC_VERSION FPC_COMPILERINFO
+unexport CHECKDEPEND ALLDEPENDENCIES
+ifndef CPU_TARGET
+ifdef CPU_TARGET_DEFAULT
+CPU_TARGET=$(CPU_TARGET_DEFAULT)
+endif
+endif
+ifndef OS_TARGET
+ifdef OS_TARGET_DEFAULT
+OS_TARGET=$(OS_TARGET_DEFAULT)
+endif
+endif
+ifneq ($(words $(FPC_COMPILERINFO)),5)
+FPC_COMPILERINFO+=$(shell $(FPC) -iSP)
+FPC_COMPILERINFO+=$(shell $(FPC) -iTP)
+FPC_COMPILERINFO+=$(shell $(FPC) -iSO)
+FPC_COMPILERINFO+=$(shell $(FPC) -iTO)
+endif
+ifndef CPU_SOURCE
+CPU_SOURCE:=$(word 2,$(FPC_COMPILERINFO))
+endif
+ifndef CPU_TARGET
+CPU_TARGET:=$(word 3,$(FPC_COMPILERINFO))
+endif
+ifndef OS_SOURCE
+OS_SOURCE:=$(word 4,$(FPC_COMPILERINFO))
+endif
+ifndef OS_TARGET
+OS_TARGET:=$(word 5,$(FPC_COMPILERINFO))
+endif
+FULL_TARGET=$(CPU_TARGET)-$(OS_TARGET)
+FULL_SOURCE=$(CPU_SOURCE)-$(OS_SOURCE)
+ifeq ($(CPU_TARGET),armeb)
+ARCH=arm
+override FPCOPT+=-Cb
+else
+ifeq ($(CPU_TARGET),armel)
+ARCH=arm
+override FPCOPT+=-CaEABI
+else
+ARCH=$(CPU_TARGET)
+endif
+endif
+ifeq ($(FULL_TARGET),arm-embedded)
+ifeq ($(SUBARCH),)
+$(error When compiling for arm-embedded, a sub-architecture (e.g. SUBARCH=armv4t or SUBARCH=armv7m) must be defined)
+endif
+override FPCOPT+=-Cp$(SUBARCH)
+endif
+ifneq ($(findstring $(OS_SOURCE),$(LIMIT83fs)),)
+TARGETSUFFIX=$(OS_TARGET)
+SOURCESUFFIX=$(OS_SOURCE)
+else
+ifneq ($(findstring $(OS_TARGET),$(LIMIT83fs)),)
+TARGETSUFFIX=$(OS_TARGET)
+else
+TARGETSUFFIX=$(FULL_TARGET)
+endif
+SOURCESUFFIX=$(FULL_SOURCE)
+endif
+ifneq ($(FULL_TARGET),$(FULL_SOURCE))
+CROSSCOMPILE=1
+endif
+ifeq ($(findstring makefile,$(MAKECMDGOALS)),)
+ifeq ($(findstring $(FULL_TARGET),$(MAKEFILETARGETS)),)
+$(error The Makefile doesn't support target $(FULL_TARGET), please run fpcmake first)
+endif
+endif
+ifneq ($(findstring $(OS_TARGET),$(BSDs)),)
+BSDhier=1
+endif
+ifeq ($(OS_TARGET),linux)
+linuxHier=1
+endif
+export OS_TARGET OS_SOURCE ARCH CPU_TARGET CPU_SOURCE FULL_TARGET FULL_SOURCE TARGETSUFFIX SOURCESUFFIX CROSSCOMPILE
+ifdef FPCDIR
+override FPCDIR:=$(subst \,/,$(FPCDIR))
+ifeq ($(wildcard $(addprefix $(FPCDIR)/,rtl units)),)
+override FPCDIR=wrong
+endif
+else
+override FPCDIR=wrong
+endif
+ifdef DEFAULT_FPCDIR
+ifeq ($(FPCDIR),wrong)
+override FPCDIR:=$(subst \,/,$(DEFAULT_FPCDIR))
+ifeq ($(wildcard $(addprefix $(FPCDIR)/,rtl units)),)
+override FPCDIR=wrong
+endif
+endif
+endif
+ifeq ($(FPCDIR),wrong)
+ifdef inUnix
+override FPCDIR=/usr/local/lib/fpc/$(FPC_VERSION)
+ifeq ($(wildcard $(FPCDIR)/units),)
+override FPCDIR=/usr/lib/fpc/$(FPC_VERSION)
+endif
+else
+override FPCDIR:=$(subst /$(FPC),,$(firstword $(strip $(wildcard $(addsuffix /$(FPC),$(SEARCHPATH))))))
+override FPCDIR:=$(FPCDIR)/..
+ifeq ($(wildcard $(addprefix $(FPCDIR)/,rtl units)),)
+override FPCDIR:=$(FPCDIR)/..
+ifeq ($(wildcard $(addprefix $(FPCDIR)/,rtl units)),)
+override FPCDIR:=$(BASEDIR)
+ifeq ($(wildcard $(addprefix $(FPCDIR)/,rtl units)),)
+override FPCDIR=c:/pp
+endif
+endif
+endif
+endif
+endif
+ifndef CROSSBINDIR
+CROSSBINDIR:=$(wildcard $(FPCDIR)/bin/$(TARGETSUFFIX))
+endif
+ifneq ($(findstring $(OS_TARGET),darwin iphonesim),)
+ifeq ($(OS_SOURCE),darwin)
+DARWIN2DARWIN=1
+endif
+endif
+ifndef BINUTILSPREFIX
+ifndef CROSSBINDIR
+ifdef CROSSCOMPILE
+ifndef DARWIN2DARWIN
+BINUTILSPREFIX=$(CPU_TARGET)-$(OS_TARGET)-
+endif
+endif
+endif
+endif
+UNITSDIR:=$(wildcard $(FPCDIR)/units/$(TARGETSUFFIX))
+ifeq ($(UNITSDIR),)
+UNITSDIR:=$(wildcard $(FPCDIR)/units/$(OS_TARGET))
+endif
+PACKAGESDIR:=$(wildcard $(FPCDIR) $(FPCDIR)/packages $(FPCDIR)/packages/base $(FPCDIR)/packages/extra)
+ifndef FPCFPMAKE
+ifdef CROSSCOMPILE
+ifeq ($(strip $(wildcard $(addsuffix /compiler/ppc$(SRCEXEEXT),$(FPCDIR)))),)
+FPCPROG:=$(strip $(wildcard $(addsuffix /fpc$(SRCEXEEXT),$(SEARCHPATH))))
+ifneq ($(FPCPROG),)
+FPCPROG:=$(firstword $(FPCPROG))
+FPCFPMAKE:=$(shell $(FPCPROG) -PB)
+ifeq ($(strip $(wildcard $(FPCFPMAKE))),)
+FPCFPMAKE:=$(firstword $(FPCPROG))
+endif
+else
+override FPCFPMAKE=$(firstword $(strip $(wildcard $(addsuffix /ppc386$(SRCEXEEXT),$(SEARCHPATH)))))
+endif
+else
+FPCFPMAKE=$(strip $(wildcard $(addsuffix /compiler/ppc$(SRCEXEEXT),$(FPCDIR))))
+FPMAKE_SKIP_CONFIG=-n
+export FPCFPMAKE
+export FPMAKE_SKIP_CONFIG
+endif
+else
+FPMAKE_SKIP_CONFIG=-n
+FPCFPMAKE=$(FPC)
+endif
+endif
+override PACKAGE_NAME=fpindexer
+override PACKAGE_VERSION=2.7.1
+FPMAKE_BIN_CLEAN=$(wildcard .$(PATHSEP)fpmake$(SRCEXEEXT))
+ifdef OS_TARGET
+FPC_TARGETOPT+=--os=$(OS_TARGET)
+endif
+ifdef CPU_TARGET
+FPC_TARGETOPT+=--cpu=$(CPU_TARGET)
+endif
+LOCALFPMAKE=.$(PATHSEP)fpmake$(SRCEXEEXT)
+override INSTALL_FPCPACKAGE=y
+ifdef REQUIRE_UNITSDIR
+override UNITSDIR+=$(REQUIRE_UNITSDIR)
+endif
+ifdef REQUIRE_PACKAGESDIR
+override PACKAGESDIR+=$(REQUIRE_PACKAGESDIR)
+endif
+ifdef ZIPINSTALL
+ifneq ($(findstring $(OS_TARGET),$(UNIXs)),)
+UNIXHier=1
+endif
+else
+ifneq ($(findstring $(OS_SOURCE),$(UNIXs)),)
+UNIXHier=1
+endif
+endif
+ifndef INSTALL_PREFIX
+ifdef PREFIX
+INSTALL_PREFIX=$(PREFIX)
+endif
+endif
+ifndef INSTALL_PREFIX
+ifdef UNIXHier
+INSTALL_PREFIX=/usr/local
+else
+ifdef INSTALL_FPCPACKAGE
+INSTALL_BASEDIR:=/pp
+else
+INSTALL_BASEDIR:=/$(PACKAGE_NAME)
+endif
+endif
+endif
+export INSTALL_PREFIX
+ifdef INSTALL_FPCSUBDIR
+export INSTALL_FPCSUBDIR
+endif
+ifndef DIST_DESTDIR
+DIST_DESTDIR:=$(BASEDIR)
+endif
+export DIST_DESTDIR
+ifndef COMPILER_UNITTARGETDIR
+ifdef PACKAGEDIR_MAIN
+COMPILER_UNITTARGETDIR=$(PACKAGEDIR_MAIN)/units/$(TARGETSUFFIX)
+else
+COMPILER_UNITTARGETDIR=units/$(TARGETSUFFIX)
+endif
+endif
+ifndef COMPILER_TARGETDIR
+COMPILER_TARGETDIR=.
+endif
+ifndef INSTALL_BASEDIR
+ifdef UNIXHier
+ifdef INSTALL_FPCPACKAGE
+INSTALL_BASEDIR:=$(INSTALL_PREFIX)/lib/fpc/$(FPC_VERSION)
+else
+INSTALL_BASEDIR:=$(INSTALL_PREFIX)/lib/$(PACKAGE_NAME)
+endif
+else
+INSTALL_BASEDIR:=$(INSTALL_PREFIX)
+endif
+endif
+ifndef INSTALL_BINDIR
+ifdef UNIXHier
+INSTALL_BINDIR:=$(INSTALL_PREFIX)/bin
+else
+INSTALL_BINDIR:=$(INSTALL_BASEDIR)/bin
+ifdef INSTALL_FPCPACKAGE
+ifdef CROSSCOMPILE
+ifdef CROSSINSTALL
+INSTALL_BINDIR:=$(INSTALL_BINDIR)/$(SOURCESUFFIX)
+else
+INSTALL_BINDIR:=$(INSTALL_BINDIR)/$(TARGETSUFFIX)
+endif
+else
+INSTALL_BINDIR:=$(INSTALL_BINDIR)/$(TARGETSUFFIX)
+endif
+endif
+endif
+endif
+ifndef INSTALL_UNITDIR
+INSTALL_UNITDIR:=$(INSTALL_BASEDIR)/units/$(TARGETSUFFIX)
+ifdef INSTALL_FPCPACKAGE
+ifdef PACKAGE_NAME
+INSTALL_UNITDIR:=$(INSTALL_UNITDIR)/$(PACKAGE_NAME)
+endif
+endif
+endif
+ifndef INSTALL_LIBDIR
+ifdef UNIXHier
+INSTALL_LIBDIR:=$(INSTALL_PREFIX)/lib
+else
+INSTALL_LIBDIR:=$(INSTALL_UNITDIR)
+endif
+endif
+ifndef INSTALL_SOURCEDIR
+ifdef UNIXHier
+ifdef BSDhier
+SRCPREFIXDIR=share/src
+else
+ifdef linuxHier
+SRCPREFIXDIR=share/src
+else
+SRCPREFIXDIR=src
+endif
+endif
+ifdef INSTALL_FPCPACKAGE
+ifdef INSTALL_FPCSUBDIR
+INSTALL_SOURCEDIR:=$(INSTALL_PREFIX)/$(SRCPREFIXDIR)/fpc-$(FPC_VERSION)/$(INSTALL_FPCSUBDIR)/$(PACKAGE_NAME)
+else
+INSTALL_SOURCEDIR:=$(INSTALL_PREFIX)/$(SRCPREFIXDIR)/fpc-$(FPC_VERSION)/$(PACKAGE_NAME)
+endif
+else
+INSTALL_SOURCEDIR:=$(INSTALL_PREFIX)/$(SRCPREFIXDIR)/$(PACKAGE_NAME)-$(PACKAGE_VERSION)
+endif
+else
+ifdef INSTALL_FPCPACKAGE
+ifdef INSTALL_FPCSUBDIR
+INSTALL_SOURCEDIR:=$(INSTALL_BASEDIR)/source/$(INSTALL_FPCSUBDIR)/$(PACKAGE_NAME)
+else
+INSTALL_SOURCEDIR:=$(INSTALL_BASEDIR)/source/$(PACKAGE_NAME)
+endif
+else
+INSTALL_SOURCEDIR:=$(INSTALL_BASEDIR)/source
+endif
+endif
+endif
+ifndef INSTALL_DOCDIR
+ifdef UNIXHier
+ifdef BSDhier
+DOCPREFIXDIR=share/doc
+else
+ifdef linuxHier
+DOCPREFIXDIR=share/doc
+else
+DOCPREFIXDIR=doc
+endif
+endif
+ifdef INSTALL_FPCPACKAGE
+INSTALL_DOCDIR:=$(INSTALL_PREFIX)/$(DOCPREFIXDIR)/fpc-$(FPC_VERSION)/$(PACKAGE_NAME)
+else
+INSTALL_DOCDIR:=$(INSTALL_PREFIX)/$(DOCPREFIXDIR)/$(PACKAGE_NAME)-$(PACKAGE_VERSION)
+endif
+else
+ifdef INSTALL_FPCPACKAGE
+INSTALL_DOCDIR:=$(INSTALL_BASEDIR)/doc/$(PACKAGE_NAME)
+else
+INSTALL_DOCDIR:=$(INSTALL_BASEDIR)/doc
+endif
+endif
+endif
+ifndef INSTALL_EXAMPLEDIR
+ifdef UNIXHier
+ifdef INSTALL_FPCPACKAGE
+ifdef BSDhier
+INSTALL_EXAMPLEDIR:=$(INSTALL_PREFIX)/share/examples/fpc-$(FPC_VERSION)/$(PACKAGE_NAME)
+else
+ifdef linuxHier
+INSTALL_EXAMPLEDIR:=$(INSTALL_DOCDIR)/examples
+else
+INSTALL_EXAMPLEDIR:=$(INSTALL_PREFIX)/doc/fpc-$(FPC_VERSION)/examples/$(PACKAGE_NAME)
+endif
+endif
+else
+ifdef BSDhier
+INSTALL_EXAMPLEDIR:=$(INSTALL_PREFIX)/share/examples/$(PACKAGE_NAME)-$(PACKAGE_VERSION)
+else
+ifdef linuxHier
+INSTALL_EXAMPLEDIR:=$(INSTALL_DOCDIR)/examples/$(PACKAGE_NAME)-$(PACKAGE_VERSION)
+else
+INSTALL_EXAMPLEDIR:=$(INSTALL_PREFIX)/doc/$(PACKAGE_NAME)-$(PACKAGE_VERSION)
+endif
+endif
+endif
+else
+ifdef INSTALL_FPCPACKAGE
+INSTALL_EXAMPLEDIR:=$(INSTALL_BASEDIR)/examples/$(PACKAGE_NAME)
+else
+INSTALL_EXAMPLEDIR:=$(INSTALL_BASEDIR)/examples
+endif
+endif
+endif
+ifndef INSTALL_DATADIR
+INSTALL_DATADIR=$(INSTALL_BASEDIR)
+endif
+ifndef INSTALL_SHAREDDIR
+INSTALL_SHAREDDIR=$(INSTALL_PREFIX)/lib
+endif
+ifdef CROSSCOMPILE
+ifndef CROSSBINDIR
+CROSSBINDIR:=$(wildcard $(CROSSTARGETDIR)/bin/$(SOURCESUFFIX))
+ifeq ($(CROSSBINDIR),)
+CROSSBINDIR:=$(wildcard $(INSTALL_BASEDIR)/cross/$(TARGETSUFFIX)/bin/$(FULL_SOURCE))
+endif
+endif
+else
+CROSSBINDIR=
+endif
+BATCHEXT=.bat
+LOADEREXT=.as
+EXEEXT=.exe
+PPLEXT=.ppl
+PPUEXT=.ppu
+OEXT=.o
+ASMEXT=.s
+SMARTEXT=.sl
+STATICLIBEXT=.a
+SHAREDLIBEXT=.so
+SHAREDLIBPREFIX=libfp
+STATICLIBPREFIX=libp
+IMPORTLIBPREFIX=libimp
+RSTEXT=.rst
+EXEDBGEXT=.dbg
+ifeq ($(OS_TARGET),go32v1)
+STATICLIBPREFIX=
+SHORTSUFFIX=v1
+endif
+ifeq ($(OS_TARGET),go32v2)
+STATICLIBPREFIX=
+SHORTSUFFIX=dos
+IMPORTLIBPREFIX=
+endif
+ifeq ($(OS_TARGET),watcom)
+STATICLIBPREFIX=
+OEXT=.obj
+ASMEXT=.asm
+SHAREDLIBEXT=.dll
+SHORTSUFFIX=wat
+IMPORTLIBPREFIX=
+endif
+ifeq ($(OS_TARGET),linux)
+BATCHEXT=.sh
+EXEEXT=
+HASSHAREDLIB=1
+SHORTSUFFIX=lnx
+endif
+ifeq ($(OS_TARGET),freebsd)
+BATCHEXT=.sh
+EXEEXT=
+HASSHAREDLIB=1
+SHORTSUFFIX=fbs
+endif
+ifeq ($(OS_TARGET),netbsd)
+BATCHEXT=.sh
+EXEEXT=
+HASSHAREDLIB=1
+SHORTSUFFIX=nbs
+endif
+ifeq ($(OS_TARGET),openbsd)
+BATCHEXT=.sh
+EXEEXT=
+HASSHAREDLIB=1
+SHORTSUFFIX=obs
+endif
+ifeq ($(OS_TARGET),win32)
+SHAREDLIBEXT=.dll
+SHORTSUFFIX=w32
+endif
+ifeq ($(OS_TARGET),os2)
+BATCHEXT=.cmd
+AOUTEXT=.out
+STATICLIBPREFIX=
+SHAREDLIBEXT=.dll
+SHORTSUFFIX=os2
+ECHO=echo
+IMPORTLIBPREFIX=
+endif
+ifeq ($(OS_TARGET),emx)
+BATCHEXT=.cmd
+AOUTEXT=.out
+STATICLIBPREFIX=
+SHAREDLIBEXT=.dll
+SHORTSUFFIX=emx
+ECHO=echo
+IMPORTLIBPREFIX=
+endif
+ifeq ($(OS_TARGET),amiga)
+EXEEXT=
+SHAREDLIBEXT=.library
+SHORTSUFFIX=amg
+endif
+ifeq ($(OS_TARGET),morphos)
+EXEEXT=
+SHAREDLIBEXT=.library
+SHORTSUFFIX=mos
+endif
+ifeq ($(OS_TARGET),atari)
+EXEEXT=.ttp
+SHORTSUFFIX=ata
+endif
+ifeq ($(OS_TARGET),beos)
+BATCHEXT=.sh
+EXEEXT=
+SHORTSUFFIX=be
+endif
+ifeq ($(OS_TARGET),haiku)
+BATCHEXT=.sh
+EXEEXT=
+SHORTSUFFIX=hai
+endif
+ifeq ($(OS_TARGET),solaris)
+BATCHEXT=.sh
+EXEEXT=
+SHORTSUFFIX=sun
+endif
+ifeq ($(OS_TARGET),qnx)
+BATCHEXT=.sh
+EXEEXT=
+SHORTSUFFIX=qnx
+endif
+ifeq ($(OS_TARGET),netware)
+EXEEXT=.nlm
+STATICLIBPREFIX=
+SHORTSUFFIX=nw
+IMPORTLIBPREFIX=imp
+endif
+ifeq ($(OS_TARGET),netwlibc)
+EXEEXT=.nlm
+STATICLIBPREFIX=
+SHORTSUFFIX=nwl
+IMPORTLIBPREFIX=imp
+endif
+ifeq ($(OS_TARGET),macos)
+BATCHEXT=
+EXEEXT=
+DEBUGSYMEXT=.xcoff
+SHORTSUFFIX=mac
+IMPORTLIBPREFIX=imp
+endif
+ifneq ($(findstring $(OS_TARGET),darwin iphonesim),)
+BATCHEXT=.sh
+EXEEXT=
+HASSHAREDLIB=1
+SHORTSUFFIX=dwn
+EXEDBGEXT=.dSYM
+endif
+ifeq ($(OS_TARGET),gba)
+EXEEXT=.gba
+SHAREDLIBEXT=.so
+SHORTSUFFIX=gba
+endif
+ifeq ($(OS_TARGET),symbian)
+SHAREDLIBEXT=.dll
+SHORTSUFFIX=symbian
+endif
+ifeq ($(OS_TARGET),NativeNT)
+SHAREDLIBEXT=.dll
+SHORTSUFFIX=nativent
+endif
+ifeq ($(OS_TARGET),wii)
+EXEEXT=.dol
+SHAREDLIBEXT=.so
+SHORTSUFFIX=wii
+endif
+ifneq ($(findstring $(OS_SOURCE),$(LIMIT83fs)),)
+FPCMADE=fpcmade.$(SHORTSUFFIX)
+ZIPSUFFIX=$(SHORTSUFFIX)
+ZIPCROSSPREFIX=
+ZIPSOURCESUFFIX=src
+ZIPEXAMPLESUFFIX=exm
+else
+FPCMADE=fpcmade.$(TARGETSUFFIX)
+ZIPSOURCESUFFIX=.source
+ZIPEXAMPLESUFFIX=.examples
+ifdef CROSSCOMPILE
+ZIPSUFFIX=.$(SOURCESUFFIX)
+ZIPCROSSPREFIX=$(TARGETSUFFIX)-
+else
+ZIPSUFFIX=.$(TARGETSUFFIX)
+ZIPCROSSPREFIX=
+endif
+endif
+ifndef ECHO
+ECHO:=$(strip $(wildcard $(addsuffix /gecho$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(ECHO),)
+ECHO:=$(strip $(wildcard $(addsuffix /echo$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(ECHO),)
+ECHO= __missing_command_ECHO
+else
+ECHO:=$(firstword $(ECHO))
+endif
+else
+ECHO:=$(firstword $(ECHO))
+endif
+endif
+export ECHO
+ifndef DATE
+DATE:=$(strip $(wildcard $(addsuffix /gdate$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(DATE),)
+DATE:=$(strip $(wildcard $(addsuffix /date$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(DATE),)
+DATE= __missing_command_DATE
+else
+DATE:=$(firstword $(DATE))
+endif
+else
+DATE:=$(firstword $(DATE))
+endif
+endif
+export DATE
+ifndef GINSTALL
+GINSTALL:=$(strip $(wildcard $(addsuffix /ginstall$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(GINSTALL),)
+GINSTALL:=$(strip $(wildcard $(addsuffix /install$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(GINSTALL),)
+GINSTALL= __missing_command_GINSTALL
+else
+GINSTALL:=$(firstword $(GINSTALL))
+endif
+else
+GINSTALL:=$(firstword $(GINSTALL))
+endif
+endif
+export GINSTALL
+ifndef CPPROG
+CPPROG:=$(strip $(wildcard $(addsuffix /cp$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(CPPROG),)
+CPPROG= __missing_command_CPPROG
+else
+CPPROG:=$(firstword $(CPPROG))
+endif
+endif
+export CPPROG
+ifndef RMPROG
+RMPROG:=$(strip $(wildcard $(addsuffix /rm$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(RMPROG),)
+RMPROG= __missing_command_RMPROG
+else
+RMPROG:=$(firstword $(RMPROG))
+endif
+endif
+export RMPROG
+ifndef MVPROG
+MVPROG:=$(strip $(wildcard $(addsuffix /mv$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(MVPROG),)
+MVPROG= __missing_command_MVPROG
+else
+MVPROG:=$(firstword $(MVPROG))
+endif
+endif
+export MVPROG
+ifndef MKDIRPROG
+MKDIRPROG:=$(strip $(wildcard $(addsuffix /gmkdir$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(MKDIRPROG),)
+MKDIRPROG:=$(strip $(wildcard $(addsuffix /mkdir$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(MKDIRPROG),)
+MKDIRPROG= __missing_command_MKDIRPROG
+else
+MKDIRPROG:=$(firstword $(MKDIRPROG))
+endif
+else
+MKDIRPROG:=$(firstword $(MKDIRPROG))
+endif
+endif
+export MKDIRPROG
+ifndef ECHOREDIR
+ifndef inUnix
+ECHOREDIR=echo
+else
+ECHOREDIR=$(ECHO)
+endif
+endif
+ifndef COPY
+COPY:=$(CPPROG) -fp
+endif
+ifndef COPYTREE
+COPYTREE:=$(CPPROG) -Rfp
+endif
+ifndef MKDIRTREE
+MKDIRTREE:=$(MKDIRPROG) -p
+endif
+ifndef MOVE
+MOVE:=$(MVPROG) -f
+endif
+ifndef DEL
+DEL:=$(RMPROG) -f
+endif
+ifndef DELTREE
+DELTREE:=$(RMPROG) -rf
+endif
+ifndef INSTALL
+ifdef inUnix
+INSTALL:=$(GINSTALL) -c -m 644
+else
+INSTALL:=$(COPY)
+endif
+endif
+ifndef INSTALLEXE
+ifdef inUnix
+INSTALLEXE:=$(GINSTALL) -c -m 755
+else
+INSTALLEXE:=$(COPY)
+endif
+endif
+ifndef MKDIR
+MKDIR:=$(GINSTALL) -m 755 -d
+endif
+export ECHOREDIR COPY COPYTREE MOVE DEL DELTREE INSTALL INSTALLEXE MKDIR
+ifndef PPUMOVE
+PPUMOVE:=$(strip $(wildcard $(addsuffix /ppumove$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(PPUMOVE),)
+PPUMOVE= __missing_command_PPUMOVE
+else
+PPUMOVE:=$(firstword $(PPUMOVE))
+endif
+endif
+export PPUMOVE
+ifndef FPCMAKE
+FPCMAKE:=$(strip $(wildcard $(addsuffix /fpcmake$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(FPCMAKE),)
+FPCMAKE= __missing_command_FPCMAKE
+else
+FPCMAKE:=$(firstword $(FPCMAKE))
+endif
+endif
+export FPCMAKE
+ifndef ZIPPROG
+ZIPPROG:=$(strip $(wildcard $(addsuffix /zip$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(ZIPPROG),)
+ZIPPROG= __missing_command_ZIPPROG
+else
+ZIPPROG:=$(firstword $(ZIPPROG))
+endif
+endif
+export ZIPPROG
+ifndef TARPROG
+TARPROG:=$(strip $(wildcard $(addsuffix /gtar$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(TARPROG),)
+TARPROG:=$(strip $(wildcard $(addsuffix /tar$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(TARPROG),)
+TARPROG= __missing_command_TARPROG
+else
+TARPROG:=$(firstword $(TARPROG))
+endif
+else
+TARPROG:=$(firstword $(TARPROG))
+endif
+endif
+export TARPROG
+ASNAME=$(BINUTILSPREFIX)as
+LDNAME=$(BINUTILSPREFIX)ld
+ARNAME=$(BINUTILSPREFIX)ar
+RCNAME=$(BINUTILSPREFIX)rc
+ifndef ASPROG
+ifdef CROSSBINDIR
+ASPROG=$(CROSSBINDIR)/$(ASNAME)$(SRCEXEEXT)
+else
+ASPROG=$(ASNAME)
+endif
+endif
+ifndef LDPROG
+ifdef CROSSBINDIR
+LDPROG=$(CROSSBINDIR)/$(LDNAME)$(SRCEXEEXT)
+else
+LDPROG=$(LDNAME)
+endif
+endif
+ifndef RCPROG
+ifdef CROSSBINDIR
+RCPROG=$(CROSSBINDIR)/$(RCNAME)$(SRCEXEEXT)
+else
+RCPROG=$(RCNAME)
+endif
+endif
+ifndef ARPROG
+ifdef CROSSBINDIR
+ARPROG=$(CROSSBINDIR)/$(ARNAME)$(SRCEXEEXT)
+else
+ARPROG=$(ARNAME)
+endif
+endif
+AS=$(ASPROG)
+LD=$(LDPROG)
+RC=$(RCPROG)
+AR=$(ARPROG)
+PPAS=ppas$(SRCBATCHEXT)
+ifdef inUnix
+LDCONFIG=ldconfig
+else
+LDCONFIG=
+endif
+ifdef DATE
+DATESTR:=$(shell $(DATE) +%Y%m%d)
+else
+DATESTR=
+endif
+ifndef UPXPROG
+ifeq ($(OS_TARGET),go32v2)
+UPXPROG:=1
+endif
+ifeq ($(OS_TARGET),win32)
+UPXPROG:=1
+endif
+ifdef UPXPROG
+UPXPROG:=$(strip $(wildcard $(addsuffix /upx$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(UPXPROG),)
+UPXPROG=
+else
+UPXPROG:=$(firstword $(UPXPROG))
+endif
+else
+UPXPROG=
+endif
+endif
+export UPXPROG
+ZIPOPT=-9
+ZIPEXT=.zip
+ifeq ($(USETAR),bz2)
+TAROPT=vj
+TAREXT=.tar.bz2
+else
+TAROPT=vz
+TAREXT=.tar.gz
+endif
+override REQUIRE_PACKAGES=rtl fpmkunit
+ifeq ($(FULL_TARGET),i386-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-go32v2)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-win32)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-os2)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-freebsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-beos)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-haiku)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-netbsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-solaris)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-qnx)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-netware)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-openbsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-wdosx)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-darwin)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-emx)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-watcom)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-netwlibc)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-wince)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-symbian)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-nativent)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),i386-iphonesim)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),m68k-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),m68k-freebsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),m68k-netbsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),m68k-amiga)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),m68k-atari)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),m68k-openbsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),m68k-palmos)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),m68k-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),powerpc-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),powerpc-netbsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),powerpc-amiga)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),powerpc-macos)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),powerpc-darwin)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),powerpc-morphos)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),powerpc-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),powerpc-wii)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),sparc-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),sparc-netbsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),sparc-solaris)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),sparc-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),x86_64-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),x86_64-freebsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),x86_64-solaris)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),x86_64-darwin)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),x86_64-win64)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),x86_64-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),arm-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),arm-palmos)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),arm-darwin)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),arm-wince)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),arm-gba)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),arm-nds)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),arm-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),arm-symbian)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),powerpc64-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),powerpc64-darwin)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),powerpc64-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),avr-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),armeb-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),armeb-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifeq ($(FULL_TARGET),mipsel-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+endif
+ifdef REQUIRE_PACKAGES_RTL
+PACKAGEDIR_RTL:=$(firstword $(subst /Makefile.fpc,,$(strip $(wildcard $(addsuffix /rtl/Makefile.fpc,$(PACKAGESDIR))))))
+ifneq ($(PACKAGEDIR_RTL),)
+ifneq ($(wildcard $(PACKAGEDIR_RTL)/units/$(TARGETSUFFIX)),)
+UNITDIR_RTL=$(PACKAGEDIR_RTL)/units/$(TARGETSUFFIX)
+else
+UNITDIR_RTL=$(PACKAGEDIR_RTL)
+endif
+ifneq ($(wildcard $(PACKAGEDIR_RTL)/units/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_RTL=$(PACKAGEDIR_RTL)/units/$(SOURCESUFFIX)
+else
+ifneq ($(wildcard $(PACKAGEDIR_RTL)/units_bs/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_RTL=$(PACKAGEDIR_RTL)/units_bs/$(SOURCESUFFIX)
+else
+UNITDIR_FPMAKE_RTL=$(PACKAGEDIR_RTL)
+endif
+endif
+ifdef CHECKDEPEND
+$(PACKAGEDIR_RTL)/$(OS_TARGET)/$(FPCMADE):
+	$(MAKE) -C $(PACKAGEDIR_RTL)/$(OS_TARGET) $(FPCMADE)
+override ALLDEPENDENCIES+=$(PACKAGEDIR_RTL)/$(OS_TARGET)/$(FPCMADE)
+endif
+else
+PACKAGEDIR_RTL=
+UNITDIR_RTL:=$(subst /Package.fpc,,$(strip $(wildcard $(addsuffix /rtl/Package.fpc,$(UNITSDIR)))))
+ifneq ($(UNITDIR_RTL),)
+UNITDIR_RTL:=$(firstword $(UNITDIR_RTL))
+else
+UNITDIR_RTL=
+endif
+endif
+ifdef UNITDIR_RTL
+override COMPILER_UNITDIR+=$(UNITDIR_RTL)
+endif
+ifdef UNITDIR_FPMAKE_RTL
+override COMPILER_FPMAKE_UNITDIR+=$(UNITDIR_FPMAKE_RTL)
+endif
+endif
+ifdef REQUIRE_PACKAGES_PASZLIB
+PACKAGEDIR_PASZLIB:=$(firstword $(subst /Makefile.fpc,,$(strip $(wildcard $(addsuffix /paszlib/Makefile.fpc,$(PACKAGESDIR))))))
+ifneq ($(PACKAGEDIR_PASZLIB),)
+ifneq ($(wildcard $(PACKAGEDIR_PASZLIB)/units/$(TARGETSUFFIX)),)
+UNITDIR_PASZLIB=$(PACKAGEDIR_PASZLIB)/units/$(TARGETSUFFIX)
+else
+UNITDIR_PASZLIB=$(PACKAGEDIR_PASZLIB)
+endif
+ifneq ($(wildcard $(PACKAGEDIR_PASZLIB)/units/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_PASZLIB=$(PACKAGEDIR_PASZLIB)/units/$(SOURCESUFFIX)
+else
+ifneq ($(wildcard $(PACKAGEDIR_PASZLIB)/units_bs/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_PASZLIB=$(PACKAGEDIR_PASZLIB)/units_bs/$(SOURCESUFFIX)
+else
+UNITDIR_FPMAKE_PASZLIB=$(PACKAGEDIR_PASZLIB)
+endif
+endif
+ifdef CHECKDEPEND
+$(PACKAGEDIR_PASZLIB)/$(FPCMADE):
+	$(MAKE) -C $(PACKAGEDIR_PASZLIB) $(FPCMADE)
+override ALLDEPENDENCIES+=$(PACKAGEDIR_PASZLIB)/$(FPCMADE)
+endif
+else
+PACKAGEDIR_PASZLIB=
+UNITDIR_PASZLIB:=$(subst /Package.fpc,,$(strip $(wildcard $(addsuffix /paszlib/Package.fpc,$(UNITSDIR)))))
+ifneq ($(UNITDIR_PASZLIB),)
+UNITDIR_PASZLIB:=$(firstword $(UNITDIR_PASZLIB))
+else
+UNITDIR_PASZLIB=
+endif
+endif
+ifdef UNITDIR_PASZLIB
+override COMPILER_UNITDIR+=$(UNITDIR_PASZLIB)
+endif
+ifdef UNITDIR_FPMAKE_PASZLIB
+override COMPILER_FPMAKE_UNITDIR+=$(UNITDIR_FPMAKE_PASZLIB)
+endif
+endif
+ifdef REQUIRE_PACKAGES_FCL-PROCESS
+PACKAGEDIR_FCL-PROCESS:=$(firstword $(subst /Makefile.fpc,,$(strip $(wildcard $(addsuffix /fcl-process/Makefile.fpc,$(PACKAGESDIR))))))
+ifneq ($(PACKAGEDIR_FCL-PROCESS),)
+ifneq ($(wildcard $(PACKAGEDIR_FCL-PROCESS)/units/$(TARGETSUFFIX)),)
+UNITDIR_FCL-PROCESS=$(PACKAGEDIR_FCL-PROCESS)/units/$(TARGETSUFFIX)
+else
+UNITDIR_FCL-PROCESS=$(PACKAGEDIR_FCL-PROCESS)
+endif
+ifneq ($(wildcard $(PACKAGEDIR_FCL-PROCESS)/units/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_FCL-PROCESS=$(PACKAGEDIR_FCL-PROCESS)/units/$(SOURCESUFFIX)
+else
+ifneq ($(wildcard $(PACKAGEDIR_FCL-PROCESS)/units_bs/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_FCL-PROCESS=$(PACKAGEDIR_FCL-PROCESS)/units_bs/$(SOURCESUFFIX)
+else
+UNITDIR_FPMAKE_FCL-PROCESS=$(PACKAGEDIR_FCL-PROCESS)
+endif
+endif
+ifdef CHECKDEPEND
+$(PACKAGEDIR_FCL-PROCESS)/$(FPCMADE):
+	$(MAKE) -C $(PACKAGEDIR_FCL-PROCESS) $(FPCMADE)
+override ALLDEPENDENCIES+=$(PACKAGEDIR_FCL-PROCESS)/$(FPCMADE)
+endif
+else
+PACKAGEDIR_FCL-PROCESS=
+UNITDIR_FCL-PROCESS:=$(subst /Package.fpc,,$(strip $(wildcard $(addsuffix /fcl-process/Package.fpc,$(UNITSDIR)))))
+ifneq ($(UNITDIR_FCL-PROCESS),)
+UNITDIR_FCL-PROCESS:=$(firstword $(UNITDIR_FCL-PROCESS))
+else
+UNITDIR_FCL-PROCESS=
+endif
+endif
+ifdef UNITDIR_FCL-PROCESS
+override COMPILER_UNITDIR+=$(UNITDIR_FCL-PROCESS)
+endif
+ifdef UNITDIR_FPMAKE_FCL-PROCESS
+override COMPILER_FPMAKE_UNITDIR+=$(UNITDIR_FPMAKE_FCL-PROCESS)
+endif
+endif
+ifdef REQUIRE_PACKAGES_HASH
+PACKAGEDIR_HASH:=$(firstword $(subst /Makefile.fpc,,$(strip $(wildcard $(addsuffix /hash/Makefile.fpc,$(PACKAGESDIR))))))
+ifneq ($(PACKAGEDIR_HASH),)
+ifneq ($(wildcard $(PACKAGEDIR_HASH)/units/$(TARGETSUFFIX)),)
+UNITDIR_HASH=$(PACKAGEDIR_HASH)/units/$(TARGETSUFFIX)
+else
+UNITDIR_HASH=$(PACKAGEDIR_HASH)
+endif
+ifneq ($(wildcard $(PACKAGEDIR_HASH)/units/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_HASH=$(PACKAGEDIR_HASH)/units/$(SOURCESUFFIX)
+else
+ifneq ($(wildcard $(PACKAGEDIR_HASH)/units_bs/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_HASH=$(PACKAGEDIR_HASH)/units_bs/$(SOURCESUFFIX)
+else
+UNITDIR_FPMAKE_HASH=$(PACKAGEDIR_HASH)
+endif
+endif
+ifdef CHECKDEPEND
+$(PACKAGEDIR_HASH)/$(FPCMADE):
+	$(MAKE) -C $(PACKAGEDIR_HASH) $(FPCMADE)
+override ALLDEPENDENCIES+=$(PACKAGEDIR_HASH)/$(FPCMADE)
+endif
+else
+PACKAGEDIR_HASH=
+UNITDIR_HASH:=$(subst /Package.fpc,,$(strip $(wildcard $(addsuffix /hash/Package.fpc,$(UNITSDIR)))))
+ifneq ($(UNITDIR_HASH),)
+UNITDIR_HASH:=$(firstword $(UNITDIR_HASH))
+else
+UNITDIR_HASH=
+endif
+endif
+ifdef UNITDIR_HASH
+override COMPILER_UNITDIR+=$(UNITDIR_HASH)
+endif
+ifdef UNITDIR_FPMAKE_HASH
+override COMPILER_FPMAKE_UNITDIR+=$(UNITDIR_FPMAKE_HASH)
+endif
+endif
+ifdef REQUIRE_PACKAGES_FPMKUNIT
+PACKAGEDIR_FPMKUNIT:=$(firstword $(subst /Makefile.fpc,,$(strip $(wildcard $(addsuffix /fpmkunit/Makefile.fpc,$(PACKAGESDIR))))))
+ifneq ($(PACKAGEDIR_FPMKUNIT),)
+ifneq ($(wildcard $(PACKAGEDIR_FPMKUNIT)/units/$(TARGETSUFFIX)),)
+UNITDIR_FPMKUNIT=$(PACKAGEDIR_FPMKUNIT)/units/$(TARGETSUFFIX)
+else
+UNITDIR_FPMKUNIT=$(PACKAGEDIR_FPMKUNIT)
+endif
+ifneq ($(wildcard $(PACKAGEDIR_FPMKUNIT)/units/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_FPMKUNIT=$(PACKAGEDIR_FPMKUNIT)/units/$(SOURCESUFFIX)
+else
+ifneq ($(wildcard $(PACKAGEDIR_FPMKUNIT)/units_bs/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_FPMKUNIT=$(PACKAGEDIR_FPMKUNIT)/units_bs/$(SOURCESUFFIX)
+else
+UNITDIR_FPMAKE_FPMKUNIT=$(PACKAGEDIR_FPMKUNIT)
+endif
+endif
+ifdef CHECKDEPEND
+$(PACKAGEDIR_FPMKUNIT)/$(FPCMADE):
+	$(MAKE) -C $(PACKAGEDIR_FPMKUNIT) $(FPCMADE)
+override ALLDEPENDENCIES+=$(PACKAGEDIR_FPMKUNIT)/$(FPCMADE)
+endif
+else
+PACKAGEDIR_FPMKUNIT=
+UNITDIR_FPMKUNIT:=$(subst /Package.fpc,,$(strip $(wildcard $(addsuffix /fpmkunit/Package.fpc,$(UNITSDIR)))))
+ifneq ($(UNITDIR_FPMKUNIT),)
+UNITDIR_FPMKUNIT:=$(firstword $(UNITDIR_FPMKUNIT))
+else
+UNITDIR_FPMKUNIT=
+endif
+endif
+ifdef UNITDIR_FPMKUNIT
+override COMPILER_UNITDIR+=$(UNITDIR_FPMKUNIT)
+endif
+ifdef UNITDIR_FPMAKE_FPMKUNIT
+override COMPILER_FPMAKE_UNITDIR+=$(UNITDIR_FPMAKE_FPMKUNIT)
+endif
+endif
+ifndef NOCPUDEF
+override FPCOPTDEF=$(ARCH)
+endif
+ifneq ($(OS_TARGET),$(OS_SOURCE))
+override FPCOPT+=-T$(OS_TARGET)
+endif
+ifneq ($(CPU_TARGET),$(CPU_SOURCE))
+override FPCOPT+=-P$(ARCH)
+endif
+ifeq ($(OS_SOURCE),openbsd)
+override FPCOPT+=-FD$(NEW_BINUTILS_PATH)
+override FPCMAKEOPT+=-FD$(NEW_BINUTILS_PATH)
+endif
+ifndef CROSSBOOTSTRAP
+ifneq ($(BINUTILSPREFIX),)
+override FPCOPT+=-XP$(BINUTILSPREFIX)
+override FPCMAKEOPT+=-XP$(BINUTILSPREFIX)
+endif
+ifneq ($(BINUTILSPREFIX),)
+override FPCOPT+=-Xr$(RLINKPATH)
+endif
+endif
+ifdef UNITDIR
+override FPCOPT+=$(addprefix -Fu,$(UNITDIR))
+endif
+ifdef LIBDIR
+override FPCOPT+=$(addprefix -Fl,$(LIBDIR))
+endif
+ifdef OBJDIR
+override FPCOPT+=$(addprefix -Fo,$(OBJDIR))
+endif
+ifdef INCDIR
+override FPCOPT+=$(addprefix -Fi,$(INCDIR))
+endif
+ifdef LINKSMART
+override FPCOPT+=-XX
+endif
+ifdef CREATESMART
+override FPCOPT+=-CX
+endif
+ifdef DEBUG
+override FPCOPT+=-gl
+override FPCOPTDEF+=DEBUG
+endif
+ifdef RELEASE
+ifneq ($(findstring 2.0.,$(FPC_VERSION)),)
+ifeq ($(CPU_TARGET),i386)
+FPCCPUOPT:=-OG2p3
+endif
+ifeq ($(CPU_TARGET),powerpc)
+FPCCPUOPT:=-O1r
+endif
+else
+FPCCPUOPT:=-O2
+endif
+override FPCOPT+=-Ur -Xs $(FPCCPUOPT) -n
+override FPCOPTDEF+=RELEASE
+endif
+ifdef STRIP
+override FPCOPT+=-Xs
+endif
+ifdef OPTIMIZE
+override FPCOPT+=-O2
+endif
+ifdef VERBOSE
+override FPCOPT+=-vwni
+endif
+ifdef COMPILER_OPTIONS
+override FPCOPT+=$(COMPILER_OPTIONS)
+endif
+ifdef COMPILER_UNITDIR
+override FPCOPT+=$(addprefix -Fu,$(COMPILER_UNITDIR))
+endif
+ifdef COMPILER_LIBRARYDIR
+override FPCOPT+=$(addprefix -Fl,$(COMPILER_LIBRARYDIR))
+endif
+ifdef COMPILER_OBJECTDIR
+override FPCOPT+=$(addprefix -Fo,$(COMPILER_OBJECTDIR))
+endif
+ifdef COMPILER_INCLUDEDIR
+override FPCOPT+=$(addprefix -Fi,$(COMPILER_INCLUDEDIR))
+endif
+ifdef CROSSBINDIR
+override FPCOPT+=-FD$(CROSSBINDIR)
+endif
+ifdef COMPILER_TARGETDIR
+override FPCOPT+=-FE$(COMPILER_TARGETDIR)
+ifeq ($(COMPILER_TARGETDIR),.)
+override TARGETDIRPREFIX=
+else
+override TARGETDIRPREFIX=$(COMPILER_TARGETDIR)/
+endif
+endif
+ifdef COMPILER_UNITTARGETDIR
+override FPCOPT+=-FU$(COMPILER_UNITTARGETDIR)
+ifeq ($(COMPILER_UNITTARGETDIR),.)
+override UNITTARGETDIRPREFIX=
+else
+override UNITTARGETDIRPREFIX=$(COMPILER_UNITTARGETDIR)/
+endif
+else
+ifdef COMPILER_TARGETDIR
+override COMPILER_UNITTARGETDIR=$(COMPILER_TARGETDIR)
+override UNITTARGETDIRPREFIX=$(TARGETDIRPREFIX)
+endif
+endif
+ifdef CREATESHARED
+override FPCOPT+=-Cg
+ifeq ($(CPU_TARGET),i386)
+override FPCOPT+=-Aas
+endif
+endif
+ifeq ($(findstring 2.0.,$(FPC_VERSION)),)
+ifneq ($(findstring $(OS_TARGET),freebsd openbsd netbsd linux solaris),)
+ifeq ($(CPU_TARGET),x86_64)
+override FPCOPT+=-Cg
+endif
+endif
+endif
+ifdef LINKSHARED
+endif
+ifdef OPT
+override FPCOPT+=$(OPT)
+endif
+ifdef FPCOPTDEF
+override FPCOPT+=$(addprefix -d,$(FPCOPTDEF))
+endif
+ifdef CFGFILE
+override FPCOPT+=@$(CFGFILE)
+endif
+ifdef USEENV
+override FPCEXTCMD:=$(FPCOPT)
+override FPCOPT:=!FPCEXTCMD
+export FPCEXTCMD
+endif
+override AFULL_TARGET=$(CPU_TARGET)-$(OS_TARGET)
+override AFULL_SOURCE=$(CPU_SOURCE)-$(OS_SOURCE)
+ifneq ($(AFULL_TARGET),$(AFULL_SOURCE))
+override ACROSSCOMPILE=1
+endif
+ifdef ACROSSCOMPILE
+override FPCOPT+=$(CROSSOPT)
+endif
+override COMPILER:=$(FPC) $(FPCOPT)
+ifeq (,$(findstring -s ,$(COMPILER)))
+EXECPPAS=
+else
+ifeq ($(FULL_SOURCE),$(FULL_TARGET))
+ifdef RUNBATCH
+EXECPPAS:=@$(RUNBATCH) $(PPAS)
+else
+EXECPPAS:=@$(PPAS)
+endif
+endif
+endif
+ifdef TARGET_RSTS
+override RSTFILES=$(addsuffix $(RSTEXT),$(TARGET_RSTS))
+override CLEANRSTFILES+=$(RSTFILES)
+endif
+.PHONY: fpc_install fpc_sourceinstall fpc_exampleinstall
+ifdef INSTALL_UNITS
+override INSTALLPPUFILES+=$(addsuffix $(PPUEXT),$(INSTALL_UNITS))
+endif
+ifdef INSTALL_BUILDUNIT
+override INSTALLPPUFILES:=$(filter-out $(INSTALL_BUILDUNIT)$(PPUEXT),$(INSTALLPPUFILES))
+endif
+ifdef INSTALLPPUFILES
+override INSTALLPPULINKFILES:=$(subst $(PPUEXT),$(OEXT),$(INSTALLPPUFILES)) $(addprefix $(STATICLIBPREFIX),$(subst $(PPUEXT),$(STATICLIBEXT),$(INSTALLPPUFILES))) $(addprefix $(IMPORTLIBPREFIX),$(subst $(PPUEXT),$(STATICLIBEXT),$(INSTALLPPUFILES)))
+ifneq ($(UNITTARGETDIRPREFIX),)
+override INSTALLPPUFILES:=$(addprefix $(UNITTARGETDIRPREFIX),$(notdir $(INSTALLPPUFILES)))
+override INSTALLPPULINKFILES:=$(wildcard $(addprefix $(UNITTARGETDIRPREFIX),$(notdir $(INSTALLPPULINKFILES))))
+endif
+override INSTALL_CREATEPACKAGEFPC=1
+endif
+ifdef INSTALLEXEFILES
+ifneq ($(TARGETDIRPREFIX),)
+override INSTALLEXEFILES:=$(addprefix $(TARGETDIRPREFIX),$(notdir $(INSTALLEXEFILES)))
+endif
+endif
+fpc_install: all $(INSTALLTARGET)
+ifdef INSTALLEXEFILES
+	$(MKDIR) $(INSTALL_BINDIR)
+ifdef UPXPROG
+	-$(UPXPROG) $(INSTALLEXEFILES)
+endif
+	$(INSTALLEXE) $(INSTALLEXEFILES) $(INSTALL_BINDIR)
+endif
+ifdef INSTALL_CREATEPACKAGEFPC
+ifdef FPCMAKE
+ifdef PACKAGE_VERSION
+ifneq ($(wildcard Makefile.fpc),)
+	$(FPCMAKE) -p -T$(CPU_TARGET)-$(OS_TARGET) Makefile.fpc
+	$(MKDIR) $(INSTALL_UNITDIR)
+	$(INSTALL) Package.fpc $(INSTALL_UNITDIR)
+endif
+endif
+endif
+endif
+ifdef INSTALLPPUFILES
+	$(MKDIR) $(INSTALL_UNITDIR)
+	$(INSTALL) $(INSTALLPPUFILES) $(INSTALL_UNITDIR)
+ifneq ($(INSTALLPPULINKFILES),)
+	$(INSTALL) $(INSTALLPPULINKFILES) $(INSTALL_UNITDIR)
+endif
+ifneq ($(wildcard $(LIB_FULLNAME)),)
+	$(MKDIR) $(INSTALL_LIBDIR)
+	$(INSTALL) $(LIB_FULLNAME) $(INSTALL_LIBDIR)
+ifdef inUnix
+	ln -sf $(LIB_FULLNAME) $(INSTALL_LIBDIR)/$(LIB_NAME)
+endif
+endif
+endif
+ifdef INSTALL_FILES
+	$(MKDIR) $(INSTALL_DATADIR)
+	$(INSTALL) $(INSTALL_FILES) $(INSTALL_DATADIR)
+endif
+fpc_sourceinstall: distclean
+	$(MKDIR) $(INSTALL_SOURCEDIR)
+	$(COPYTREE) $(BASEDIR)/* $(INSTALL_SOURCEDIR)
+fpc_exampleinstall: $(addsuffix _distclean,$(TARGET_EXAMPLEDIRS))
+ifdef HASEXAMPLES
+	$(MKDIR) $(INSTALL_EXAMPLEDIR)
+endif
+ifdef EXAMPLESOURCEFILES
+	$(COPY) $(EXAMPLESOURCEFILES) $(INSTALL_EXAMPLEDIR)
+endif
+ifdef TARGET_EXAMPLEDIRS
+	$(COPYTREE) $(addsuffix /*,$(TARGET_EXAMPLEDIRS)) $(INSTALL_EXAMPLEDIR)
+endif
+.PHONY: fpc_distinstall
+fpc_distinstall: install exampleinstall
+.PHONY: fpc_zipinstall fpc_zipsourceinstall fpc_zipexampleinstall
+ifndef PACKDIR
+ifndef inUnix
+PACKDIR=$(BASEDIR)/../fpc-pack
+else
+PACKDIR=/tmp/fpc-pack
+endif
+endif
+ifndef ZIPNAME
+ifdef DIST_ZIPNAME
+ZIPNAME=$(DIST_ZIPNAME)
+else
+ZIPNAME=$(PACKAGE_NAME)
+endif
+endif
+ifndef FULLZIPNAME
+FULLZIPNAME=$(ZIPCROSSPREFIX)$(ZIPPREFIX)$(ZIPNAME)$(ZIPSUFFIX)
+endif
+ifndef ZIPTARGET
+ifdef DIST_ZIPTARGET
+ZIPTARGET=DIST_ZIPTARGET
+else
+ZIPTARGET=install
+endif
+endif
+ifndef USEZIP
+ifdef inUnix
+USETAR=1
+endif
+endif
+ifndef inUnix
+USEZIPWRAPPER=1
+endif
+ifdef USEZIPWRAPPER
+ZIPPATHSEP=$(PATHSEP)
+ZIPWRAPPER=$(subst /,$(PATHSEP),$(DIST_DESTDIR)/fpczip$(SRCBATCHEXT))
+else
+ZIPPATHSEP=/
+endif
+ZIPCMD_CDPACK:=cd $(subst /,$(ZIPPATHSEP),$(PACKDIR))
+ZIPCMD_CDBASE:=cd $(subst /,$(ZIPPATHSEP),$(BASEDIR))
+ifdef USETAR
+ZIPDESTFILE:=$(DIST_DESTDIR)/$(FULLZIPNAME)$(TAREXT)
+ZIPCMD_ZIP:=$(TARPROG) c$(TAROPT)f $(ZIPDESTFILE) *
+else
+ZIPDESTFILE:=$(DIST_DESTDIR)/$(FULLZIPNAME)$(ZIPEXT)
+ZIPCMD_ZIP:=$(subst /,$(ZIPPATHSEP),$(ZIPPROG)) -Dr $(ZIPOPT) $(ZIPDESTFILE) *
+endif
+fpc_zipinstall:
+	$(MAKE) $(ZIPTARGET) INSTALL_PREFIX=$(PACKDIR) ZIPINSTALL=1
+	$(MKDIR) $(DIST_DESTDIR)
+	$(DEL) $(ZIPDESTFILE)
+ifdef USEZIPWRAPPER
+ifneq ($(ECHOREDIR),echo)
+	$(ECHOREDIR) -e "$(subst \,\\,$(ZIPCMD_CDPACK))" > $(ZIPWRAPPER)
+	$(ECHOREDIR) -e "$(subst \,\\,$(ZIPCMD_ZIP))" >> $(ZIPWRAPPER)
+	$(ECHOREDIR) -e "$(subst \,\\,$(ZIPCMD_CDBASE))" >> $(ZIPWRAPPER)
+else
+	echo $(ZIPCMD_CDPACK) > $(ZIPWRAPPER)
+	echo $(ZIPCMD_ZIP) >> $(ZIPWRAPPER)
+	echo $(ZIPCMD_CDBASE) >> $(ZIPWRAPPER)
+endif
+ifdef inUnix
+	/bin/sh $(ZIPWRAPPER)
+else
+ifdef RUNBATCH
+	$(RUNBATCH) $(ZIPWRAPPER)
+else
+	$(ZIPWRAPPER)
+endif
+endif
+	$(DEL) $(ZIPWRAPPER)
+else
+	$(ZIPCMD_CDPACK) ; $(ZIPCMD_ZIP) ; $(ZIPCMD_CDBASE)
+endif
+	$(DELTREE) $(PACKDIR)
+fpc_zipsourceinstall:
+	$(MAKE) fpc_zipinstall ZIPTARGET=sourceinstall ZIPSUFFIX=$(ZIPSOURCESUFFIX)
+fpc_zipexampleinstall:
+ifdef HASEXAMPLES
+	$(MAKE) fpc_zipinstall ZIPTARGET=exampleinstall ZIPSUFFIX=$(ZIPEXAMPLESUFFIX)
+endif
+fpc_zipdistinstall:
+	$(MAKE) fpc_zipinstall ZIPTARGET=distinstall
+.PHONY: fpc_clean fpc_cleanall fpc_distclean
+ifdef EXEFILES
+override CLEANEXEFILES:=$(addprefix $(TARGETDIRPREFIX),$(CLEANEXEFILES))
+override CLEANEXEDBGFILES:=$(addprefix $(TARGETDIRPREFIX),$(CLEANEXEDBGFILES))
+endif
+ifdef CLEAN_PROGRAMS
+override CLEANEXEFILES+=$(addprefix $(TARGETDIRPREFIX),$(addsuffix $(EXEEXT), $(CLEAN_PROGRAMS)))
+override CLEANEXEDBGFILES+=$(addprefix $(TARGETDIRPREFIX),$(addsuffix $(EXEDBGEXT), $(CLEAN_PROGRAMS)))
+endif
+ifdef CLEAN_UNITS
+override CLEANPPUFILES+=$(addsuffix $(PPUEXT),$(CLEAN_UNITS))
+endif
+ifdef CLEANPPUFILES
+override CLEANPPULINKFILES:=$(subst $(PPUEXT),$(OEXT),$(CLEANPPUFILES)) $(addprefix $(STATICLIBPREFIX),$(subst $(PPUEXT),$(STATICLIBEXT),$(CLEANPPUFILES))) $(addprefix $(IMPORTLIBPREFIX),$(subst $(PPUEXT),$(STATICLIBEXT),$(CLEANPPUFILES)))
+ifdef DEBUGSYMEXT
+override CLEANPPULINKFILES+=$(subst $(PPUEXT),$(DEBUGSYMEXT),$(CLEANPPUFILES))
+endif
+override CLEANPPUFILES:=$(addprefix $(UNITTARGETDIRPREFIX),$(CLEANPPUFILES))
+override CLEANPPULINKFILES:=$(wildcard $(addprefix $(UNITTARGETDIRPREFIX),$(CLEANPPULINKFILES)))
+endif
+fpc_clean: $(CLEANTARGET)
+ifdef CLEANEXEFILES
+	-$(DEL) $(CLEANEXEFILES)
+endif
+ifdef CLEANEXEDBGFILES
+	-$(DELTREE) $(CLEANEXEDBGFILES)
+endif
+ifdef CLEANPPUFILES
+	-$(DEL) $(CLEANPPUFILES)
+endif
+ifneq ($(CLEANPPULINKFILES),)
+	-$(DEL) $(CLEANPPULINKFILES)
+endif
+ifdef CLEANRSTFILES
+	-$(DEL) $(addprefix $(UNITTARGETDIRPREFIX),$(CLEANRSTFILES))
+endif
+ifdef CLEAN_FILES
+	-$(DEL) $(CLEAN_FILES)
+endif
+ifdef LIB_NAME
+	-$(DEL) $(LIB_NAME) $(LIB_FULLNAME)
+endif
+	-$(DEL) $(FPCMADE) Package.fpc $(PPAS) script.res link.res $(FPCEXTFILE) $(REDIRFILE)
+	-$(DEL) *$(ASMEXT) *_ppas$(BATCHEXT)
+fpc_cleanall: $(CLEANTARGET)
+ifdef CLEANEXEFILES
+	-$(DEL) $(CLEANEXEFILES)
+endif
+ifdef COMPILER_UNITTARGETDIR
+ifdef CLEANPPUFILES
+	-$(DEL) $(CLEANPPUFILES)
+endif
+ifneq ($(CLEANPPULINKFILES),)
+	-$(DEL) $(CLEANPPULINKFILES)
+endif
+ifdef CLEANRSTFILES
+	-$(DEL) $(addprefix $(UNITTARGETDIRPREFIX),$(CLEANRSTFILES))
+endif
+endif
+ifdef CLEAN_FILES
+	-$(DEL) $(CLEAN_FILES)
+endif
+	-$(DELTREE) units
+	-$(DEL) *$(OEXT) *$(PPUEXT) *$(RSTEXT) *$(ASMEXT) *$(STATICLIBEXT) *$(SHAREDLIBEXT) *$(PPLEXT)
+ifneq ($(PPUEXT),.ppu)
+	-$(DEL) *.o *.ppu *.a
+endif
+	-$(DELTREE) *$(SMARTEXT)
+	-$(DEL) fpcmade.* Package.fpc $(PPAS) script.res link.res $(FPCEXTFILE) $(REDIRFILE)
+	-$(DEL) *_ppas$(BATCHEXT)
+ifdef AOUTEXT
+	-$(DEL) *$(AOUTEXT)
+endif
+ifdef DEBUGSYMEXT
+	-$(DEL) *$(DEBUGSYMEXT)
+endif
+fpc_distclean: cleanall
+.PHONY: fpc_baseinfo
+override INFORULES+=fpc_baseinfo
+fpc_baseinfo:
+	@$(ECHO)
+	@$(ECHO)  == Package info ==
+	@$(ECHO)  Package Name..... $(PACKAGE_NAME)
+	@$(ECHO)  Package Version.. $(PACKAGE_VERSION)
+	@$(ECHO)
+	@$(ECHO)  == Configuration info ==
+	@$(ECHO)
+	@$(ECHO)  FPC.......... $(FPC)
+	@$(ECHO)  FPC Version.. $(FPC_VERSION)
+	@$(ECHO)  Source CPU... $(CPU_SOURCE)
+	@$(ECHO)  Target CPU... $(CPU_TARGET)
+	@$(ECHO)  Source OS.... $(OS_SOURCE)
+	@$(ECHO)  Target OS.... $(OS_TARGET)
+	@$(ECHO)  Full Source.. $(FULL_SOURCE)
+	@$(ECHO)  Full Target.. $(FULL_TARGET)
+	@$(ECHO)  SourceSuffix. $(SOURCESUFFIX)
+	@$(ECHO)  TargetSuffix. $(TARGETSUFFIX)
+	@$(ECHO)  FPC fpmake... $(FPCFPMAKE)
+	@$(ECHO)
+	@$(ECHO)  == Directory info ==
+	@$(ECHO)
+	@$(ECHO)  Required pkgs... $(REQUIRE_PACKAGES)
+	@$(ECHO)
+	@$(ECHO)  Basedir......... $(BASEDIR)
+	@$(ECHO)  FPCDir.......... $(FPCDIR)
+	@$(ECHO)  CrossBinDir..... $(CROSSBINDIR)
+	@$(ECHO)  UnitsDir........ $(UNITSDIR)
+	@$(ECHO)  PackagesDir..... $(PACKAGESDIR)
+	@$(ECHO)
+	@$(ECHO)  GCC library..... $(GCCLIBDIR)
+	@$(ECHO)  Other library... $(OTHERLIBDIR)
+	@$(ECHO)
+	@$(ECHO)  == Tools info ==
+	@$(ECHO)
+	@$(ECHO)  As........ $(AS)
+	@$(ECHO)  Ld........ $(LD)
+	@$(ECHO)  Ar........ $(AR)
+	@$(ECHO)  Rc........ $(RC)
+	@$(ECHO)
+	@$(ECHO)  Mv........ $(MVPROG)
+	@$(ECHO)  Cp........ $(CPPROG)
+	@$(ECHO)  Rm........ $(RMPROG)
+	@$(ECHO)  GInstall.. $(GINSTALL)
+	@$(ECHO)  Echo...... $(ECHO)
+	@$(ECHO)  Shell..... $(SHELL)
+	@$(ECHO)  Date...... $(DATE)
+	@$(ECHO)  FPCMake... $(FPCMAKE)
+	@$(ECHO)  PPUMove... $(PPUMOVE)
+	@$(ECHO)  Upx....... $(UPXPROG)
+	@$(ECHO)  Zip....... $(ZIPPROG)
+	@$(ECHO)
+	@$(ECHO)  == Object info ==
+	@$(ECHO)
+	@$(ECHO)  Target Loaders........ $(TARGET_LOADERS)
+	@$(ECHO)  Target Units.......... $(TARGET_UNITS)
+	@$(ECHO)  Target Implicit Units. $(TARGET_IMPLICITUNITS)
+	@$(ECHO)  Target Programs....... $(TARGET_PROGRAMS)
+	@$(ECHO)  Target Dirs........... $(TARGET_DIRS)
+	@$(ECHO)  Target Examples....... $(TARGET_EXAMPLES)
+	@$(ECHO)  Target ExampleDirs.... $(TARGET_EXAMPLEDIRS)
+	@$(ECHO)
+	@$(ECHO)  Clean Units......... $(CLEAN_UNITS)
+	@$(ECHO)  Clean Files......... $(CLEAN_FILES)
+	@$(ECHO)
+	@$(ECHO)  Install Units....... $(INSTALL_UNITS)
+	@$(ECHO)  Install Files....... $(INSTALL_FILES)
+	@$(ECHO)
+	@$(ECHO)  == Install info ==
+	@$(ECHO)
+	@$(ECHO)  DateStr.............. $(DATESTR)
+	@$(ECHO)  ZipName.............. $(ZIPNAME)
+	@$(ECHO)  ZipPrefix............ $(ZIPPREFIX)
+	@$(ECHO)  ZipCrossPrefix....... $(ZIPCROSSPREFIX)
+	@$(ECHO)  ZipSuffix............ $(ZIPSUFFIX)
+	@$(ECHO)  FullZipName.......... $(FULLZIPNAME)
+	@$(ECHO)  Install FPC Package.. $(INSTALL_FPCPACKAGE)
+	@$(ECHO)
+	@$(ECHO)  Install base dir..... $(INSTALL_BASEDIR)
+	@$(ECHO)  Install binary dir... $(INSTALL_BINDIR)
+	@$(ECHO)  Install library dir.. $(INSTALL_LIBDIR)
+	@$(ECHO)  Install units dir.... $(INSTALL_UNITDIR)
+	@$(ECHO)  Install source dir... $(INSTALL_SOURCEDIR)
+	@$(ECHO)  Install doc dir...... $(INSTALL_DOCDIR)
+	@$(ECHO)  Install example dir.. $(INSTALL_EXAMPLEDIR)
+	@$(ECHO)  Install data dir..... $(INSTALL_DATADIR)
+	@$(ECHO)
+	@$(ECHO)  Dist destination dir. $(DIST_DESTDIR)
+	@$(ECHO)  Dist zip name........ $(DIST_ZIPNAME)
+	@$(ECHO)
+.PHONY: fpc_info
+fpc_info: $(INFORULES)
+.PHONY: fpc_makefile fpc_makefiles fpc_makefile_sub1 fpc_makefile_sub2 \
+	fpc_makefile_dirs
+fpc_makefile:
+	$(FPCMAKE) -w -T$(OS_TARGET) Makefile.fpc
+fpc_makefile_sub1:
+ifdef TARGET_DIRS
+	$(FPCMAKE) -w -T$(OS_TARGET) $(addsuffix /Makefile.fpc,$(TARGET_DIRS))
+endif
+ifdef TARGET_EXAMPLEDIRS
+	$(FPCMAKE) -w -T$(OS_TARGET) $(addsuffix /Makefile.fpc,$(TARGET_EXAMPLEDIRS))
+endif
+fpc_makefile_sub2: $(addsuffix _makefile_dirs,$(TARGET_DIRS) $(TARGET_EXAMPLEDIRS))
+fpc_makefile_dirs: fpc_makefile_sub1 fpc_makefile_sub2
+fpc_makefiles: fpc_makefile fpc_makefile_dirs
+units:
+examples:
+shared:
+sourceinstall: fpc_sourceinstall
+exampleinstall: fpc_exampleinstall
+zipinstall: fpc_zipinstall
+zipsourceinstall: fpc_zipsourceinstall
+zipexampleinstall: fpc_zipexampleinstall
+zipdistinstall: fpc_zipdistinstall
+cleanall:
+info: fpc_info
+makefiles: fpc_makefiles
+.PHONY: units examples shared sourceinstall exampleinstall zipinstall zipsourceinstall zipexampleinstall zipdistinstall cleanall info makefiles
+ifneq ($(wildcard fpcmake.loc),)
+include fpcmake.loc
+endif
+.NOTPARALLEL:
+fpmake: fpmake.pp
+	$(FPCFPMAKE) fpmake.pp $(FPMAKE_SKIP_CONFIG) $(addprefix -Fu,$(COMPILER_FPMAKE_UNITDIR)) $(FPCMAKEOPT)
+all:	fpmake
+	$(LOCALFPMAKE) compile --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) -bu
+smart:	fpmake
+	$(LOCALFPMAKE) compile --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) -bu -o -XX -o -CX
+release:	fpmake
+	$(LOCALFPMAKE) compile --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) -bu -o -dRELEASE
+debug:	fpmake
+	$(LOCALFPMAKE) compile --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) -bu -o -dDEBUG
+ifeq ($(FPMAKE_BIN_CLEAN),)
+clean:	
+else
+clean:	
+	$(FPMAKE_BIN_CLEAN) clean --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC)
+endif
+ifeq ($(FPMAKE_BIN_CLEAN),)
+distclean:	$(addsuffix _distclean,$(TARGET_DIRS)) fpc_cleanall
+else
+distclean:	
+ifdef inUnix
+	{ $(FPMAKE_BIN_CLEAN) distclean --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC); if [ $$? != "0" ]; then { echo Something wrong with fpmake exectable. Remove the executable and call make recursively to recover.; $(DEL) $(FPMAKE_BIN_CLEAN); $(MAKE) fpc_cleanall; }; fi;  }
+else
+	$(FPMAKE_BIN_CLEAN) distclean --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC)
+endif
+	-$(DEL) $(LOCALFPMAKE)
+endif
+install:	fpmake
+ifdef UNIXHier
+	$(LOCALFPMAKE) install --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) --prefix=$(INSTALL_PREFIX) --baseinstalldir=$(INSTALL_LIBDIR)/fpc/$(FPC_VERSION) --unitinstalldir=$(INSTALL_UNITDIR)
+else
+	$(LOCALFPMAKE) install --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) --prefix=$(INSTALL_BASEDIR) --unitinstalldir=$(INSTALL_UNITDIR)
+endif
+distinstall:	fpmake
+ifdef UNIXHier
+	$(LOCALFPMAKE) install --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) --prefix=$(INSTALL_PREFIX) --baseinstalldir=$(INSTALL_LIBDIR)/fpc/$(FPC_VERSION) --unitinstalldir=$(INSTALL_UNITDIR) -ie
+else
+	$(LOCALFPMAKE) install --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) --prefix=$(INSTALL_BASEDIR)  --unitinstalldir=$(INSTALL_UNITDIR) -ie
+endif

+ 76 - 0
packages/fpindexer/Makefile.fpc

@@ -0,0 +1,76 @@
+#
+#   Makefile.fpc for running fpmake
+#
+
+[package]
+name=fpindexer
+version=2.7.1
+
+[require]
+packages=rtl fpmkunit
+
+[install]
+fpcpackage=y
+
+[default]
+fpcdir=../..
+
+[prerules]
+FPMAKE_BIN_CLEAN=$(wildcard .$(PATHSEP)fpmake$(SRCEXEEXT))
+ifdef OS_TARGET
+FPC_TARGETOPT+=--os=$(OS_TARGET)
+endif
+ifdef CPU_TARGET
+FPC_TARGETOPT+=--cpu=$(CPU_TARGET)
+endif
+LOCALFPMAKE=.$(PATHSEP)fpmake$(SRCEXEEXT)
+
+[rules]
+.NOTPARALLEL:
+
+fpmake: fpmake.pp
+	$(FPCFPMAKE) fpmake.pp $(FPMAKE_SKIP_CONFIG) $(addprefix -Fu,$(COMPILER_FPMAKE_UNITDIR)) $(FPCMAKEOPT)
+all:	fpmake
+	$(LOCALFPMAKE) compile --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) -bu
+smart:	fpmake
+	$(LOCALFPMAKE) compile --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) -bu -o -XX -o -CX
+release:	fpmake
+	$(LOCALFPMAKE) compile --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) -bu -o -dRELEASE
+debug:	fpmake
+	$(LOCALFPMAKE) compile --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) -bu -o -dDEBUG
+# If no fpmake exists and (dist)clean is called, do not try to build fpmake, it will
+# most often fail because the dependencies are cleared.
+# In case of a clean, simply do nothing
+ifeq ($(FPMAKE_BIN_CLEAN),)
+clean:	
+else
+clean:	
+	$(FPMAKE_BIN_CLEAN) clean --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC)
+endif
+# In case of a distclean, perform an 'old'-style distclean. This to avoid problems
+# when the package is compiled using fpcmake prior to running this clean using fpmake
+ifeq ($(FPMAKE_BIN_CLEAN),)
+distclean:	$(addsuffix _distclean,$(TARGET_DIRS)) fpc_cleanall
+else
+distclean:	
+ifdef inUnix
+        { $(FPMAKE_BIN_CLEAN) distclean --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC); if [ $$? != "0" ]; then { echo Something wrong with fpmake exectable. Remove the executable and call make recursively to recover.; $(DEL) $(FPMAKE_BIN_CLEAN); $(MAKE) fpc_cleanall; }; fi;  }
+else
+        $(FPMAKE_BIN_CLEAN) distclean --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC)
+endif
+	-$(DEL) $(LOCALFPMAKE)
+endif
+install:	fpmake
+ifdef UNIXHier
+	$(LOCALFPMAKE) install --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) --prefix=$(INSTALL_PREFIX) --baseinstalldir=$(INSTALL_LIBDIR)/fpc/$(FPC_VERSION) --unitinstalldir=$(INSTALL_UNITDIR)
+else
+	$(LOCALFPMAKE) install --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) --prefix=$(INSTALL_BASEDIR) --unitinstalldir=$(INSTALL_UNITDIR)
+endif
+# distinstall also installs the example-sources
+distinstall:	fpmake
+ifdef UNIXHier
+	$(LOCALFPMAKE) install --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) --prefix=$(INSTALL_PREFIX) --baseinstalldir=$(INSTALL_LIBDIR)/fpc/$(FPC_VERSION) --unitinstalldir=$(INSTALL_UNITDIR) -ie
+else
+	$(LOCALFPMAKE) install --localunitdir=../.. --globalunitdir=.. $(FPC_TARGETOPT) $(addprefix -o ,$(FPCOPT)) --compiler=$(FPC) --prefix=$(INSTALL_BASEDIR)  --unitinstalldir=$(INSTALL_UNITDIR) -ie
+endif
+

+ 170 - 0
packages/fpindexer/examples/TestDBindexer.lpi

@@ -0,0 +1,170 @@
+<?xml version="1.0"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="9"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <MainUnit Value="0"/>
+      <ResourceType Value="res"/>
+      <UseXPManifest Value="True"/>
+      <ActiveWindowIndexAtStart Value="0"/>
+    </General>
+    <i18n>
+      <EnableI18N LFM="False"/>
+    </i18n>
+    <VersionInfo>
+      <StringTable ProductVersion=""/>
+    </VersionInfo>
+    <BuildModes Count="1" Active="Default">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/>
+      <ExcludeFileFilter Value="*.(bak|ppu|o|so);*~;backup"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+        <LaunchingApplication PathPlusParams="/usr/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="LazIndexer"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="10">
+      <Unit0>
+        <Filename Value="TestDBindexer.pp"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="TestDBindexer"/>
+        <EditorIndex Value="0"/>
+        <WindowIndex Value="0"/>
+        <TopLine Value="32"/>
+        <CursorPos X="45" Y="49"/>
+        <UsageCount Value="20"/>
+        <Loaded Value="True"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="dbindexer.pp"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="dbIndexer"/>
+        <WindowIndex Value="0"/>
+        <TopLine Value="332"/>
+        <CursorPos X="1" Y="337"/>
+        <UsageCount Value="20"/>
+      </Unit1>
+      <Unit2>
+        <Filename Value="fpindexer.pp"/>
+        <UnitName Value="fpIndexer"/>
+        <WindowIndex Value="0"/>
+        <TopLine Value="1393"/>
+        <CursorPos X="3" Y="1400"/>
+        <UsageCount Value="10"/>
+      </Unit2>
+      <Unit3>
+        <Filename Value="../FPC/trunk/rtl/objpas/classes/classesh.inc"/>
+        <WindowIndex Value="0"/>
+        <TopLine Value="919"/>
+        <CursorPos X="15" Y="930"/>
+        <UsageCount Value="10"/>
+      </Unit3>
+      <Unit4>
+        <Filename Value="../FPC/trunk/rtl/objpas/classes/streams.inc"/>
+        <WindowIndex Value="0"/>
+        <TopLine Value="773"/>
+        <CursorPos X="2" Y="776"/>
+        <UsageCount Value="10"/>
+      </Unit4>
+      <Unit5>
+        <Filename Value="ireadertxt.pp"/>
+        <UnitName Value="IReaderTXT"/>
+        <WindowIndex Value="0"/>
+        <TopLine Value="3"/>
+        <CursorPos X="18" Y="19"/>
+        <UsageCount Value="10"/>
+      </Unit5>
+      <Unit6>
+        <Filename Value="fbindexdb.pp"/>
+        <UnitName Value="fbIndexdb"/>
+        <WindowIndex Value="0"/>
+        <TopLine Value="6"/>
+        <CursorPos X="36" Y="8"/>
+        <UsageCount Value="10"/>
+      </Unit6>
+      <Unit7>
+        <Filename Value="sqldbindexdb.pp"/>
+        <UnitName Value="SQLDBIndexDB"/>
+        <WindowIndex Value="0"/>
+        <TopLine Value="1"/>
+        <CursorPos X="1" Y="1"/>
+        <UsageCount Value="10"/>
+      </Unit7>
+      <Unit8>
+        <Filename Value="../lazarus/fplazindexer.pp"/>
+        <UnitName Value="fpLazIndexer"/>
+        <EditorIndex Value="1"/>
+        <WindowIndex Value="0"/>
+        <TopLine Value="1"/>
+        <CursorPos X="12" Y="6"/>
+        <UsageCount Value="10"/>
+        <Loaded Value="True"/>
+      </Unit8>
+      <Unit9>
+        <Filename Value="../../../../lazarus/lcl/lresources.pp"/>
+        <UnitName Value="LResources"/>
+        <IsVisibleTab Value="True"/>
+        <EditorIndex Value="2"/>
+        <WindowIndex Value="0"/>
+        <TopLine Value="26"/>
+        <CursorPos X="40" Y="39"/>
+        <UsageCount Value="10"/>
+        <Loaded Value="True"/>
+      </Unit9>
+    </Units>
+    <JumpHistory Count="4" HistoryIndex="3">
+      <Position1>
+        <Filename Value="TestDBindexer.pp"/>
+        <Caret Line="14" Column="24" TopLine="7"/>
+      </Position1>
+      <Position2>
+        <Filename Value="../lazarus/fplazindexer.pp"/>
+        <Caret Line="1" Column="1" TopLine="1"/>
+      </Position2>
+      <Position3>
+        <Filename Value="../lazarus/fplazindexer.pp"/>
+        <Caret Line="1" Column="1" TopLine="1"/>
+      </Position3>
+      <Position4>
+        <Filename Value="../../../../lazarus/lcl/lresources.pp"/>
+        <Caret Line="39" Column="40" TopLine="26"/>
+      </Position4>
+    </JumpHistory>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+    </SearchPaths>
+    <Other>
+      <CompilerPath Value="$(CompPath)"/>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 70 - 0
packages/fpindexer/examples/TestDBindexer.pp

@@ -0,0 +1,70 @@
+program TestDBindexer;
+
+{$mode objfpc}{$H+}
+
+uses
+  {$IFDEF UNIX}{$IFDEF UseCThreads}
+  cthreads,
+  {$ENDIF}{$ENDIF}
+  Classes, SysUtils, sqldb, dbIndexer, ibconnection, fbindexdb, fpindexer;
+
+{$R *.res}
+
+Function SetupDB : TCustomIndexDB;
+
+Var
+  IB: TFBIndexDB;
+
+begin
+  IB := TFBIndexDB.Create(nil);
+  try
+    IB.DatabasePath := Paramstr(2);
+    IB.UserName := 'username';
+    IB.Password := 'secret';
+    if not FileExists(IB.DatabasePath) then
+      IB.CreateDB
+    else
+      begin
+      IB.Connect;
+      IB.CreateIndexerTables;
+      end;
+  except
+    FreeAndNil(IB);
+    Raise;
+  end;
+  Result:=IB;
+end;
+
+Var
+  DBI : TDBIndexer;
+  Start : TDateTime;
+
+begin
+  If ParamCount<>2 then
+    begin
+    Writeln('Usage ',ExtractFileName(ParamStr(0)),' dbpath indexdbpath');
+    Halt(1);
+    end;
+  DBI:=TIBIndexer.Create(Nil);
+  try
+    DBI.IndexDB:=SetupDB;
+    try
+      DBI.Database:=TIBConnection.Create(DBI);
+      DBI.Database.Transaction:=TSQLTransaction.Create(DBI);
+      DBI.Database.DatabaseName:=ParamStr(1);
+      DBI.Database.UserName:='username';
+      DBI.Database.Password:='SysteemD';
+      DBI.Database.Connected:=True;
+      DBI.SkipTables.add('AANWEZIGHEIDSREGISTER');
+      DBI.SkipTables.add('EDISONZENDING');
+      Start:=Now;
+      DBI.IndexDatabase;
+      Writeln('Finished : ',FormatDateTime('hh:nn:ss',Now-Start));
+    finally
+      DBI.IndexDB.Free;
+    end;
+  finally
+    DBI.Free;
+  end;
+end.
+

+ 107 - 0
packages/fpindexer/examples/TestIndexer.lpi

@@ -0,0 +1,107 @@
+<?xml version="1.0"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="9"/>
+    <PathDelim Value="\"/>
+    <General>
+      <Flags>
+        <SaveClosedFiles Value="False"/>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="TestIndexer"/>
+      <ResourceType Value="res"/>
+      <UseXPManifest Value="True"/>
+    </General>
+    <i18n>
+      <EnableI18N LFM="False"/>
+    </i18n>
+    <VersionInfo>
+      <StringTable ProductVersion=""/>
+    </VersionInfo>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/>
+      <ExcludeFileFilter Value="*.(bak|ppu|o|so);*~;backup"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+        <LaunchingApplication PathPlusParams="\usr\bin\xterm -T 'Lazarus Run Output' -e $(LazarusDir)\tools\runwait.sh $(TargetCmdLine)"/>
+      </local>
+    </RunParams>
+    <Units Count="5">
+      <Unit0>
+        <Filename Value="TestIndexer.pp"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="TestIndexer"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="fpindexer.pp"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="fpIndexer"/>
+      </Unit1>
+      <Unit2>
+        <Filename Value="fpsearch.pp"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="fpSearch"/>
+      </Unit2>
+      <Unit3>
+        <Filename Value="sqliteindexdb.pp"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="SQLiteIndexDB"/>
+      </Unit3>
+      <Unit4>
+        <Filename Value="ireadertxt.pp"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="IReaderTXT"/>
+      </Unit4>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <PathDelim Value="\"/>
+    <Target>
+      <Filename Value="index"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <OtherUnitFiles Value="..\textcat;.."/>
+      <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <CodeGeneration>
+      <Optimizations>
+        <OptimizationLevel Value="3"/>
+      </Optimizations>
+    </CodeGeneration>
+    <Linking>
+      <Debugging>
+        <UseHeaptrc Value="True"/>
+      </Debugging>
+    </Linking>
+    <Other>
+      <CompilerMessages>
+        <UseMsgFile Value="True"/>
+      </CompilerMessages>
+      <CompilerPath Value="$(CompPath)"/>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 150 - 0
packages/fpindexer/examples/TestIndexer.pp

@@ -0,0 +1,150 @@
+program TestIndexer;
+
+{ $define usefirebird}
+{ $define usemem}
+{$mode objfpc}{$H+}
+{$IFDEF UNIX}
+  {$linklib pthread}
+{$ENDIF}
+
+uses
+  SysUtils,
+  {$IFDEF UNIX} {$IFDEF UseCThreads}
+    cthreads,
+  {$ENDIF} {$ENDIF}
+  {$ifdef usefirebird}
+    ibase60dyn,SQLDBIndexDB, fbIndexdb,
+  {$else}
+    {$ifdef usemem}
+      memindexdb,
+    {$else}
+      SQLIteIndexDB,
+    {$endif}
+  {$endif}
+   fpIndexer,
+  //indexer readers
+  IReaderTXT, IReaderPAS, IReaderHTML, fpTextCat;
+
+Type
+
+  { TProgressLog }
+
+  TProgressLog = Class(TObject)
+    procedure DoLog(Sender : TObject; Const ACurrent,ACount : Integer; Const AURL : String);
+  end;
+
+{$ifdef usefirebird}
+  function SetupDB : TCustomIndexDB;
+  var
+    IB: TFBIndexDB;
+  begin
+    IB := TFBIndexDB.Create(nil);
+    try
+      IB.DatabasePath := '/home/firebird/index.fb';
+      IB.UserName := 'SYSDBA';
+      IB.Password := 'masterkey';
+      if not FileExists(IB.DatabasePath) then
+        IB.CreateDB
+      else
+      begin
+        IB.Connect;
+        IB.CreateIndexerTables;
+      end;
+    except
+      FreeAndNil(IB);
+      Raise;
+    end;
+    Result:=IB;
+  end;
+{$else}
+  {$ifdef usemem}
+    Function SetupDB : TCustomIndexDB;
+    Var
+      FI : TFileIndexDB;
+    begin
+      FI:=TFileIndexDB.Create(Nil);
+      FI.FileName:='index.dat';
+      FI.Connect;
+      FI.WriteOnCommit:=True;;
+      Result:=FI;
+    end;
+  {$else}
+    Function SetupDB : TCustomIndexDB;
+    Var
+      SB: TSQLIteIndexDB;
+    begin
+      SB := TSQLIteIndexDB.Create(nil);
+      SB.FileName := 'index.db';
+      if not FileExists(SB.FileName) then
+        SB.CreateDB
+      else
+      begin
+        SB.Connect;
+        SB.CreateIndexerTables;
+      end;
+      Result:=SB;
+    end;
+  {$endif}
+{$endif}
+
+Procedure Testindex(ADir : String);
+var
+  Indexer: TFPIndexer; //indexes files
+  start: TDateTime;
+  n: int64;
+  endtime: TDateTime;
+  Logger : TProgressLog;
+begin
+  //SetHeapTraceOutput('heap.trc');
+  start := Now;
+  Indexer := TFPIndexer.Create(Nil);
+  try
+    Indexer.Database:=SetupDB;
+    //setup parameters for indexing
+    if (ADir<>'') then
+      Indexer.SearchPath:=ADir
+    else
+{$ifdef unix}
+      Indexer.SearchPath := '/home/michael/fpc/docs/fcl';
+{$else}
+      Indexer.SearchPath := 'C:\fcl';
+{$endif}
+    Indexer.FileMask := '*.pas;*.html;readme.txt'; //semicolon separated list
+    Indexer.SearchRecursive := True;
+    Indexer.DetectLanguage := False;
+    IgnoreListManager.LoadIgnoreWordsFromFile('english','english.txt');
+    indexer.Language:='english';
+    Indexer.UseIgnoreList:=true;
+    Logger := TProgressLog.Create;
+    try
+      Indexer.OnProgress:[email protected];
+      n := Indexer.Execute(True);
+    finally
+      Logger.Free;
+    end;
+    //execute the search
+
+    endtime := Now;
+    if N <> 0 then
+      writeln('indexing succesfull')
+    else
+      writeln('error indexing.');
+    writeln(Format('done in %.1f sec.', [(endtime - start) * 24 * 3600]));
+  finally
+    Indexer.Database.free;
+    FreeAndNil(Indexer);
+  end;
+end;
+
+{ TProgressLog }
+
+procedure TProgressLog.DoLog(Sender: TObject; const ACurrent, ACount: Integer;
+  const AURL: String);
+begin
+  Writeln((ACurrent/ACount*100):5:2,'% : ',ACurrent,'/',ACount,' : ',AURL);
+end;
+
+begin
+  TestIndex(ParamStr(1));
+end.
+

+ 87 - 0
packages/fpindexer/examples/TestSearch.lpi

@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="9"/>
+    <General>
+      <Flags>
+        <SaveClosedFiles Value="False"/>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <ResourceType Value="res"/>
+      <UseXPManifest Value="True"/>
+    </General>
+    <i18n>
+      <EnableI18N LFM="False"/>
+    </i18n>
+    <VersionInfo>
+      <StringTable ProductVersion=""/>
+    </VersionInfo>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/>
+      <ExcludeFileFilter Value="*.(bak|ppu|o|so);*~;backup"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+        <CommandLineParams Value="index.db tev*"/>
+        <LaunchingApplication PathPlusParams="/usr/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/>
+      </local>
+    </RunParams>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="TestSearch.pp"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="TestSearch"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="search"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <OtherUnitFiles Value=".."/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <CodeGeneration>
+      <Optimizations>
+        <OptimizationLevel Value="3"/>
+      </Optimizations>
+    </CodeGeneration>
+    <Linking>
+      <Debugging>
+        <DebugInfoType Value="dsStabs"/>
+        <UseLineInfoUnit Value="False"/>
+      </Debugging>
+      <LinkSmart Value="True"/>
+    </Linking>
+    <Other>
+      <CompilerMessages>
+        <UseMsgFile Value="True"/>
+      </CompilerMessages>
+      <CompilerPath Value="$(CompPath)"/>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 130 - 0
packages/fpindexer/examples/TestSearch.pp

@@ -0,0 +1,130 @@
+program TestSearch;
+
+{ $define usefirebird}
+{ $define usemem}
+{$mode objfpc}{$H+}
+{$IFDEF UNIX}
+  {$linklib pthread}
+{$ENDIF}
+
+uses
+  SysUtils,
+  {$IFDEF UNIX} {$IFDEF UseCThreads}
+    cthreads,
+  {$ENDIF} {$ENDIF}
+  {$ifdef usefirebird}
+    SQLDBIndexDB, fbIndexdb,
+  {$else}
+    {$ifdef usemem}
+      memindexdb,
+    {$else}
+      SQLIteIndexDB,
+    {$endif}
+  {$endif}
+  fpIndexer;
+
+procedure usage;
+
+begin
+  Writeln('Usage : ',ExtractFileName(ParamStr(0)),' [-e] databasename word');
+  Writeln(' -e  : Exact match only');
+  halt(1);
+end;
+
+var
+  Search: TFPSearch;   //searches phrases
+  start: TDateTime;
+  endtime: TDateTime;
+  i: integer;
+  n: int64;
+
+{$ifdef usefirebird}
+  Function CreateDB(const dbName : String) : TCustomIndexDB;
+  Var
+    IB: TFBIndexDB;
+  begin
+    IB := TFBIndexDB.Create(nil);
+    IB.DatabasePath := dbname;
+    IB.UserName := 'WISASOFT';
+    IB.Password := 'SysteemD';
+    if not FileExists(IB.DatabasePath) then
+    begin
+      writeln('error: could not find index database');
+      halt;
+    end
+    else
+      IB.Connect;
+    Result:=IB;
+  end;
+{$else}
+  {$ifdef usemem}
+    Function CreateDB (const dbName : String) : TCustomIndexDB;
+    Var
+      FB: TFileIndexDB;
+    begin
+      FB:=TFileIndexDB.Create(Nil);
+      FB.FileName:=dbName;
+      FB.Connect;
+      Result:=FB;
+    end;
+  {$else}
+    Function CreateDB  (const dbName : String) : TCustomIndexDB;
+    Var
+      SB: TSQLIteIndexDB;
+    begin
+      SB := TSQLIteIndexDB.Create(nil);
+      SB.FileName := dbname;
+      if not FileExists(SB.FileName) then
+      begin
+        writeln('error: could not find index database');
+        halt;
+      end
+      else
+        SB.Connect;
+      Result:=SB;
+    end;
+  {$endif}
+{$endif}
+
+Var
+  DB : String;
+
+{$R *.res}
+
+begin
+  start := Now;
+  Search := TFPSearch.Create(nil);
+  //setup parameters for indexing
+  if (ParamCount<2) or (ParamCount>3) then
+    Usage;
+  if (ParamCount=2)  then
+    begin
+    DB:=ParamStr(1);
+    Search.SetSearchWord(ParamStr(2));
+    Search.Options := [soContains]; //allowed to search with wildcards
+    end
+  else
+    begin
+    if (ParamStr(1)<>'-e') then
+      Usage;
+    DB:=ParamStr(2);
+    Search.SetSearchWord(ParamStr(3));
+    end;
+  Search.Database := CreateDB(DB);
+  //execute the search
+  N := Search.Execute;
+  endtime := Now;
+  if N <> 0 then
+  begin
+    writeln('Searching for word: ', ParamStr(1));
+    writeln;
+
+    for i := 0 to Search.RankedCount - 1 do
+      with Search.RankedResults[i] do
+        writeln(Format('rank:%d word:%s pos:%d lang:%s %s filedate:%s context:%s', [Rank, SearchWord, Position, Language, URL, DateTimeToStr(FileDate), Context]));
+  end;
+
+  writeln;
+  writeln(Format('done in %.3f sec.', [(endtime - start) * 24 * 3600]));
+end.
+

+ 21 - 0
packages/fpindexer/examples/english.txt

@@ -0,0 +1,21 @@
+up
+one
+at
+use
+no
+or
+not
+as
+an
+if
+by
+it
+be
+see
+in
+and
+is
+on
+of
+to
+the

+ 75 - 0
packages/fpindexer/fpmake.pp

@@ -0,0 +1,75 @@
+{$ifndef ALLPACKAGES}
+{$mode objfpc}{$H+}
+program fpmake;
+
+uses fpmkunit;
+
+Const
+  SqldbConnectionOSes = [beos,linux,freebsd,win32,win64,wince,darwin,iphonesim,netbsd,openbsd];
+  SqliteOSes          = [beos,haiku,linux,freebsd,darwin,iphonesim,solaris,netbsd,openbsd,win32,wince];
+    
+Var
+  T : TTarget;
+  P : TPackage;
+begin
+  With Installer do
+    begin
+{$endif ALLPACKAGES}
+
+    P:=AddPackage('fpindexer');
+{$ifdef ALLPACKAGES}
+    P.Directory:='fpindexer';
+{$endif ALLPACKAGES}
+    P.Version:='2.7.1';
+    P.OSes := [beos,haiku,freebsd,darwin,solaris,netbsd,openbsd,linux,win32,win64,wince];
+    P.Dependencies.Add('fcl-base');
+    P.Dependencies.Add('fcl-db');
+    P.Dependencies.Add('chm'); // for fastreaderhtml
+    P.Dependencies.Add('sqlite');
+    P.Author := 'Free Pascal development team';
+    P.License := 'LGPL with modification, ';
+    P.HomepageURL := 'www.freepascal.org';
+    P.Email := '';
+    P.Description := 'Free Pascal text indexer and search engine.';
+    P.NeedLibC:= false;
+
+    P.SourcePath.Add('src');
+
+    T:=P.Targets.AddUnit('fpmasks.pp');
+   
+    T:=P.Targets.AddUnit('fpindexer.pp');
+    T.Dependencies.AddUnit('fpmasks');
+    T.ResourceStrings:=true;
+    
+    T:=P.Targets.AddUnit('memindexdb.pp');
+    T.ResourceStrings:=true;
+    T.Dependencies.AddUnit('fpindexer');
+    
+    T:=P.Targets.AddUnit('ireaderhtml.pp');
+    T.Dependencies.AddUnit('fpindexer');
+    
+    T:=P.Targets.AddUnit('ireaderpas.pp');
+    T.Dependencies.AddUnit('fpindexer');
+    
+    T:=P.Targets.AddUnit('ireadertxt.pp');
+    T.Dependencies.AddUnit('fpindexer');
+
+    T:=P.Targets.AddUnit('sqldbindexdb.pp',SqldbConnectionOSes);
+    T.Dependencies.AddUnit('fpindexer');
+    
+    T:=P.Targets.AddUnit('sqliteindexdb.pp',SqliteOSes);
+    T.Dependencies.AddUnit('fpindexer');
+    
+    T:=P.Targets.AddUnit('fbindexdb.pp',SqliteOSes);
+    T.Dependencies.AddUnit('fpindexer');
+    
+    T:=P.Targets.AddUnit('dbindexer.pp',SqldbConnectionOSes);
+    T.Dependencies.AddUnit('fpindexer');
+    T.Dependencies.AddUnit('ireadertxt');
+    
+    
+{$ifndef ALLPACKAGES}
+    Run;
+    end;
+end.
+{$endif ALLPACKAGES}

+ 442 - 0
packages/fpindexer/src/dbindexer.pp

@@ -0,0 +1,442 @@
+{
+    This file is part of the Free Component Library (FCL)
+    Copyright (c) 2012 by the Free Pascal development team
+
+    Database indexer
+    
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    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.
+
+ **********************************************************************}
+ 
+unit dbIndexer;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, ireadertxt, db, sqldb, ibconnection, fpindexer;
+
+Type
+
+  { TDBIndexer }
+
+  TDBIndexer = Class(TComponent)
+  private
+    FDatabase: TSQLConnection;
+    FFieldInURL: Boolean;
+    FIndexDB: TCustomIndexDB;
+    FIndexer: TFPIndexer;
+    FMFL: integer;
+    FSKipTables: TStrings;
+    function GetRecordCount(const TableName: string): Int64;
+    Function IndexRecord(const TableName: String; Dataset: TDataset;
+      KeyField: TField; List: TStrings) : int64;
+    procedure SetDatabase(AValue: TSQLConnection);
+    procedure SetIndexDB(AValue: TCustomIndexDB);
+    procedure SetSkipTables(AValue: TStrings);
+  Protected
+    Procedure CreateIndexer;
+    Procedure FreeIndexer;
+    Procedure Log(Msg : String);
+    Procedure Log(Fmt : String; Args : Array of const);
+    procedure Notification(AComponent: TComponent;Operation: TOperation); override;
+    procedure GetFieldNames(const TableName : String; List: TStrings; out KeyField: String); virtual;
+    Function IndexTable(const TableName: string) : int64; virtual;
+    Property Indexer : TFPIndexer read FIndexer;
+  Public
+    Constructor Create(AOwner : TComponent); override;
+    Destructor Destroy; override;
+    Procedure IndexDatabase;
+    Property Database : TSQLConnection Read FDatabase Write SetDatabase;
+    Property IndexDB : TCustomIndexDB Read FIndexDB Write SetIndexDB;
+    Property FieldInURL : Boolean Read FFieldInURL Write FFIeldInURl;
+    Property MinFieldLength : integer Read FMFL Write FMFL;
+    Property SkipTables : TStrings Read FSKipTables Write SetSkipTables;
+  end;
+
+  { TIBIndexer }
+
+  TIBIndexer = Class(TDBIndexer)
+    procedure GetFieldNames(const TableName : String; List: TStrings; out KeyField: String); override;
+  end;
+
+implementation
+
+uses dateutils;
+
+{ TIBIndexer }
+
+procedure TIBIndexer.GetFieldNames(const TableName: String; List: TStrings;
+  out KeyField: String);
+Const
+  SQLFieldNames =
+'  SELECT'
++'       rel_field.rdb$field_name AS FIELDNAME'
+{+'       rdb$field_type AS FIELDTYPE,'
++'       rdb$field_sub_type AS FIELDSUBTYPE,'
++'       rel_field.rdb$null_flag AS FIELDISNULL,'
++'       rdb$field_length AS FIELDSIZE,'
++'       rdb$field_scale AS FIELDSCALE,'
++'       rdb$character_length AS FIELDCHARLENGTH,'
++'       rdb$field_precision AS FIELDPRECISION,'
++'       field.rdb$default_source AS FIELDDEFAULT,'
++'       field.rdb$validation_source AS FIELDCHECK'}
++'     FROM'
++'       rdb$relations rel'
++'       JOIN rdb$relation_fields rel_field'
++'         ON rel_field.rdb$relation_name = rel.rdb$relation_name'
++'       JOIN rdb$fields field'
++'         ON rel_field.rdb$field_source = field.rdb$field_name'
++'     WHERE'
++'       (rel.rdb$relation_name = :TableName)'
++'       AND ((rdb$field_type in (14,37)'
++'             and (rdb$character_length >= :MinLength))'
++'            or ((rdb$field_type=261) AND(rdb$field_sub_type in (0,1))))'
++'     ORDER BY'
++'       rel_field.rdb$field_position,'
++'       rel_field.rdb$field_name';
+SQLPrimaryKeyField =
+'       SELECT'
++'            ixf.rdb$field_name as FIELDNAME'
+{
+  '            ix.rdb$relation_name AS TABLENAME,'
+  '            ix.rdb$index_name AS INDEXNAME,'
+  '            ix.rdb$unique_flag AS INDEXUNIQUE,'
+  '            ix.rdb$index_type AS INDEX_TYPE'
+}
++'          FROM'
++'            rdb$indices ix'
++'            JOIN rdb$relations rel'
++'              ON ix.rdb$relation_name = rel.rdb$relation_name'
++'            JOIN rdb$relation_constraints rel_con'
++'             on ((ix.rdb$relation_name = rel_con.rdb$relation_name)'
++'                 and (ix.rdb$index_name=rel_con.rdb$index_name))'
++'             JOIN rdb$index_segments ixf'
++'             on (ixf.rdb$index_name = ix.rdb$index_name)'
++'          WHERE'
++'              (rel.rdb$system_flag <> 1 OR rel.rdb$system_flag IS NULL)'
++'            AND'
++'              rel.rdb$relation_name = :TableName'
++'              AND rel_con.rdb$constraint_type=''PRIMARY KEY'''
++'          ORDER BY ix.rdb$relation_name, ix.rdb$index_name';
+
+Var
+  Q : TSQLQuery;
+
+begin
+  Q:=TSQLQuery.Create(Self);
+  try
+    Q.Database:=Self.Database;
+    Q.SQL.TExt:=SQLFieldNames;
+    Q.ParamByName('TableName').AsString:=TableName;
+    If MinFieldLength=0 then
+      MinFieldLength:=2;
+    Q.ParamByName('MinLength').AsInteger:=MinFieldLength;
+    Q.Open;
+    try
+      While not Q.EOF do
+        begin
+        List.Add(Trim(Q.Fields[0].AsString));
+        Q.Next;
+        end;
+    finally
+      Q.Close;
+    end;
+    Q.SQL.TExt:=SQLPrimaryKeyField;
+    Q.ParamByName('TableName').AsString:=TableName;
+    Q.Open;
+    try
+      If not Q.EOF then
+        KeyField:=Trim(Q.Fields[0].AsString);
+      Q.Next;
+      If not Q.EOF then
+        Raise Exception.CreateFmt('Primary key of table "%s" has multiple fields',[TableName]);
+    finally
+      Q.Close;
+    end;
+  finally
+    Q.Free;
+  end;
+
+end;
+
+{ TDBIndexer }
+
+procedure TDBIndexer.SetDatabase(AValue: TSQLConnection);
+begin
+  if FDatabase=AValue then exit;
+  if Assigned(FDatabase) then
+    FDatabase.RemoveFreeNotification(Self);
+  FDatabase:=AValue;
+  if Assigned(FDatabase) then
+    FDatabase.FreeNotification(Self);
+end;
+
+procedure TDBIndexer.SetIndexDB(AValue: TCustomIndexDB);
+begin
+  if FIndexDB=AValue then exit;
+  if Assigned(FIndexDB) then
+    FIndexDB.RemoveFreeNotification(Self);
+  FIndexDB:=AValue;
+  if Assigned(FIndexDB) then
+    FIndexDB.FreeNotification(Self);
+end;
+
+procedure TDBIndexer.SetSkipTables(AValue: TStrings);
+begin
+  if FSKipTables=AValue then exit;
+  FSKipTables.Assign(AValue);
+end;
+
+procedure TDBIndexer.CreateIndexer;
+begin
+  FIndexer:=TFPIndexer.Create(Self);
+  Findexer.FreeNotification(Self);
+  Findexer.Database:=IndexDB;
+  FIndexer.DetectLanguage:=False;
+  FIndexer.CommitFiles:=True;
+end;
+
+procedure TDBIndexer.FreeIndexer;
+begin
+  Findexer.Free;
+end;
+
+procedure TDBIndexer.Log(Msg: String);
+begin
+  Writeln(Msg);
+end;
+
+procedure TDBIndexer.Log(Fmt: String; Args: array of const);
+begin
+  Log(Format(Fmt,Args));
+end;
+
+procedure TDBIndexer.IndexDatabase;
+
+Var
+  TL : TStringList;
+  I : Integer;
+  Start : TDateTime;
+  C,D : Int64;
+
+begin
+  if FMFL=0 then
+    FMFL:=2;
+  TL:=TStringList.Create;
+  try
+    Database.GetTableNames(TL);
+    For I:=TL.Count-1 downto 0 do
+      if FSkipTables.IndexOf(TL[i])<>-1 then
+        TL.Delete(I);
+    Log('Found %d tables.',[TL.Count]);
+    if TL.Count=0 then
+      exit;
+    CreateIndexer;
+    try
+      For I:=0 to TL.Count-1 do
+        begin
+        Log('Indexing table %d/%d : %s',[I+1,TL.Count,TL[i]]);
+        Start:=Now;
+        C:=IndexTable(TL[i]);
+        D:=SecondsBetween(Now,Start);
+        if (D<>0) then
+          D:=Round(C/D);
+        Log('%d records. %d records/sec',[C,D]);
+        end;
+    finally
+      FreeIndexer;
+    end;
+  finally
+    TL.Free;
+  end;
+end;
+
+procedure TDBIndexer.Notification(AComponent: TComponent; Operation: TOperation
+  );
+
+begin
+  Inherited;
+  if Operation=opRemove then
+    if AComponent=FIndexer then
+      FIndexer:=Nil
+    else if AComponent=FDatabase then
+      FDatabase:=Nil;
+end;
+
+procedure TDBIndexer.GetFieldNames(Const TableName : String; List : TStrings; Out KeyField : String);
+
+begin
+  Database.GetFieldNames(TableName,List);
+  KeyField:=List[0];
+end;
+
+Function TDBIndexer.GetRecordCount(Const TableName : string) : Int64;
+
+begin
+  With TSQLQuery.Create(Self) do
+    try
+      Database:=Self.Database;
+      SQL.Text:=Format('SELECT COUNT(*) AS THECOUNT FROM %s',[TableName]);
+      Open;
+      If not (EOF and BOF) then
+        begin
+        if Fields[0].DataType=ftLargeInt then
+          Result:=(Fields[0] as TLargeIntField).AsLargeInt
+        else
+          Result:=Fields[0].AsInteger;
+        end
+      else
+        Result:=0;
+    finally
+      Free;
+    end;
+end;
+
+function TDBIndexer.IndexTable(Const TableName : string) : int64;
+
+Var
+  FL  : TStringList;
+  KF  : String;
+  SQL : String;
+  Q   : TSQLQuery;
+  I   : Integer;
+  KFF : TField;
+  RCount,TCount : Int64;
+  BS : Integer;
+
+begin
+  Result:=0;
+  FL:=TStringList.Create;
+  try
+    GetFieldNames(TableName,FL,KF);
+    if FL.Count=0 then
+      begin
+      Log('Table "%s" has no indexable fields.',[TAbleName]);
+      exit;
+      end;
+    if (KF='') then
+      begin
+      Log('Table "%s" has no key field.',[TableName]);
+      exit;
+      end;
+    FL.Sorted:=True;
+    SQL:=KF;
+    For I:=0 to FL.Count-1 do
+      begin
+      if (FL[i]<>KF) then
+        SQL:=SQL+', '+FL[i];
+      end;
+    SQL:='SELECT '+SQL+' FROM '+TableName;
+    Log('SQL : %s',[SQL]);
+    RCount:=0;
+    Result:=0;
+    TCount:=GetRecordCount(TableName);
+    if TCount>10000 then
+      BS:=1000
+    else
+      BS:=100;
+    Q:=TSQLQuery.Create(Self);
+    try
+      Q.SQL.Text:=SQL;
+      Q.UniDirectional:=True;
+      Q.DataBase:=Self.Database;
+      Q.Open;
+      KFF:=Q.FieldByName(KF);
+      For I:=0 to FL.Count-1 do
+        FL.Objects[i]:=Q.FieldByName(FL[i]);
+      While Not Q.EOF do
+        begin
+        Inc(RCount);
+        If (RCount mod BS)=1 then
+          Log('Indexing record %d of %d',[RCount,TCount]);
+        IndexRecord(TableName,Q,KFF,FL);
+        Inc(Result);
+        Q.Next;
+        end;
+    finally
+      Q.Free;
+    end;
+  finally
+    FL.Free;
+  end;
+end;
+
+constructor TDBIndexer.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FSkipTables:=TStringList.Create;
+  TStringList(FSkipTables).Sorted:=True;
+end;
+
+destructor TDBIndexer.Destroy;
+begin
+  FreeAndNil(FSkipTables);
+  inherited Destroy;
+end;
+
+Function TDBIndexer.IndexRecord(Const TableName : String; Dataset : TDataset; KeyField : TField; List : TStrings) : Int64;
+
+Var
+  URL,FURL : String;
+  F   : TField;
+  SS  : TStringStream;
+  I : Integer;
+  R : TIReaderTxt;
+  S,T : String;
+  wc : Int64;
+
+begin
+  Result:=0;
+  R:=TIReaderTXT.Create;
+  try
+    URL:=TableName+'/'+KeyField.AsString;
+    For I:=0 to List.Count-1 do
+      begin
+      F:=TField(List.Objects[i]);
+      if (F.DataType in [ftString,ftWideString,ftMemo]) and (F.Size>MinFieldLength) then
+        begin
+        If FieldInURL Then
+          begin
+          SS:=TStringStream.Create(F.AsString);
+          try
+            FURL:=URL+'/'+F.AsString;
+            WC:=Indexer.IndexStream(FURL,Now,SS,R);
+            Result:=Result+WC;
+  //        Writeln(URL,' : ',F.FieldName,' (',F.Size,')');
+          finally
+            SS.Free;
+          end;
+          end
+        else
+          begin
+          S:=Trim(F.AsString);
+          if (S<>'') then
+            T:=T+' '+F.AsString;
+          end;
+        end;
+      end;
+    if (not FieldInURL) and (T<>'') then
+      begin
+      SS:=TStringStream.Create(T);
+      try
+        WC:=Indexer.IndexStream(URL,Now,SS,R);
+        Result:=WC;
+//        Writeln(URL,' : "',T,'" : ',wc);
+      finally
+        SS.Free;
+      end;
+      end
+  finally
+    R.Free;
+  end;
+end;
+
+end.
+

+ 177 - 0
packages/fpindexer/src/fbindexdb.pp

@@ -0,0 +1,177 @@
+{
+    This file is part of the Free Component Library (FCL)
+    Copyright (c) 2012 by the Free Pascal development team
+
+    SQLDB Firebird based indexer
+    
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    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.
+
+ **********************************************************************}
+unit fbIndexdb;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpIndexer, sqldbIndexDB ,sqldb, ibconnection;
+
+type
+  { TFBIndexDB }
+
+  TFBIndexDB = class(TSQLDBIndexDB)
+  private
+    FIB: TIBConnection;
+    FGenQuery: Array[TIndexTable] of TSQLQuery;
+    Function GetGenQuery(ATable : TIndexTable) : TSQLQuery;
+    function GetS(AIndex: integer): string;
+    procedure SetS(AIndex: integer; const AValue: string);
+  protected
+    procedure InsertMatch(AWordID, aFileID, aLanguageID: int64; const ASearchData: TSearchWordData); override;
+    function InsertWord(const AWord: string): int64; override;
+    function InsertURL(const URL: string; ATimeStamp: TDateTime; ALanguageID: int64): int64; override;
+    function InsertLanguage(const ALanguage: string): int64; override;
+    function GetConnection: TSQLConnection; override;
+    function GetID(TableType: TIndexTable): int64;
+    procedure FinishCreateTable(const TableType: TIndexTable); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+  published
+    property DatabasePath: string Index 0 read GetS write SetS;
+    property UserName: string Index 1 read GetS write SetS;
+    property Password: string Index 2 read GetS write SetS;
+  end;
+
+implementation
+
+{ TFBIndexDB }
+
+function TFBIndexDB.GetID(TableType: TIndexTable): int64;
+
+var
+  Q: TSQLQuery;
+  S: string;
+
+begin
+  Q := GetGenQuery(TableType);
+  Q.Open;
+  try
+    if (Q.EOF and Q.BOF) then
+      raise Exception.CreateFmt('Could not get ID for table %s', [GetTableName(TableType)]);
+    Result := Q.Fields[0].AsLargeInt;
+  finally
+    Q.Close;
+  end;
+end;
+
+function TFBIndexDB.InsertLanguage(const ALanguage: string): int64;
+var
+  Q: TSQLQuery;
+begin
+  Result := getID(itLanguages);
+  Q := CreateCachedQuery(cqtInsertLanguage, InsertSQL(itLanguages));
+  Q.ParamByName(GetFieldName(ifLanguagesID)).AsLargeInt := Result;
+  Q.ParamByName(GetFieldName(ifLanguagesName)).AsString := ALanguage;
+  Q.ExecSQL;
+end;
+
+function TFBIndexDB.InsertWord(const AWord: string): int64;
+var
+  Q: TSQLQuery;
+begin
+  Result := getID(itWords);
+  Q := CreateCachedQuery(cqtInsertWord, InsertSQL(itWords));
+  Q.ParamByName(GetFieldName(ifWordsID)).AsLargeInt := Result;
+  Q.ParamByName(GetFieldName(ifWordsWord)).AsString := AWord;
+  Q.ExecSQL;
+end;
+
+function TFBIndexDB.InsertURL(const URL: string; ATimeStamp: TDateTime; ALanguageID: int64): int64;
+var
+  Q: TSQLQuery;
+begin
+  Result := getID(itFiles);
+  Q := CreateCachedQuery(cqtInsertFile, InsertSQL(itFiles));
+  Q.ParamByName(GetFieldName(ifFilesID)).AsLargeInt := Result;
+  Q.ParamByName(GetFieldName(ifFilesURL)).AsString := URL;
+  Q.ParamByName(GetFieldName(ifFilesTimeStamp)).AsDateTime := ATimeStamp;
+  Q.ParamByName(GetFieldName(ifFilesLanguageID)).AsLargeInt := ALanguageID;
+  Q.ParamByName(GetFieldName(ifFilesUpdated)).AsInteger := 0;
+  Q.ParamByName(GetFieldName(ifFilesReindex)).AsInteger := 0;
+  Q.ExecSQL;
+end;
+
+procedure TFBIndexDB.InsertMatch(AWordID, aFileID, aLanguageID: int64; const ASearchData: TSearchWordData);
+var
+  Q: TSQLQuery;
+begin
+  Q := CreateCachedQuery(cqtInsertMatch, InsertSQL(itMatches));
+  Q.ParamByName(GetFieldName(ifMatchesID)).AsLargeInt := GetID(itMatches);
+  Q.ParamByName(GetFieldName(ifMatchesLanguageID)).AsLargeInt := aLanguageID;
+  Q.ParamByName(GetFieldName(ifMatchesWordID)).AsLargeInt := aWordID;
+  Q.ParamByName(GetFieldName(ifMatchesFileID)).AsLargeInt := aFileID;
+  Q.ParamByName(GetFieldName(ifMatchesPosition)).AsLargeInt := ASearchData.Position;
+  Q.ParamByName(GetFieldName(ifMatchesContext)).AsString := ASearchData.Context;
+  Q.ExecSQL;
+end;
+
+function TFBIndexDB.GetConnection: TSQLConnection;
+begin
+  Result := FiB;
+end;
+
+procedure TFBIndexDB.FinishCreateTable(const TableType: TIndexTable);
+begin
+  Execute('CREATE GENERATOR ' + DefaultGeneratorNames[TableType], True);
+end;
+
+constructor TFBIndexDB.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FIB := TIBConnection.Create(nil);
+end;
+
+destructor TFBIndexDB.Destroy;
+begin
+  // Parent destroys FIB.
+  inherited Destroy;
+end;
+
+function TFBIndexDB.GetGenQuery(ATable: TIndexTable): TSQLQuery;
+begin
+  If (FGenQuery[ATable]=Nil) then
+    begin
+    FGenQuery[ATable]:=CreateQuery(Format('SELECT GEN_ID(%S,1) FROM RDB$DATABASE', [DefaultGeneratorNames[ATable]]));
+    FGenQuery[ATable].Prepare;
+    end;
+  Result:=FGenQuery[ATable];
+end;
+
+function TFBIndexDB.GetS(AIndex: integer): string;
+begin
+  case Aindex of
+    0: Result := FIB.DatabaseName;
+    1: Result := FIB.UserName;
+    2: Result := FIB.Password;
+    3: Result := FIB.HostName;
+  end;
+end;
+
+procedure TFBIndexDB.SetS(AIndex: integer; const AValue: string);
+begin
+  case Aindex of
+    0: FIB.DatabaseName := AValue;
+    1: FIB.UserName := AValue;
+    2: FIB.Password := AValue;
+    3: FIB.HostName := AValue;
+  end;
+end;
+
+end.
+

+ 1704 - 0
packages/fpindexer/src/fpindexer.pp

@@ -0,0 +1,1704 @@
+{
+    This file is part of the Free Component Library (FCL)
+    Copyright (c) 2012 by the Free Pascal development team
+
+    Basic indexer
+    
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    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.
+
+ **********************************************************************}
+ 
+unit fpIndexer;
+
+{$mode objfpc}{$H+}
+
+{ $define LangDetect}
+
+interface
+
+uses
+  Classes, SysUtils;
+
+type
+  TWordTokenType = (wtOr, wtAnd, wtWord);
+
+  TWordToken = record
+    Value: string;
+    TokenType: TWordTokenType;
+  end;
+
+  TIgnoreListDef = class;
+
+  { TWordParser }
+
+  TWordParser = class
+  private
+    FCount: integer;
+    FWildCardChar: char;
+    WordList: array of TWordToken;
+
+    procedure AddToken(AValue: string; ATokenType: TWordTokenType);
+    function GetSearchWordQuery: string;
+    function GetToken(index: integer): TWordToken;
+    procedure SetCount(AValue: integer);
+  public
+    constructor Create(ASearchWords: string);
+
+    property Count: integer read FCount write SetCount;
+    property WildCardChar: char read FWildCardChar write FWildCardChar;
+    property SearchWordQuery: string read GetSearchWordQuery;
+    property Token[index: integer]: TWordToken read GetToken;
+  end;
+
+  TSearchOption = (soContains);
+  TSearchOptions = set of TSearchOption;
+
+  TSearchWordData = record
+    Context: string;
+    FileDate: TDateTime;
+    Language: string;
+    Position: int64;
+    Rank: integer;
+    SearchWord: string;
+    URL: string;
+  end;
+
+  TFPSearch = class;
+
+  { TCustomIndexDB }
+
+  TCustomIndexDB = class(TComponent)
+  public
+    procedure CreateDB; virtual; abstract;
+    procedure Connect; virtual; abstract;
+    procedure Disconnect; virtual;
+    procedure CompactDB; virtual; abstract;
+    procedure BeginTrans; virtual; abstract;
+    procedure CommitTrans; virtual; abstract;
+    procedure DeleteWordsFromFile(URL: string); virtual; abstract;
+    procedure AddSearchData(ASearchData: TSearchWordData); virtual; abstract;
+    procedure FindSearchData(SearchWord: TWordParser; FPSearch: TFPSearch; SearchOptions: TSearchOptions); virtual; abstract;
+    procedure CreateIndexerTables; virtual; abstract;
+  end;
+
+  TDatabaseID = record
+    Name: string;
+    ID: integer;
+  end;
+
+  { ---------------------------------------------------------------------
+    SQL-Based databases support
+    ---------------------------------------------------------------------}
+
+  TIndexTable = (itWords, itLanguages, itFiles, itMatches);
+  TIndexIndex = (iiWords, iiMatches, iiLanguages, iiFiles);
+  TIndexField = (ifWordsID, ifWordsWord,
+    ifMatchesID, ifMatchesWordId, ifMatchesFileID, ifMatchesLanguageID,
+    ifMatchesPosition, ifMatchesContext, ifLanguagesID, ifLanguagesName,
+    ifFilesID, ifFilesURL, ifFilesReindex, ifFilesUpdated, ifFilesTimeStamp,
+    ifFilesLanguageID);
+  TIndexForeignKey = (ikFilesLanguage, ikMatchesWord, ikMatchesFile, ikMatchesLanguage);
+  TIndexTables = set of TIndexTable;
+  TIndexIndexes = set of TIndexIndex;
+  TIndexFields = set of TIndexField;
+
+const
+  MaxContextLen = 255;
+  TableFields: array[TIndexField] of TIndexTable =
+    (itWords, itWords,
+    itMatches, itMatches, itMatches, itMatches, itMatches, itMatches,
+    itLanguages, itLanguages,
+    itFiles, itFiles, itFiles, itFiles, itFiles, itFiles);
+
+  DefaultTableNames: array[TIndexTable] of string = ('WORDS', 'FILELANGUAGES', 'FILENAMES', 'WORDMATCHES');
+  DefaultIndexNames: array[TIndexIndex] of string = ('I_WORDS', 'I_WORDMATCHES', 'I_FILELANGUAGES', 'I_FILENAMES');
+  DefaultFieldNames: array[TIndexField] of string = (
+    'W_ID', 'W_WORD',
+    'WM_ID', 'WM_WORD_FK', 'WM_FILE_FK', 'WM_LANGUAGE_FK', 'WM_POSITION', 'WM_CONTEXT',
+    'FL_ID', 'FL_NAME',
+    'FN_ID', 'FN_URL', 'FN_REINDEX', 'FN_UPDATED', 'FN_TIMESTAMP', 'FN_LANGUAGE_FK');
+  ForeignKeyTables: array[TIndexForeignKey] of TIndexTable = (itFiles, itMatches, itMatches, itMatches);
+  ForeignKeyTargets: array[TIndexForeignKey] of TIndexTable = (itLanguages, itWords, itFiles, itLanguages);
+  ForeignKeyFields: array[TIndexForeignKey] of TIndexField = (ifFilesLanguageID, ifMatchesWordID, ifMatchesFileID, ifMatchesLanguageID);
+  ForeignKeyTargetFields: array[TIndexForeignKey] of TIndexField = (ifLanguagesID, ifWordsID, ifFilesID, ifLanguagesID);
+  DefaultForeignKeyNames: array[TIndexForeignKey] of string = ('R_FILES_LANGUAGE', 'R_MATCHES_WORD', 'R_MATCHES_FILE', 'R_MATCHES_LANGUAGE');
+  IdFieldType = 'BIGINT NOT NULL';
+  PrimaryFieldType = IdFieldType + ' PRIMARY KEY';
+  PosFieldType = 'BIGINT';
+  FlagFieldType = 'SMALLINT';
+  TextFieldType = 'VARCHAR(100) NOT NULL';
+  LargeTextFieldType = 'VARCHAR(255) NOT NULL';
+  TimeStampFieldType = 'TIMESTAMP';
+  DefaultFieldTypes: array[TIndexField] of string = (
+    PrimaryFieldType, TextFieldType, PrimaryFieldType, IdFieldType, IdFieldType,
+    IdFieldType, PosFieldType, LargeTextFieldType, PrimaryFieldType, TextFieldType,
+    PrimaryFieldType, LargeTextFieldType, FlagFieldType, FlagFieldType, TimeStampFieldType,
+    IdFieldType);
+
+type
+
+  { TSQLIndexDB }
+
+  TSQLIndexDB = class(TCustomIndexDB)
+  protected
+    function CreateForeignKey(const ForeignKey: TIndexForeignKey; ForCreate: boolean = False): string;
+    function CreateIndexSQL(const AIndexName, ATableName: string; const AFieldList: array of string): string; virtual;
+    function CreateTableIndex(IndexType: TIndexIndex): string; virtual;
+    function CreateTableSQL(const TableType: TIndexTable): string; virtual;
+    function DeleteWordsSQL(UseParams: boolean = True): string; virtual;
+    function DropTableSQl(TableType: TIndexTable): string; virtual;
+    function GetFieldName(FieldType: TIndexField): string; virtual;
+    function GetFieldType(FieldType: TIndexField): string; virtual;
+    function GetForeignKeyName(ForeignKey: TIndexForeignKey): string; virtual;
+    function GetIndexName(IndexType: TIndexIndex): string; virtual;
+    function GetLanguageSQL(UseParams: boolean = True): string; virtual;
+    function GetMatchSQL(SearchOptions: TSearchOptions; SearchWord: TWordParser; UseParams: boolean = True): string; virtual;
+    function GetSearchFileSQL(UseParams: boolean = True): string; virtual;
+    function GetSearchSQL(ATable: TIndexTable; IDField, SearchField: TINdexField; UseParams: boolean = True): string; virtual;
+    function GetTableName(TableType: TIndexTable): string; virtual;
+    function GetUrlSQL(UseParams: boolean = True): string; virtual;
+    function GetWordSQL(UseParams: boolean = True): string; virtual;
+    function InsertSQL(const TableType: TIndexTable; UseParams: boolean = True): string; virtual;
+    procedure FinishCreateTable(const TableType: TIndexTable); virtual;
+  protected
+    class function AllowForeignKeyInTable: boolean; virtual;
+    procedure Execute(const sql: string; ignoreErrors: boolean = True); virtual; abstract;
+    function GetURLID(const URL: string; ATimeStamp: TDateTime; ALanguageID: int64; DoCreate: boolean = True): int64; virtual; abstract;
+  public
+    procedure CreateIndexerTables; override;
+    procedure DeleteWordsFromFile(URL: string); override;
+  end;
+
+  TCustomFileReader = class;
+
+  TOnSearchWordEvent = procedure(AReader: TCustomFileReader; var AWord: TSearchWordData) of object;
+  { TCustomFileReader }
+
+  TCustomFileReader = class
+  private
+    FCount: integer;
+    FDetectLanguage: boolean;
+    FIgnoreNumeric: boolean;
+    FLanguage: string;
+    FOnAdd: TOnSearchWordEvent;
+    FSearchWord: array of TSearchWordData;
+    FStream: TStream;
+    FStreamPos: integer;
+    FUseIgnoreList: boolean;
+    FIgnoreListDef: TIgnoreListDef;
+    FNoListFound: boolean;
+    FTokenStartPos: integer;
+    FContext: string;
+    function GetCapacity: integer;
+    function GetSearchWord(index: integer): TSearchWordData;
+    procedure SetCapacity(AValue: integer);
+    procedure SetStream(AValue: TStream);
+    procedure SetStreamPos(AValue: integer);
+  protected
+    function AllowedToken(token: string): boolean; virtual;
+    function GetToken: string; virtual;
+    function GetContext: string;
+    function AllowWord(var ASearchWord: TSearchWordData): boolean;
+    procedure Add(var ASearchWord: TSearchWordData);
+    procedure DoDetectLanguage;
+    property Stream: TStream read FStream write SetStream;
+    property StreamPos: integer read FStreamPos write SetStreamPos;
+    property TokenStartPos: integer read FTokenStartPos;
+  public
+    constructor Create;
+    destructor Destroy; override;
+    property DetectLanguage: boolean read FDetectLanguage write FDetectLanguage;
+    property Language: string read FLanguage write FLanguage;
+    procedure LoadFromStream(FileStream: TStream); virtual;
+    property SearchWord[index: integer]: TSearchWordData read GetSearchWord;
+    property Count: integer read FCount;
+    property Capacity: integer read GetCapacity write SetCapacity;
+    property OnAddSearchWord: TOnSearchWordEvent read FOnAdd write FOnAdd;
+    property UseIgnoreList: boolean read FUseIgnoreList write FUseIgnoreList;
+    property IgnoreNumeric: boolean read FIgnoreNumeric write FIgnoreNumeric;
+  end;
+
+  TCustomFileReaderClass = class of TCustomFileReader;
+
+
+  TAddWordStub = class(TObject)
+  private
+    FCount: int64;
+    FURL: string;
+    FDateTime: TDateTime;
+    FDatabase: TCustomIndexDB;
+  public
+    constructor Create(const AURL: string; const ADateTime: TDateTime; ADatabase: TCustomIndexDB);
+    procedure DoAddWord(AReader: TCustomFileReader; var AWord: TSearchWordData); virtual;
+    property Count: int64 read FCount;
+  end;
+
+  { TFPIndexer }
+
+  TIndexProgressEvent = procedure(Sender: TObject; const ACurrent, ACount: integer; const AURL: string) of object;
+
+  TFPIndexer = class(TComponent)
+  private
+    FCommitFiles: boolean;
+    FDatabase: TCustomIndexDB;
+    FDetectLanguage: boolean;
+    FErrorCount: int64;
+    FExcludeFileMask: string;
+    FFileMask: string;
+    FIgnoreNumeric: boolean;
+    FLanguage: string;
+    FOnProgress: TIndexProgressEvent;
+    FSearchPath: string;
+    FSearchRecursive: boolean;
+    FUseIgnoreList: boolean;
+    ExcludeMaskPatternList: TStrings;
+    MaskPatternList: TStrings;
+    procedure SetDatabase(AValue: TCustomIndexDB);
+    procedure SetExcludeFileMask(AValue: string);
+    procedure SetFileMask(AValue: string);
+    procedure SetSearchPath(AValue: string);
+  protected
+    procedure DoProgress(const ACurrent, ACount: integer; const URL: string); virtual;
+    procedure SearchFiles(const PathName, FileName: string; const Recursive: boolean; AList: TStrings); virtual;
+    procedure ExcludeFiles(const ExcludeMask: string; AList: TStrings); virtual;
+  public
+    constructor Create(AOwner: TComponent); overload;
+    destructor Destroy; override;
+
+    function IndexStream(const AURL: string; ADateTime: TDateTime; S: TStream; Reader: TCustomFileReader): int64; virtual;
+    function IndexFile(AURL: string; AllowErrors: boolean; const ALanguage: string = ''): int64;
+    function Execute(AllowErrors: boolean): int64;
+    property ErrorCount: int64 read FErrorCount;
+    property Language: string read FLanguage write FLanguage;
+    property OnProgress: TIndexProgressEvent read FOnProgress write FOnProgress;
+    property UseIgnoreList: boolean read FUseIgnoreList write FUseIgnoreList;
+    property IgnoreNumeric: boolean read FIgnoreNumeric write FIgnoreNumeric;
+    property CommitFiles: boolean read FCommitFiles write FCommitFiles;
+  published
+    property Database: TCustomIndexDB read FDatabase write SetDatabase;
+    property ExcludeFileMask: string read FExcludeFileMask write SetExcludeFileMask;
+    property FileMask: string read FFileMask write SetFileMask;
+    property SearchPath: string read FSearchPath write SetSearchPath;
+    property SearchRecursive: boolean read FSearchRecursive write FSearchRecursive;
+    property DetectLanguage: boolean read FDetectLanguage write FDetectLanguage;
+  end;
+
+  { TFileReaderDef }
+
+  TFileReaderDef = class(TCollectionItem)
+  private
+    FExtensions, FTypeName, FDefaultExt: string;
+    FReader: TCustomFileReaderClass;
+  public
+    function HandlesExtension(const Ext: string): boolean; virtual;
+    function CreateReader(const AURL: string): TCustomFileReader; virtual;
+    procedure DisposeReader(AReader: TCustomFileReader); virtual;
+    property Extensions: string read FExtensions write FExtensions;
+    property TypeName: string read FTypeName write FTypeName;
+    property DefaultExt: string read FDefaultExt write FDefaultExt;
+    property Reader: TCustomFileReaderClass read FReader write FReader;
+  end;
+
+  { TFileReaderDefs }
+
+  TFileReaderDefs = class(TCollection)
+  private
+    function GetD(AIndex: integer): TFileReaderDef;
+    procedure SetD(AIndex: integer; AValue: TFileReaderDef);
+  public
+    function AddFileReader(const ATypeName: string): TFileReaderDef;
+    function IndexOfTypeName(const ATypeName: string): integer;
+    property Defs[AIndex: integer]: TFileReaderDef read GetD write SetD; default;
+  end;
+
+  { TFileHandlersManager }
+
+  TFileHandlersManager = class
+  private
+    FData: TFileReaderDefs;
+    function GetCount: integer;
+    function GetData(const ATypeName: string): TFileReaderDef;
+    function GetData(index: integer): TFileReaderDef;
+    function GetDefExt(const TypeName: string): string;
+    function GetExt(const TypeName: string): string;
+    function GetReader(const TypeName: string): TCustomFileReaderClass;
+    function GetTypeName(index: integer): string;
+  protected
+    function CreateFileReaderDefs: TFileReaderDefs; virtual;
+  public
+    constructor Create;
+    destructor Destroy; override;
+    function GetDefsForExtension(const Extension: string; List: TStrings): integer;
+    procedure RegisterFileReader(const ATypeName, TheExtensions: string; AReader: TCustomFileReaderClass);
+    property Count: integer read GetCount;
+    property DefaultExtension[const TypeName: string]: string read GetDefExt;
+    property Extensions[const TypeName: string]: string read GetExt;
+    property FileReader[const TypeName: string]: TCustomFileReaderClass read GetReader;
+    property TypeNames[index: integer]: string read GetTypeName;
+  end;
+
+  { TFPSearch }
+
+  TFPSearch = class (TComponent)
+  private
+    FCount: integer;
+    FDatabase: TCustomIndexDB;
+    FOptions: TSearchOptions;
+    FRankedCount: integer;
+    FSearchWord: TWordParser;
+    ResultList: array of TSearchWordData;
+    RankedList: array of TSearchWordData;
+    function GetRankedResults(index: integer): TSearchWordData;
+    function GetResults(index: integer): TSearchWordData;
+    procedure SetDatabase(AValue: TCustomIndexDB);
+    procedure RankResults;
+    procedure SetRankedCount(AValue: integer);
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+
+    function Execute: int64;
+    procedure AddResult(index: integer; AValue: TSearchWordData);
+    property Count: integer read FCount;
+    property RankedCount: integer read FRankedCount write SetRankedCount;
+    property Results[index: integer]: TSearchWordData read GetResults;
+    property RankedResults[index: integer]: TSearchWordData read GetRankedResults;
+    procedure SetSearchWord(AValue: string);
+  published
+    property Database: TCustomIndexDB read FDatabase write SetDatabase;
+    property Options: TSearchOptions read FOptions write FOptions;
+    property SearchWord: TWordParser read FSearchWord;
+  end;
+
+  { TIgnoreListDef }
+
+  TIgnoreListDef = class(TCollectionItem)
+  private
+    FLanguage: string;
+    FList: TStrings;
+    procedure SetStrings(AValue: TStrings);
+  public
+    constructor Create(ACollection: TCollection); override;
+    destructor Destroy; override;
+    procedure BeginLoading;
+    procedure EndLoading;
+    function IgnoreWord(const AWord: string): boolean;
+    property List: TStrings read FList write SetStrings;
+    property Language: string read FLanguage write FLanguage;
+  end;
+
+  { TIgnoreLists }
+
+  TIgnoreLists = class(TCollection)
+  private
+    function getL(AIndex: integer): TIgnoreListDef;
+    procedure SetL(AIndex: integer; AValue: TIgnoreListDef);
+  public
+    function IndexOfLanguage(const ALanguage: string): integer;
+    function FindLanguage(const ALanguage: string): TIgnoreListDef;
+    function LanguageByName(const ALanguage: string): TIgnoreListDef;
+    function AddLanguage(const ALanguage: string): TIgnoreListDef;
+    property Lists[AIndex: integer]: TIgnoreListDef read getL write SetL; default;
+  end;
+
+  { TIgnoreListManager }
+
+  TIgnoreListManager = class(TComponent)
+  private
+    FLists: TIgnoreLists;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    procedure RegisterIgnoreWords(const ALanguage: string; AList: TStrings);
+    procedure LoadIgnoreWordsFromFile(const ALanguage, AFileName: string);
+    property Lists: TIgnoreLists read FLists;
+  end;
+
+  EFPIndexer = class(Exception);
+
+var
+  FileHandlers: TFileHandlersManager;
+  IgnoreListManager: TIgnoreListManager;
+
+function DateToISO8601(DateTime: TDateTime): string;
+function ISO8601ToDate(DateTime: string): TDateTime;
+
+function QuoteString(S: string): string;
+
+implementation
+
+uses
+  {$ifdef LangDetect}
+     fpTextCat, Math,
+  {$endif}
+  fpmasks;     //please note that this is an LCL unit, should be moved to FCL afaic
+
+resourcestring
+  SErrNoSuchLanguage = 'Unknown language : "%s".';
+
+function DateToISO8601(DateTime: TDateTime): string;
+begin
+  Result := FormatDateTime('yyyy-mm-dd', DateTime) + 'T' +
+            FormatDateTime('hh:mm:ss', DateTime)
+end;
+
+function ISO8601ToDate(DateTime: string): TDateTime;
+begin
+  Result := EncodeDate(StrToInt(copy(DateTime, 1, 4)),
+                       StrToInt(copy(DateTime, 6, 2)),
+                       StrToInt(copy(DateTime, 9, 2))) +
+            EncodeTime(StrToInt(copy(DateTime, 12, 2)),
+                       StrToInt(copy(DateTime, 15, 2)),
+                       StrToInt(copy(DateTime, 18, 2)),
+                       0);
+end;
+
+function QuoteString(S: string): string;
+begin
+  Result := '''' + S + '''';
+end;
+
+function CalcDefExt(TheExtensions: string): string;
+var
+  p: integer;
+begin
+  p := pos(';', TheExtensions);
+  if p = 0 then
+    Result := TheExtensions
+  else
+    Result := Copy(TheExtensions, 1, p - 1);
+end;
+
+{ TIgnoreListManager }
+
+constructor TIgnoreListManager.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FLists := TIgnoreLists.Create(TIgnoreListDef);
+end;
+
+destructor TIgnoreListManager.Destroy;
+begin
+  FreeAndNil(FLists);
+  inherited Destroy;
+end;
+
+procedure TIgnoreListManager.RegisterIgnoreWords(const ALanguage: string; AList: TStrings);
+
+var
+  L: TIgnoreListDef;
+
+begin
+  L := FLists.FindLanguage(ALanguage);
+  if (L = nil) then
+  begin
+    L := FLists.AddLanguage(ALanguage);
+  end;
+  L.BeginLoading;
+  try
+    L.List.AddStrings(AList);
+  finally
+    L.EndLoading;
+  end;
+end;
+
+procedure TIgnoreListManager.LoadIgnoreWordsFromFile(const ALanguage, AFileName: string);
+
+var
+  L: TStringList;
+
+begin
+  L := TStringList.Create;
+  try
+    L.LoadFromFile(AFileName);
+    RegisterIgnoreWords(ALanguage, L)
+  finally
+    L.Free;
+  end;
+end;
+
+{ TIgnoreLists }
+
+function TIgnoreLists.getL(AIndex: integer): TIgnoreListDef;
+begin
+  Result := TIgnoreListDef(Items[AIndex]);
+end;
+
+procedure TIgnoreLists.SetL(AIndex: integer; AValue: TIgnoreListDef);
+begin
+  Items[AIndex] := AValue;
+end;
+
+function TIgnoreLists.IndexOfLanguage(const ALanguage: string): integer;
+begin
+  Result := Count - 1;
+  while (Result >= 0) and (CompareText(ALanguage, GetL(Result).Language) <> 0) do
+    Dec(Result);
+end;
+
+function TIgnoreLists.FindLanguage(const ALanguage: string): TIgnoreListDef;
+
+var
+  i: integer;
+
+begin
+  I := IndexOfLanguage(ALanguage);
+  if (I = -1) then
+    Result := nil
+  else
+    Result := GetL(I);
+end;
+
+function TIgnoreLists.LanguageByName(const ALanguage: string): TIgnoreListDef;
+begin
+  Result := FindLanguage(Alanguage);
+  if (Result = nil) then
+    raise EFPIndexer.CreateFmt(SErrNoSuchLanguage, [ALanguage]);
+end;
+
+function TIgnoreLists.AddLanguage(const ALanguage: string): TIgnoreListDef;
+begin
+  Result := Add as TIgnoreListDef;
+  Result.Language := ALanguage;
+end;
+
+{ TIgnoreListDef }
+
+procedure TIgnoreListDef.SetStrings(AValue: TStrings);
+begin
+  if FList = AValue then
+    exit;
+  FList.Assign(AValue);
+end;
+
+constructor TIgnoreListDef.Create(ACollection: TCollection);
+begin
+  inherited Create(ACollection);
+  FList := TStringList.Create;
+end;
+
+destructor TIgnoreListDef.Destroy;
+begin
+  FreeAndNil(FList);
+  inherited Destroy;
+end;
+
+procedure TIgnoreListDef.BeginLoading;
+begin
+  TStringList(FList).Sorted := False;
+end;
+
+procedure TIgnoreListDef.EndLoading;
+begin
+  TStringList(FList).Sorted := True;
+end;
+
+function TIgnoreListDef.IgnoreWord(const AWord: string): boolean;
+begin
+  Result := FList.IndexOf(AWord) <> -1;
+end;
+
+{ TCustomIndexDB }
+
+procedure TCustomIndexDB.Disconnect;
+begin
+  // Do nothing
+end;
+
+function TFileReaderDef.HandlesExtension(const Ext: string): boolean;
+begin
+  Result := Pos(lowercase(ext) + ';', FExtensions + ';') <> 0;
+end;
+
+function TFileReaderDef.CreateReader(const AURL: string): TCustomFileReader;
+begin
+  Result := FReader.Create;
+end;
+
+procedure TFileReaderDef.DisposeReader(AReader: TCustomFileReader);
+begin
+  AReader.Free;
+end;
+
+{ TFileReaderDefs }
+
+function TFileReaderDefs.GetD(AIndex: integer): TFileReaderDef;
+begin
+  Result := TFileReaderDef(Items[AIndex]);
+end;
+
+procedure TFileReaderDefs.SetD(AIndex: integer; AValue: TFileReaderDef);
+begin
+  Items[AIndex] := AValue;
+end;
+
+function TFileReaderDefs.AddFileReader(const ATypeName: string): TFileReaderDef;
+begin
+  Result := Add as TFileReaderDef;
+  Result.FTypeName := ATypeName;
+end;
+
+function TFileReaderDefs.IndexOfTypeName(const ATypeName: string): integer;
+begin
+  Result := Count - 1;
+  while (Result >= 0) and (CompareText(AtypeName, GetD(Result).TypeName) <> 0) do
+    Dec(Result);
+end;
+
+
+{ TWordParser }
+
+procedure TWordParser.AddToken(AValue: string; ATokenType: TWordTokenType);
+begin
+  Count := Count + 1;
+
+  //insert an OR if two following tokens are of type wtWord
+  if (FCount > 1) and (WordList[FCount - 2].TokenType = wtWord) and (ATokenType = wtWord) then
+  begin
+    WordList[FCount - 1].Value := 'or';
+    WordList[FCount - 1].TokenType := wtOR;
+    Count := Count + 1;
+  end;
+
+  WordList[FCount - 1].Value := AValue;
+  WordList[FCount - 1].TokenType := ATokenType;
+end;
+
+function TWordParser.GetSearchWordQuery: string;
+var
+  s: string;
+  i: integer;
+begin
+  s := '';
+  for i := 0 to FCount - 1 do
+    if i = FCount - 1 then
+      s := S+WordList[i].Value
+    else
+      s := S+WordList[i].Value + ' ';
+
+  //replace wildcard '*' with the SQL variant '%'
+  Result := StringReplace(s, '*', WildCardChar, [rfReplaceAll, rfIgnoreCase]);
+end;
+
+function TWordParser.GetToken(index: integer): TWordToken;
+begin
+  if (index >= 0) and (index < FCount) then
+    Result := WordList[index];
+
+  Result.Value := StringReplace(Result.Value, '*', WildCardChar, [rfReplaceAll, rfIgnoreCase]);
+end;
+
+procedure TWordParser.SetCount(AValue: integer);
+begin
+  if FCount = AValue then
+    Exit;
+  FCount := AValue;
+
+  SetLength(WordList, FCount);
+end;
+
+constructor TWordParser.Create(ASearchWords: string);
+var
+  list: TStringList;
+  i: integer;
+begin
+  //erase list
+  FCount := 0;
+
+  FWildCardChar := '%';
+
+  list := TStringList.Create;
+
+  try
+    list.Delimiter := ' ';
+    list.StrictDelimiter := True;
+
+    list.DelimitedText := LowerCase(ASearchWords);
+
+    //create the search clause
+    for i := 0 to list.Count - 1 do
+    begin
+      if list[i] = 'or' then
+        AddToken('or', wtOR)
+      else
+      begin
+        if list[i] = 'and' then
+          AddToken('and', wtOR)
+        else
+          AddToken(QuoteString(list[i]), wtWord);
+      end;
+    end;
+  finally
+    FreeAndNil(list);
+  end;
+end;
+
+{ TFPSearch }
+
+procedure TFPSearch.SetDatabase(AValue: TCustomIndexDB);
+begin
+  if FDatabase = AValue then
+    exit;
+  FDatabase := AValue;
+end;
+
+procedure TFPSearch.RankResults;
+var
+  i: integer;
+  best_value: TSearchWordData;
+  best_j: integer;
+  j: integer;
+
+  procedure AddNewRankedItem(Data: TSearchWordData);
+  begin
+    //add item to ranked list
+    RankedCount := RankedCount + 1;
+    RankedList[FRankedCount - 1] := Data;
+    RankedList[FRankedCount - 1].Rank := 1;
+  end;
+
+begin
+  for i := 0 to FCount - 1 do
+  begin
+    if FRankedCount > 0 then
+    begin
+      if RankedList[FRankedCount - 1].URL <> ResultList[i].URL then
+        AddNewRankedItem(ResultList[i])
+      else
+        RankedList[FRankedCount - 1].Rank := RankedList[FRankedCount - 1].Rank+ 1;
+    end
+    else
+      AddNewRankedItem(ResultList[i]);
+  end;
+
+  //sort ranked list
+  for i := 0 to FRankedCount - 2 do
+  begin
+    // Find the smallest remaining item.
+    best_value := RankedList[i];
+    best_j := i;
+    for j := i + 1 to FRankedCount - 1 do
+      if (RankedList[j].Rank > best_value.Rank) then
+      begin
+        best_value := RankedList[j];
+        best_j := j;
+      end;
+
+    // Swap it into position.
+    RankedList[best_j] := RankedList[i];
+    RankedList[i] := best_value;
+  end;
+end;
+
+procedure TFPSearch.SetRankedCount(AValue: integer);
+begin
+  if FRankedCount = AValue then
+    Exit;
+
+  FRankedCount := AValue;
+  SetLength(RankedList, AValue);
+end;
+
+procedure TFPSearch.AddResult(index: integer; AValue: TSearchWordData);
+begin
+  //grow result list if needed
+  if index >= Count then
+  begin
+    FCount := index;
+    SetLength(ResultList, FCount + 1);
+  end;
+
+  ResultList[index] := AValue;
+end;
+
+procedure TFPSearch.SetSearchWord(AValue: string);
+begin
+  if Assigned(FSearchWord) then
+    FreeAndNil(FSearchWord);
+
+  FSearchWord := TWordParser.Create(AValue);
+  FSearchWord.WildCardChar := '%';   //should come from DataBase
+end;
+
+function TFPSearch.GetResults(index: integer): TSearchWordData;
+begin
+  Result := ResultList[index];
+end;
+
+function TFPSearch.GetRankedResults(index: integer): TSearchWordData;
+begin
+  Result := RankedList[index];
+end;
+
+constructor TFPSearch.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+
+  FCount := 0;
+  FRankedCount := 0;
+end;
+
+destructor TFPSearch.Destroy;
+begin
+  inherited Destroy;
+end;
+
+function TFPSearch.Execute: int64;
+begin
+  Result := 0;
+
+  //reset previous searches
+  FCount := 0;
+  SetLength(ResultList, FCount);
+  Database.Connect;
+  Database.FindSearchData(SearchWord, Self, Options);
+  Result := Count;
+
+  //rank the results
+  RankResults;
+end;
+
+{ TCustomFileReader }
+
+function TCustomFileReader.GetSearchWord(index: integer): TSearchWordData;
+begin
+  Result := FSearchWord[index];
+end;
+
+function TCustomFileReader.GetCapacity: integer;
+begin
+  Result := Length(FSearchWord);
+end;
+
+procedure TCustomFileReader.SetCapacity(AValue: integer);
+begin
+  SetLength(FSearchWord, AValue);
+end;
+
+//a very basic tokenizer that only returns numeric and alphanumeric characters
+function TCustomFileReader.GetToken: string;
+
+var
+  s: string;
+  c: char;
+begin
+  Result := '';
+
+  if not Assigned(Stream) then
+    exit;
+
+  try
+    //writeln('pos:', Stream.Position, ' size:', Stream.Size);
+    if Stream.Position >= Stream.Size - 1 then
+      exit;
+    c := Chr(Stream.ReadByte);
+    //writeln('pos:', Stream.Position, ' size:', Stream.Size);
+    if Stream.Position >= Stream.Size - 1 then
+      exit;
+
+    //eat all invalid characters
+    while not (C in ['a'..'z', 'A'..'Z', '0'..'9']) and (Stream.Position < Stream.Size) do
+      c := Chr(Stream.ReadByte);
+    S := c;
+    //now read all valid characters from stream and append
+    FTokenStartPos := Stream.Position;
+    c := Chr(Stream.ReadByte);
+    while (c in ['a'..'z', 'A'..'Z', '0'..'9']) and (Stream.Position < Stream.Size) do
+    begin
+      s :=S+ c;
+      c := chr(Stream.ReadByte);
+    end;
+    FContext := FContext + (' ' + S);
+    if (Length(FContext) > MaxContextlen) then
+      Delete(FContext, 1, Length(FContext) - MaxContextLen);
+    Result := s;
+  except
+  end;
+end;
+
+function TCustomFileReader.GetContext: string;
+begin
+  Result := FContext;
+end;
+
+function TCustomFileReader.AllowWord(var ASearchWord: TSearchWordData): boolean;
+
+var
+  F: double;
+
+begin
+  Result := True;
+  if FIgnoreNumeric and (Length(ASearchWord.SearchWord) < 20) then
+    Result := not TryStrToFloat(ASearchWord.SearchWord, F);
+  if Result and UseIgnoreList then
+  begin
+    if not Assigned(FIgnoreListDef) and not FNoListFound then
+    begin
+      if (Language <> '') then
+      begin
+        FIgnoreListDef := IgnoreListManager.Lists.FindLanguage(Language);
+        FNoListFound := FIgnoreListDef = nil;
+      end;
+    end;
+    if Assigned(FIgnoreListDef) then
+    begin
+      Result := not FIgnoreListDef.IgnoreWord(ASearchWord.SearchWord);
+    end;
+  end;
+end;
+
+function TCustomFileReader.AllowedToken(token: string): boolean;
+begin
+  Result := True;
+end;
+
+procedure TCustomFileReader.SetStream(AValue: TStream);
+begin
+  if FStream = AValue then
+    Exit;
+  FStream := AValue;
+  StreamPos := 0;
+  FStream.Seek(0, soFromBeginning);
+  FContext := '';
+end;
+
+procedure TCustomFileReader.SetStreamPos(AValue: integer);
+begin
+  if FStreamPos = AValue then
+    Exit;
+  FStreamPos := AValue;
+end;
+
+procedure TCustomFileReader.Add(var ASearchWord: TSearchWordData);
+
+var
+  C: integer;
+
+begin
+  if not AllowWord(ASearchWord) then
+    exit;
+  Inc(FCount);
+  if (FOnAdd <> nil) then
+    FonAdd(Self, AsearchWord)
+  else
+  begin
+    C := Capacity;
+    if (FCount > C) then
+      if (C < 10) then
+        C := 10
+      else
+        Capacity := C + C div 2;
+    FSearchWord[FCount - 1] := ASearchWord;
+  end;
+end;
+
+procedure TCustomFileReader.DoDetectLanguage;
+{$ifdef LangDetect}
+var
+  tc: TFPTextCat;
+  i: integer;
+  s: string = '';
+{$endif}
+begin
+{$ifdef LangDetect}
+  tc := TFPTextCat.Create;
+  try
+    for i := 0 to Min(1000, Count - 1) do
+      s :=S+ FSearchWord[i].SearchWord + ' ';
+
+    tc.LoadFromString(s);
+    tc.Classify;
+    FLanguage := tc.Language;
+  finally
+    FreeAndNil(tc);
+  end;
+{$endif}
+end;
+
+constructor TCustomFileReader.Create;
+begin
+  FCount := 0;
+  FLanguage := 'unknown';
+  FignoreNumeric := True;
+end;
+
+destructor TCustomFileReader.Destroy;
+begin
+  SetLength(FSearchWord, 0);
+  inherited Destroy;
+end;
+
+procedure TCustomFileReader.LoadFromStream(FileStream: TStream);
+begin
+  Stream := FileStream;
+  if (FOnAdd = nil) then
+    Capacity := Stream.Size div 10;
+  FNoListFound := False;
+end;
+
+{ TFileHandlersManager }
+
+function TFileHandlersManager.GetReader(const TypeName: string): TCustomFileReaderClass;
+var
+  ih: TFileReaderDef;
+begin
+  ih := GetData(TypeName);
+  if assigned(ih) then
+    Result := ih.FReader
+  else
+    Result := nil;
+end;
+
+function TFileHandlersManager.GetExt(const TypeName: string): string;
+
+var
+  ih: TFileReaderDef;
+begin
+  ih := GetData(TypeName);
+  if assigned(ih) then
+    Result := ih.Extensions
+  else
+    Result := '';
+end;
+
+function TFileHandlersManager.GetDefExt(const TypeName: string): string;
+var
+  ih: TFileReaderDef;
+begin
+  ih := GetData(TypeName);
+  if assigned(ih) then
+    Result := ih.FDefaultExt
+  else
+    Result := '';
+end;
+
+function TFileHandlersManager.GetTypeName(index: integer): string;
+var
+  ih: TFileReaderDef;
+begin
+  ih := TFileReaderDef(FData[index]);
+  Result := ih.FTypeName;
+end;
+
+function TFileHandlersManager.GetData(const ATypeName: string): TFileReaderDef;
+var
+  r: integer;
+begin
+  r := FData.IndexOfTypeName(ATypeName);
+  if r >= 0 then
+    Result := FData[r]
+  else
+    Result := nil;
+end;
+
+function TFileHandlersManager.GetData(index: integer): TFileReaderDef;
+begin
+  if (index >= 0) and (index < FData.Count) then
+    Result := TFileReaderDef(FData[index])
+  else
+    Result := nil;
+end;
+
+function TFileHandlersManager.GetCount: integer;
+begin
+  Result := FData.Count;
+end;
+
+constructor TFileHandlersManager.Create;
+begin
+  inherited Create;
+  FData := CreateFileReaderDefs;
+end;
+
+function TFileHandlersManager.CreateFileReaderDefs: TFileReaderDefs;
+begin
+  Result := TFileReaderDefs.Create(TFileReaderDef);
+end;
+
+destructor TFileHandlersManager.Destroy;
+begin
+  FData.Free;
+  inherited Destroy;
+end;
+
+{$note function result is not set, convert to procedure?}
+function TFileHandlersManager.GetDefsForExtension(const Extension: string; List: TStrings): integer;
+var
+  I: integer;
+  D: TFileReaderDef;
+begin
+  for I := 0 to FData.Count - 1 do
+  begin
+    D := FData[i];
+    if D.HandlesExtension(Extension) and (D.Reader <> nil) then
+      List.AddObject(D.TypeName, D);
+  end;
+end;
+
+procedure TFileHandlersManager.RegisterFileReader(const ATypeName, TheExtensions: string; AReader: TCustomFileReaderClass);
+var
+  ih: TFileReaderDef;
+begin
+  ih := GetData(ATypeName);
+  if assigned(ih) then
+  begin
+    if assigned(ih.FReader) then
+      raise EFPindexer.CreateFmt('File reader "%s" already registered', [ATypeName]);
+  end
+  else
+  begin
+    ih := FData.AddFileReader(ATypeName);
+    with ih do
+    begin
+      Extensions := Lowercase(TheExtensions);
+      DefaultExt := CalcDefExt(TheExtensions);
+    end;
+  end;
+  ih.FReader := AReader;
+end;
+
+{ TAddWordStub }
+
+constructor TAddWordStub.Create(const AURL: string; const ADateTime: TDateTime; ADatabase: TCustomIndexDB);
+begin
+  FURL := AURl;
+  FDateTime := ADateTime;
+  FDatabase := ADatabase;
+  FCount := 0;
+end;
+
+procedure TAddWordStub.DoAddWord(AReader: TCustomFileReader; var AWord: TSearchWordData);
+begin
+  AWord.URL := FURL;
+  AWord.FileDate := FDateTime;
+  AWord.Language := AReader.Language;
+  AWord.SearchWord := LowerCase(AWord.SearchWord);
+  FDataBase.AddSearchData(AWord);
+  Inc(FCount);
+end;
+
+
+{ TFPIndexer }
+
+procedure TFPIndexer.SetDatabase(AValue: TCustomIndexDB);
+begin
+  if FDatabase = AValue then
+    Exit;
+  FDatabase := AValue;
+end;
+
+procedure TFPIndexer.SetExcludeFileMask(AValue: string);
+begin
+  if FExcludeFileMask = AValue then
+    exit;
+
+  FExcludeFileMask := AValue;
+  ExcludeMaskPatternList.DelimitedText := FExcludeFileMask;
+end;
+
+procedure TFPIndexer.SearchFiles(const PathName, FileName: string; const Recursive: boolean; AList: TStrings);
+var
+  Rec: TSearchRec;
+  Path: string;
+begin
+  Path := IncludeTrailingBackslash(PathName);
+  try
+    if FindFirst(Path + FileName, faAnyFile - faDirectory, Rec) = 0 then
+      repeat
+        AList.Add(Path + Rec.Name);
+      until FindNext(Rec) <> 0;
+  finally
+    FindClose(Rec);
+  end;
+  if not Recursive then
+    Exit;
+  if FindFirst(Path + AllFilesMask, faDirectory, Rec) = 0 then
+    try
+      repeat
+        if ((Rec.Attr and faDirectory) <> 0) and (Rec.Name <> '.') and (Rec.Name <> '..') then
+          SearchFiles(Path + Rec.Name, FileName, True, AList);
+      until FindNext(Rec) <> 0;
+    finally
+      FindClose(Rec);
+    end;
+end;
+
+procedure TFPIndexer.ExcludeFiles(const ExcludeMask: string; AList: TStrings);
+var
+  i: integer;
+begin
+  for i := AList.Count - 1 downto 0 do
+    if MatchesMask(AList[i], ExcludeMask) then
+      AList.Delete(i);
+end;
+
+procedure TFPIndexer.SetFileMask(AValue: string);
+begin
+  if FFileMask = AValue then
+    exit;
+
+  FFileMask := AValue;
+  MaskPatternList.DelimitedText := FFileMask;
+end;
+
+procedure TFPIndexer.SetSearchPath(AValue: string);
+begin
+  if FSearchPath = AValue then
+    exit;
+  FSearchPath := ExtractFilePath(ExpandFileName(IncludeTrailingPathDelimiter(AValue)));
+end;
+
+procedure TFPIndexer.DoProgress(const ACurrent, ACount: integer; const URL: string);
+begin
+  if Assigned(FOnProgress) then
+    FOnProgress(Self, ACurrent, ACount, URL);
+end;
+
+constructor TFPIndexer.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+
+  ExcludeMaskPatternList := TStringList.Create;
+  ExcludeMaskPatternList.StrictDelimiter := True;
+  ExcludeMaskPatternList.Delimiter := ';';
+
+  MaskPatternList := TStringList.Create;
+  MaskPatternList.StrictDelimiter := True;
+  MaskPatternList.Delimiter := ';';
+
+  DetectLanguage := False;
+  FIgnoreNumeric := True;
+end;
+
+destructor TFPIndexer.Destroy;
+begin
+  MaskPatternList.Clear;
+  FreeAndNil(MaskPatternList);
+
+  ExcludeMaskPatternList.Clear;
+  FreeAndNil(ExcludeMaskPatternList);
+
+  inherited Destroy;
+end;
+
+function TFPIndexer.IndexStream(const AURL: string; ADateTime: TDateTime; S: TStream; Reader: TCustomFileReader): int64;
+var
+  i: integer;
+  Stub: TAddWordStub;
+  AWord: TSearchWordData;
+begin
+  Result := 0;
+  DataBase.DeleteWordsFromFile(AURL);
+  // If reader must detect language, the stub cannot be used.
+  if not DetectLanguage then
+  begin
+    Stub := TAddWordStub.Create(AURL, ADateTime, Database);
+    try
+      Reader.OnAddSearchWord := @Stub.DoAddWord;
+      Reader.LoadFromStream(S);
+      Result := Stub.Count;
+    finally
+      Stub.Free;
+    end;
+  end
+  else
+  begin
+    Reader.LoadFromStream(S);
+    for i := 0 to Reader.Count - 1 do
+    begin
+      AWord := Reader.SearchWord[i];
+      AWord.URL := AURL;
+      AWord.FileDate := ADateTime;
+      AWord.Language := Reader.Language;
+      AWord.SearchWord := LowerCase(AWord.SearchWord);
+      FDataBase.AddSearchData(AWord);
+      Inc(Result);
+    end;
+  end;
+  if CommitFiles then
+  begin
+    Database.CommitTrans;
+    Database.BeginTrans;
+  end;
+end;
+
+function TFPIndexer.IndexFile(AURL: string; AllowErrors: boolean; const ALanguage: string): int64;
+var
+  e: string;
+  i: integer;
+  d: TFileReaderDef;
+  reader: TCustomFileReader;
+  fs: TFileStream;
+  L: TStringList;
+  DT: TDateTime;
+begin
+  Result := 0;
+  if not FileExists(AURL) then
+    raise EFPIndexer.Create('error: could not find file: ' + AURL);
+  //get cleaned file extension
+  E := LowerCase(ExtractFileExt(AURL));
+  if (e <> '') and (e[1] = '.') then
+    Delete(e, 1, 1);
+  L := TStringList.Create;
+  try
+    FileHandlers.GetDefsForExtension(e, L);
+    for I := 0 to L.Count - 1 do
+    begin
+      d := L.Objects[i] as TFileReaderDef;
+      reader := D.CreateReader(AURL);
+      try
+        try
+          Reader.IgnoreNumeric := True;
+          if (ALanguage <> '') then
+          begin
+            Reader.Language := ALanguage;
+            Reader.DetectLanguage := False;
+          end
+          else
+            Reader.DetectLanguage := DetectLanguage;
+          Reader.UseIgnoreList := UseIgnoreList;
+          fs := TFileStream.Create(AURL, fmOpenRead);
+          try
+            DT := FileDateToDateTime(FileAge(AURL));
+            Result := Result + IndexStream(AURL, DT, FS, Reader);
+          finally
+            fs.Free;
+          end
+        except
+          On E: Exception do
+            if not AllowErrors then
+              raise
+            else
+            begin
+              Inc(FErrorCount);
+            end;
+        end;
+      finally
+        D.DisposeReader(reader);
+      end;
+    end;
+  finally
+    L.Free;
+  end;
+end;
+
+function TFPIndexer.Execute(AllowErrors: boolean): int64;
+var
+  m: integer;
+  List: TStringList;
+  url: string;
+begin
+  Result := 0;
+  FErrorCount := 0;
+  if not Assigned(FDatabase) then
+    raise EFPIndexer.Create('database not assigned');
+  if not DirectoryExists(SearchPath) then
+    raise EFPIndexer.CreateFmt('Search path "%s" does not exist', [SearchPath]);
+  Database.Connect;
+  try
+    // execute search for each mask pattern
+    List := TStringList.Create;
+    try
+      for m := 0 to MaskPatternList.Count - 1 do
+        SearchFiles(SearchPath, MaskPatternList[m], SearchRecursive, List);
+      if (List.Count > 0) then
+      begin
+        List.Sort;
+        DataBase.BeginTrans;
+        for m := 0 to List.Count - 1 do
+        begin
+          URL := List[m];
+          DoProgress(M, List.Count, URL);
+          Result := Result + IndexFile(URL, AllowErrors, Language);
+        end;
+        {$note perform cleanup here on orphaned search words}
+        DataBase.CommitTrans;
+        Database.CompactDB;
+      end;
+    finally
+      List.Free;
+    end;
+  finally
+    Database.Disconnect;
+  end;
+end;
+
+{ TSQLIndexDB }
+
+procedure TSQLIndexDB.CreateIndexerTables;
+var
+  T: TIndexTable;
+  I: TIndexIndex;
+  k: TIndexForeignKey;
+begin
+
+  //create a new database
+  BeginTrans;
+
+  for t := low(TIndexTable) to High(TindexTable) do
+    Execute(DropTableSQl(t));
+  CommitTrans;
+
+  BeginTrans;
+
+  for t := low(TIndexTable) to High(TindexTable) do
+    Execute(CreateTableSQl(t), False);
+  CommitTrans;
+
+  BeginTrans;
+
+  for I := low(TIndexIndex) to High(TIndexIndex) do
+    Execute(CreateTableIndex(i), False);
+
+  CommitTrans;
+
+  BeginTrans;
+
+  if not AllowForeignKeyInTable then
+    for k := low(TIndexForeignKey) to High(TIndexForeignKey) do
+      Execute(CreateForeignKey(k), False);
+
+  CommitTrans;
+
+  BeginTrans;
+
+  for t := low(TIndexTable) to High(TindexTable) do
+    FinishCreateTable(t);
+
+  CommitTrans;
+end;
+
+function TSQLIndexDB.GetTableName(TableType: TIndexTable): string;
+begin
+  Result := DefaultTableNames[TableType];
+end;
+
+function TSQLIndexDB.GetIndexName(IndexType: TIndexIndex): string;
+begin
+  Result := DefaultIndexNames[IndexType];
+end;
+
+function TSQLIndexDB.GetFieldName(FieldType: TIndexField): string;
+begin
+  Result := DefaultFieldNames[FieldType];
+end;
+
+function TSQLIndexDB.GetForeignKeyName(ForeignKey: TIndexForeignKey): string;
+begin
+  Result := DefaultForeignKeyNames[ForeignKey];
+end;
+
+function TSQLIndexDB.GetFieldType(FieldType: TIndexField): string;
+begin
+  Result := DefaultFieldTypes[FieldType];
+end;
+
+function TSQLIndexDB.DropTableSQl(TableType: TIndexTable): string;
+begin
+  Result := 'DROP TABLE ' + GetTableName(TableType);
+end;
+
+function TSQLIndexDB.CreateTableSQL(const TableType: TIndexTable): string;
+var
+  f: TIndexField;
+  K: TIndexForeignKey;
+begin
+  for F := Low(TIndexField) to High(TIndexField) do
+    if TableFields[F] = TableType then
+    begin
+      if (Result <> '') then
+        Result := Result + ',' + sLineBreak;
+      Result := Result + GetFieldName(f) + ' ' + GetFieldType(f);
+    end;
+  if AllowForeignKeyInTable then
+    for K := Low(TIndexForeignKey) to High(TIndexForeignKey) do
+      if (ForeignKeyTables[k] = TableType) then
+      begin
+        if (Result <> '') then
+          Result := Result + ',' + sLineBreak;
+        Result := Result + CreateForeignKey(k, True);
+      end;
+  Result := 'CREATE TABLE ' + GetTableName(TableType) + ' (' + Result + ')';
+end;
+
+function TSQLIndexDB.CreateForeignKey(const ForeignKey: TIndexForeignKey; ForCreate: boolean = False): string;
+var
+  STN, TTN, FKN, FKF, FTK: string;
+begin
+  STN := GetTableName(ForeignKeyTables[ForeignKey]);
+  TTN := GetTableName(ForeignKeyTargets[ForeignKey]);
+  FKN := GetForeignKeyName(Foreignkey);
+  FKF := GetFieldName(ForeignKeyFields[ForeignKey]);
+  FTK := GetFieldName(ForeignKeyTargetFields[ForeignKey]);
+  if ForCreate then
+    Result := Format('CONSTRAINT %S FOREIGN KEY (%s) REFERENCES %S(%s)', [FKN, FKF, TTN, FTK])
+  else
+    Result := Format('ALTER TABLE %s ADD CONSTRAINT %S FOREIGN KEY (%s) REFERENCES %S(%s)', [STN, FKN, FKF, TTN, FTK]);
+end;
+
+procedure TSQLIndexDB.FinishCreateTable(const TableType: TIndexTable);
+begin
+  // Do nothing
+end;
+
+function TSQLIndexDB.InsertSQL(const TableType: TIndexTable; UseParams: boolean = True): string;
+var
+  FL: string = '';
+  VL: string = '';
+  F: TIndexField;
+begin
+  for F := Low(TIndexField) to High(TIndexField) do
+    if TableFields[F] = TableType then
+    begin
+      if (FL <> '') then
+      begin
+        FL := FL + ', ';
+        VL := VL + ', ';
+      end;
+      FL := FL + GetfieldName(F);
+      if UseParams then
+        VL := VL + ':' + GetfieldName(F)
+      else
+        VL := VL + '%s';
+    end;
+  Result := Format('INSERT INTO %s (%s) VALUES (%s)', [GetTableName(TableType), FL, VL]);
+end;
+
+function TSQLIndexDB.CreateTableIndex(IndexType: TIndexIndex): string;
+var
+  TIN: string;
+begin
+  TIN := GetindexName(IndexType);
+  case IndexType of
+    iiWords: Result := CreateIndexSQL(TIN, GetTableName(itWords), [GetFieldName(ifWordsWord)]);
+    iiFiles: Result := CreateIndexSQL(TIN, GetTableName(itFiles), [GetFieldName(ifFilesURL)]);
+    iiLanguages: Result := CreateIndexSQL(TIN, GetTableName(itLanguages), [GetFieldName(ifLanguagesName)]);
+  end;
+end;
+
+function TSQLIndexDB.CreateIndexSQL(const AIndexName, ATableName: string; const AFieldList: array of string): string;
+var
+  I: integer;
+begin
+  Result := 'CREATE UNIQUE INDEX ' + AIndexName + ' ON ' + ATableName + ' (';
+  for I := Low(AFieldList) to High(AFieldList) do
+  begin
+    Result := Result + AFieldList[i];
+    if I < High(AFieldList) then
+      Result := Result + ',';
+  end;
+  Result := Result + ');';
+end;
+
+function TSQLIndexDB.GetUrlSQL(UseParams: boolean): string;
+begin
+  Result := GetSearchSQL(itFiles, ifFilesID, ifFilesURL, UseParams);
+end;
+
+function TSQLIndexDB.GetSearchSQL(ATable: TIndexTable; IDField, SearchField: TINdexField; UseParams: boolean = True): string;
+var
+  IDF, TN, URLF: string;
+begin
+  TN := GetTableName(ATable);
+  IDF := GetFieldName(IDField);
+  URLF := GetFieldName(SearchField);
+  Result := 'SELECT %s from %s where (%s = ';
+  Result := Format(Result, [IDF, TN, URLF]);
+  if UseParams then
+    Result := Result + ':' + URLF + ')'
+  else
+    Result := Result + '%s)';
+end;
+
+function TSQLIndexDB.GetLanguageSQL(UseParams: boolean = True): string;
+begin
+  Result := GetSearchSQL(itLanguages, ifLanguagesID, ifLanguagesName, UseParams);
+end;
+
+function TSQLIndexDB.GetWordSQL(UseParams: boolean = True): string;
+begin
+  Result := GetSearchSQL(itWords, ifWordsID, ifWordsWord, UseParams);
+end;
+
+function TSQLIndexDB.GetSearchFileSQL(UseParams: boolean = True): string;
+begin
+  Result := GetSearchSQL(itFiles, ifFilesID, ifFilesURL, UseParams);
+end;
+
+function TSQLIndexDB.DeleteWordsSQL(UseParams: boolean): string;
+begin
+  Result := Format('DELETE FROM %s WHERE (%s =', [GetTableName(itMatches), GetFieldName(ifMatchesFileID)]);
+  if UseParams then
+    Result := Result + ':' + GetFieldName(ifMatchesFileID) + ')'
+  else
+    Result := Result + '%d)';
+end;
+
+class function TSQLIndexDB.AllowForeignKeyInTable: boolean;
+begin
+  Result := False;
+end;
+
+function TSQLIndexDB.GetMatchSQL(SearchOptions: TSearchOptions; SearchWord: TWordParser; UseParams: boolean = True): string;
+var
+  WW, MN, FN, WN, LN: string;
+  i: integer;
+begin
+  WW := getFieldName(ifWordsWord);
+  Result := Format('SELECT %s, %s, %s, %s, %s, %s', [GetFieldName(ifMatchesPosition),
+    GetFieldName(ifFilesURL), GetFieldName(ifMatchesContext),
+    WW, GetFieldName(ifFilesTimeStamp), GetFieldName(ifLanguagesName)]);
+  MN := GetTableName(itMatches);
+  FN := getTableName(itFiles);
+  WN := getTableName(itWords);
+  LN := getTableName(itLanguages);
+  Result := Result + Format(' FROM %s, %s ,%s, %s', [MN, FN, WN, LN]);
+  Result := Result + Format(' WHERE (%s.%s=%s.%s)', [MN, getFieldName(ifMatchesWordID), WN, getFieldName(ifWordsID)]);
+  Result := Result + Format(' AND (%s.%s=%s.%s)', [MN, getFieldName(ifMatchesFileID), FN, getFieldName(ifFilesID)]);
+  Result := Result + Format(' AND (%s.%s=%s.%s)', [LN, getFieldName(ifLanguagesID), FN, getFieldName(ifFilesLanguageID)]);
+  Result := Result + ' AND (';
+
+  for i := 0 to SearchWord.Count - 1 do
+  begin
+    if SearchWord.Token[i].TokenType = wtWord then
+    begin
+      Result := Result + Format('(%s.%s ', [WN, WW]);
+      if (soContains in SearchOptions) then
+        Result := Result + 'Like '
+      else
+        Result := Result + '= ';
+      if UseParams then
+        Result := Result + ':' + WW + IntToStr(i) + ')'
+      else
+        Result := Result + Format('%s)', [SearchWord.Token[i].Value]);
+    end
+    else
+      Result := Result + Format(' %s ', [SearchWord.Token[i].Value]);
+  end;
+  Result := Result + Format(') ORDER BY %s', [GetFieldName(ifFilesURL)]);
+end;
+
+procedure TSQLIndexDB.DeleteWordsFromFile(URL: string);
+var
+  FID: integer;
+begin
+  FID := GetURLID(URL, 0, -1, False);
+
+  if (FID <> -1) then
+    Execute(Format(DeleteWordsSQL(False), [FID]), False);
+end;
+
+initialization
+  FileHandlers := TFileHandlersManager.Create;
+  IgnoreListManager := TIgnoreListManager.Create(nil);
+
+finalization
+  FreeAndNil(IgnoreListManager);
+  FreeAndNil(FileHandlers);
+end.
+

+ 405 - 0
packages/fpindexer/src/fpmasks.pp

@@ -0,0 +1,405 @@
+{
+ /***************************************************************************
+                                  fpmasks.pas
+                                  ---------
+
+  Moved here from LCL
+ ***************************************************************************/
+
+ *****************************************************************************
+ *                                                                           *
+ *  This file is part of the Lazarus Component Library (LCL)                 *
+ *                                                                           *
+ *  See the file COPYING.modifiedLGPL.txt, included in this distribution,    *
+ *  for details about the copyright.                                         *
+ *                                                                           *
+ *  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.                     *
+ *                                                                           *
+ *****************************************************************************
+}
+unit fpmasks;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  // For Smart Linking: Do not use the LCL!
+  Classes, SysUtils, Contnrs;
+
+type
+  TMaskCharType = (mcChar, mcCharSet, mcAnyChar, mcAnyText);
+  
+  TCharSet = set of Char;
+  PCharSet = ^TCharSet;
+  
+  TMaskChar = record
+    case CharType: TMaskCharType of
+      mcChar: (CharValue: Char);
+      mcCharSet: (Negative: Boolean; SetValue: PCharSet);
+      mcAnyChar, mcAnyText: ();
+  end;
+  
+  TMaskString = record
+    MinLength: Integer;
+    MaxLength: Integer;
+    Chars: Array of TMaskChar;
+  end;
+
+  { TMask }
+
+  TMask = class
+  private
+    FMask: TMaskString;
+  public
+    constructor Create(const AValue: String);
+    destructor Destroy; override;
+    
+    function Matches(const AFileName: String): Boolean;
+  end;
+  
+  { TParseStringList }
+
+  TParseStringList = class(TStringList)
+  public
+    constructor Create(const AText, ASeparators: String);
+  end;
+  
+  { TMaskList }
+
+  TMaskList = class
+  private
+    FMasks: TObjectList;
+    function GetCount: Integer;
+    function GetItem(Index: Integer): TMask;
+  public
+    constructor Create(const AValue: String; ASeparator: Char = ';');
+    destructor Destroy; override;
+    
+    function Matches(const AFileName: String): Boolean;
+    
+    property Count: Integer read GetCount;
+    property Items[Index: Integer]: TMask read GetItem;
+  end;
+
+function MatchesMask(const FileName, Mask: String): Boolean;
+function MatchesMaskList(const FileName, Mask: String; Separator: Char = ';'): Boolean;
+
+implementation
+
+function MatchesMask(const FileName, Mask: String): Boolean;
+var
+  AMask: TMask;
+begin
+  AMask := TMask.Create(Mask);
+  try
+    Result := AMask.Matches(FileName);
+  finally
+    AMask.Free;
+  end;
+end;
+
+function MatchesMaskList(const FileName, Mask: String; Separator: Char): Boolean;
+var
+  AMaskList: TMaskList;
+begin
+  AMaskList := TMaskList.Create(Mask, Separator);
+  try
+    Result := AMaskList.Matches(FileName);
+  finally
+    AMaskList.Free;
+  end;
+end;
+
+{ TMask }
+
+constructor TMask.Create(const AValue: String);
+var
+  I: Integer;
+  SkipAnyText: Boolean;
+  
+  procedure CharSetError;
+  begin
+    raise EConvertError.CreateFmt('Invalid charset %s', [AValue]);
+  end;
+  
+  procedure AddAnyText;
+  begin
+    if SkipAnyText then
+    begin
+      Inc(I);
+      Exit;
+    end;
+    
+    SetLength(FMask.Chars, Length(FMask.Chars) + 1);
+    FMask.Chars[High(FMask.Chars)].CharType := mcAnyText;
+
+    FMask.MaxLength := MaxInt;
+    SkipAnyText := True;
+    Inc(I);
+  end;
+  
+  procedure AddAnyChar;
+  begin
+    SkipAnyText := False;
+
+    SetLength(FMask.Chars, Length(FMask.Chars) + 1);
+    FMask.Chars[High(FMask.Chars)].CharType := mcAnyChar;
+
+    Inc(FMask.MinLength);
+    if FMask.MaxLength < MaxInt then Inc(FMask.MaxLength);
+    
+    Inc(I);
+  end;
+  
+  procedure AddCharSet;
+  var
+    CharSet: TCharSet;
+    Valid: Boolean;
+    C, Last: Char;
+  begin
+    SkipAnyText := False;
+    
+    SetLength(FMask.Chars, Length(FMask.Chars) + 1);
+    FMask.Chars[High(FMask.Chars)].CharType := mcCharSet;
+
+    Inc(I);
+    if (I <= Length(AValue)) and (AValue[I] = '!') then
+    begin
+      FMask.Chars[High(FMask.Chars)].Negative := True;
+      Inc(I);
+    end
+    else FMask.Chars[High(FMask.Chars)].Negative := False;
+
+    Last := '-';
+    CharSet := [];
+    Valid := False;
+    while I <= Length(AValue) do
+    begin
+      case AValue[I] of
+        '-':
+          begin
+            if Last = '-' then CharSetError;
+            Inc(I);
+            
+            if (I > Length(AValue)) then CharSetError;
+            //DebugLn('Set:  ' + Last + '-' + UpCase(AValue[I]));
+            for C := Last to UpCase(AValue[I]) do Include(CharSet, C);
+            Inc(I);
+          end;
+        ']':
+          begin
+            Valid := True;
+            Break;
+          end;
+        else
+        begin
+          Last := UpCase(AValue[I]);
+          Include(CharSet, Last);
+          Inc(I);
+        end;
+      end;
+    end;
+    
+    if (not Valid) or (CharSet = []) then CharSetError;
+
+    New(FMask.Chars[High(FMask.Chars)].SetValue);
+    FMask.Chars[High(FMask.Chars)].SetValue^ := CharSet;
+    
+    Inc(FMask.MinLength);
+    if FMask.MaxLength < MaxInt then Inc(FMask.MaxLength);
+
+    Inc(I);
+  end;
+  
+  procedure AddChar;
+  begin
+    SkipAnyText := False;
+
+    SetLength(FMask.Chars, Length(FMask.Chars) + 1);
+    with FMask.Chars[High(FMask.Chars)] do
+    begin
+      CharType := mcChar;
+      CharValue := UpCase(AValue[I]);
+    end;
+
+    Inc(FMask.MinLength);
+    if FMask.MaxLength < MaxInt then Inc(FMask.MaxLength);
+
+    Inc(I);
+  end;
+  
+begin
+  SetLength(FMask.Chars, 0);
+  FMask.MinLength := 0;
+  FMask.MaxLength := 0;
+  SkipAnyText := False;
+  
+  I := 1;
+  while I <= Length(AValue) do
+  begin
+    case AValue[I] of
+      '*': AddAnyText;
+      '?': AddAnyChar;
+      '[': AddCharSet;
+      else AddChar;
+    end;
+  end;
+end;
+
+destructor TMask.Destroy;
+var
+  I: Integer;
+begin
+  for I := 0 to High(FMask.Chars) do
+    if FMask.Chars[I].CharType = mcCharSet then
+      Dispose(FMask.Chars[I].SetValue);
+
+  inherited Destroy;
+end;
+
+function TMask.Matches(const AFileName: String): Boolean;
+var
+  L: Integer;
+  S: String;
+  
+  function MatchToEnd(MaskIndex, CharIndex: Integer): Boolean;
+  var
+    I, J: Integer;
+  begin
+    Result := False;
+    
+    for I := MaskIndex to High(FMask.Chars) do
+    begin
+      case FMask.Chars[I].CharType of
+        mcChar:
+          begin
+            if CharIndex > L then Exit;
+            //DebugLn('Match ' + S[CharIndex] + '<?>' + FMask.Chars[I].CharValue);
+            if S[CharIndex] <> FMask.Chars[I].CharValue then Exit;
+            Inc(CharIndex);
+          end;
+        mcCharSet:
+          begin
+            if CharIndex > L then Exit;
+            if FMask.Chars[I].Negative xor
+               (S[CharIndex] in FMask.Chars[I].SetValue^) then Inc(CharIndex)
+            else Exit;
+          end;
+        mcAnyChar:
+          begin
+            if CharIndex > L then Exit;
+            Inc(CharIndex);
+          end;
+        mcAnyText:
+          begin
+            if I = High(FMask.Chars) then
+            begin
+              Result := True;
+              Exit;
+            end;
+            
+            for J := CharIndex to L do
+              if MatchToEnd(I + 1, J) then
+              begin
+                Result := True;
+                Exit;
+              end;
+          end;
+      end;
+    end;
+    
+    Result := CharIndex > L;
+  end;
+  
+begin
+  Result := False;
+  L := Length(AFileName);
+  if L = 0 then
+  begin
+    if FMask.MinLength = 0 then Result := True;
+    Exit;
+  end;
+  
+  if (L < FMask.MinLength) or (L > FMask.MaxLength) then Exit;
+
+  S := UpperCase(AFileName);
+  Result := MatchToEnd(0, 1);
+end;
+
+{ TParseStringList }
+
+constructor TParseStringList.Create(const AText, ASeparators: String);
+var
+  I, S: Integer;
+begin
+  inherited Create;
+
+  S := 1;
+  for I := 1 to Length(AText) do
+  begin
+    if Pos(AText[I], ASeparators) > 0 then
+    begin
+      if I > S then Add(Copy(AText, S, I - S));
+      S := I + 1;
+    end;
+  end;
+  
+  if Length(AText) >= S then Add(Copy(AText, S, Length(AText) - S + 1));
+end;
+
+{ TMaskList }
+
+function TMaskList.GetItem(Index: Integer): TMask;
+begin
+  Result := TMask(FMasks.Items[Index]);
+end;
+
+function TMaskList.GetCount: Integer;
+begin
+  Result := FMasks.Count;
+end;
+
+constructor TMaskList.Create(const AValue: String; ASeparator: Char);
+var
+  S: TParseStringList;
+  I: Integer;
+begin
+  FMasks := TObjectList.Create(True);
+  
+  S := TParseStringList.Create(AValue, ASeparator + ' ');
+  try
+    for I := 0 to S.Count - 1 do
+      FMasks.Add(TMask.Create(S[I]));
+  finally
+    S.Free;
+  end;
+end;
+
+destructor TMaskList.Destroy;
+begin
+  FMasks.Free;
+  
+  inherited Destroy;
+end;
+
+function TMaskList.Matches(const AFileName: String): Boolean;
+var
+  I: Integer;
+begin
+  Result := False;
+  
+  for I := 0 to FMasks.Count - 1 do
+  begin
+    if TMask(FMasks.Items[I]).Matches(AFileName) then
+    begin
+      Result := True;
+      Exit;
+    end;
+  end;
+end;
+
+end.
+

+ 166 - 0
packages/fpindexer/src/ireaderhtml.pp

@@ -0,0 +1,166 @@
+{
+    This file is part of the Free Component Library (FCL)
+    Copyright (c) 2012 by the Free Pascal development team
+
+    HTML text reader
+    
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    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.
+
+ **********************************************************************}
+unit IReaderHTML;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  FastHTMLParser, //, HTMLUtil,          // Fast Parser Functions
+  Classes, fpIndexer;
+
+type
+
+  { TIReaderHTML }
+
+  TIReaderHTML = class(TCustomFileReader)
+  private
+    sLine: string;
+    StartPos: integer;
+    Offset: integer;
+    LinePos: integer;
+    Tg, Tx: integer;
+    FParser: THTMLParser; //our htmlparser class
+    procedure OnTag(NoCaseTag, ActualTag: string);
+    procedure OnText(Text: string);
+  protected
+    function GetToken: string; override;
+    function AllowedToken(token: string): boolean; override;
+  public
+    procedure LoadFromStream(FileStream: TStream); override;
+  end;
+
+implementation
+
+{ TIReaderHTML }
+
+procedure TIReaderHTML.OnTag(NoCaseTag, ActualTag: string);
+begin
+end;
+
+procedure TIReaderHTML.OnText(Text: string);
+var
+  token: string;
+  s: TSearchWordData;
+  i : Integer;
+begin
+  sLine := Text;
+  LinePos := 1;
+  Offset:=FParser.CurrentPos;
+  token := GetToken;
+  while token <> '' do
+    begin
+    if AllowedToken(token) then
+      begin
+      s.SearchWord := token;
+      s.Position := Offset+StartPos;
+      // Copy area around text.
+      I:=StartPos-(MaxContextLen div 2);
+      If I<1 then
+        I:=1;
+      s.Context := Copy(SLine,I,I+MaxContextLen);
+      Add(s);
+      end;
+    token := GetToken;
+    end;
+end;
+
+function TIReaderHTML.GetToken: string;
+var
+  s: string;
+  c: string;
+begin
+  Result := '';
+
+  if (sLine = '') or (LinePos >= Length(sLine)) then
+    exit;
+
+  c := sLine[LinePos];
+  Inc(LinePos);
+
+  if LinePos <= Length(sLine) then
+  begin
+
+    //eat all invalid characters
+    while not (c[1] in ['a'..'z', 'A'..'Z', '0'..'9']) and (LinePos <= Length(sLine)) do
+    begin
+      c := sLine[LinePos];
+      Inc(LinePos);
+    end;
+
+    if not (c[1] in ['a'..'z', 'A'..'Z', '0'..'9']) then
+      s := ''
+    else
+      s := c;
+    StartPos:=LinePos;
+    if LinePos <= Length(sLine) then
+    begin
+      //now read all valid characters from stream and append
+      c := sLine[LinePos];
+      Inc(LinePos);
+      while (c[1] in ['a'..'z', 'A'..'Z', '0'..'9']) and (LinePos <= Length(sLine)) do
+      begin
+        s := S + c;
+        c := sLine[LinePos];
+        Inc(LinePos);
+      end;
+    end;
+
+    if not (c[1] in ['a'..'z', 'A'..'Z', '0'..'9']) then
+      Result := LowerCase(s)
+    else
+      Result := LowerCase(s + c);
+  end;
+end;
+
+function TIReaderHTML.AllowedToken(token: string): boolean;
+begin
+  Result := (Length(token) > 1) and
+            (token <> 'nbsp') and (token <> 'quot') and (token <> 'apos') and
+            (token <> 'amp') and (token <> 'lt') and (token <> 'gt');
+end;
+
+procedure TIReaderHTML.LoadFromStream(FileStream: TStream);
+var
+  S : TStringStream;
+
+begin
+  inherited LoadFromStream(FileStream);
+  S:=TStringStream.Create('');
+  try
+    S.CopyFrom(FileStream,0);
+    Tg := 0;
+    Tx := 0;
+    FParser := THTMLParser.Create(S.DataString);
+    try
+      FParser.OnFoundTag := @OnTag;
+      FParser.OnFoundText := @OnText;
+      FParser.Exec;
+    finally
+      FParser.Free;
+     end;
+  finally
+    S.Free;
+  end;
+  if DetectLanguage then
+    DoDetectLanguage;
+end;
+
+initialization
+  FileHandlers.RegisterFileReader('HTML format', 'html;htm', TIReaderHTML);
+
+end.
+

+ 54 - 0
packages/fpindexer/src/ireaderpas.pp

@@ -0,0 +1,54 @@
+{
+    This file is part of the Free Component Library (FCL)
+    Copyright (c) 2012 by the Free Pascal development team
+
+    Pascal text reader
+    
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    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.
+
+ **********************************************************************}
+unit IReaderPAS;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, fpIndexer, IReaderTXT;
+
+type
+
+  { TIReaderPAS }
+
+  TIReaderPAS = class(TIReaderTXT)
+  private
+  protected
+    function AllowedToken(token: string): boolean; override;
+  public
+    procedure LoadFromStream(FileStream: TStream); override;
+  end;
+
+implementation
+
+{ TIReaderPAS }
+
+function TIReaderPAS.AllowedToken(token: string): boolean;
+begin
+  Result:=inherited AllowedToken(token);
+end;
+
+procedure TIReaderPAS.LoadFromStream(FileStream: TStream);
+begin
+  inherited LoadFromStream(FileStream);
+end;
+
+initialization
+  FileHandlers.RegisterFileReader('Pascal format', 'pas', TIReaderPAS);
+
+end.
+

+ 69 - 0
packages/fpindexer/src/ireadertxt.pp

@@ -0,0 +1,69 @@
+{
+    This file is part of the Free Component Library (FCL)
+    Copyright (c) 2012 by the Free Pascal development team
+
+    Plain text reader
+    
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    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.
+
+ **********************************************************************}
+unit IReaderTXT;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, fpIndexer;
+
+type
+
+  { TIReaderTXT }
+
+  TIReaderTXT = class(TCustomFileReader)
+  private
+  protected
+    function AllowedToken(token: string): boolean; override;
+  public
+    procedure LoadFromStream(FileStream: TStream); override;
+  end;
+
+implementation
+
+{ TIReaderTXT }
+
+function TIReaderTXT.AllowedToken(token: string): boolean;
+begin
+  Result := inherited AllowedToken(token) and (Length(token) > 1);
+end;
+
+procedure TIReaderTXT.LoadFromStream(FileStream: TStream);
+var
+  token: string;
+  p: TSearchWordData;
+begin
+  inherited LoadFromStream(FileStream);
+  token := GetToken;
+  while token <> '' do
+  begin
+    if AllowedToken(token) then
+    begin
+      p.SearchWord := token;
+      P.Position:=TokenStartPos;
+      p.Context:=GetContext;
+      Add(p);
+    end;
+    token := GetToken;
+  end;
+end;
+
+initialization
+  FileHandlers.RegisterFileReader('Text format', 'txt', TIReaderTXT);
+
+end.
+

+ 869 - 0
packages/fpindexer/src/memindexdb.pp

@@ -0,0 +1,869 @@
+{
+    This file is part of the Free Component Library (FCL)
+    Copyright (c) 2012 by the Free Pascal development team
+
+    Memory database
+    
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    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.
+
+ **********************************************************************}
+unit memindexdb;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpindexer, contnrs;
+
+Type
+  TMatch = Class;
+
+  { TDescrItem }
+
+  TDescrItem = Class(TCollectionItem)
+  private
+    FDescription: String;
+    FTheID: Integer;
+  Protected
+    Function BlockSize : Integer; virtual;
+    Procedure WriteStringToStream(Astream : TStream; S : String);
+    Procedure WriteToStream(S : TStream); virtual;
+    Procedure WriteRefToStream(AStream : Tstream; AItem : TDescrItem);
+    Function ReadStringFromStream(Astream : TStream) :  String;
+    Function ReadFromStream(S : TStream) : Integer; virtual;
+  Published
+    // The description
+    Property Description : String Read FDescription Write FDescription;
+    // ID. Not used during work. Only used when loading/saving,
+    // it then equals the Index. (index is slow, uses a linear search)
+    Property TheID : Integer Read FTheID Write FTheID;
+  end;
+
+  { TDescrCollection }
+
+  TDescrCollection = Class(TCollection)
+  private
+    FHash : TFPhashList;
+    FLoadCount : Integer;
+    function GetD(AIndex : Integer): TDescrItem;
+    procedure SetD(AIndex : Integer; AValue: TDescrItem);
+  Protected
+    Procedure RebuildHash;
+    Function Resolve(AIndex : Integer) : TDescrItem;
+  Public
+    destructor destroy; override;
+    Procedure BeginLoading;
+    Procedure EndLoading;
+    Procedure AllocateIDS;
+    Function FindDescr(Const ADescription : String) : TDescrItem;
+    Function AddDescr(Const ADescription : String) : TDescrItem;
+    Property Descr [AIndex : Integer] : TDescrItem Read GetD Write SetD; default;
+  end;
+
+  TLanguageItem = Class(TDescrItem);
+
+  TMatchedItem = Class(TDescrItem)
+  Private
+    FList : TFPList;
+    function GetMatch(AIndex : Integer): TMatch;
+    function GetMatchCount: Integer;
+  Protected
+    Function AddMatch(AMatch : TMatch) : Integer;
+    Procedure RemoveMatch(AMatch : TMatch);
+  Public
+    Constructor Create(ACollection : TCollection); override;
+    Destructor Destroy; override;
+    Property Matches [AIndex : Integer] : TMatch Read GetMatch; default;
+    Property MatchCount :  Integer Read GetMatchCount;
+  end;
+
+  { TWordItem }
+
+  TWordItem = Class(TMatchedItem);
+
+  { TURLItem }
+
+  TURLItem = Class(TMatchedItem)
+  private
+    FLanguage: TLanguageItem;
+    FURLDate: TDateTime;
+    FLangID : Integer;
+  protected
+    Function BlockSize : Integer; override;
+    Procedure WriteToStream(S : TStream); override;
+    Function ReadFromStream(S : TStream) : Integer; override;
+    Procedure Resolve(Languages: TDescrCollection);
+  Public
+    Property URLDate : TDateTime Read FURLDate Write FURLDate;
+    Property Language : TLanguageItem Read FLanguage Write FLanguage;
+  end;
+
+
+
+  { TMatch }
+
+  TMatch = Class(TDescrItem)
+  private
+    FPosition: Int64;
+    FURL: TURLItem;
+    FWord: TWordItem;
+    FWordID : Integer;
+    FURLID : Integer;
+    function GetContext: String;
+    procedure SetContext(AValue: String);
+    procedure SetURL(AValue: TURLItem);
+    procedure SetWord(AValue: TWordItem);
+  protected
+    Function BlockSize : Integer; override;
+    Procedure WriteToStream(S : TStream); override;
+    Procedure Resolve(Words, URLS : TDescrCollection);
+    Function ReadFromStream(S : TStream) : Integer; override;
+  Public
+    Property Word : TWordItem Read FWord Write SetWord;
+    Property URL : TURLItem Read FURL Write SetURL;
+    Property Position : Int64 Read FPosition Write FPosition;
+    Property Context : String Read GetContext Write SetContext;
+  end;
+
+  { TMatches }
+
+  TMatches = Class(TCollection)
+  private
+    function GetM(AIndex : Integer): TMatch;
+    procedure SetM(AIndex : Integer; AValue: TMatch);
+  Public
+    Function AddMatch(AWord : TWordItem; AURL : TURLItem) : TMatch;
+    Property Matches[AIndex : Integer] : TMatch Read GetM Write SetM; default;
+  end;
+
+  { TMemIndexDB }
+
+  TMemIndexDB = class(TCustomIndexDB)
+  Private
+    FStream: TStream;
+    FURLS : TDescrCollection;
+    FLanguages : TDescrCollection;
+    FWords : TDescrCollection;
+    FMatches : TMatches;
+    procedure GetMatches(AWord: String; SearchOptions: TSearchOptions;  AList: TFPList);
+    procedure IntersectMatches(ListA, ListB: TFPList);
+    procedure UnionMatches(ListA, ListB: TFPList);
+  protected
+    Procedure LoadFromStream; virtual; abstract;
+    Procedure SaveToStream; virtual; abstract;
+    procedure Clear;virtual;
+  public
+    Constructor Create(AOwner : TComponent); override;
+    Destructor Destroy; override;
+    procedure Connect; override;
+    procedure DisConnect; override;
+    procedure CommitTrans; override;
+    procedure BeginTrans; override;
+    procedure CompactDB; override;
+    procedure DeleteWordsFromFile(URL: string); override;
+    procedure AddSearchData(ASearchData: TSearchWordData); override;
+    procedure FindSearchData(SearchWord: TWordParser; FPSearch: TFPSearch; SearchOptions: TSearchOptions); override;
+    procedure CreateIndexerTables; override;
+    Property Stream : TStream Read FStream Write FStream;
+  end;
+
+  { TFileIndexDB }
+
+  TFileIndexDB = Class(TMemIndexDB)
+  private
+    FFIleName: String;
+    FWriteOnCommit: Boolean;
+  Protected
+    Procedure LoadFromStream; override;
+    Procedure SaveToStream; override;
+  Public
+    procedure Connect; override;
+    procedure DisConnect; override;
+    procedure CommitTrans; override;
+    Property FileName : String Read FFIleName Write FFileName;
+    Property WriteOnCommit : Boolean Read FWriteOnCommit Write FWriteOnCommit;
+  end;
+
+implementation
+
+uses bufstream;
+
+{ TMemIndexDB }
+
+Resourcestring
+  SErrNoStream = 'No stream assigned';
+  SInvalidStreamData = 'Invalid data at offset %d. Got %d, expected %d.';
+
+{ TFileIndexDB }
+
+Const
+  FileVersion    = 1;
+  LanguageBlock  = 1;
+  URLBlock       = 2;
+  WordBlock      = 3;
+  MatchBlock     = 4;
+
+{ TURLItem }
+
+function TURLItem.BlockSize: Integer;
+begin
+  Result:=inherited BlockSize;
+  Result:=Result+sizeOf(FURLDate)+SizeOf(Integer);
+end;
+
+procedure TURLItem.WriteToStream(S: TStream);
+
+Var
+  I : Integer;
+
+begin
+  inherited WriteToStream(S);
+  S.WriteBuffer(FURLDate,SizeOf(FURLDate));
+  WriteRefToStream(S,FLanguage);
+end;
+
+function TURLItem.ReadFromStream(S: TStream): Integer;
+begin
+  Result:=inherited ReadFromStream(S);
+  S.ReadBuffer(FURLDate,SizeOf(FURLDate));
+  S.ReadBuffer(FLangID,SizeOf(FLangID));
+end;
+
+procedure TURLItem.Resolve(Languages: TDescrCollection);
+begin
+  FLanguage:=TLanguageItem(Languages.Resolve(FLangID));
+end;
+
+{ TDescrItem }
+
+function TDescrItem.BlockSize: Integer;
+begin
+  Result:=Sizeof(Integer)+Length(FDescription)*SizeOf(Char);
+end;
+
+procedure TDescrItem.WriteStringToStream(Astream: TStream; S: String);
+Var
+  L : Integer;
+begin
+  L:=Length(S);
+  AStream.WriteBuffer(L,SizeOf(L));
+  if (L>0) then
+    AStream.WriteBuffer(S[1],L*SizeOf(Char));
+end;
+
+procedure TDescrItem.WriteToStream(S: TStream);
+
+begin
+  S.WriteDWord(BlockSize);
+  WriteStringToStream(S,FDescription);
+end;
+
+procedure TDescrItem.WriteRefToStream(AStream : Tstream; AItem: TDescrItem);
+
+Var
+  I : Integer;
+
+begin
+  If AItem=Nil then
+    I:=0
+  else
+    I:=AItem.TheID;
+  AStream.WriteBuffer(I,SizeOf(I));
+end;
+
+function TDescrItem.ReadStringFromStream(Astream: TStream): String;
+Var
+  L : Integer;
+begin
+  AStream.ReadBuffer(L,SizeOf(L));
+  SetLength(Result,L);
+  if (L>0) then
+    AStream.ReadBuffer(Pointer(Result)^,L*SizeOf(Char));
+end;
+
+function TDescrItem.ReadFromStream(S: TStream) : Integer;
+
+begin
+  S.ReadBuffer(Result,SizeOf(Result));
+  Description:=ReadStringFromStream(S);
+end;
+
+
+procedure TFileIndexDB.LoadFromStream;
+Var
+  I,S,L : Integer;
+  U : TURLItem;
+  W : TWordItem;
+  M : TMatch;
+  Li : TLanguageItem;
+
+begin
+  Clear;
+  L:=Stream.ReadDWord;
+  if (L<>FileVersion) then
+     Raise EFPIndexEr.CreateFmt(SInvalidStreamData,[Stream.Position,L,FileVersion]);
+  L:=Stream.ReadDWord;
+  if (L<>LanguageBlock) then
+    Raise EFPIndexEr.CreateFmt(SInvalidStreamData,[Stream.Position,L,LanguageBlock]);
+  L:=Stream.ReadDWord;
+  FLanguages.BeginLoading;
+  For I:=0 to L-1 do
+    begin
+    Li:=TLanguageItem(FLanguages.Add);
+    S:=Li.ReadFromStream(Stream);
+    if (S<>Li.BlockSize) then
+      Raise EFPIndexEr.CreateFmt(SInvalidStreamData,[Stream.Position,S,Li.BlockSize]);
+    end;
+  FLanguages.EndLoading;
+  L:=Stream.ReadDWord;
+  if (L<>URLBlock) then
+    Raise EFPIndexEr.CreateFmt(SInvalidStreamData,[Stream.Position,L,URLBlock]);
+  L:=Stream.ReadDWord;
+  FURLS.BeginLoading;
+  For I:=0 to L-1 do
+    begin
+    U:=TURLItem(FURLS.AddDescr(''));
+    S:=U.ReadFromStream(Stream);
+    if (S<>U.BlockSize) then
+      Raise EFPIndexEr.CreateFmt(SInvalidStreamData,[Stream.Position,S,U.BlockSize]);
+    U.Resolve(FLanguages);
+    end;
+  FURLS.EndLoading;
+  L:=Stream.ReadDWord;
+  if (L<>WordBlock) then
+    Raise EFPIndexEr.CreateFmt(SInvalidStreamData,[Stream.Position,L,WordBlock]);
+  L:=Stream.ReadDWord;
+  FWords.BeginLoading;
+  For I:=0 to L-1 do
+    begin
+    W:=TWordItem(FWords.AddDescr(''));
+    S:=W.ReadFromStream(Stream);
+    if (S<>W.BlockSize) then
+      Raise EFPIndexEr.CreateFmt(SInvalidStreamData,[Stream.Position,S,W.BlockSize]);
+    end;
+  FWords.EndLoading;
+  L:=Stream.ReadDWord;
+  if (L<>MatchBlock) then
+    Raise EFPIndexEr.CreateFmt(SInvalidStreamData,[Stream.Position,L,MatchBlock]);
+  L:=Stream.ReadDWord;
+  For I:=0 to L-1 do
+    begin
+    M:=TMatch(FMatches.Add);
+    S:=M.ReadFromStream(Stream);
+    M.Resolve(FWords,FURLS);
+    if (S<>M.BlockSize) then
+      Raise EFPIndexEr.CreateFmt(SInvalidStreamData,[Stream.Position,S,M.BlockSize]);
+    end;
+end;
+
+procedure TFileIndexDB.SaveToStream;
+
+Var
+  I : Integer;
+  L : Integer;
+  U : TURLItem;
+
+begin
+  Stream.WriteDWord(FileVersion);
+  Stream.WriteDWord(LanguageBlock);
+  Stream.WriteDWord(FLanguages.Count);
+  For I:=0 to FLanguages.Count-1 do
+    FLanguages[i].WriteToStream(Stream);
+  Stream.WriteDWord(URLBlock);
+  Stream.WriteDWord(FURLS.Count);
+  For I:=0 to FURLS.Count-1 do
+    FURLS[i].WriteToStream(Stream);
+  Stream.WriteDWord(WordBlock);
+  Stream.WriteDWord(FWords.Count);
+  For I:=0 to FWords.Count-1 do
+    FWords[i].WriteToStream(Stream);
+  Stream.WriteDWord(MatchBlock);
+  Stream.WriteDWord(FMatches.Count);
+  For I:=0 to FMatches.Count-1 do
+    FMatches[i].WriteToStream(Stream);
+end;
+
+procedure TFileIndexDB.Connect;
+
+Var
+  F : TFileStream;
+  B : TReadBufStream;
+
+begin
+  B:=Nil;
+  F:=Nil;
+  if FileExists(FileName) then
+    begin
+    F:=TFileStream.Create(FileName,fmOpenRead);
+    B:=TReadBufStream.Create(F,1000000);
+    B.SourceOwner:=True;
+    end;
+  try
+    Stream:=B;
+    inherited Connect;
+  finally
+    Stream.Free;
+  end;
+end;
+
+procedure TFileIndexDB.DisConnect;
+Var
+  F : TFileStream;
+  B : TWriteBufStream;
+begin
+  F:=TFileStream.Create(FileName,fmCreate);
+  B:=TWriteBufStream.Create(F,1000000);
+  B.SourceOwner:=True;
+  try
+    Stream:=B;
+    inherited DisConnect;
+  finally
+    Stream.Free;
+    Stream:=Nil;
+  end;
+end;
+
+procedure TFileIndexDB.CommitTrans;
+begin
+  If WriteOnCommit and (FileName<>'') then
+    Disconnect;
+end;
+
+procedure TMemIndexDB.CompactDB;
+begin
+  // Do nothing
+end;
+
+procedure TMemIndexDB.BeginTrans;
+begin
+  // Do nothing
+end;
+
+procedure TMemIndexDB.CommitTrans;
+begin
+  // Do nothing
+end;
+
+procedure TMemIndexDB.Clear;
+begin
+  FMatches.Clear;
+  FWords.Clear;
+  FURLS.Clear;
+  FLanguages.Clear;
+end;
+
+constructor TMemIndexDB.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FURLS:=TDescrCollection.Create(TURLItem);
+  FWords:=TDescrCollection.Create(TWordItem);
+  FLanguages:=TDescrCollection.Create(TLanguageItem);
+  FMatches:=TMatches.Create(TMatch);
+end;
+
+destructor TMemIndexDB.Destroy;
+begin
+  Clear;
+  FreeAndNil(FMatches);
+  FreeAndNil(FWords);
+  FreeAndNil(FURLS);
+  FreeAndNil(FLanguages);
+  inherited Destroy;
+end;
+
+procedure TMemIndexDB.Connect;
+begin
+  if Assigned(Stream) then
+    LoadFromStream;
+end;
+
+procedure TMemIndexDB.DisConnect;
+begin
+  if Assigned(Stream) then
+    begin
+    FLanguages.AllocateIDs;
+    FURLS.AllocateIDs;
+    FWords.AllocateIDs;
+    SaveToStream
+    end;
+end;
+
+procedure TMemIndexDB.DeleteWordsFromFile(URL: string);
+begin
+ // inherited DeleteWordsFromFile(URL);
+end;
+
+procedure TMemIndexDB.AddSearchData(ASearchData: TSearchWordData);
+
+Var
+  AURL : TURLItem;
+  AWord : TWordItem;
+  M : TMatch;
+  L : TLanguageItem;
+
+begin
+//  Writeln('Adding search data : ',ASearchData.URL,' : ',ASearchData.SearchWord);
+
+  AURL:=TURLItem(FURLs.FindDescr(ASearchData.URL));
+  If (AURL=Nil) then
+    begin
+    L:=TLanguageItem(FLanguages.FindDescr(ASearchData.Language));
+    If (L=Nil) then
+      begin
+//      Writeln('adding language : ',ASearchData.Language);
+      L:=TLanguageItem(FLanguages.AddDescr(ASearchData.Language));
+      end;
+//    Writeln('adding URL : ',ASearchData.URL);
+    AURL:=TURLItem(FURLS.AddDescr(ASearchData.URL));
+    AURL.URLDate:=ASearchData.FileDate;
+    AURL.Language:=L;
+    end;
+  AWord:=TWordItem(FWords.FindDescr(ASearchData.SearchWord));
+  If (AWord=Nil) then
+    begin
+//    Writeln('adding Word : ',ASearchData.SearchWord);
+    AWord:=TWordItem(FWords.AddDescr(ASearchData.SearchWord));
+    end;
+//  Writeln('Adding match : ',ASearchData.Position, ' ',ASearchData.Context);
+  M:=FMatches.AddMatch(AWord,AURL);
+  M.Position:=ASearchData.Position;
+  M.Context:=ASearchData.Context;
+end;
+
+procedure TMemIndexDB.UnionMatches(ListA,ListB : TFPList);
+
+begin
+  ListA.AddList(ListB);
+end;
+
+procedure TMemIndexDB.IntersectMatches(ListA,ListB : TFPList);
+
+Var
+  L : TFPList;
+  URL : TURLItem;
+  I,J : Integer;
+  OK : Boolean;
+
+begin
+  For I:=ListA.Count-1 downto 0 do
+    begin
+    URL:=TMatch(ListA[i]).URL;
+    OK:=False;
+    J:=ListB.Count-1;
+    While (J>=0) and (TMatch(ListB[i]).URL<>URL) do
+      Dec(J);
+    if (J=-1) then
+      ListA.Delete(I);
+    end;
+end;
+
+procedure TMemIndexDB.GetMatches(AWord : String; SearchOptions: TSearchOptions; AList : TFPList);
+
+  Procedure AddMatches(W : TWordItem);
+  Var
+    I : Integer;
+
+  begin
+    For I:=0 to W.MatchCount-1 do
+      AList.Add(W.Matches[i]);
+  end;
+
+Var
+  W : TWordItem;
+  I : Integer;
+
+begin
+  If (AWord='') then exit;
+  if (AWord[1]='''') then
+    Delete(AWord,1,1);
+  I:=Length(AWord);
+  if (AWord[i]='''') then
+    Delete(AWord,i,1);
+  AWord:=LowerCase(AWord);
+  if soContains in SearchOptions then
+    begin
+    For I:=0 to FWords.Count-1 do
+      begin
+      W:=TWordItem(FWords[i]);
+      If Pos(Aword,W.Description)<>0 then
+        AddMatches(W);
+      end
+    end
+  else
+    begin
+    W:=TWordItem(FWords.FindDescr(AWord));
+    if (W<>Nil) then
+      AddMatches(W);
+    end;
+end;
+
+procedure TMemIndexDB.FindSearchData(SearchWord: TWordParser;
+  FPSearch: TFPSearch; SearchOptions: TSearchOptions);
+
+Var
+  L,W : TFPList;
+  S : String;
+  I : Integer;
+  M : TMatch;
+  WD : TSearchWordData;
+
+begin
+  L:=TFPList.Create;
+  try
+    W:=TFPList.Create;
+    for I:=0 to SearchWord.Count-1 do
+      begin
+      Case SearchWord.Token[i].TokenType of
+        wtWord : begin
+                 S:=LowerCase(SearchWord.Token[i].Value);
+                 if (I=0) then
+                   GetMatches(S,SearchOptions,L)
+                 else
+                   GetMatches(S,SearchOptions,W);
+                 end;
+        wtOr :
+          UnionMatches(L,W);
+        wtAnd :
+          InterSectMatches(L,W);
+      end;
+      end;
+    For I:=0 to L.Count-1 do
+      begin
+      M:=TMatch(L[i]);
+      WD.SearchWord:=M.Word.Description;
+      WD.Context:=M.Context;
+      WD.FileDate:=M.URL.URLDate;
+      WD.URL:=M.URL.Description;
+      WD.Position:=M.Position;
+      WD.Language:=M.URL.Language.Description;
+      FPSearch.AddResult(i,WD);
+      end;
+  finally
+    L.Free;
+  end;
+end;
+
+procedure TMemIndexDB.CreateIndexerTables;
+begin
+  Clear;
+end;
+
+{ TMatches }
+
+function TMatches.GetM(AIndex : Integer): TMatch;
+begin
+  Result:=TMatch(Items[AIndex]);
+end;
+
+procedure TMatches.SetM(AIndex : Integer; AValue: TMatch);
+begin
+  Items[AIndex]:=AValue;
+end;
+
+function TMatches.AddMatch(AWord: TWordItem; AURL: TURLItem): TMatch;
+begin
+  Result:=TMatch(Add);
+  Result.URL:=AURl;
+  Result.Word:=AWord;
+end;
+
+{ TMatch }
+
+procedure TMatch.SetURL(AValue: TURLItem);
+begin
+  if FURL=AValue then exit;
+  If (FURL<>Nil) then
+    FURL.RemoveMatch(Self);
+  FURL:=AValue;
+  If (FURL<>Nil) then
+    FURL.AddMatch(Self);
+end;
+
+function TMatch.GetContext: String;
+begin
+  Result:=Description;
+end;
+
+procedure TMatch.SetContext(AValue: String);
+begin
+  Description:=AValue;
+end;
+
+procedure TMatch.SetWord(AValue: TWordItem);
+begin
+  if FWord=AValue then exit;
+  If (FWord<>Nil) then
+    FWord.RemoveMatch(Self);
+  FWord:=AValue;
+  If (FWord<>Nil) then
+    FWord.AddMatch(Self);
+end;
+
+function TMatch.BlockSize: Integer;
+begin
+  Result:=inherited BlockSize;
+  Result:=Result+SizeOf(FPosition)+2*SizeOf(Integer);
+end;
+
+procedure TMatch.WriteToStream(S: TStream);
+
+Var
+  L : Integer;
+
+begin
+  inherited WriteToStream(S);
+  S.WriteBuffer(FPosition,Sizeof(FPosition));
+  WriteRefToStream(S,FWord);
+  WriteRefToStream(S,FUrl);
+end;
+
+procedure TMatch.Resolve(Words, URLS: TDescrCollection);
+begin
+  Word:=TWordItem(Words.Resolve(FWordID));
+  URL:=TURLItem(URLS.Resolve(FURLID));
+end;
+
+function TMatch.ReadFromStream(S: TStream): Integer;
+begin
+  Result:=inherited ReadFromStream(S);
+  S.ReadBuffer(FPosition,Sizeof(FPosition));
+  S.ReadBuffer(FWordID,SizeOf(FWordID));
+  S.ReadBuffer(FURLID,SizeOf(FURLID));
+end;
+
+{ TDescrCollection }
+
+function TDescrCollection.GetD(AIndex : Integer): TDescrItem;
+begin
+  Result:=TDescrItem(Items[AIndex])
+end;
+
+procedure TDescrCollection.SetD(AIndex : Integer; AValue: TDescrItem);
+begin
+  Items[AIndex]:=AValue;
+end;
+
+procedure TDescrCollection.RebuildHash;
+
+Var
+  I : Integer;
+  D : TDescrItem;
+
+begin
+  if FHash<>Nil then
+    FHash.Clear
+  else
+    FHash:=TFPhashList.Create;
+  For I:=0 to Count-1 do
+    begin
+    D:=GetD(I);
+    FHash.Add(D.Description,D);
+    end;
+end;
+
+function TDescrCollection.Resolve(AIndex: Integer): TDescrItem;
+begin
+  If (Aindex=-1) or (AIndex>=Count) then
+    Result:=Nil
+  else
+    Result:=TDescrItem(Items[AIndex]);
+end;
+
+destructor TDescrCollection.destroy;
+begin
+  FreeAndNil(FHash);
+  inherited destroy;
+end;
+
+
+procedure TDescrCollection.BeginLoading;
+begin
+  Inc(FLoadCount);
+end;
+
+procedure TDescrCollection.EndLoading;
+begin
+  if (FLoadCount>0) then
+    begin
+    Dec(FLoadCount);
+    If (FLoadCount=0) then
+      RebuildHash;
+    end;
+end;
+
+procedure TDescrCollection.AllocateIDS;
+
+Var
+  I : Integer;
+
+begin
+  For I:=0 to Count-1 do
+    GetD(i).TheID:=I;
+end;
+
+function TDescrCollection.FindDescr(const ADescription: String): TDescrItem;
+begin
+
+  If FHash=Nil then
+    Result:=Nil
+  else
+    Result:=TDescrItem(FHash.Find(ADescription));
+end;
+
+function TDescrCollection.AddDescr(const ADescription: String): TDescrItem;
+begin
+  Result:=Add as TDescrItem;
+  Result.Description:=ADescription;
+  if (FLoadCount=0) then
+    begin
+    If FHash=Nil then
+      ReBuildHash
+    else
+      FHash.Add(ADescription,Result);
+    end;
+end;
+
+
+
+{ TWordItem }
+
+function TMatchedItem.GetMatch(AIndex : Integer): TMatch;
+begin
+  Result:=TMatch(FList[AIndex]);
+end;
+
+function TMatchedItem.GetMatchCount: Integer;
+begin
+  Result:=FList.Count;
+end;
+
+function TMatchedItem.AddMatch(AMatch: TMatch): Integer;
+begin
+  FList.Add(AMatch);
+end;
+
+procedure TMatchedItem.RemoveMatch(AMatch: TMatch);
+begin
+  Flist.Remove(AMatch);
+end;
+
+constructor TMatchedItem.Create(ACollection: TCollection);
+begin
+  inherited Create(ACollection);
+  FList:=TFPList.Create;
+end;
+
+destructor TMatchedItem.Destroy;
+begin
+  FreeAndNil(Flist);
+  inherited Destroy;
+end;
+
+end.
+

+ 304 - 0
packages/fpindexer/src/sqldbindexdb.pp

@@ -0,0 +1,304 @@
+{
+    This file is part of the Free Component Library (FCL)
+    Copyright (c) 2012 by the Free Pascal development team
+
+    SQLDB-based index database
+    
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    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.
+
+ **********************************************************************}
+unit SQLDBIndexDB;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  SysUtils, fpIndexer, sqldb, db;
+
+// SQLDB Specific, cache query objects
+type
+  TCachedQueryType = (cqtInsertWord, cqtGetWordID, cqtInsertFile, cqtGetFileID,
+    cqtInsertLanguage, cqtGetLanguageID, cqtInsertMatch);
+
+// Interbase specific
+const
+  {$note @MvC, TIndexTable is defined as: itWords, itLanguages, itFiles, itMatches, below order seems to be wrong}
+  DefaultGeneratorNames: array[TIndexTable] of string = ('GEN_WORDS', 'GEN_MATCHES', 'GEN_LANGUAGES', 'GEN_FILES');
+
+type
+
+  { TSQLDBIndexDB }
+
+  TSQLDBIndexDB = class(TSQLIndexDB)
+  private
+    // SQLDB specific
+    db: TSQLConnection;
+    FLastURLID: int64;
+    FLastURL: string;
+    FLastLanguageID: int64;
+    FLastLanguage: string;
+    FLastWordID: int64;
+    FLastWord: string;
+    FQueries: array [TCachedQueryType] of TSQLQuery;
+  protected
+    // SQLDB Specific statements
+    procedure Execute(const sql: string; ignoreErrors: boolean = True); override;
+    function GetLanguageID(const ALanguage: string): int64;
+    function GetWordID(const AWord: string): int64;
+    function GetURLID(const URL: string; ATimeStamp: TDateTime; ALanguageID: int64; DoCreate: boolean = True): int64; override;
+    function CreateQuery(const ASQL: string): TSQLQuery;
+    function CreateCachedQuery(QueryType: TCachedQueryType; const ASQL: string): TSQLQuery;
+    // Connection specific, need to be overridden
+    function GetConnection: TSQLConnection; virtual; abstract;
+    procedure InsertMatch(AWordID, aFileID, aLanguageID: int64; const ASearchData: TSearchWordData); virtual; abstract;
+    function InsertWord(const AWord: string): int64; virtual; abstract;
+    function InsertURL(const URL: string; ATimeStamp: TDateTime; ALanguageID: int64): int64; virtual; abstract;
+    function InsertLanguage(const ALanguage: string): int64; virtual; abstract;
+  public
+    destructor Destroy; override;
+    procedure Connect; override;
+    procedure Disconnect; override;
+    procedure CreateDB; override;
+    procedure BeginTrans; override;
+    procedure CommitTrans; override;
+    procedure CompactDB; override;
+    procedure AddSearchData(ASearchData: TSearchWordData); override;
+    procedure FindSearchData(SearchWord: TWordParser; FPSearch: TFPSearch; SearchOptions: TSearchOptions); override;
+    procedure DeleteWordsFromFile(URL: string); override;
+  end;
+
+implementation
+
+{ TSQLDBIndexDB }
+
+function TSQLDBIndexDB.GetLanguageID(const ALanguage: string): int64;
+var
+  Q: TSQLQuery;
+begin
+  if SameFileName(FLastLanguage, ALanguage) then
+    Result := FLastLanguageID
+  else
+  begin
+    Q := CreateCachedQuery(cqtGetLanguageID, GetLanguageSQL);
+    Q.ParamByName(GetFieldName(ifLanguagesName)).AsString := ALanguage;
+    Q.Open;
+    try
+      if (Q.EOF and Q.BOF) then
+        Result := InsertLanguage(ALanguage)
+      else
+        Result := Q.FieldByName(GetFieldName(ifLanguagesID)).AsLargeInt;
+      FLastLanguageID := Result;
+      FLastLanguage := ALanguage;
+    finally
+      Q.Close;
+    end;
+  end;
+end;
+
+function TSQLDBIndexDB.GetWordID(const AWord: string): int64;
+var
+  Q: TSQLQuery;
+begin
+  if (FLastWord = AWord) then
+    Result := FLastWordID
+  else
+  begin
+    Q := CreateCachedQuery(cqtGetWordID, GetWordSQL);
+    Q.ParamByName(GetFieldName(ifWordsWord)).AsString := AWord;
+    Q.Open;
+    try
+      if (Q.EOF and Q.BOF) then
+        Result := InsertWord(AWord)
+      else
+        Result := Q.FieldByName(GetFieldName(ifWordsID)).AsLargeInt;
+      FLastWordID := Result;
+      FLastWord := AWord;
+    finally
+      Q.Close;
+    end;
+  end;
+end;
+
+function TSQLDBIndexDB.CreateQuery(const ASQL: string): TSQLQuery;
+begin
+  Result := TSQLQuery.Create(Self);
+  Result.Database := Self.db;
+  Result.Transaction := Self.db.Transaction;
+  Result.SQL.Text := ASQL;
+  //Writeln('SQL  :',ASQL);
+end;
+
+function TSQLDBIndexDB.GetURLID(const URL: string; ATimeStamp: TDateTime; ALanguageID: int64; DoCreate: boolean = True): int64;
+var
+  Q: TSQLQuery;
+begin
+  if SameFileName(FLastURL, URL) then
+    Result := FLastURLID
+  else
+  begin
+    Q := CreateCachedQuery(cqtGetFileID, GetSearchFileSQL);
+    If Length(URL)>255 then
+      Writeln('URL Length : ',Length(URL),' : ',URL);
+    Q.ParamByName(GetFieldName(ifFilesURL)).AsString := URL;
+    Q.Open;
+    try
+      if (Q.EOF and Q.BOF) then
+      begin
+        if DoCreate then
+          Result := InsertURL(URL, ATimeStamp, ALanguageID)
+        else
+          Result := -1;
+      end
+      else
+        Result := Q.FieldByName(GetFieldName(ifFilesID)).AsLargeInt;
+      FLastURLID := Result;
+      FLastURL := URL;
+    finally
+      Q.Close;
+    end;
+  end;
+end;
+
+function TSQLDBIndexDB.CreateCachedQuery(QueryType: TCachedQueryType; const ASQL: string): TSQLQuery;
+begin
+  if FQueries[QueryType] = nil then
+  begin
+    FQueries[QueryType] := CreateQuery(ASQL);
+    FQueries[QueryType].Prepare;
+  end;
+  Result := FQueries[QueryType];
+end;
+
+procedure TSQLDBIndexDB.AddSearchData(ASearchData: TSearchWordData);
+var
+  WID, FID, LID: int64;
+begin
+  //check if the SearchWord already is in the list
+  LID := GetLanguageID(ASearchData.Language);
+  FID := GetURLID(ASearchData.URL, ASearchData.FileDate, LID);
+  WID := GetWordID(ASearchData.SearchWord);
+  InsertMatch(WID, FID, LID, ASearchData);
+end;
+
+procedure TSQLDBIndexDB.FindSearchData(SearchWord: TWordParser; FPSearch: TFPSearch; SearchOptions: TSearchOptions);
+var
+  Q: TSQLQuery;
+  FN, FP, FD, FW, FC: TField;
+  Res: TSearchWordData;
+  S,WW : String;
+  I,L : Integer;
+
+begin
+  Q := CreateQuery(GetMatchSQL(SearchOptions,SearchWord,True));
+  try
+    Writeln(Q.SQL.Text);
+    WW := getFieldName(ifWordsWord);
+    for i := 0 to SearchWord.Count - 1 do
+      If SearchWord.Token[i].TokenType=wtWord then
+        begin
+        S:=SearchWord.Token[i].Value;
+        if (Length(S)>0) and (S[1]='''') then
+          Delete(S,1,1);
+        L:=Length(S);
+        if (l>0) and (S[l]='''') then
+          Delete(S,l,1);
+        if (soContains in Searchoptions) then
+          S:='%'+S+'%';
+        Q.ParamByName(WW+IntToStr(i)).AsString:=S;
+        end;
+    Q.Open;
+    FN := Q.FieldByName(GetFieldName(ifFilesURL));
+    FD := Q.FieldByName(GetFieldName(ifFilesTimeStamp));
+    FC := Q.FieldByName(GetFieldName(ifMatchesContext));
+    FP := Q.FieldByName(GetFieldName(ifMatchesPosition));
+    FW := Q.FieldByName(GetFieldName(ifWordsWord));
+    while not Q.EOF do
+    begin
+      Res.FileDate := FD.AsDateTime;
+      Res.URL := FN.AsString;
+      Res.SearchWord := FW.AsString;
+      Res.Position := FP.AsInteger;
+      Res.Context:=FC.aSString;
+      FPSearch.AddResult(Q.RecNo, Res);
+      Q.Next;
+    end;
+  finally
+    Q.Free;
+  end;
+end;
+
+procedure TSQLDBIndexDB.DeleteWordsFromFile(URL: string);
+begin
+  inherited DeleteWordsFromFile(URL);
+  FLastURL := '';
+end;
+
+procedure TSQLDBIndexDB.Execute(const sql: string; ignoreErrors: boolean = True);
+begin
+  if SQL = '' then
+    exit;
+  try
+    DB.ExecuteDirect(sql);
+  except
+    if not IgnoreErrors then
+      raise;
+  end;
+end;
+
+procedure TSQLDBIndexDB.Connect;
+begin
+  if (DB = nil) then
+    db := GetConnection;
+  if DB.Transaction = nil then
+    DB.Transaction := TSQLTransaction.Create(db);
+  DB.Connected := True;
+end;
+
+procedure TSQLDBIndexDB.Disconnect;
+Var
+  T : TCachedQueryType;
+
+begin
+  For T:=Low(TCachedQueryType) to High(TCachedQueryType) do
+    FreeAndNil(FQueries[T]);
+  FreeAndNil(DB);
+end;
+
+procedure TSQLDBIndexDB.CreateDB;
+begin
+  if DB = nil then
+    DB := GetConnection;
+  DB.CreateDB;
+  Connect;
+  CreateIndexerTables;
+end;
+
+destructor TSQLDBIndexDB.Destroy;
+begin
+  Disconnect;
+  inherited Destroy;
+end;
+
+procedure TSQLDBIndexDB.BeginTrans;
+begin
+  DB.Transaction.StartTransaction;
+end;
+
+procedure TSQLDBIndexDB.CommitTrans;
+begin
+  DB.Transaction.Commit;
+end;
+
+procedure TSQLDBIndexDB.CompactDB;
+begin
+  //not yet implemented
+end;
+
+end.
+

+ 291 - 0
packages/fpindexer/src/sqliteindexdb.pp

@@ -0,0 +1,291 @@
+{
+    This file is part of the Free Component Library (FCL)
+    Copyright (c) 2012 by the Free Pascal development team
+
+    SQLite-based index database
+    
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    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.
+
+ **********************************************************************}
+unit SQLiteIndexDB;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  SysUtils, fpIndexer, ctypes, sqlite3;
+
+type
+  TDatabaseID = record
+    ID: int64;
+    Name: string;
+  end;
+
+  { TSQLiteIndexDB }
+
+  TSQLiteIndexDB = class(TSQLIndexDB)
+  private
+    db: Psqlite3;
+    FFileName: string;
+    Frow: integer;
+    FSearchClass: TFPSearch;
+    LanguageID: TDatabaseID;
+    QueryResult: string;
+    SearchWordID: TDatabaseID;
+    URLID: TDatabaseID;
+    procedure CheckSQLite(Rc: cint; pzErrMsg: PChar);
+  protected
+    class function AllowForeignKeyInTable: boolean; override;
+    function GetFieldType(FieldType: TIndexField): string; override;
+    function GetLanguageID(const ALanguage: string): int64;
+    function GetURLID(const URL: string; ATimeStamp: TDateTime; ALanguageID: int64; DoCreate: boolean = True): int64; override;
+    function GetWordID(const AWord: string): int64; virtual;
+    function InsertLanguage(const ALanguage: string): int64; virtual;
+    function InsertURL(const URL: string; ATimeStamp: TDateTime; ALanguageID: int64): int64;
+    function InsertWord(const AWord: string): int64; virtual;
+    procedure Execute(const sql: string; ignoreErrors: boolean = True); override;
+  public
+    destructor Destroy; override;
+    procedure AddSearchData(ASearchData: TSearchWordData); override;
+    procedure BeginTrans; override;
+    procedure CommitTrans; override;
+    procedure CompactDB; override;
+    procedure Connect; override;
+    procedure CreateDB; override;
+    procedure DeleteWordsFromFile(URL: string); override;
+    procedure FindSearchData(SearchWord: TWordParser; FPSearch: TFPSearch; SearchOptions: TSearchOptions); override;
+  published
+    property FileName: string read FFileName write FFileName;
+  end;
+
+implementation
+
+function SearchCallback(_para1: pointer; plArgc: longint; argv: PPchar; argcol: PPchar): longint; cdecl;
+var
+  PVal: ^PChar;
+  SearchRes: TSearchWordData;
+begin
+  PVal := argv;
+  with SearchRes do
+  begin
+    Position := StrToInt64(PVal^);     Inc(PVal);
+    URL := PVal^;                      Inc(PVal);
+    Context := PVal^;                  Inc(PVal);
+    SearchWord := PVal^;               Inc(PVal);
+    FileDate := ISO8601ToDate(PVal^);  Inc(PVal);
+    Language := PVal^;
+  end;
+
+  with TSQLiteIndexDB(_para1) do
+  begin
+    FSearchClass.AddResult(FRow, SearchRes);
+    Inc(Frow);
+  end;
+  Result := 0;
+end;
+
+function IndexCallback(_para1: pointer; plArgc: longint; argv: PPchar; argcol: PPchar): longint; cdecl;
+begin
+  //store the query result
+  TSQLiteIndexDB(_para1).QueryResult := argv^;
+  Result := 0;
+end;
+
+{ TSQLiteIndexDB }
+
+procedure TSQLiteIndexDB.Execute(const sql: string; ignoreErrors: boolean = True);
+var
+  pzErrMsg: PChar;
+  rc: cint;
+begin
+  QueryResult := '';
+  //Writeln('Executing  : ',SQL);
+  rc := sqlite3_exec(db, PChar(sql), @IndexCallback, self, @pzErrMsg);
+  if not ignoreErrors then
+    CheckSQLite(rc, pzErrMsg);
+end;
+
+function TSQLiteIndexDB.GetURLID(const URL: string; ATimeStamp: TDateTime; ALanguageID: int64; DoCreate: boolean): int64;
+var
+  SQL: string;
+begin
+  if (URL = URLID.Name) then
+    Result := URLID.ID
+  else
+  begin
+    SQL := Format(GetURLSQL(False), [QuoteString(URL)]);
+    Execute(SQL, False);
+    Result := StrToInT64Def(QueryResult, -1);
+    if (Result = -1) and DoCreate then
+      Result := InsertURL(URL, ATimeStamp, ALanguageID);
+    URLID.ID := Result;
+    URLID.Name := URL;
+  end;
+end;
+
+function TSQLiteIndexDB.GetLanguageID(const ALanguage: string): int64;
+var
+  SQL: string;
+begin
+  if (ALanguage = LanguageID.Name) then
+    Result := LanguageID.ID
+  else
+  begin
+    SQL := Format(GetLanguageSQL(False), [QuoteString(Alanguage)]);
+    Execute(SQL, False);
+    Result := StrToInT64Def(QueryResult, -1);
+    if (Result = -1) then
+      Result := InsertLanguage(ALanguage);
+    LanguageID.ID := Result;
+    LanguageID.Name := ALanguage;
+  end;
+end;
+
+function TSQLiteIndexDB.GetWordID(const AWord: string): int64;
+var
+  SQL: string;
+begin
+  if (AWord = SearchWordID.Name) then
+    Result := SearchWordID.ID
+  else
+  begin
+    SQL := Format(GetWordSQL(False), [QuoteString(AWord)]);
+    Execute(SQL, False);
+    Result := StrToInT64Def(QueryResult, -1);
+    if (Result = -1) then
+      Result := InsertWord(AWord);
+    SearchWordID.ID := Result;
+    SearchWordID.Name := AWord;
+  end;
+end;
+
+function TSQLiteIndexDB.InsertWord(const AWord: string): int64;
+begin
+  Execute(Format(InsertSQL(itWords, False), ['Null', QuoteString(AWord)]), False);
+  Result := sqlite3_last_insert_rowid(db);
+end;
+
+function TSQLiteIndexDB.InsertURL(const URL: string; ATimeStamp: TDateTime; ALanguageID: int64): int64;
+begin
+  // ifFilesID,ifFilesURL,ifFilesReindex,ifFilesUpdated,ifFilesTimeStamp,ifFilesLanguageID
+  Execute(Format(InsertSQL(itFiles, False), ['Null', QuoteString(URL), '0', '0', QuoteString(DateToISO8601(ATimeStamp)), IntToStr(AlanguageID)]), False);
+  Result := sqlite3_last_insert_rowid(db);
+end;
+
+function TSQLiteIndexDB.InsertLanguage(const ALanguage: string): int64;
+begin
+  Execute(Format(InsertSQL(itLanguages, False), ['Null', QuoteString(ALanguage)]), False);
+  Result := sqlite3_last_insert_rowid(db);
+end;
+
+function TSQLiteIndexDB.GetFieldType(FieldType: TIndexField): string;
+begin
+  Result := inherited GetFieldType(FieldType);
+  if (Result = PrimaryFieldType) then
+    Result := 'INTEGER PRIMARY KEY NOT NULL';
+end;
+
+class function TSQLiteIndexDB.AllowForeignKeyInTable: boolean;
+begin
+  Result := True;
+end;
+
+procedure TSQLiteIndexDB.DeleteWordsFromFile(URL: string);
+begin
+  inherited DeleteWordsFromFile(URL);
+
+  //reset the cached URL ID
+  URLID.ID := -1;
+  URLID.Name := '';
+end;
+
+procedure TSQLiteIndexDB.CreateDB;
+begin
+  Connect;
+  CreateIndexerTables;
+end;
+
+procedure TSQLiteIndexDB.Connect;
+var
+  rc: cint;
+begin
+  if (Filename = '') then
+    raise EFPIndexer.Create('Error: no filename specified');
+  rc := sqlite3_open(PChar(FFilename), @db);
+  if rc <> SQLITE_OK then
+    raise EFPIndexer.CreateFmt('Cannot open database: %s', [filename]);
+end;
+
+destructor TSQLiteIndexDB.Destroy;
+begin
+  sqlite3_close(db);
+  inherited Destroy;
+end;
+
+procedure TSQLiteIndexDB.BeginTrans;
+begin
+  Execute('BEGIN IMMEDIATE TRANSACTION');
+end;
+
+procedure TSQLiteIndexDB.CommitTrans;
+begin
+  Execute('COMMIT TRANSACTION');
+end;
+
+procedure TSQLiteIndexDB.CompactDB;
+begin
+  {$note this does not work, why?}
+  //Execute('VACUUM');
+end;
+
+procedure TSQLiteIndexDB.AddSearchData(ASearchData: TSearchWordData);
+var
+  WID, LID, FID: int64;
+  SQL: string;
+begin
+  WID := GetWordID(ASearchData.SearchWord);
+  LID := GetLanguageID(ASearchData.Language);
+  FID := GetURLID(ASearchData.URL, ASearchData.FileDate, LID, True);
+  SQL := InsertSQL(itMatches, False);
+  // ifMatchesID,ifMatchesWordId,ifMatchesFileID,ifMatchesLanguageID,ifMatchesPosition,ifMatchesContext,
+  SQL := Format(SQL, ['Null', IntToStr(WID), IntToStr(FID), IntToStr(LID), IntToStr(ASearchData.Position), QuoteString(ASearchData.Context)]);
+  //add to SearchWordList
+  Execute(SQL, False);
+  // Result:=sqlite3_last_insert_rowid(db);
+end;
+
+procedure TSQLiteIndexDB.CheckSQLite(Rc: cint; pzErrMsg: PChar);
+var
+  S: string;
+begin
+  if (rc <> SQLITE_OK) then
+  begin
+    if (pzErrMsg <> nil) then
+      S := strPas(pzErrMsg);
+    raise EFPIndexer.CreateFmt('SQLite error: %s', [S]);
+  end;
+end;
+
+procedure TSQLiteIndexDB.FindSearchData(SearchWord: TWordParser; FPSearch: TFPSearch; SearchOptions: TSearchOptions);
+var
+  pzErrMsg: PChar;
+  rc: cint;
+  sql: string;
+begin
+  FSearchClass := FPSearch;
+  Frow := 0;
+
+  sql := GetMatchSQL(SearchOptions, SearchWord, False);
+  //sql := Format(sql, [SearchWord]);
+  rc := sqlite3_exec(db, PChar(sql), @SearchCallback, self, @pzErrMsg);
+  CheckSQLite(rc, pzErrMsg);
+end;
+
+end.
+