[vdr-plugin-epg2vdr] 01/09: Imported Upstream version 0.1.12.git20150203.1541

Tobias Grimm tiber-guest at moszumanska.debian.org
Sun Feb 22 13:10:16 UTC 2015


This is an automated email from the git hooks/post-receive script.

tiber-guest pushed a commit to branch master
in repository vdr-plugin-epg2vdr.

commit 4dd0b7b634d1564b4594c86e1114b235f39bfc5e
Author: etobi <git at e-tobi.net>
Date:   Sun Feb 22 12:41:21 2015 +0100

    Imported Upstream version 0.1.12.git20150203.1541
---
 COPYING                                          |  340 ++++++
 HISTORY                                          |   92 ++
 Make.config                                      |   52 +
 Makefile                                         |    1 +
 Makefile.pre-1.7.35                              |  143 +++
 Makefile.since-1.7.35                            |  137 +++
 README                                           |  138 +++
 configs/epgsearch/epgsearch.conf                 |    4 +
 configs/epgsearch/epgsearchcats.conf             |   42 +
 configs/epgsearch/epgsearchuservars.conf         |   12 +
 contrib/epg2vdr.ignore                           |   25 +
 epg2vdr.c                                        |  371 +++++++
 epg2vdr.h                                        |   59 +
 handler.h                                        | 1000 +++++++++++++++++
 lib/Makefile                                     |   73 ++
 lib/common.c                                     | 1290 ++++++++++++++++++++++
 lib/common.h                                     |  284 +++++
 lib/config.c                                     |   73 ++
 lib/config.h                                     |   84 ++
 lib/curl.c                                       |  176 +++
 lib/db.c                                         | 1209 ++++++++++++++++++++
 lib/db.h                                         | 1068 ++++++++++++++++++
 lib/dbdict.c                                     |  158 +++
 lib/dbdict.h                                     |   52 +
 lib/demo.c                                       |  280 +++++
 lib/imgtools.c                                   |  189 ++++
 lib/imgtools.h                                   |   30 +
 lib/tabledef.c                                   |  926 ++++++++++++++++
 lib/tabledef.h                                   |  867 +++++++++++++++
 lib/test.c                                       |  377 +++++++
 patches/epghandler-segment-transfer.patch        |   65 ++
 patches/vdr-1.7.27-to-epghandler-of-1.7.31.patch |  219 ++++
 patches/vdr-1.7.28-epghandledexternally.diff     |  118 ++
 patches/vdr-1.7.29-epgIsUpdate.diff              |   52 +
 po/de_DE.po                                      |   86 ++
 po/it_IT.po                                      |   92 ++
 timer.c                                          |  127 +++
 update.c                                         | 1243 +++++++++++++++++++++
 update.h                                         |  150 +++
 39 files changed, 11704 insertions(+)

diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..f90922e
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/HISTORY b/HISTORY
new file mode 100644
index 0000000..237e541
--- /dev/null
+++ b/HISTORY
@@ -0,0 +1,92 @@
+
+--------------------------------------
+VDR Plugin 'epg2vdr' Revision History
+--------------------------------------
+
+2015-02-03: version 0.1.12
+   - change: fixed compile without X headers
+
+2014-12-28: version 0.1.11
+   - change: deleting old timers from timer table
+
+2014-05-13: version 0.1.10
+   - bugfix: fixed mutex problem in epg handler
+
+2014-05-10: version 0.1.9
+   - change: build lib for ther ./lib stuff
+   - change: improved the speed and optimized cpu load
+
+2014-03-22: version 0.1.8
+   - bugfix: fixed name lookup for mapdb
+
+2014-01-03: version 0.1.7
+   - bugfix: fixed epg handler
+
+2014-01-03: version 0.1.6
+   - change: adapted epgdata plugin to modified header
+   - change: added fields producer, other and camera
+   - change: removed fields origtitle and team
+
+2013-02-05: version 0.1.5
+   - change: removed activity check, instead wait up to 20 seconds to give the mail loop time to finish
+
+2013-12-31: version 0.1.4
+   - change: increased field guest to 1000
+
+2013-12-04: version 0.1.3
+   - bugfix: fixed insert handling with multimerge
+   - change: speedup reload with multimerge
+
+2013-11-20: version 0.1.2
+   - change: increased shorttext and compshorttext to 300 chars, topic and guest to 500 chars
+             therefore you have to alter your tables
+
+2013-10-28: version 0.1.1
+   - bugfix: fixed core on missing database
+
+2013-10-23: version 0.1.0
+   - change:  first release with epg merge
+   - added:   'LoadImages', 'Shutdown On Busy', 'Schedule Boot For Update'
+              and 'Prohibit Shutdown On Busy epgd' to plugin setup menu
+   - removed: "UpdateTime" Option
+    
+2013-09-27: version 0.0.7b
+   - change: first alpha version with epg merge
+   - added:  optional schedule of wakeup 
+   - added:  optional "Prohibit Shutdown On Busy 'epgd'"
+   - change: improved picture load and link handling
+
+2013-09-23: version 0.0.7a
+   - change: started devel branch for epg merge
+
+2013-09-16: version 0.0.6
+   - bugfix: fixed install of locale files 
+   - bugfix: fixed handler bug (thread conflict), reported by 3po
+   - change: improved handler speed 
+   - change: enhanced connection check reported by OleS
+   - bugfix: problem with blacklist, reportet by theChief
+
+2013-09-05: version 0.0.5
+   - change: pause update only if epgd is busy with events (not while epgd is loading images)
+
+2013-09-04: version 0.0.4
+   - added: removed cyclic update og EPG from database 
+            -> instead auto trigger update after epgd finished download
+   - added: get changes by DVB of "vdr:000" channels every 5 minutes
+   - added: pause epg handler while epgd is busy
+   - added: pause update while epgd is busy
+
+2013-08-31: version 0.0.3
+   - change: improved speed of connection check
+             (speed of 0.0.1 restored)
+
+2013-08-30: version 0.0.2
+   - bugfix: improved database reconnect
+   - added:  check DBAPI on startup
+   - bugfix: empty shorttext now NULL instead of ""
+
+2013-08-28: version 0.0.1
+   - first official release
+
+2012-12-19: version 0.0.1-rc1
+   - initiale version
diff --git a/Make.config b/Make.config
new file mode 100644
index 0000000..8f2cb16
--- /dev/null
+++ b/Make.config
@@ -0,0 +1,52 @@
+
+# Make.config
+#
+# See the README file for copyright information and how to reach the author.
+#
+#
+
+# user defined stuff
+
+PREFIX   = /usr/local
+BINDEST  = $(DESTDIR)$(PREFIX)/bin
+CACHEDIR = /var/cache/epgd
+PLGDEST  = $(DESTDIR)$(PREFIX)/lib/epgd/plugins
+CONFDEST = $(DESTDIR)/etc/epgd
+HTTPDEST = /var/epgd/www
+
+DEBUG = 1
+
+# -----------------------
+# don't touch below ;)
+
+
+# TEST_EPG_MERGE = 1
+
+CC        = g++
+doCompile = $(CC) -c $(CFLAGS) $(DEFINES)
+doLink    = $(CC) $(LFLAGS)
+doLib     = ar -rs
+
+PLGSRCDIR = ./PLUGINS
+TMPDIR = /tmp
+
+USES = -DUSEUUID
+DEFINES += -D_GNU_SOURCE -DTARGET='"$(TARGET)"' -DLOG_PREFIX='""' -DPLGDIR='"$(PLGDEST)"' \
+           -DUSEMD5 -DUSEUUID -DUSELIBXML -DUSELIBARCHIVE \
+           $(shell xml2-config --cflags) $(shell xslt-config --cflags) 
+
+ifdef DEBUG
+  CFLAGS += -ggdb -O0
+endif
+
+CFLAGS += -fPIC -Wreturn-type -Wall -Wno-parentheses -Wformat -pedantic -Wunused-variable -Wunused-label \
+          -Wunused-value -Wunused-function \
+          -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
+
+
+ifdef TEST_EPG_MERGE
+  CFLAGS += -DTEST_EPG_MERGE
+endif
+
+%.o: %.c
+	$(doCompile) -o $@ $<
diff --git a/Makefile b/Makefile
new file mode 120000
index 0000000..b3f546d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1 @@
+Makefile.since-1.7.35
\ No newline at end of file
diff --git a/Makefile.pre-1.7.35 b/Makefile.pre-1.7.35
new file mode 100644
index 0000000..272dcee
--- /dev/null
+++ b/Makefile.pre-1.7.35
@@ -0,0 +1,143 @@
+#
+# Makefile for a Video Disk Recorder plugin
+#
+
+PLUGIN = epg2vdr
+HLIB   = -L./lib -lhorchi
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = $(shell grep 'static const char\* VERSION *=' $(PLUGIN).h | awk '{ print $$6 }' | sed -e 's/[";]//g')
+
+### The C++ compiler and options:
+
+CXX ?= g++
+
+### The directory environment:
+
+VDRDIR = ../../..
+LIBDIR = ../../lib
+TMPDIR = /tmp
+
+#  remove !!!
+VDRDIR = /usr/include/vdr
+LIBDIR = .
+LOCALEDIR = locale
+# !!!!!
+
+### Data storage path - defaults tu the VDRs plugin configuration directory
+
+EPG2VDR_DATA_DIR = "/var/cache/vdr"
+
+### Libraries
+
+LIBS = $(HLIB) $(shell mysql_config --libs_r) -luuid
+
+### Allow user defined options to overwrite defaults:
+
+-include $(VDRDIR)/Make.config
+
+### The version number of VDR's plugin API (taken from VDR's "config.h"):
+
+APIVERSION = $(shell grep 'define APIVERSION ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g')
+
+# This is required for VDRs older than 1.3.47
+ifeq ($(strip $(APIVERSION)),)
+  APIVERSION = $(shell grep 'define VDRVERSION ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g')
+endif
+
+VDRVERSNUM = $(shell sed -ne '/define VDRVERSNUM/ s/^.[a-zA-Z ]*\([0-9]*\) .*$$/\1/p' $(VDRDIR)/config.h)
+APIVERSNUM = $(shell sed -ne '/define APIVERSNUM/ s/^.[a-zA-Z ]*\([0-9]*\) .*$$/\1/p' $(VDRDIR)/config.h)
+ifeq ($(strip $(APIVERSNUM)),)
+	APIVERSNUM = $(VDRVERSNUM)
+endif
+
+### The name of the distribution archive:
+
+ARCHIVE = $(PLUGIN)-$(VERSION)
+PACKAGE = vdr-$(ARCHIVE)
+
+### Includes and Defines (add further entries here):
+
+INCLUDES += -I$(VDRDIR)/include
+INCLUDES += $(shell mysql_config --include)
+
+#DEFINES += -D_IMG_LINK
+DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' -DLOG_PREFIX='"$(PLUGIN): "'
+DEFINES += -DVDR_PLUGIN -DUSEUUID
+
+ifdef EPG2VDR_DATA_DIR
+DEFINES += -DEPG2VDR_DATA_DIR='$(EPG2VDR_DATA_DIR)'
+endif
+
+CXXFLAGS += -ggdb -Wno-unused-result
+
+### The object files (add further files here):
+
+OBJS = $(PLUGIN).o update.o timer.o lib/db.o lib/tabledef.o lib/common.o lib/config.o
+
+### Implicit rules:
+
+%.o: %.c
+	$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
+
+# Dependencies:
+
+MAKEDEP = $(CXX) -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+	@$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@
+
+-include $(DEPFILE)
+
+### Internationalization (I18N):
+
+PODIR     = po
+LOCALEDIR = $(VDRDIR)/locale
+I18Npo    = $(wildcard $(PODIR)/*.po)
+I18Nmsgs  = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
+I18Npot   = $(PODIR)/$(PLUGIN).pot
+
+%.mo: %.po
+	msgfmt -c -o $@ $<
+
+$(I18Npot): $(wildcard *.c)
+	xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --msgid-bugs-address='<gda at dachsweb.de>' -o $@ $^
+
+%.po: $(I18Npot)
+	msgmerge -U --no-wrap --no-location --backup=none -q $@ $<
+	@touch $@
+
+$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
+	@mkdir -p $(dir $@)
+	cp $< $@
+
+.PHONY: i18n
+i18n: $(I18Nmsgs)
+
+### Targets:
+
+.PHONY: all dist clean
+
+all: hlib libvdr-$(PLUGIN).so i18n 
+
+hlib: 
+	(cd lib && make -s lib)
+
+libvdr-$(PLUGIN).so: $(OBJS)
+	$(CXX) $(CXXFLAGS) -shared $(OBJS) $(LIBS) -o $@
+	@cp $@ $(LIBDIR)/$@.$(APIVERSION)
+
+dist: clean
+	@-rm -rf $(TMPDIR)/$(ARCHIVE)
+	@mkdir $(TMPDIR)/$(ARCHIVE)
+	@cp -a * $(TMPDIR)/$(ARCHIVE)
+	@tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE)
+	@-rm -rf $(TMPDIR)/$(ARCHIVE)
+	@echo Distribution package created as $(PACKAGE).tgz
+
+clean:
+	@-rm -f $(PODIR)/*.mo $(PODIR)/*.pot
+	@-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ lib/*~
+	@-rm -f epgt 
+	@-rm -f libvdr-$(PLUGIN).so*
diff --git a/Makefile.since-1.7.35 b/Makefile.since-1.7.35
new file mode 100644
index 0000000..204eb88
--- /dev/null
+++ b/Makefile.since-1.7.35
@@ -0,0 +1,137 @@
+#
+# Makefile for a Video Disk Recorder plugin
+#
+
+PLUGIN = epg2vdr
+HLIB   = -L./lib -lhorchi
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = $(shell grep 'static const char\* VERSION *=' $(PLUGIN).h | awk '{ print $$6 }' | sed -e 's/[";]//g')
+
+### The directory environment:
+
+# Use package data if installed...otherwise assume we're under the VDR source directory:
+PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell pkg-config --variable=$(1) vdr || pkg-config --variable=$(1) ../../../vdr.pc))
+
+LIBDIR = $(call PKGCFG,libdir)
+LOCDIR = $(call PKGCFG,locdir)
+PLGCFG = $(call PKGCFG,plgcfg)
+#
+TMPDIR ?= /tmp
+
+### The compiler options:
+
+export CFLAGS   += $(call PKGCFG,cflags)
+export CXXFLAGS += $(call PKGCFG,cxxflags) -Wno-unused-result -Wunused-variable
+
+### The version number of VDR's plugin API:
+
+APIVERSION = $(call PKGCFG,apiversion)
+
+### Allow user defined options to overwrite defaults:
+
+-include $(PLGCFG)
+
+#DEFINES += -D_IMG_LINK
+
+LIBS = $(HLIB) $(shell mysql_config --libs_r) -luuid -lcrypto 
+EPG2VDR_DATA_DIR = "/var/cache/vdr"
+
+ifdef EPG2VDR_DATA_DIR
+DEFINES += -DEPG2VDR_DATA_DIR='$(EPG2VDR_DATA_DIR)'
+endif
+
+### The name of the distribution archive:
+
+ARCHIVE = $(PLUGIN)-$(VERSION)
+PACKAGE = vdr-$(ARCHIVE)
+
+### The name of the shared object file:
+
+SOFILE = libvdr-$(PLUGIN).so
+
+### Includes and Defines (add further entries here):
+
+INCLUDES += $(shell mysql_config --include)
+
+DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"' -DLOG_PREFIX='"$(PLUGIN): "'
+DEFINES += -DVDR_PLUGIN -DUSEUUID
+
+### The object files (add further files here):
+
+OBJS = $(PLUGIN).o update.o timer.o
+
+### The main target:
+
+all: $(SOFILE) i18n
+
+hlib: 
+	(cd lib && $(MAKE) -s lib)
+
+### Implicit rules:
+
+%.o: %.c
+	$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
+
+### Dependencies:
+
+MAKEDEP = $(CXX) -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+	@$(MAKEDEP) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@
+
+-include $(DEPFILE)
+
+### Internationalization (I18N):
+
+PODIR     = po
+I18Npo    = $(wildcard $(PODIR)/*.po)
+I18Nmo    = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file))))
+I18Nmsgs  = $(addprefix $(DESTDIR)$(LOCDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
+I18Npot   = $(PODIR)/$(PLUGIN).pot
+
+%.mo: %.po
+	msgfmt -c -o $@ $<
+
+$(I18Npot): $(wildcard *.c)
+	xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='<vdr at jwendel.de>' -o $@ `ls $^`
+
+%.po: $(I18Npot)
+	msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $<
+	@touch $@
+
+$(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
+	install -D -m644 $< $@
+
+.PHONY: i18n
+i18n: $(I18Nmo) $(I18Npot)
+
+install-i18n: $(I18Nmsgs)
+
+### Targets:
+
+$(SOFILE): hlib $(OBJS)
+	$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) $(LIBS) -o $@
+
+install-lib: $(SOFILE)
+	install -D -m644 $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION)
+
+install: install-lib install-i18n
+
+dist: clean
+	@-rm -rf $(TMPDIR)/$(ARCHIVE)
+	@mkdir $(TMPDIR)/$(ARCHIVE)
+	@cp -a * $(TMPDIR)/$(ARCHIVE)
+	@tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE)
+	@-rm -rf $(TMPDIR)/$(ARCHIVE)
+	@echo Distribution package created as $(PACKAGE).tgz
+
+clean:
+	@-rm -f $(OBJS) $(DEPFILE) *.so *.core* *~ 
+	@-rm -f $(PODIR)/*.mo $(PODIR)/*.pot $(PODIR)/*~
+	@-rm -f $(PACKAGE).tgz
+	(cd lib && $(MAKE) clean)
+
+cppchk:
+	cppcheck --template="{file}:{line}:{severity}:{message}" --quiet --force *.c *.h 
diff --git a/README b/README
new file mode 100644
index 0000000..a9cc9c5
--- /dev/null
+++ b/README
@@ -0,0 +1,138 @@
+-----------------------------------------------------------------------------------
+- epg2vdr
+-
+- This is a "plugin" for the Video Disk Recorder (VDR).
+-
+- Written by: C++/SQL         - J�rg Wendel (vdr at jwendel dot de)
+-             SQL/Procedures  - Christian Kaiser
+-             Documetation    - Ingo Prochaska
+-
+- Homepage:          http://projects.vdr-developer.org/projects/plg-epg2vdr 
+- Latest version at: http://projects.vdr-developer.org/git/vdr-plugin-epg2vdr.git
+-
+- See the file COPYING for license information.
+-----------------------------------------------------------------------------------
+
+
+Requirements:
+-------------
+
+  - VDR 1.7.27+     (since it use the EPG handler interface)
+  - libmysql >= 5.07
+  - uuid-dev
+
+Ubuntu (12.10):
+  - libmysqlclient-dev libmysqlclient18
+  - uuid-dev
+
+
+Description:
+------------
+
+This plugin is used to retrieve EPG data into the VDR. The EPG data 
+was loaded from a mysql database. 
+   
+Setup Menu:
+-----------
+
+  Log level
+      Logging level 0-4 { Errors, Infos, ..., Debug, ... }
+
+  Update DVB EPG Database
+      Master/Slave Mode (one client should be master, e.g. 24/7 Server, all others slave)
+        - auto: Normally first VDR will be master - the master transfers the DVB events to the mySQL Server
+        - yes: Master mode on
+        - no: Master mode off
+      Make sure that only one VDR will be master or you run into DB performance issues. Makes just sense, if you have different channels on your clients.
+
+  Show In Main Menu
+      Shows entry in main menu to perform update manually
+
+  MySQL Host
+      IP of mySQL Server
+
+  Port
+      Port of your mySQL Server
+
+  Database Name
+      Database name
+
+  User
+      Database user
+
+  Password
+      Database password
+
+  Blacklist not configured Channels
+      Blacklist (like noepg-plug) all channels which not listst in the channelmap table
+
+  LoadImages
+      Load images from MySQL-Server to local file system
+
+  Prohibit Shutdown On Busy 'epgd'
+      Don't shutdown VDR while epgd is busy
+
+  Schedule Boot For Update
+      Schedule automatic boot for EPG update
+
+
+Get the source from git:
+------------------------
+
+get the source - probably done, when you reading this locally:
+	git clone http://projects.vdr-developer.org/git/vdr-plugin-epg2vdr.git
+
+update the source:
+	cd /to/your/source/epg2vdr
+	git pull
+
+throwing away local changes and update to latest source:
+	cd /to/your/source/epg2vdr
+	git checkout -f master
+	git reset --hard
+	git pull
+
+setup.conf (put the IP of your mySQL server):
+---------------------------------------------
+
+  epg2vdr.UpdateTime = 2
+  epg2vdr.ShowInMainMenu = 1
+  epg2vdr.Blacklist = 1
+  epg2vdr.LogLevel = 1
+  epg2vdr.DbHost = 192.168.xxx.xxx
+  epg2vdr.DbName = epg2vdr
+  epg2vdr.DbUser = epg2vdr
+  epg2vdr.DbPass = epg
+
+
+SVDRP Commands:
+---------------
+
+  The plugin provides SVDRP commands to control the plugin via command line or
+  shell scripts.
+
+  RELOAD      - Drop the whiole EPG and reload all events from the database
+  UPDATE      - Trigger a update to load all new events from database
+
+
+Installation:
+-------------
+
+- Patch the VDR. Patches found in patches/ since vdr 1.7.27. 
+  For Versions after 1.7.31 you need only the epghandler-segment-transfer.patch
+  Sinve VDR 2.1.1 no patch is needed!
+
+- Unpack the package into "PLUGINS/src" directory.
+- Call "make plugins" in the VDR root directory.
+- Start vdr with plugin epg2vdr (-Pepg2vdr) the database must running already
+
+Create database and user:
+--------------------------
+
+already done when the epgd was installed!
+
+HINTS:
+------
+- Don't load the epgtableid0 (epgtableid0 disables VDRs new EPG handler interface)
+- Don't load the noepg plugin
+(best you don't load any other epg manipulating plugin or patch if you not shure how they work together ;))
diff --git a/configs/epgsearch/epgsearch.conf b/configs/epgsearch/epgsearch.conf
new file mode 100644
index 0000000..bb41591
--- /dev/null
+++ b/configs/epgsearch/epgsearch.conf
@@ -0,0 +1,4 @@
+8:CSI - Den Tätern auf der Spur:0:::1:S19.2E-1-1019-10301|S19.2E-133-15-59:0:1:1:1:0:0:::1:0:0:1:%Series%:50:99:4:6:0:0:0::1:0:1:1:0:0:0:0:0:1:0:0::1:0:0:0:0:0:0:0:0:0:90::0
+22:Dr. House:0:::1:S19.2E-1-1019-10301|S19.2E-133-15-59:0:0:1:1:0:0:::1:0:0:1:%Series%:50:99:4:6:0:0:0::1:0:1:1:0:0:0:0:0:1:0:0::1:0:0:0:0:0:0:0:0:0:90::0
+24:Grey's Anatomy:0:::1:S19.2E-1-1019-10301|S19.2E-133-15-59:0:0:1:1:0:0:::1:0:0:1:%Series%:50:99:4:6:0:0:0::1:0:1:1:0:0:0:0:0:1:0:0::1:0:0:0:0:0:0:0:0:0:90::0
+41:The Closer:0:::1:S19.2E-1-1019-10301|S19.2E-133-15-59:0:4:1:1:0:0:::1:0:0:1:%Series%:50:99:4:6:0:0:0::1:0:1:1:0:0:0:0:0:1:0:0::1:0:0:0:0:0:0:0:0:0:90::0
diff --git a/configs/epgsearch/epgsearchcats.conf b/configs/epgsearch/epgsearchcats.conf
new file mode 100644
index 0000000..a0d5680
--- /dev/null
+++ b/configs/epgsearch/epgsearchcats.conf
@@ -0,0 +1,42 @@
+# -----------------------------------------------------------------------------
+# This is just a sample - all items deactivated. Please edit!
+# Perhaps a category or its value list should be removed. Also the
+# 'name in menu' should be adjusted to your language.
+# The order of items determines the order listed in epgsearch. It does not
+# depend on the ID, which is used by epgsearch.
+# Format:
+# ID|category name|name in menu|values separated by ',' (option)|searchmode (option)
+# - 'ID' should be a unique positive integer
+#   (changing the id later on will force you to reedit your search timers!)
+# - 'category name' is the name in your epg.data
+# - 'name in menu' is the name displayed in epgsearch.
+# - 'values' is an optional list of possible values
+# - 'searchmode' is an optional parameter specifying the mode of search:
+#     0 - the whole term must appear as substring
+#     1 - all single words (delimiters are ',', ';', '|' or '~')
+#         must exist as substrings. This is the default search mode.
+#     2 - at least one word (delimiters are ',', ';', '|' or '~')
+#         must exist as substring.
+#     3 - matches exactly
+#     4 - regular expression
+# -----------------------------------------------------------------------------
+
+#1|Category|Kategorie|Information,Kinder,Musik,Serie,Show,Spielfilm,Sport|3
+#2|Genre|Genre|Abenteuer,Action,Boxen,Comedy,Dokumentarfilm,Drama,Erotik,Familien-Show,Fantasy,Fussball,Geschichte,Gesellschaft,Gesundheit,Gymnastik,Handball,Heimat,Humor,Jazz,Kinderfilme,Kindernachrichten,Kinderserien,Klassik,Krankenhaus,Krimi,Kultur,Kurzfilm,Motor+Verkehr,Motorsport,Musik,Mystery,Nachrichten,Natur,Politik,Radsport,Ratgeber,Reise,Rock,Romantik/Liebe,Science Fiction,Soap,Spielshows,Talkshows,Tennis,Thriller,Verschiedenes,Volksmusik,Wassersport,Western,Wintersport,Wirtsch [...]
+#3|Format|Video-Format|16:9,4:3|3
+#4|Audio|Audio|Dolby Surround,Stereo|3
+#5|Year|Jahr||0
+#6|Cast|Besetzung||2
+#7|Director|Regisseur||2
+#8|Moderation|Moderation||2
+#9|Rating|Bewertung|Tagestip,Tip|3
+#10|FSK|FSK|6,12,16,18|3
+
+1|Serie|Serie||2
+2|Kurzname|Kurzname||2
+3|Episode|Episode||2
+4|Staffel,%02i|Staffel||2
+5|Staffelfolge,%02i|Staffelfolge||2
+6|Folge,%03i|Folge||2
+7|Kategorie|Kategorie||2
+8|Jahr|Jahr||13
diff --git a/configs/epgsearch/epgsearchuservars.conf b/configs/epgsearch/epgsearchuservars.conf
new file mode 100644
index 0000000..cb88a78
--- /dev/null
+++ b/configs/epgsearch/epgsearchuservars.conf
@@ -0,0 +1,12 @@
+%Langname%=%Serie% ? %Serie% : %Title%
+%Name%=%Kurzname% ? %Kurzname% : %Langname%
+
+%Epg%=%Name%~%Staffel%x%Staffelfolge% - %Folge%. %Subtitle%
+%Dummy%=%Name%~?x? - ?. %Subtitle%
+%Season%=%Staffel% ? %Epg% : %Dummy%
+
+%DateVar%=%time_w% %date% %time%
+%SerieSD%=%Subtitle% ? %Subtitle% : %DateVar%
+%SerieVar1%=Serie~%Name%~%SerieSD%
+
+%Series%=%Subtitle% ? %Season% : %SerieVar1%
diff --git a/contrib/epg2vdr.ignore b/contrib/epg2vdr.ignore
new file mode 100644
index 0000000..8fe05d6
--- /dev/null
+++ b/contrib/epg2vdr.ignore
@@ -0,0 +1,25 @@
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Auto check master role
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Change handler state to.*active
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Cleanup finished, removed.*images and.*symlinks
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: DEBUG: mysql_library_end
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: DVB event found -1 -> for
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Finished|Start reading
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Got.*new images from database
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Handle insert|update of event
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Init handler instance for thread
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Load images from database
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Processed tvm|vdr channel
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Remove old symlinks
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Removed file
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: SQL client character now
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Scheduled next auto update in
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Set locale to
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Starting cleanup of images in
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Statement.*insert|select
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Statement.*update|delete
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Store image.*/var/vdr/epgimages/images/.*
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Update EPG, loading changes since
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Update thread started|ended
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: Updated.*channels and.*events.*in.*seconds
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: detected UTF-8
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ vdr: EPG2VDR: duration.*was
diff --git a/epg2vdr.c b/epg2vdr.c
new file mode 100644
index 0000000..cda0ef5
--- /dev/null
+++ b/epg2vdr.c
@@ -0,0 +1,371 @@
+/*
+ * epg2vdr.c: EPG2VDR plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "lib/config.h"
+
+#include "update.h"
+
+#if defined (APIVERSNUM) && (APIVERSNUM < 10600)
+# error VDR API versions < 1.6.0 are not supported !
+#endif
+
+cUpdate* oUpdate;
+const char* logPrefix = LOG_PREFIX;
+
+//***************************************************************************
+// Plugin Main Menu
+//***************************************************************************
+
+class cEpgPluginMenu : public cOsdMenu
+{
+   public:
+
+      cEpgPluginMenu(const char* title, cPluginEPG2VDR* aPlugin);
+      virtual ~cEpgPluginMenu() { };
+      
+      virtual eOSState ProcessKey(eKeys key);
+
+   protected:
+
+      cPluginEPG2VDR* plugin;
+};
+
+cEpgPluginMenu::cEpgPluginMenu(const char* title, cPluginEPG2VDR* aPlugin)
+   : cOsdMenu(title)
+{
+   plugin = aPlugin;
+
+   Clear();
+
+   cOsdMenu::Add(new cOsdItem(tr("Reload")));
+   cOsdMenu::Add(new cOsdItem(tr("Update")));
+
+   SetHelp(0, 0, 0,0);
+
+   Display();
+}
+
+//***************************************************************************
+// Process Key
+//***************************************************************************
+
+eOSState cEpgPluginMenu::ProcessKey(eKeys key)
+{
+   eOSState state = cOsdMenu::ProcessKey(key);
+
+   if (state != osUnknown)
+      return state;
+
+   switch (key)
+   {
+      case kOk:
+      {
+         if (Current() == 0)
+         {
+            Skins.Message(mtInfo, tr("Reload EPG"));
+            oUpdate->triggerUpdate(true);
+         }
+         
+         else if (Current() == 1)
+         {
+            Skins.Message(mtInfo, tr("Update EPG"));
+            oUpdate->triggerUpdate();
+         }
+
+         return osEnd;
+      }
+
+      default:
+         break;
+   }
+
+   return state;
+}
+
+//***************************************************************************
+// Plugin Setup Menu
+//***************************************************************************
+
+class cMenuSetupEPG2VDR : public cMenuSetupPage 
+{
+   private:
+
+      cEPG2VDRConfig data; 
+      virtual void Setup();
+
+   protected:
+
+      virtual eOSState ProcessKey(eKeys Key);
+      virtual void Store();
+
+   public:
+
+      cMenuSetupEPG2VDR();
+};
+
+cMenuSetupEPG2VDR::cMenuSetupEPG2VDR() 
+{
+  data = EPG2VDRConfig;
+
+  Setup();
+}
+
+void cMenuSetupEPG2VDR::Setup()
+{
+   static const char* masterModes[] =
+   {
+      "auto",
+      "yes",
+      "no",
+
+      0
+   };
+
+   char* buf;
+   int current = Current();
+   
+   Clear();
+
+   asprintf(&buf, "-------------------- %s ----------------------------------", tr("EPG Update"));
+   Add(new cOsdItem(buf));
+   free(buf);
+   cList<cOsdItem>::Last()->SetSelectable(false);
+
+   Add(new cMenuEditBoolItem(tr("Show In Main Menu"), &data.mainmenuVisible));
+   cOsdMenu::Add(new cMenuEditStraItem(tr("Update DVB EPG Database"), (int*)&data.masterMode, cUpdate::mmCount, masterModes));
+   Add(new cMenuEditBoolItem(tr("Load Images"), &data.getepgimages));
+   Add(new cMenuEditBoolItem(tr("Prohibit Shutdown On Busy 'epgd'"), &data.activeOnEpgd));
+   Add(new cMenuEditBoolItem(tr("Schedule Boot For Update"), &data.scheduleBoot));
+
+   asprintf(&buf, "--------------------- %s ---------------------------------", tr("MySQL"));   
+   Add(new cOsdItem(buf));
+   free(buf);
+   cList<cOsdItem>::Last()->SetSelectable(false);
+   Add(new cMenuEditStrItem(tr("Host"), data.dbHost, sizeof(data.dbHost), tr(FileNameChars)));
+   Add(new cMenuEditIntItem(tr("Port"), &data.dbPort, 1, 99999));
+   Add(new cMenuEditStrItem(tr("Database Name"), data.dbName, sizeof(data.dbName), tr(FileNameChars)));
+   Add(new cMenuEditStrItem(tr("User"), data.dbUser, sizeof(data.dbUser), tr(FileNameChars)));
+   Add(new cMenuEditStrItem(tr("Password"), data.dbPass, sizeof(data.dbPass), tr(FileNameChars)));
+
+   asprintf(&buf, "--------------------- %s ---------------------------------", tr("NO EPG"));   
+   Add(new cOsdItem(buf));
+   free(buf);
+   Add(new cMenuEditBoolItem(tr("Blacklist not configured Channels"), &data.blacklist));
+
+   asprintf(&buf, "--------------------- %s ---------------------------------", tr("Technical Stuff"));
+   Add(new cOsdItem(buf));
+   free(buf);
+   cList<cOsdItem>::Last()->SetSelectable(false);
+   Add(new cMenuEditIntItem(tr("Log level"), &data.loglevel, 0, 4));
+
+   SetCurrent(Get(current));
+   Display();
+}
+
+eOSState cMenuSetupEPG2VDR::ProcessKey(eKeys Key) 
+{
+   eOSState state = cMenuSetupPage::ProcessKey(Key);
+   
+   switch (state) 
+   {
+      case osContinue:
+      {
+         if (NORMALKEY(Key) == kUp || NORMALKEY(Key) == kDown) 
+         {
+            cOsdItem* item = Get(Current());
+
+            if (item)
+               item->ProcessKey(kNone);
+         }
+
+         break;
+      }
+         
+      default: break;
+   }
+
+   return state;
+}
+
+void cMenuSetupEPG2VDR::Store(void)
+{
+   // int updatetime = EPG2VDRConfig.updatetime;
+
+   EPG2VDRConfig = data;
+
+   SetupStore("LogLevel", EPG2VDRConfig.loglevel);
+   SetupStore("ShowInMainMenu", EPG2VDRConfig.mainmenuVisible);
+   SetupStore("Blacklist", EPG2VDRConfig.blacklist);
+   SetupStore("DbHost", EPG2VDRConfig.dbHost);
+   SetupStore("DbPort", EPG2VDRConfig.dbPort);
+   SetupStore("DbName", EPG2VDRConfig.dbName);
+   SetupStore("DbUser", EPG2VDRConfig.dbUser);
+   SetupStore("DbPass", EPG2VDRConfig.dbPass);
+   SetupStore("MasterMode", EPG2VDRConfig.masterMode);
+   SetupStore("LoadImages", EPG2VDRConfig.getepgimages);
+   SetupStore("ActiveOnEpgd", EPG2VDRConfig.activeOnEpgd);
+   SetupStore("ScheduleBoot", EPG2VDRConfig.scheduleBoot);
+}
+
+//***************************************************************************
+// Plugin - EPG2VDR
+//***************************************************************************
+
+cPluginEPG2VDR::cPluginEPG2VDR(void)
+{
+   oUpdate = 0;
+
+   cDbConnection::init();
+}
+
+cPluginEPG2VDR::~cPluginEPG2VDR()
+{
+   delete oUpdate;
+
+   cDbConnection::exit();
+}
+
+void cPluginEPG2VDR::DisplayMessage(const char *s) 
+{
+   tell(0, "%s", s);
+
+   Skins.Message(mtInfo, tr(s));
+   sleep(Setup.OSDMessageTime);
+}
+
+const char *cPluginEPG2VDR::CommandLineHelp(void)
+{
+   return 0;
+}
+
+const char **cPluginEPG2VDR::SVDRPHelpPages(void) 
+{
+   static const char *HelpPages[] = 
+   {
+      "UPDATE\n"
+      "    Load new/changed events database.",
+      "RELOAD\n"
+      "    Reload all events from database to EPG",
+      0
+   };
+
+   return HelpPages;
+}
+
+cString cPluginEPG2VDR::SVDRPCommand(const char *Cmd, const char *Option, int &ReplyCode) 
+{
+   if (strcasecmp(Cmd, "UPDATE") == 0) 
+   {
+      oUpdate->triggerUpdate();
+      return "EPG2VDR update started.";
+   }
+   
+   else if (strcasecmp(Cmd, "RELOAD") == 0) 
+   {
+      oUpdate->triggerUpdate(true);
+      return "EPG2VDR full reload of events from database.";
+   }
+   
+   return 0;
+}
+
+//***************************************************************************
+// Initialize
+//***************************************************************************
+
+bool cPluginEPG2VDR::Initialize()
+{
+   return true;
+}
+
+//***************************************************************************
+// Start
+//***************************************************************************
+
+bool cPluginEPG2VDR::Start()
+{
+   oUpdate = new cUpdate(this);
+   oUpdate->Start();   // start thread
+   
+   return true;
+}
+
+cString cPluginEPG2VDR::Active()
+{
+//   time_t timeoutAt = time(0) + 10;
+
+   if (EPG2VDRConfig.activeOnEpgd && oUpdate->isEpgdUpdating())
+      return tr("EPG2VDR Waiting on epgd");
+   
+//    while (oUpdate->isUpdateActive())
+//    {
+//       tell(0, "EPG2VDR EPG update running, wating up to 10 seconds ..");
+
+//       if (time(0) > timeoutAt)
+//       {
+//          tell(0, "EPG2VDR EPG update running, shutdown timed out, aborting");
+//          return 0;
+//       }
+      
+//       usleep(500000);
+//    }
+   
+   return 0;
+}
+
+time_t cPluginEPG2VDR::WakeupTime()
+{
+   // return custom wakeup time for shutdown script
+
+   if (EPG2VDRConfig.scheduleBoot && oUpdate->getNextEpgdUpdateAt())
+      return oUpdate->getNextEpgdUpdateAt();
+
+   return 0;
+}
+
+cOsdObject* cPluginEPG2VDR::MainMenuAction()
+{
+   return new cEpgPluginMenu(MAINMENUENTRY, this);
+}
+
+cMenuSetupPage* cPluginEPG2VDR::SetupMenu(void)
+{
+  return new cMenuSetupEPG2VDR;
+}
+
+bool cPluginEPG2VDR::SetupParse(const char *Name, const char *Value)
+{
+  // Parse your own setup parameters and store their values.
+
+  if      (!strcasecmp(Name, "LogLevel"))        EPG2VDRConfig.loglevel = atoi(Value);
+  else if (!strcasecmp(Name, "ShowInMainMenu"))  EPG2VDRConfig.mainmenuVisible = atoi(Value);
+  else if (!strcasecmp(Name, "Blacklist"))       EPG2VDRConfig.blacklist = atoi(Value);
+  else if (!strcasecmp(Name, "DbHost"))          sstrcpy(EPG2VDRConfig.dbHost, Value, sizeof(EPG2VDRConfig.dbHost));
+  else if (!strcasecmp(Name, "DbPort"))          EPG2VDRConfig.dbPort = atoi(Value);
+  else if (!strcasecmp(Name, "DbName"))          sstrcpy(EPG2VDRConfig.dbName, Value, sizeof(EPG2VDRConfig.dbName));
+  else if (!strcasecmp(Name, "DbUser"))          sstrcpy(EPG2VDRConfig.dbUser, Value, sizeof(EPG2VDRConfig.dbUser));
+  else if (!strcasecmp(Name, "DbPass"))          sstrcpy(EPG2VDRConfig.dbPass, Value, sizeof(EPG2VDRConfig.dbPass));
+  else if (!strcasecmp(Name, "MasterMode"))      EPG2VDRConfig.masterMode = atoi(Value);
+  else if (!strcasecmp(Name, "LoadImages"))      EPG2VDRConfig.getepgimages = atoi(Value);
+  else if (!strcasecmp(Name, "ActiveOnEpgd"))    EPG2VDRConfig.activeOnEpgd = atoi(Value);
+  else if (!strcasecmp(Name, "ScheduleBoot"))    EPG2VDRConfig.scheduleBoot = atoi(Value);
+  else if (!strcasecmp(Name, "Uuid"))            sstrcpy(EPG2VDRConfig.uuid, Value, sizeof(EPG2VDRConfig.uuid));
+
+  else
+     return false;
+
+  return true;
+}
+
+void cPluginEPG2VDR::Stop()
+{
+   oUpdate->Stop();
+}
+
+//***************************************************************************
+
+VDRPLUGINCREATOR(cPluginEPG2VDR);
diff --git a/epg2vdr.h b/epg2vdr.h
new file mode 100644
index 0000000..20726fa
--- /dev/null
+++ b/epg2vdr.h
@@ -0,0 +1,59 @@
+/*
+ * epg2vdr.h: EPG2VDR plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __EPG2VDR_H
+#define __EPG2VDR_H
+
+#include <vdr/plugin.h>
+#include "lib/config.h"
+
+//***************************************************************************
+// Constants
+//***************************************************************************
+
+static const char* DESCRIPTION   = trNOOP("epg2vdr plugin");
+static const char* MAINMENUENTRY = tr("epg handler");
+
+static const char* VERSION      = "0.1.12";
+static const char* VERSION_DATE = "03.02.2015";
+#define DB_API  4
+
+//***************************************************************************
+// cPluginEPG2VDR
+//***************************************************************************
+
+class cPluginEPG2VDR : public cPlugin 
+{
+   public:
+
+      cPluginEPG2VDR(void);
+      virtual ~cPluginEPG2VDR();
+      virtual const char* Version(void)          { return VERSION; }
+      virtual const char* VersionDate(void)      { return VERSION_DATE; }
+      virtual const char* Description(void)      { return tr(DESCRIPTION); }
+      virtual const char* CommandLineHelp(void);
+      virtual const char** SVDRPHelpPages(void);
+      virtual cString SVDRPCommand(const char* Cmd, const char* Option, int& ReplyCode);
+      virtual bool Initialize(void);
+      virtual bool Start(void);
+      virtual cString Active(void);
+      virtual const char* MainMenuEntry(void) 
+      { return EPG2VDRConfig.mainmenuVisible ? MAINMENUENTRY : 0; }
+      virtual cOsdObject* MainMenuAction(void);
+      virtual cMenuSetupPage* SetupMenu(void);
+      virtual bool SetupParse(const char* Name, const char* Value);
+      virtual void Stop();
+      virtual void DisplayMessage(const char* s);
+      virtual time_t WakeupTime(void);
+   
+   private:
+
+      // ...
+};
+
+//***************************************************************************
+#endif // EPG2VDR_H
diff --git a/handler.h b/handler.h
new file mode 100644
index 0000000..ed80e42
--- /dev/null
+++ b/handler.h
@@ -0,0 +1,1000 @@
+/*
+ * handler.h: EPG2VDR plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __HANDLER_H
+#define __HANDLER_H
+
+#include "update.h"
+
+//***************************************************************************
+// Mutex Try
+//***************************************************************************
+
+class cMutexTry
+{
+   public:
+
+      cMutexTry()
+      {
+         locked = 0;
+
+         pthread_mutexattr_t attr;
+         pthread_mutexattr_init(&attr);
+         pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
+         pthread_mutex_init(&mutex, &attr);
+      }
+      
+      ~cMutexTry()
+      { 
+         pthread_mutex_destroy(&mutex); 
+      }
+
+      int tryLock()
+      {
+         if (pthread_mutex_trylock(&mutex) == 0)
+         {
+            locked++;
+            // tell(0, "[%d] got lock (%d) [%p]", cThread::ThreadId(), locked, this);
+
+            return yes;
+         }
+
+         // tell(0, "[%d] don't got lock (%d) [%p]", cThread::ThreadId(), locked, this);
+
+         return no;
+      }
+
+      void unlock()
+      {
+         if (locked)
+         {
+            // tell(0, "[%d] unlock (%d) [%p]", cThread::ThreadId(), locked, this);
+            locked--;
+            pthread_mutex_unlock(&mutex);
+         }
+      }
+      
+   private:
+
+      pthread_mutex_t mutex;
+      int locked;
+};
+
+//***************************************************************************
+// EPG Handler
+//***************************************************************************
+
+class cEpgHandlerInstance
+{
+   public:
+      
+      cEpgHandlerInstance()
+      { 
+         initialized = no;
+         connection = 0;
+         eventsDb = 0;
+         compDb = 0;
+         mapDb = 0;
+         vdrDb = 0;
+         endTime = 0;
+         updateDelFlg = 0;
+         selectDelFlg = 0;
+         delCompOf = 0;
+         selectMergeSp = 0;
+
+         tell(0, "Init handler instance for thread %d", cThread::ThreadId());
+      }
+
+      virtual ~cEpgHandlerInstance() { exitDb(); }
+
+      virtual int dbConnected(int force = no) 
+      { 
+         return initialized 
+            && connection 
+            && (!force || connection->check() == success); 
+      }
+
+      int checkConnection()
+      {
+         static int retry = 0;
+         
+         // check connection
+         
+         if (!dbConnected(yes))
+         {
+            // try to connect
+            
+            tell(0, "Trying to re-connect to database!");
+            retry++;
+            
+            if (initDb() != success)
+            {
+               exitDb();
+               
+               return fail;
+            }
+            
+            retry = 0;         
+            tell(0, "Connection established successfull!");
+         }
+         
+         return success;
+      }
+
+      int isEpgdBusy()
+      {
+         int busy = no;
+         
+         if (!dbConnected())
+            return true;
+
+         vdrDb->clear();
+         vdrDb->setValue(cTableVdrs::fiUuid, EPGDNAME);
+         
+         if (vdrDb->find())
+         {
+            if (strcasecmp(vdrDb->getStrValue(cTableVdrs::fiState), "busy (events)") == 0)
+               busy = yes;
+         }
+         
+         vdrDb->reset();
+         
+         return busy;
+      }
+
+      int initDb()
+      {
+         int status = success;
+
+         exitDb();
+
+         connection = new cDbConnection();
+
+         vdrDb = new cTableVdrs(connection);
+         if (vdrDb->open() != success) return fail;
+
+         mapDb = new cTableChannelMap(connection);
+         if (mapDb->open() != success) return fail;
+
+         eventsDb = new cTableEvents(connection);
+         if (eventsDb->open() != success) return fail;
+         
+         compDb = new cTableComponents(connection);
+         if (compDb->open() != success) return fail;
+
+         // prepare statement to get mapsp
+
+         // select 
+         //   mergesp from channelmap 
+         // where 
+         //   source != 'vdr' 
+         //   and channelid = ? limit 1
+
+         selectMergeSp = new cDbStatement(mapDb);
+         
+         selectMergeSp->build("select ");
+         selectMergeSp->bind(cTableChannelMap::fiMergeSp, cDBS::bndOut);
+         selectMergeSp->build(" from %s where source != 'vdr'", mapDb->TableName());
+         selectMergeSp->bind(cTableChannelMap::fiChannelId, cDBS::bndIn |cDBS::bndSet, " and ");
+         selectMergeSp->build(" limit 1");
+
+         status += selectMergeSp->prepare();
+
+         // prepare statement to mark wasted DVB events
+
+         // update events set delflg = ?, updsp = ? 
+         //   where channelid = ? and source = ? 
+         //      and starttime+duration > ? 
+         //      and starttime < ? 
+         //      and (tableid > ? or (tableid = ? and version <> ?))
+
+         endTime = new cDbValue("starttime+duration", cDBS::ffInt, 10);
+         updateDelFlg = new cDbStatement(eventsDb);
+         
+         updateDelFlg->build("update %s set ", eventsDb->TableName());
+         updateDelFlg->bind(cTableEvents::fiDelFlg, cDBS::bndIn |cDBS::bndSet);
+         updateDelFlg->bind(cTableEvents::fiUpdFlg, cDBS::bndIn |cDBS::bndSet, ", ");
+         updateDelFlg->bind(cTableEvents::fiUpdSp, cDBS::bndIn | cDBS::bndSet, ", ");
+         updateDelFlg->build(" where ");
+         updateDelFlg->bind(cTableEvents::fiChannelId, cDBS::bndIn | cDBS::bndSet);
+         updateDelFlg->bind(cTableEvents::fiSource, cDBS::bndIn | cDBS::bndSet, " and ");
+         updateDelFlg->bindCmp(0, endTime, ">" , " and ");         
+         updateDelFlg->bindCmp(0, cTableEvents::fiStartTime, 0, "<" ,  " and ");
+         updateDelFlg->bindCmp(0, cTableEvents::fiTableId,   0, ">" ,  " and (");
+         updateDelFlg->bindCmp(0, cTableEvents::fiTableId,   0, "=" ,  " or (");
+         updateDelFlg->bindCmp(0, cTableEvents::fiVersion,   0, "<>" , " and ");
+         updateDelFlg->build("));");
+         
+         status += updateDelFlg->prepare();
+
+         // we need the same as select :(
+
+         selectDelFlg = new cDbStatement(eventsDb);
+         
+         selectDelFlg->build("select ");
+         selectDelFlg->bind(cTableComponents::fiEventId, cDBS::bndOut);
+         selectDelFlg->build(" from %s where ", eventsDb->TableName());
+         selectDelFlg->bind(cTableEvents::fiChannelId, cDBS::bndIn | cDBS::bndSet);
+         selectDelFlg->bind(cTableEvents::fiSource, cDBS::bndIn | cDBS::bndSet, " and ");
+         selectDelFlg->bindCmp(0, endTime, ">" , " and ");
+         selectDelFlg->bindCmp(0, cTableEvents::fiStartTime, 0, "<" ,  " and ");
+         selectDelFlg->bindCmp(0, cTableEvents::fiTableId,   0, ">" ,  " and (");
+         selectDelFlg->bindCmp(0, cTableEvents::fiTableId,   0, "=" ,  " or (");
+         selectDelFlg->bindCmp(0, cTableEvents::fiVersion,   0, "<>" , " and ");
+         selectDelFlg->build("));");
+         
+         status += selectDelFlg->prepare();
+
+         // -----------------
+         // delete from components where eventid = ?;
+
+         delCompOf = new cDbStatement(compDb);
+         
+         delCompOf->build("delete from %s where ", compDb->TableName());
+         delCompOf->bind(cTableComponents::fiEventId, cDBS::bndIn |cDBS::bndSet);
+         delCompOf->build(";");         
+
+         status += delCompOf->prepare();
+
+         if (status == success)
+         {
+            status += updateMemList();
+            status += updateExternalIdsMap();
+         }
+
+         initialized = yes;
+         return status;
+      }
+
+      int exitDb()
+      {
+         initialized = no;
+
+         if (connection)
+         {
+            delete endTime;       endTime = 0;
+            delete updateDelFlg;  updateDelFlg = 0;
+            delete selectDelFlg;  selectDelFlg = 0;
+            delete delCompOf;     delCompOf = 0;
+
+            delete vdrDb;         vdrDb = 0;
+            delete eventsDb;      eventsDb = 0;
+            delete compDb;        compDb = 0;
+            delete mapDb;         mapDb = 0;
+            delete selectMergeSp; selectMergeSp = 0;
+
+            delete connection;    connection = 0;
+         }
+
+         return done;
+      }
+
+      int updateMemList()
+      {
+         time_t start = time(0);
+
+         evtMemList.clear();
+         
+         // select eventid, channelid, version, tableid, delflg
+         //   from events where source = 'vdr'
+         
+         cDbStatement* selectAllVdr = new cDbStatement(eventsDb);
+         
+         selectAllVdr->build("select ");
+         selectAllVdr->bind(cTableEvents::fiEventId, cDBS::bndOut);
+         selectAllVdr->bind(cTableEvents::fiChannelId, cDBS::bndOut, ", ");
+         selectAllVdr->bind(cTableEvents::fiVersion, cDBS::bndOut, ", ");
+         selectAllVdr->bind(cTableEvents::fiTableId, cDBS::bndOut, ", ");
+         selectAllVdr->bind(cTableEvents::fiDelFlg, cDBS::bndOut, ", ");
+         selectAllVdr->build(" from %s where source = 'vdr'", eventsDb->TableName());
+         
+         if (selectAllVdr->prepare() != success)
+         {
+            tell(0, "Aborted reading hashes from db due to prepare error");
+            delete selectAllVdr;
+            return fail;
+         }
+         
+         tell(1, "Start reading hashes from db");
+
+         eventsDb->clear();
+         
+         for (int f = selectAllVdr->find(); f; f = selectAllVdr->fetch())
+         {
+            char evtKey[100];
+            
+            if (eventsDb->hasValue(cTableEvents::fiDelFlg, "Y"))
+               continue;
+            
+            sprintf(evtKey, "%ld:%s", 
+                    eventsDb->getIntValue(cTableEvents::fiEventId),
+                    eventsDb->getStrValue(cTableEvents::fiChannelId));
+            
+            evtMemList[evtKey].version = eventsDb->getIntValue(cTableEvents::fiVersion);
+            evtMemList[evtKey].tableid = eventsDb->getIntValue(cTableEvents::fiTableId);
+         }
+         
+         selectAllVdr->freeResult();
+         delete selectAllVdr;
+         
+         tell(1, "Finished reading hashes from db, got %d hashes (in %ld seconds)", 
+              (int)evtMemList.size(), time(0)-start);
+         
+         return success;
+      }
+
+      //***************************************************************************
+      // NOEPG feature - so we don't need the noepg plugin
+      //***************************************************************************
+
+      virtual bool IgnoreChannel(const cChannel* Channel)
+      { 
+         static time_t nextRetryAt = time(0);
+         LogDuration l("IgnoreChannel", 5);
+
+         // if this method is called the channel is 
+         // for the db an we have the active role!
+         // (already checked by cEpg2VdrEpgHandler)
+
+         // check connection
+
+         if (!dbConnected() && time(0) < nextRetryAt)
+            return true;
+         
+         if (checkConnection() != success || externIdMap.size() < 1)
+         {
+            nextRetryAt = time(0) + 60;
+            return true;
+         }
+
+         return false;
+      }
+
+      //***************************************************************************
+      // Transaction Stuff
+      //***************************************************************************
+
+      virtual bool BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus)
+      {
+         // inital die channelid setzen
+
+         channleId = Channel->GetChannelID();
+
+         // start transaction
+         
+         // if (!OnlyRunningStatus && dbConnected() && getExternalIdOfChannel(&channleId) != "")
+         //   connection->startTransaction();
+
+         return false; 
+      }
+
+      //***************************************************************************
+      // Handled Externally
+      //   hier wird festgelegt ob das Event via VDR im EPG landen soll
+      //***************************************************************************
+
+      virtual bool HandledExternally(const cChannel* Channel)
+      {
+         LogDuration l("HandledExternally", 5);
+
+         if (dbConnected() && getExternalIdOfChannel(&channleId) != "")
+            return true;
+         
+         return false;
+      }
+
+      virtual bool EndSegmentTransfer(bool Modified, bool OnlyRunningStatus)
+      {
+         if (OnlyRunningStatus || !dbConnected() || !connection->inTransaction())
+            return false;
+
+         if (Modified) 
+            connection->commit();
+         else
+            connection->rollback();
+
+         if (EPG2VDRConfig.loglevel > 2)
+            connection->showStat("handler");
+
+         return false; 
+      }
+
+      //***************************************************************************
+      // Is Update
+      //***************************************************************************
+
+      virtual bool IsUpdate(tEventID EventID, time_t StartTime, uchar TableID, uchar Version) 
+      {
+         char evtKey[100];
+         LogDuration l("IsUpdate", 5);
+
+         if (!dbConnected())
+            return false;
+
+         if (!isZero(getExternalIdOfChannel(&channleId).c_str()) && StartTime > time(0) + 4 * tmeSecondsPerDay)
+            return false;
+
+         sprintf(evtKey, "%ld:%s", (long)EventID, (const char*)channleId.ToString());
+
+         if (evtMemList.find(evtKey) == evtMemList.end())
+         {
+            tell(4, "Handle insert of event %d for channel '%s'", 
+                 EventID, (const char*)channleId.ToString());
+
+            if (!connection->inTransaction())
+               connection->startTransaction();
+
+            return true;
+         }
+
+         uchar oldTableId = ::max(uchar(evtMemList[evtKey].tableid), uchar(0x4E)); 
+         
+         // skip if old tid is already lower
+         
+         if (oldTableId < TableID)
+         {
+            tell(4, "Ignoring update with old tableid for event '%s'", evtKey);
+            return false;
+         }
+         
+         // skip if version an tid identical
+
+         if (oldTableId == TableID && evtMemList[evtKey].version == Version)
+         {
+            tell(4, "Ignoring 'non' update for event '%s'", evtKey);
+            return false;
+         }
+         
+         if (EPG2VDRConfig.loglevel > 3)
+            tell(4, "Handle update of event %d [%s] %d/%d - %d/%d", EventID, evtKey, 
+                 Version, TableID, 
+                 evtMemList[evtKey].version, evtMemList[evtKey].tableid);
+
+         if (!connection->inTransaction())
+            connection->startTransaction();
+
+         return true;
+      }
+
+      //***************************************************************************
+      // Handle Event
+      //***************************************************************************
+
+      virtual bool HandleEvent(cEvent* event)
+      {
+         if (!dbConnected() || !event || !channleId.Valid())
+            return false;
+
+         LogDuration l("HandleEvent", 5);
+
+         // External-ID:
+         //     na  -> Kanal nicht konfiguriert -> wird ignoriert
+         //    = 0  -> wird vom Sender genommen -> und in der DB abgelegt
+         //    > 0  -> echte externe ID -> wird von extern ins epg übertragen, 
+         //            das Sender EPG wird nur (zusätzlich) in der DB gehalten
+
+         // Events der Kanaele welche nicht in der map zu finden sind ignorieren
+
+         if (getExternalIdOfChannel(&channleId) == "")
+            return false;
+
+         if (!connection->inTransaction())
+         {
+            tell(0, "Error missing tact in HandleEvent");
+            return false;
+         }
+
+         string comp;
+
+         eventsDb->clear();
+         eventsDb->setValue(cTableEvents::fiEventId, (long)event->EventID());
+         eventsDb->setValue(cTableEvents::fiChannelId, channleId.ToString());
+         int insert = !eventsDb->find();
+
+         // reinstate ??
+
+         if (eventsDb->hasValue(cTableEvents::fiDelFlg, "Y"))
+         {
+            char updFlg = Us::usPassthrough;
+
+            mapDb->clear();
+            mapDb->setValue(cTableChannelMap::fiChannelId, channleId.ToString());
+
+            if (selectMergeSp->find()) 
+            {
+               time_t mergesp = mapDb->getIntValue(cTableChannelMap::fiMergeSp);
+               long masterid = eventsDb->getIntValue(cTableEvents::fiMasterId);
+               long useid = eventsDb->getIntValue(cTableEvents::fiUseId);
+               
+               if (event->StartTime() > mergesp)
+                  updFlg = Us::usRemove;
+               else if (event->StartTime() <= mergesp && masterid == useid)
+                  updFlg = Us::usActive;
+               else if (event->StartTime() <= mergesp && masterid != useid)
+                  updFlg = Us::usLink;
+            }
+
+            eventsDb->setCharValue(cTableEvents::fiUpdFlg, updFlg); 
+            eventsDb->setValue(cTableEvents::fiDelFlg, 0, 0);        // set to NULL
+         }
+
+         if (!insert && abs(event->StartTime() - eventsDb->getIntValue(cTableEvents::fiStartTime)) > 6*tmeSecondsPerHour)
+         {
+            tell(3, "Info: Start time of %d/%s - '%s' moved %ld hours from %s to %s - '%s'",
+                 event->EventID(), (const char*)channleId.ToString(),
+                 eventsDb->getStrValue(cTableEvents::fiTitle),
+                 (event->StartTime() - eventsDb->getIntValue(cTableEvents::fiStartTime)) / tmeSecondsPerHour,
+                 l2pTime(eventsDb->getIntValue(cTableEvents::fiStartTime)).c_str(),
+                 l2pTime(event->StartTime()).c_str(), 
+                 event->Title());
+         }
+
+         time_t end = event->StartTime() + event->Duration();
+
+         if (!insert 
+             && end < time(0) - 2*tmeSecondsPerHour 
+             && eventsDb->getIntValue(cTableEvents::fiStartTime) >  time(0)
+             && event->StartTime() < eventsDb->getIntValue(cTableEvents::fiStartTime))
+         {
+            tell(0, "Got update of %d/%s with startime more than 2h in past "
+                 "(%s/%d) before (%s), ignoring update, set delflg instead",
+                 event->EventID(), (const char*)channleId.ToString(),
+                 l2pTime(event->StartTime()).c_str(), event->Duration(),
+                 l2pTime(eventsDb->getIntValue(cTableEvents::fiStartTime)).c_str());
+
+            eventsDb->setValue(cTableEvents::fiDelFlg, "Y");
+            eventsDb->setCharValue(cTableEvents::fiUpdFlg, Us::usDelete);
+         }
+         else
+         {
+            eventsDb->setValue(cTableEvents::fiStartTime, event->StartTime());
+         }
+
+         eventsDb->setValue(cTableEvents::fiSource, "vdr");
+         eventsDb->setValue(cTableEvents::fiTableId, event->TableID());
+         eventsDb->setValue(cTableEvents::fiVersion, event->Version());
+         eventsDb->setValue(cTableEvents::fiTitle, event->Title());
+         eventsDb->setValue(cTableEvents::fiLongDescription, event->Description());
+         eventsDb->setValue(cTableEvents::fiDuration, event->Duration());
+         eventsDb->setValue(cTableEvents::fiParentalRating, event->ParentalRating());
+         eventsDb->setValue(cTableEvents::fiVps, event->Vps());
+
+         if (!isEmpty(event->ShortText()))
+            eventsDb->setValue(cTableEvents::fiShortText, event->ShortText());
+
+         // components ..
+
+         compDb->clear();
+         compDb->setValue(cTableComponents::fiEventId, (long)event->EventID());
+         delCompOf->execute();
+
+         if (event->Components()) 
+         {
+            for (int i = 0; i < event->Components()->NumComponents(); i++) 
+            {
+               tComponent* p = event->Components()->Component(i);
+
+               compDb->clear();
+               compDb->setValue(cTableComponents::fiEventId, (long)event->EventID());
+               compDb->setValue(cTableComponents::fiChannelId, channleId.ToString());
+               compDb->setValue(cTableComponents::fiStream, p->stream);
+               compDb->setValue(cTableComponents::fiType, p->type);
+               compDb->setValue(cTableComponents::fiLang, p->language);
+               compDb->setValue(cTableComponents::fiDescription, p->description ? p->description : "");
+               compDb->store();
+            }
+         }
+
+         // compressed ..
+
+         if (event->Title())
+         {
+            comp = event->Title();
+            prepareCompressed(comp);
+            eventsDb->setValue(cTableEvents::fiCompTitle, comp.c_str());
+         }
+
+         if (!isEmpty(event->ShortText()))
+         {
+            comp = event->ShortText();
+            prepareCompressed(comp);
+            eventsDb->setValue(cTableEvents::fiCompShortText, comp.c_str());
+         }
+
+         if (insert)
+         {
+            eventsDb->setValue(cTableEvents::fiUseId, 0L);
+            eventsDb->setCharValue(cTableEvents::fiUpdFlg, Us::usPassthrough); // default (vdr:000 events)
+
+            mapDb->clear();
+            mapDb->setValue(cTableChannelMap::fiChannelId, channleId.ToString());
+
+            if (selectMergeSp->find())
+            {
+               // vdr event for merge with external event
+
+               time_t mergesp = mapDb->getIntValue(cTableChannelMap::fiMergeSp);
+               eventsDb->setCharValue(cTableEvents::fiUpdFlg, 
+                                      event->StartTime() > mergesp ? Us::usInactive : Us::usActive);
+            }
+
+            eventsDb->insert();
+         }
+         else
+         {
+            eventsDb->update();
+         }
+
+         // update hash map
+
+         char evtKey[100];
+
+         sprintf(evtKey, "%ld:%s", (long)event->EventID(), (const char*)channleId.ToString());
+
+         evtMemList[evtKey].version = event->Version();
+         evtMemList[evtKey].tableid = event->TableID();
+
+         return true;
+      }
+
+      //***************************************************************************
+      // Drop Outdated
+      //***************************************************************************
+
+      virtual bool DropOutdated(cSchedule* Schedule, time_t SegmentStart, 
+                                time_t SegmentEnd, uchar TableID, uchar Version) 
+      {
+         // we handle only vdr events here (provided by DVB)
+
+         if (!dbConnected() || getExternalIdOfChannel(&channleId) == "")
+            return false;
+
+         if (!connection->inTransaction())
+         {
+            tell(0, "Error missing tact DropOutdated");
+            return false;
+         }
+
+         if (SegmentStart <= 0 || SegmentEnd <= 0)
+            return false;
+
+         LogDuration l("DropOutdated", 5);
+
+         eventsDb->clear();
+         eventsDb->setValue(cTableEvents::fiChannelId, Schedule->ChannelID().ToString());
+         eventsDb->setValue(cTableEvents::fiSource, "vdr");
+         eventsDb->setValue(cTableEvents::fiUpdSp, time(0));
+         eventsDb->setValue(cTableEvents::fiStartTime, SegmentEnd);
+         eventsDb->setValue(cTableEvents::fiTableId, TableID);
+         eventsDb->setValue(cTableEvents::fiVersion, Version);
+         endTime->setValue(SegmentStart);
+
+         // remove segment from cache
+
+         for (int f = selectDelFlg->find(); f; f = selectDelFlg->fetch())
+         {
+            char evtKey[100];
+
+            sprintf(evtKey, "%ld:%s", 
+                    eventsDb->getIntValue(cTableEvents::fiEventId),
+                    eventsDb->getStrValue(cTableEvents::fiChannelId));
+
+            evtMemList.erase(evtKey);
+         }
+
+         selectDelFlg->freeResult();
+
+         eventsDb->setValue(cTableEvents::fiDelFlg, "Y");
+         eventsDb->setCharValue(cTableEvents::fiUpdFlg, Us::usDelete);
+
+         // mark segment as deleted
+
+         updateDelFlg->execute();
+
+         return true;
+      }
+
+   private:
+      
+      string getExternalIdOfChannel(tChannelID* channelId)
+      {
+         char* id = 0;
+         string extid = "";
+
+         if (externIdMap.size() < 1)
+            return "";
+
+         id = strdup(channelId->ToString());
+
+         if (externIdMap.find(id) != externIdMap.end())
+            extid = externIdMap[id];
+
+         free(id);
+
+         return extid;
+      }
+
+      int updateExternalIdsMap()
+      {
+         externIdMap.clear();
+         tell(1, "Start reading external ids from db");
+         mapDb->clear();
+
+         // select extid, channelid
+         //   from channlemap
+         
+         cDbStatement* selectAll = new cDbStatement(mapDb);
+         
+         selectAll->build("select ");
+         selectAll->bind(cTableChannelMap::fiExternalId, cDBS::bndOut);
+         selectAll->bind(cTableChannelMap::fiChannelId, cDBS::bndOut, ", ");
+         selectAll->build(" from %s", mapDb->TableName());
+
+         if (selectAll->prepare() != success)
+         {
+            tell(0, "Reading external id's from db aborted due to prepare error");
+            delete selectAll;
+            return fail;
+         }
+         
+         for (int f = selectAll->find(); f; f = selectAll->fetch())
+         {
+            const char* extid = mapDb->getStrValue(cTableChannelMap::fiExternalId);
+            const char* chan = mapDb->getStrValue(cTableChannelMap::fiChannelId);
+
+            externIdMap[chan] = extid;
+         }
+
+         tell(1, "Finished reading external id's from db, got %d id's", 
+              (int)externIdMap.size());
+
+         selectAll->freeResult();
+         delete selectAll;
+
+         return success;
+      }
+
+      struct MemMap
+      {
+         int version;
+         int tableid;
+      };
+
+      map<string,MemMap> evtMemList;
+      map<string,string> externIdMap;
+      tChannelID channleId;
+
+      int initialized;
+      cDbConnection* connection;
+
+      cTableEvents* eventsDb;
+      cTableChannelMap* mapDb;
+      cTableVdrs* vdrDb;
+      cTableComponents* compDb;
+
+      cDbValue* endTime;
+      cDbStatement* updateDelFlg;
+      cDbStatement* selectDelFlg;
+      cDbStatement* delCompOf;
+      cDbStatement* selectMergeSp;
+      cUpdate* update;
+};
+
+//***************************************************************************
+// cEpg2VdrEpgHandler
+//***************************************************************************
+
+class cEpg2VdrEpgHandler : public cEpgHandler
+{
+   public:
+      
+      cEpg2VdrEpgHandler(cUpdate* aUpdate) : cEpgHandler() { active = no; }
+
+      ~cEpg2VdrEpgHandler()
+      {
+         map<tThreadId,cEpgHandlerInstance*>::iterator it;
+
+         for (it = handler.begin(); it != handler.end(); ++it)
+         {
+            delete handler[it->first];
+            handler[it->first] = 0;
+         }
+      }
+
+      int getActive()           { return active; }
+      void setActive(int state) { active = state; }
+
+      //***************************************************************************
+      // Ignore Channel
+      //***************************************************************************
+
+      virtual bool IgnoreChannel(const cChannel* Channel)
+      {
+         // IgnoreChannel ist der erste Anlaufpunkt des EIT handlers, 
+         // wird hier ignoriert bricht die Verarbeitung des Kanals direkt ab
+
+         tChannelID tmp = Channel->GetChannelID();
+
+         if (externIdMap.size() < 1)
+            return true;
+
+         // Kanäle welche nicht in der map konfiguriert sind (na) werden nicht in der DB verwaltet 
+         // abhängig von blacklist durchgelassen oder ignoriert
+
+         if (getExternalIdOfChannel(&tmp) == "")
+            return EPG2VDRConfig.blacklist;
+
+         // ingnore - wenn nicht aktiv
+
+         if (!active)
+            return true;
+
+         // vom handler ignoriert (wegen db Problemen etc.)?
+
+         if (getHandler()->IgnoreChannel(Channel))
+            return true;
+
+         // solange der handler beschäftigt (wir den lock nicht bekommen) ist erst mal ignorieren
+
+         if (!handlerMutex.tryLock())
+            return true;
+
+         return false;
+      }
+
+      virtual bool HandledExternally(const cChannel* Channel)
+      { 
+         cEpgHandlerInstance* h = getHandler();
+         return h->HandledExternally(Channel);
+      }
+
+      virtual bool BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus)
+      { 
+         cEpgHandlerInstance* h = getHandler();
+         return h->BeginSegmentTransfer(Channel, OnlyRunningStatus);
+      }
+
+      virtual bool EndSegmentTransfer(bool Modified, bool OnlyRunningStatus)
+      { 
+         handlerMutex.unlock();
+
+         cEpgHandlerInstance* h = getHandler();
+         return h->EndSegmentTransfer(Modified, OnlyRunningStatus);
+      }
+
+      virtual bool IsUpdate(tEventID EventID, time_t StartTime, uchar TableID, uchar Version) 
+      {
+
+         cEpgHandlerInstance* h = getHandler();
+         return h->IsUpdate(EventID, StartTime, TableID, Version);
+      }
+
+      virtual bool HandleEvent(cEvent* event)
+      {
+         cEpgHandlerInstance* h = getHandler();
+         return h->HandleEvent(event);
+      }
+
+      virtual bool DropOutdated(cSchedule* Schedule, time_t SegmentStart, 
+                                time_t SegmentEnd, uchar TableID, uchar Version) 
+      { 
+         cEpgHandlerInstance* h = getHandler();
+         return h->DropOutdated(Schedule, SegmentStart, SegmentEnd, TableID, Version);
+      }
+
+      int updateExternalIdsMap(cTableChannelMap* mapDb)
+      {
+         cMutexLock lock(&mapMutex);
+
+         externIdMap.clear();
+         tell(1, "Start reading external ids from db");
+         mapDb->clear();
+
+         // select extid, channelid
+         //   from channlemap
+         
+         cDbStatement* selectAll = new cDbStatement(mapDb);
+         
+         selectAll->build("select ");
+         selectAll->bind(cTableChannelMap::fiExternalId, cDBS::bndOut);
+         selectAll->bind(cTableChannelMap::fiChannelId, cDBS::bndOut, ", ");
+         selectAll->bind(cTableChannelMap::fiSource, cDBS::bndOut, ", ");
+         selectAll->bind(cTableChannelMap::fiChannelName, cDBS::bndOut, ", ");
+         selectAll->bind(cTableChannelMap::fiMerge, cDBS::bndOut, ", ");
+         selectAll->build(" from %s", mapDb->TableName());
+
+         if (selectAll->prepare() != success)
+         {
+            tell(0, "Reading external id's from db aborted due to prepare error");
+            delete selectAll;
+            return fail;
+         }
+         
+         for (int f = selectAll->find(); f; f = selectAll->fetch())
+         {
+            string extid = mapDb->getStrValue(cTableChannelMap::fiExternalId);
+            int merge = mapDb->getIntValue(cTableChannelMap::fiMerge);
+
+            const char* strChannelId = mapDb->getStrValue(cTableChannelMap::fiChannelId);
+            cChannel* channel = Channels.GetByChannelID(tChannelID::FromString(strChannelId));
+
+            // update channelname in channelmap
+            
+            if (channel && !mapDb->hasValue(cTableChannelMap::fiChannelName, channel->Name()))
+            {
+               mapDb->find();              // get all fields from table (needed for update)!
+               mapDb->setValue(cTableChannelMap::fiChannelName, channel->Name());
+               mapDb->update();
+               mapDb->reset();
+            }
+
+            // we should get the merge > 1 channels already via a merge 1 entry of the channelmap!
+
+            if (merge > 1)
+               continue;
+
+            // if extid > 0 (no vdr:000 channel) and map not configured disable channel via 'na'
+
+            if (!isZero(extid.c_str()) && !merge)
+            {
+               tell(1, "Handler ignore channel '%s' due to merge = 0", strChannelId);
+               extid = "";
+            }
+
+            // insert into map
+
+            externIdMap[strChannelId] = extid;
+         }
+
+         tell(1, "Finished reading external id's from db, got %d id's", 
+              (int)externIdMap.size());
+
+         selectAll->freeResult();
+         delete selectAll;
+
+         return success;
+      }
+
+   private:
+
+      string getExternalIdOfChannel(tChannelID* channelId)
+      {
+         char* id = 0;
+         string extid = "";
+
+         cMutexLock lock(&mapMutex);
+
+         if (externIdMap.size() < 1)
+            return "";
+
+         id = strdup(channelId->ToString());
+
+         if (externIdMap.find(id) != externIdMap.end())
+            extid = externIdMap[id];
+
+         free(id);
+
+         return extid;
+      }
+
+      cEpgHandlerInstance* getHandler()
+      {
+         if (handler.find(cThread::ThreadId()) == handler.end())
+            handler[cThread::ThreadId()] = new cEpgHandlerInstance();
+         
+         return handler[cThread::ThreadId()];
+      }
+      
+      map<string,string> externIdMap;
+      map<tThreadId,cEpgHandlerInstance*> handler;
+      int active;
+      cMutex mapMutex;
+      cMutexTry handlerMutex;
+};
+
+//***************************************************************************
+#endif // __HANDLER_H
diff --git a/lib/Makefile b/lib/Makefile
new file mode 100644
index 0000000..5ce87cf
--- /dev/null
+++ b/lib/Makefile
@@ -0,0 +1,73 @@
+#
+# Makefile
+#
+# See the README file for copyright information and how to reach the author.
+#
+
+include ../Make.config
+
+LIBTARGET = libhorchi
+HLIB      = -L. -lhorchi
+
+DEMO = demo
+TEST = tst
+
+LIBOBJS = common.o curl.o imgtools.o config.o db.o tabledef.o dbdict.o
+
+BASELIBS = -lrt -lz -larchive -lcurl -luuid -lcrypto
+BASELIBS += $(shell mysql_config --libs_r)
+BASELIBS += $(shell xml2-config --libs) $(shell xslt-config --libs)
+DEBUG = 1
+
+ifdef DEBUG
+  CFLAGS += -ggdb -O0
+endif
+
+CFLAGS += -fPIC -Wreturn-type -Wall -Wno-parentheses -Wformat -pedantic -Wunused-variable -Wunused-label \
+          -Wunused-value -Wunused-function \
+          -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
+
+CFLAGS += $(shell mysql_config --include)
+CFLAGS += $(shell xml2-config --cflags) $(shell xslt-config --cflags)
+
+DEFINES = -DPLGDIR='"$(PLGDEST)"' $(USES)
+
+all: lib $(TEST) $(DEMO)
+lib: $(LIBTARGET).a
+
+$(LIBTARGET).a : $(LIBOBJS)
+	@echo Building Lib ...
+	$(doLib) $@ $(LIBOBJS)
+
+tst: test.o
+	$(doLink) test.o $(HLIB) $(BASELIBS) -o $@
+
+demo: demo.o
+	$(doLink) demo.o $(HLIB) $(BASELIBS) -o $@
+
+clean:
+	rm -f *.o *~ core $(TEST) $(DEMO) $(LIBTARGET).a
+
+cppchk:
+	cppcheck --template="{file}:{line}:{severity}:{message}" --quiet --force *.c *.h 
+
+%.o: %.c
+	@echo Compile "$(*F)" ...
+	$(doCompile) $(*F).c -o $@
+
+#--------------------------------------------------------
+# dependencies
+#--------------------------------------------------------
+
+HEADER = db.h common.h config.h
+
+common.o    :  common.c      $(HEADER) common.h
+curl.o      :  curl.c        $(HEADER)
+imgtools.o  :  imgtools.c    $(HEADER) imgtools.h
+config.o    :  config.c      $(HEADER) config.h
+db.o        :  db.c          $(HEADER) db.h
+tabledef.o  :  tabledef.c    $(HEADER) tabledef.h
+dbdict.o    :  dbdict.c      $(HEADER) dbdict.h
+
+demo.o      :  demo.c        $(HEADER) 
+test.o      :  test.c        $(HEADER) 
diff --git a/lib/common.c b/lib/common.c
new file mode 100644
index 0000000..31b5a0d
--- /dev/null
+++ b/lib/common.c
@@ -0,0 +1,1290 @@
+/*
+ * common.c: 
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#ifdef USEUUID
+# include <uuid/uuid.h>
+#endif
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <zlib.h>
+#include <errno.h>
+
+#ifdef USELIBARCHIVE
+# include <archive.h>
+# include <archive_entry.h>
+#endif
+
+#include "common.h"
+#include "config.h"
+
+cMyMutex logMutex;
+
+//***************************************************************************
+// Debug
+//***************************************************************************
+
+const char* getLogPrefix() 
+{ 
+   return logPrefix; 
+}
+
+void tell(int eloquence, const char* format, ...)
+{
+   if (EPG2VDRConfig.loglevel < eloquence)
+      return ;
+
+   const int sizeBuffer = 100000;
+   char t[sizeBuffer+100]; *t = 0;
+   va_list ap;
+
+   logMutex.Lock();
+
+   va_start(ap, format);
+
+   if (getLogPrefix())
+      snprintf(t, sizeBuffer, "%s", getLogPrefix());
+
+   vsnprintf(t+strlen(t), sizeBuffer-strlen(t), format, ap);
+   
+   if (EPG2VDRConfig.logstdout)
+   {
+      char buf[50+TB];
+      timeval tp;
+
+      gettimeofday(&tp, 0);
+      
+      tm* tm = localtime(&tp.tv_sec);
+      
+      sprintf(buf,"%2.2d:%2.2d:%2.2d,%3.3ld ",
+              tm->tm_hour, tm->tm_min, tm->tm_sec, 
+              tp.tv_usec / 1000);
+
+      printf("%s %s\n", buf, t);     
+   }
+   else
+      syslog(LOG_ERR, "%s", t);
+
+   logMutex.Unlock();
+
+   va_end(ap);
+}
+
+//***************************************************************************
+// Save Realloc
+//***************************************************************************
+
+char* srealloc(void* ptr, size_t size)
+{
+   void* n = realloc(ptr, size);
+
+   if (!n)
+   {
+      free(ptr);
+      ptr = 0;
+   }
+
+   return (char*)n;
+}
+
+//***************************************************************************
+// us now
+//***************************************************************************
+
+double usNow()
+{
+   struct timeval tp;
+
+   gettimeofday(&tp, 0); 
+
+   return tp.tv_sec * 1000000.0 + tp.tv_usec;
+}
+
+//***************************************************************************
+// Host ID
+//***************************************************************************
+
+unsigned int getHostId()
+{
+   static unsigned int id = gethostid() & 0xFFFFFFFF;
+   return id;
+}
+
+//***************************************************************************
+// String Operations
+//***************************************************************************
+
+void toUpper(std::string& str)
+{
+   const char* s = str.c_str();
+   int lenSrc = str.length();
+
+   char* dest = (char*)malloc(lenSrc+TB); *dest = 0;
+   char* d = dest;
+
+   int csSrc;  // size of character
+
+   for (int ps = 0; ps < lenSrc; ps += csSrc)
+   {
+      csSrc = max(mblen(&s[ps], lenSrc-ps), 1);
+      
+      if (csSrc == 1)
+         *d++ = toupper(s[ps]);
+      else if (csSrc == 2 && s[ps] == (char)0xc3 && s[ps+1] >= (char)0xa0)
+      {
+         *d++ = s[ps];
+         *d++ = s[ps+1] - 32;
+      }
+      else
+      {
+         for (int i = 0; i < csSrc; i++)
+            *d++ = s[ps+i];
+      }
+   }
+
+   *d = 0;
+
+   str = dest;
+   free(dest);
+}
+
+//***************************************************************************
+// To Case (UTF-8 save)
+//***************************************************************************
+
+const char* toCase(Case cs, char* str)
+{
+   char* s = str;
+   int lenSrc = strlen(str);
+
+   int csSrc;  // size of character
+
+   for (int ps = 0; ps < lenSrc; ps += csSrc)
+   {
+      csSrc = max(mblen(&s[ps], lenSrc-ps), 1);
+      
+      if (csSrc == 1)
+         s[ps] = cs == cUpper ? toupper(s[ps]) : tolower(s[ps]);
+      else if (csSrc == 2 && s[ps] == (char)0xc3 && s[ps+1] >= (char)0xa0)
+      {
+         s[ps] = s[ps];
+         s[ps+1] = cs == cUpper ? toupper(s[ps+1]) : tolower(s[ps+1]);
+      }
+      else
+      {
+         for (int i = 0; i < csSrc; i++)
+            s[ps+i] = s[ps+i];
+      }
+   }
+
+   return str;
+}
+
+void removeChars(std::string& str, const char* ignore)
+{
+   const char* s = str.c_str();
+   int lenSrc = str.length();
+   int lenIgn = strlen(ignore);
+
+   char* dest = (char*)malloc(lenSrc+TB); *dest = 0;
+   char* d = dest;
+
+   int csSrc;  // size of character
+   int csIgn;  // 
+
+   for (int ps = 0; ps < lenSrc; ps += csSrc)
+   {
+      int skip = no;
+
+      csSrc = max(mblen(&s[ps], lenSrc-ps), 1);
+
+      for (int pi = 0; pi < lenIgn; pi += csIgn)
+      {
+         csIgn = max(mblen(&ignore[pi], lenIgn-pi), 1);
+
+         if (csSrc == csIgn && strncmp(&s[ps], &ignore[pi], csSrc) == 0)
+         {
+            skip = yes;
+            break;
+         }
+      }
+
+      if (!skip)
+      {
+         for (int i = 0; i < csSrc; i++)
+            *d++ = s[ps+i];
+      }
+   }
+
+   *d = 0;
+
+   str = dest;
+   free(dest);
+}
+
+void removeCharsExcept(std::string& str, const char* except)
+{
+   const char* s = str.c_str();
+   int lenSrc = str.length();
+   int lenIgn = strlen(except);
+
+   char* dest = (char*)malloc(lenSrc+TB); *dest = 0;
+   char* d = dest;
+
+   int csSrc;  // size of character
+   int csIgn;  // 
+
+   for (int ps = 0; ps < lenSrc; ps += csSrc)
+   {
+      int skip = yes;
+
+      csSrc = max(mblen(&s[ps], lenSrc-ps), 1);
+
+      for (int pi = 0; pi < lenIgn; pi += csIgn)
+      {
+         csIgn = max(mblen(&except[pi], lenIgn-pi), 1);
+
+         if (csSrc == csIgn && strncmp(&s[ps], &except[pi], csSrc) == 0)
+         {
+            skip = no;
+            break;
+         }
+      }
+
+      if (!skip)
+      {
+         for (int i = 0; i < csSrc; i++)
+            *d++ = s[ps+i];
+      }
+   }
+
+   *d = 0;
+
+   str = dest;
+   free(dest);
+}
+
+void removeWord(std::string& pattern, std::string word)
+{
+   size_t  pos;
+
+   if ((pos = pattern.find(word)) != std::string::npos)
+      pattern.swap(pattern.erase(pos, word.length()));
+}
+
+//***************************************************************************
+// String Manipulation
+//***************************************************************************
+
+void prepareCompressed(std::string& pattern)
+{
+   // const char* ignore = " (),.;:-_+*!#?=&%$<>§/'`´@~\"[]{}"; 
+   const char* notignore = "ABCDEFGHIJKLMNOPQRSTUVWXYZßÖÄÜöäü0123456789"; 
+
+   toUpper(pattern);
+   removeWord(pattern, " TEIL ");
+   removeWord(pattern, " FOLGE ");
+   removeCharsExcept(pattern, notignore);
+}
+
+//***************************************************************************
+// Left Trim
+//***************************************************************************
+
+char* lTrim(char* buf)
+{
+   if (buf)
+   {
+      char *tp = buf;
+
+      while (*tp && strchr("\n\r\t ",*tp)) 
+         tp++;
+
+      memmove(buf, tp, strlen(tp) +1);
+   }
+   
+   return buf;
+}
+
+//*************************************************************************
+// Right Trim
+//*************************************************************************
+
+char* rTrim(char* buf)
+{
+   if (buf)
+   {
+      char *tp = buf + strlen(buf);
+
+      while (tp >= buf && strchr("\n\r\t ",*tp)) 
+         tp--;
+
+      *(tp+1) = 0;
+   }
+   
+   return buf;
+}
+
+//*************************************************************************
+// All Trim
+//*************************************************************************
+
+char* allTrim(char* buf)
+{
+   return lTrim(rTrim(buf));
+}
+
+//***************************************************************************
+// Number to String
+//***************************************************************************
+
+std::string num2Str(int num)
+{
+   char txt[16];
+
+   snprintf(txt, sizeof(txt), "%d", num);
+
+   return std::string(txt);
+}
+
+//***************************************************************************
+// Long to Pretty Time
+//***************************************************************************
+
+std::string l2pTime(time_t t)
+{
+   char txt[30];
+   tm* tmp = localtime(&t);
+   
+   strftime(txt, sizeof(txt), "%d.%m.%Y %T", tmp);
+   
+   return std::string(txt);
+}
+
+//***************************************************************************
+// MS to Duration
+//***************************************************************************
+
+std::string ms2Dur(uint64_t t)
+{
+   char txt[30];
+   
+   int s = t / 1000;
+   int ms = t % 1000;
+
+   snprintf(txt, sizeof(txt), "%d.%03d seconds", s, ms);
+   
+   return std::string(txt);
+}
+
+//***************************************************************************
+// Char to Char-String
+//***************************************************************************
+
+const char* c2s(char c, char* buf)
+{
+   sprintf(buf, "%c", c);
+
+   return buf;
+}
+
+//***************************************************************************
+// Store To File
+//***************************************************************************
+
+int storeToFile(const char* filename, const char* data, int size)
+{
+   FILE* fout;
+
+   if ((fout = fopen(filename, "w+")))
+   {
+      fwrite(data, sizeof(char), size, fout);
+      fclose(fout);
+   }
+   else
+   {
+      tell(0, "Error, can't store '%s' to filesystem '%s'", filename, strerror(errno));
+      return fail;
+   }
+
+   return success;
+}
+
+//***************************************************************************
+// Load From File
+//***************************************************************************
+
+int loadFromFile(const char* infile, MemoryStruct* data)
+{
+   FILE* fin;
+   struct stat sb;
+
+   data->clear();
+
+   if (!fileExists(infile))
+   {
+      tell(0, "File '%s' not found'", infile);
+      return fail;
+   }
+
+   if (stat(infile, &sb) < 0)
+   {
+      tell(0, "Can't get info of '%s', error was '%s'", infile, strerror(errno));
+      return fail;
+   }
+
+   if ((fin = fopen(infile, "r")))
+   {
+      const char* sfx = suffixOf(infile);
+
+      data->size = sb.st_size;
+      data->modTime = sb.st_mtime;
+      data->memory = (char*)malloc(data->size);
+      fread(data->memory, sizeof(char), data->size, fin);
+      fclose(fin);
+      sprintf(data->tag, "%ld", (long int)data->size);
+
+      if (strcmp(sfx, "gz") == 0)
+         sprintf(data->contentEncoding, "gzip");
+      
+      if (strcmp(sfx, "js") == 0)
+         sprintf(data->contentType, "application/javascript");
+
+      else if (strcmp(sfx, "png") == 0 || strcmp(sfx, "jpg") == 0 || strcmp(sfx, "gif") == 0)
+         sprintf(data->contentType, "image/%s", sfx);
+
+      else if (strcmp(sfx, "ico") == 0)
+         strcpy(data->contentType, "image/x-icon");
+
+      else
+         sprintf(data->contentType, "text/%s", sfx);
+   }
+   else
+   {
+      tell(0, "Error, can't open '%s' for reading, error was '%s'", infile, strerror(errno));
+      return fail;
+   }
+
+   return success;
+}
+
+//***************************************************************************
+// TOOLS
+//***************************************************************************
+
+int isEmpty(const char* str)
+{
+   return !str || !*str;
+}
+
+const char* notNull(const char* str)
+{
+   if (!str)
+      return "<null>";
+
+   return str;
+}
+
+int isZero(const char* str)
+{
+   const char* p = str;
+
+   while (p && *p)
+   {
+      if (*p != '0')
+         return no;
+
+      p++;
+   }
+
+   return yes;
+}
+
+//***************************************************************************
+// 
+//***************************************************************************
+
+char* sstrcpy(char* dest, const char* src, int max)
+{
+   if (!dest || !src)
+      return 0;
+
+   strncpy(dest, src, max);
+   dest[max-1] = 0;
+   
+   return dest;
+}
+
+int isLink(const char* path)
+{
+   struct stat sb;
+
+   if (lstat(path, &sb) == 0)
+      return S_ISLNK(sb.st_mode);
+
+   tell(0, "Error: Detecting state for '%s' failed, error was '%s'", path, strerror(errno));
+
+   return false;
+}
+
+const char* suffixOf(const char* path)
+{
+   const char* p;
+
+   if (path && (p = strrchr(path, '.')))
+      return p+1;
+
+   return "";
+}
+
+int fileSize(const char* path)
+{
+   struct stat sb;
+
+   if (lstat(path, &sb) == 0)
+      return sb.st_size;
+
+   tell(0, "Error: Detecting state for '%s' failed, error was '%s'", path, strerror(errno));
+
+   return 0;
+}
+
+time_t fileModTime(const char* path)
+{
+   struct stat sb;
+
+   if (lstat(path, &sb) == 0)
+      return sb.st_mtime;
+
+   tell(0, "Error: Detecting state for '%s' failed, error was '%s'", path, strerror(errno));
+
+   return 0;
+}
+
+
+int fileExists(const char* path)
+{
+   return access(path, F_OK) == 0; 
+}
+
+int createLink(const char* link, const char* dest, int force)
+{
+   if (!fileExists(link) || force)
+   {
+      // may be the link exists and point to a wrong or already deleted destination ...
+      //   .. therefore we delete the link at first
+      
+      unlink(link);
+      
+      if (symlink(dest, link) != 0)
+      {
+         tell(0, "Failed to create symlink '%s', error was '%s'", link, strerror(errno));
+         return fail;
+      }
+   }
+
+   return success;
+}
+
+//***************************************************************************
+// Remove File
+//***************************************************************************
+
+int removeFile(const char* filename)
+{
+   int lnk = isLink(filename);
+
+   if (unlink(filename) != 0)
+   {
+      tell(0, "Can't remove file '%s', '%s'", filename, strerror(errno));
+      
+      return 1;
+   }
+
+   tell(3, "Removed %s '%s'", lnk ? "link" : "file", filename);
+   
+   return 0;
+}
+
+//***************************************************************************
+// Check Dir
+//***************************************************************************
+
+int chkDir(const char* path)
+{
+   struct stat fs;
+   
+   if (stat(path, &fs) != 0 || !S_ISDIR(fs.st_mode))
+   {
+      tell(0, "Creating directory '%s'", path);
+      
+      if (mkdir(path, ACCESSPERMS) == -1)
+      {
+         tell(0, "Can't create directory '%s'", strerror(errno));
+         return fail;
+      }
+   }
+
+   return success;
+}
+
+#ifdef USELIBXML
+
+//***************************************************************************
+// Load XSLT
+//***************************************************************************
+
+xsltStylesheetPtr loadXSLT(const char* name, const char* path, int utf8)
+{
+   xsltStylesheetPtr stylesheet;
+   char* xsltfile;
+
+   asprintf(&xsltfile, "%s/%s-%s.xsl", path, name, utf8 ? "utf-8" : "iso-8859-1");
+   
+   if ((stylesheet = xsltParseStylesheetFile((const xmlChar*)xsltfile)) == 0)
+      tell(0, "Error: Can't load xsltfile %s", xsltfile);
+   else
+      tell(0, "Info: Stylesheet '%s' loaded", xsltfile);
+      
+   free(xsltfile);
+   return stylesheet;
+}
+#endif
+
+//***************************************************************************
+// Gnu Unzip
+//***************************************************************************
+
+int gunzip(MemoryStruct* zippedData, MemoryStruct* unzippedData)
+{
+   const int growthStep = 1024;
+
+   z_stream stream = {0,0,0,0,0,0,0,0,0,0,0,Z_NULL,Z_NULL,Z_NULL};
+   unsigned int resultSize = 0;
+   int res = 0;
+
+   unzippedData->clear();
+
+   // determining the size in this way is taken from the sources of the gzip utility.
+
+   memcpy(&unzippedData->size, zippedData->memory + zippedData->size -4, 4); 
+   unzippedData->memory = (char*)malloc(unzippedData->size);
+
+   // zlib initialisation
+
+   stream.avail_in  = zippedData->size;
+   stream.next_in   = (Bytef*)zippedData->memory;
+   stream.avail_out = unzippedData->size;
+   stream.next_out  = (Bytef*)unzippedData->memory;
+
+   // The '+ 32' tells zlib to process zlib&gzlib headers
+
+   res = inflateInit2(&stream, MAX_WBITS + 32);
+
+   if (res != Z_OK)
+   {
+      tellZipError(res, " during zlib initialisation", stream.msg);
+      inflateEnd(&stream);
+      return fail;
+   }
+
+   // skip the header
+
+   res = inflate(&stream, Z_BLOCK);
+
+   if (res != Z_OK)
+   {
+      tellZipError(res, " while skipping the header", stream.msg);
+      inflateEnd(&stream);
+      return fail;
+   }
+
+   while (res == Z_OK)
+   {
+      if (stream.avail_out == 0)
+      {
+         unzippedData->size += growthStep;
+         unzippedData->memory = (char*)realloc(unzippedData->memory, unzippedData->size);
+
+         // Set the stream pointers to the potentially changed buffer!
+
+         stream.avail_out = resultSize - stream.total_out;
+         stream.next_out  = (Bytef*)(unzippedData + stream.total_out);
+      }
+
+      res = inflate(&stream, Z_SYNC_FLUSH);
+      resultSize = stream.total_out;
+   }
+
+   if (res != Z_STREAM_END)
+   {
+      tellZipError(res, " during inflating", stream.msg);
+      inflateEnd(&stream);
+      return fail;
+   }
+
+   unzippedData->size = resultSize;
+   inflateEnd(&stream);
+
+   return success;
+}
+
+//***************************************************************************
+// gzip
+//***************************************************************************
+
+int _gzip(Bytef* dest, uLongf* destLen, const Bytef* source, uLong sourceLen)
+{
+    z_stream stream;
+    int res;
+
+    stream.next_in = (Bytef *)source;
+    stream.avail_in = (uInt)sourceLen;
+    stream.next_out = dest;
+    stream.avail_out = (uInt)*destLen;
+    if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
+
+    stream.zalloc = (alloc_func)0;
+    stream.zfree = (free_func)0;
+    stream.opaque = (voidpf)0;
+
+    if ((res = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY)) != Z_OK)
+       return res;
+
+    res = deflate(&stream, Z_FINISH);
+
+    if (res != Z_STREAM_END) 
+    {
+        deflateEnd(&stream);
+        return res == Z_OK ? Z_BUF_ERROR : res;
+    }
+
+    *destLen = stream.total_out;
+    res = deflateEnd(&stream);
+
+    return res;
+}
+
+int gzip(MemoryStruct* data, MemoryStruct* zippedData)
+{
+   int res;
+   uLong sizeMax = compressBound(data->size) + 512;
+
+   zippedData->clear();
+   zippedData->memory = (char*)malloc(sizeMax);
+
+   if ((res = _gzip((Bytef*)zippedData->memory, &sizeMax, (Bytef*)data->memory, data->size)) != Z_OK)
+   {
+      tellZipError(res, " during compression", "");
+      return fail;
+   }
+
+   zippedData->copyAttributes(data);
+   zippedData->size = sizeMax;
+   sprintf(zippedData->contentEncoding, "gzip");
+
+   return success;
+}
+
+//*************************************************************************
+// tellZipError
+//*************************************************************************
+
+void tellZipError(int errorCode, const char* op, const char* msg)
+{
+   if (!op)  op  = "";
+   if (!msg) msg = "None";
+
+   switch (errorCode)
+   {
+      case Z_OK:           return;
+      case Z_STREAM_END:   return;
+      case Z_MEM_ERROR:    tell(0, "Error: Not enough memory to zip/unzip file%s!\n", op); return;
+      case Z_BUF_ERROR:    tell(0, "Error: Couldn't zip/unzip data due to output buffer size problem%s!\n", op); return;
+      case Z_DATA_ERROR:   tell(0, "Error: Zipped input data corrupted%s! Details: %s\n", op, msg); return;
+      case Z_STREAM_ERROR: tell(0, "Error: Invalid stream structure%s. Details: %s\n", op, msg); return;
+      default:             tell(0, "Error: Couldn't zip/unzip data for unknown reason (%6d)%s!\n", errorCode, op); return;
+   }
+}
+
+//*************************************************************************
+// Host Data
+//*************************************************************************
+
+#include <sys/utsname.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+
+static struct utsname info;
+
+const char* getHostName()
+{
+   // get info from kernel
+
+   if (uname(&info) == -1)
+      return "";
+
+   return info.nodename;
+}
+
+const char* getFirstIp()
+{
+   struct ifaddrs *ifaddr, *ifa;
+   static char host[NI_MAXHOST] = "";
+
+   if (getifaddrs(&ifaddr) == -1) 
+   {
+      tell(0, "getifaddrs() failed");
+      return "";
+   }
+
+   // walk through linked interface list
+
+   for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) 
+   {
+      if (!ifa->ifa_addr)
+         continue;
+      
+      // For an AF_INET interfaces
+
+      if (ifa->ifa_addr->sa_family == AF_INET) //  || ifa->ifa_addr->sa_family == AF_INET6) 
+      {
+         int res = getnameinfo(ifa->ifa_addr, 
+                               (ifa->ifa_addr->sa_family == AF_INET) ? sizeof(struct sockaddr_in) :
+                               sizeof(struct sockaddr_in6),
+                               host, NI_MAXHOST, 0, 0, NI_NUMERICHOST);
+
+         if (res)
+         {
+            tell(0, "getnameinfo() failed: %s", gai_strerror(res));
+            return "";
+         }
+
+         // skip loopback interface
+
+         if (strcmp(host, "127.0.0.1") == 0)
+            continue;
+
+         tell(5, "%-8s %-15s %s", ifa->ifa_name, host,
+              ifa->ifa_addr->sa_family == AF_INET   ? " (AF_INET)" :
+              ifa->ifa_addr->sa_family == AF_INET6  ? " (AF_INET6)" : "");
+      }
+   }
+
+   freeifaddrs(ifaddr);
+
+   return host;
+}
+
+#ifdef USELIBARCHIVE
+
+//***************************************************************************
+// unzip <file> and get data of first content which name matches <filter>
+//***************************************************************************
+
+int unzip(const char* file, const char* filter, char*& buffer, int& size, char* entryName)
+{
+   const int step = 1024*10;
+
+   int bufSize = 0;
+   int r;
+   int res;
+
+   struct archive_entry* entry;
+   struct archive* a = archive_read_new();
+
+   *entryName = 0;
+   buffer = 0;
+   size = 0;
+
+   archive_read_support_filter_all(a);
+   archive_read_support_format_all(a);
+
+   r = archive_read_open_filename(a, file, 10204);
+
+   if (r != ARCHIVE_OK)
+   {
+      tell(0, "Error: Open '%s' failed - %s", file, strerror(errno));
+      return 1;
+   }
+
+   while (archive_read_next_header(a, &entry) == ARCHIVE_OK) 
+   {
+      strcpy(entryName, archive_entry_pathname(entry));
+
+      if (strstr(entryName, filter))
+      {
+         bufSize = step;
+         buffer = (char*)malloc(bufSize+1);
+
+         while ((res = archive_read_data(a, buffer+size, step)) > 0)
+         {
+            size += res;
+            bufSize += step;
+
+            buffer = (char*)realloc(buffer, bufSize+1);
+         }
+         
+         buffer[size] = 0;
+
+         break;
+      }
+   }
+
+   r = archive_read_free(a);
+
+   if (r != ARCHIVE_OK)
+   {
+      size = 0;
+      free(buffer);      
+      return fail;
+   }
+
+   return size > 0 ? success : fail;
+}
+
+#endif
+
+//***************************************************************************
+// cMyMutex
+//***************************************************************************
+
+cMyMutex::cMyMutex (void)
+{
+  locked = 0;
+  pthread_mutexattr_t attr;
+  pthread_mutexattr_init(&attr);
+  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK_NP);
+  pthread_mutex_init(&mutex, &attr);
+}
+
+cMyMutex::~cMyMutex()
+{
+  pthread_mutex_destroy(&mutex);
+}
+
+void cMyMutex::Lock(void)
+{
+  pthread_mutex_lock(&mutex);
+  locked++;
+}
+
+void cMyMutex::Unlock(void)
+{
+ if (!--locked)
+    pthread_mutex_unlock(&mutex);
+}
+
+//***************************************************************************
+// cMyTimeMs
+//***************************************************************************
+
+cMyTimeMs::cMyTimeMs(int Ms)
+{
+  if (Ms >= 0)
+     Set(Ms);
+  else
+     begin = 0;
+}
+
+uint64_t cMyTimeMs::Now(void)
+{
+#define MIN_RESOLUTION 5 // ms
+  static bool initialized = false;
+  static bool monotonic = false;
+  struct timespec tp;
+  if (!initialized) {
+     // check if monotonic timer is available and provides enough accurate resolution:
+     if (clock_getres(CLOCK_MONOTONIC, &tp) == 0) {
+        // long Resolution = tp.tv_nsec;
+        // require a minimum resolution:
+        if (tp.tv_sec == 0 && tp.tv_nsec <= MIN_RESOLUTION * 1000000) {
+           if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) {
+              monotonic = true;
+              }
+           else
+              tell(0, "cMyTimeMs: clock_gettime(CLOCK_MONOTONIC) failed");
+           }
+        else
+           tell(0, "cMyTimeMs: not using monotonic clock - resolution is too bad (%ld s %ld ns)", tp.tv_sec, tp.tv_nsec);
+        }
+     else
+        tell(0, "cMyTimeMs: clock_getres(CLOCK_MONOTONIC) failed");
+     initialized = true;
+     }
+  if (monotonic) {
+     if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0)
+        return (uint64_t(tp.tv_sec)) * 1000 + tp.tv_nsec / 1000000;
+     tell(0, "cMyTimeMs: clock_gettime(CLOCK_MONOTONIC) failed");
+     monotonic = false;
+     // fall back to gettimeofday()
+     }
+  struct timeval t;
+  if (gettimeofday(&t, NULL) == 0)
+     return (uint64_t(t.tv_sec)) * 1000 + t.tv_usec / 1000;
+  return 0;
+}
+
+void cMyTimeMs::Set(int Ms)
+{
+  begin = Now() + Ms;
+}
+
+bool cMyTimeMs::TimedOut(void)
+{
+  return Now() >= begin;
+}
+
+uint64_t cMyTimeMs::Elapsed(void)
+{
+  return Now() - begin;
+}
+
+//***************************************************************************
+// Class LogDuration
+//***************************************************************************
+
+LogDuration::LogDuration(const char* aMessage, int aLogLevel)
+{
+   logLevel = aLogLevel;
+   strcpy(message, aMessage);
+   
+   // at last !
+
+   durationStart = cMyTimeMs::Now();
+}
+
+LogDuration::~LogDuration()
+{
+   tell(logLevel, "duration '%s' was (%ldms)",
+        message, cMyTimeMs::Now() - durationStart);
+}
+
+void LogDuration::show(const char* label)
+{
+   tell(logLevel, "elapsed '%s' at '%s' was (%ldms)",
+        message, label, cMyTimeMs::Now() - durationStart);
+}
+
+//***************************************************************************
+// Get Unique ID
+//***************************************************************************
+
+#ifdef USEUUID
+const char* getUniqueId()
+{
+   static char uuid[sizeUuid+TB] = "";
+
+   uuid_t id;
+   uuid_generate(id);
+   uuid_unparse_upper(id, uuid);
+
+   return uuid;
+}
+#endif // USEUUID
+
+//***************************************************************************
+// Create MD5
+//***************************************************************************
+
+#ifdef USEMD5
+
+int createMd5(const char* buf, md5* md5)
+{
+   MD5_CTX c;
+   unsigned char out[MD5_DIGEST_LENGTH];
+
+   MD5_Init(&c);
+   MD5_Update(&c, buf, strlen(buf));
+   MD5_Final(out, &c);
+
+   for (int n = 0; n < MD5_DIGEST_LENGTH; n++)
+      sprintf(md5+2*n, "%02x", out[n]);
+
+   md5[sizeMd5] = 0;
+
+   return done;
+}
+
+int createMd5OfFile(const char* path, const char* name, md5* md5)
+{
+   FILE* f;
+   char buffer[1000];
+   int nread = 0;
+   MD5_CTX c;
+   unsigned char out[MD5_DIGEST_LENGTH];
+   char* file = 0;
+
+   asprintf(&file, "%s/%s", path, name);
+   
+   if (!(f = fopen(file, "r")))
+   {
+      tell(0, "Fatal: Can't access '%s'; %s", file, strerror(errno));
+      free(file);
+      return fail;
+   }
+
+   free(file);
+
+   MD5_Init(&c);   
+   
+   while ((nread = fread(buffer, 1, 1000, f)) > 0)
+      MD5_Update(&c, buffer, nread);
+   
+   fclose(f);
+
+   MD5_Final(out, &c);
+   
+   for (int n = 0; n < MD5_DIGEST_LENGTH; n++)
+      sprintf(md5+2*n, "%02x", out[n]);
+
+   md5[sizeMd5] = 0;
+
+   return success;
+}
+
+#endif // USEMD5
+
+//***************************************************************************
+// Url Unescape
+//***************************************************************************
+/*
+ * The buffer pointed to by @dst must be at least strlen(@src) bytes.
+ * Decoding stops at the first character from @src that decodes to null.
+
+ * Path normalization will remove redundant slashes and slash+dot sequences,
+ * as well as removing path components when slash+dot+dot is found. It will
+ * keep the root slash (if one was present) and will stop normalization
+ * at the first questionmark found (so query parameters won't be normalized).
+ *
+ * @param dst       destination buffer
+ * @param src       source buffer
+ * @param normalize perform path normalization if nonzero
+ * @return          number of valid characters in @dst
+ */
+
+int urlUnescape(char* dst, const char* src, int normalize)
+{
+//    CURL* curl;
+//    int resultSize;
+
+//    if (curl_global_init(CURL_GLOBAL_ALL) != 0)
+//    {
+//       tell(0, "Error, something went wrong with curl_global_init()");
+      
+//       return fail;
+//    }
+   
+//    curl = curl_easy_init();
+   
+//    if (!curl)
+//    {
+//       tell(0, "Error, unable to get handle from curl_easy_init()");
+      
+//       return fail;
+//    }
+
+//    dst = curl_easy_unescape(curl, src, strlen(src), &resultSize);
+
+//    tell(0, " [%.40s]", src);
+
+//    tell(0, "res size %d [%.40s]", resultSize, dst);
+//    return resultSize;
+
+   char* org_dst = dst;
+   int slash_dot_dot = 0;
+   char ch, a, b;
+
+   a = 0;
+
+   do {
+      ch = *src++;
+
+      if (ch == '%' && isxdigit(a = src[0]) && isxdigit(b = src[1])) 
+      {
+         if (a < 'A') 
+            a -= '0';
+         else if 
+            (a < 'a') a -= 'A' - 10;
+         else 
+            a -= 'a' - 10;
+         
+         if (b < 'A') 
+            b -= '0';
+         else if (b < 'a') 
+            b -= 'A' - 10;
+         else 
+            b -= 'a' - 10;
+         
+         ch = 16 * a + b;
+         src += 2;
+      }
+      
+      if (normalize) 
+      {
+         switch (ch) 
+         {
+            case '/':                         // compress consecutive slashes and remove slash-dot
+               if (slash_dot_dot < 3) 
+               {
+                  
+                  dst -= slash_dot_dot;
+                  slash_dot_dot = 1;
+                  break;
+               }
+               // fall-through
+
+            case '?':                         // at start of query, stop normalizing
+               if (ch == '?')
+                  normalize = 0;
+
+               // fall-through
+
+            case '\0':                        // remove trailing slash-dot-(dot)
+               if (slash_dot_dot > 1) 
+               {
+                  dst -= slash_dot_dot;
+
+                  // remove parent directory if it was two dots
+
+                  if (slash_dot_dot == 3)
+                     while (dst > org_dst && *--dst != '/')
+                        ; //  empty body
+                  slash_dot_dot = (ch == '/') ? 1 : 0;
+
+                  // keep the root slash if any
+
+                  if (!slash_dot_dot && dst == org_dst && *dst == '/')
+                     ++dst;
+
+               }
+               break;
+
+            case '.':
+               if (slash_dot_dot == 1 || slash_dot_dot == 2) 
+               {
+                  ++slash_dot_dot;
+                  break;
+               }
+               // fall-through
+
+            default:
+               slash_dot_dot = 0;
+         }
+      }
+
+      *dst++ = ch;
+   } while(ch);
+   
+   return (dst - org_dst) - 1;
+}
diff --git a/lib/common.h b/lib/common.h
new file mode 100644
index 0000000..0500df2
--- /dev/null
+++ b/lib/common.h
@@ -0,0 +1,284 @@
+/*
+ * common.h: EPG2VDR plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __COMMON_H
+#define __COMMON_H
+
+#include <stdint.h>      // uint_64_t
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+
+#include <openssl/md5.h> // MD5_*
+
+//#ifdef VDR_PLUGIN
+//#  include <vdr/tools.h>
+//#  include <vdr/thread.h>
+//#endif
+
+#ifdef USELIBXML
+# include <libxslt/transform.h>
+# include <libxslt/xsltutils.h>
+# include <libexslt/exslt.h>
+#endif
+
+//***************************************************************************
+// Misc
+//***************************************************************************
+
+// #ifndef VDR_PLUGIN
+inline long min(long a, long b) { return a < b ? a : b; }
+inline long max(long a, long b) { return a > b ? a : b; }
+// #endif
+
+enum Misc
+{
+   success  = 0,
+   done     = success,
+   fail     = -1,
+   na       = -1,
+   ignore   = -2,
+   all      = -3,
+   abrt     = -4,
+   yes      = 1,
+   on       = 1,
+   off      = 0,
+   no       = 0,
+   TB       = 1,
+
+   sizeMd5 = 2 * MD5_DIGEST_LENGTH,
+   sizeUuid = 36,
+
+   tmeSecondsPerMinute = 60,
+   tmeSecondsPerHour = tmeSecondsPerMinute * 60,
+   tmeSecondsPerDay = 24 * tmeSecondsPerHour
+};
+
+enum Case
+{
+   cUpper,
+   cLower
+};
+
+const char* toCase(Case cs, char* str);
+
+//***************************************************************************
+// Tell
+//***************************************************************************
+
+extern const char* logPrefix;
+
+const char* getLogPrefix();
+void __attribute__ ((format(printf, 2, 3))) tell(int eloquence, const char* format, ...);
+
+//***************************************************************************
+// 
+//***************************************************************************
+
+char* srealloc(void* ptr, size_t size);
+
+//***************************************************************************
+// MemoryStruct
+//***************************************************************************
+
+struct MemoryStruct
+{
+   public:
+
+      MemoryStruct()   { expireAt = time(0); memory = 0; clear(); }
+      MemoryStruct(const MemoryStruct* o)
+      {
+         size = o->size;
+         memory = (char*)malloc(size);
+         memcpy(memory, o->memory, size);
+
+         copyAttributes(o);
+      }
+      
+      void copyAttributes(const MemoryStruct* o)
+      {
+         strcpy(tag, o->tag);
+         strcpy(name, o->name);
+         strcpy(contentType, o->contentType);
+         strcpy(contentEncoding, o->contentEncoding);
+         strcpy(mimeType, o->mimeType);
+         headerOnly = o->headerOnly;
+         modTime = o->modTime;
+         expireAt = o->expireAt;
+      }
+      
+      ~MemoryStruct()  { clear(); }
+      
+      int append(const char* buf, int len)
+      {
+         memory = srealloc(memory, size+len);
+         memcpy(memory+size, buf, len);
+         size += len;
+
+         return success;
+      }
+
+      // data
+      
+      char* memory;
+      long unsigned int size;
+      
+      // tag attribute
+      
+      char tag[100];              // the tag to be compared 
+      char name[100];             // content name (filename)
+      char contentType[100];      // e.g. text/html
+      char mimeType[100];         // 
+      char contentEncoding[100];  // 
+      int headerOnly;
+      time_t modTime;
+      time_t expireAt;
+      
+      int isEmpty() { return memory == 0; }
+      
+      void clear() 
+      {
+         free(memory);
+         memory = 0;
+         size = 0;
+         *tag = 0;
+         *name = 0;
+         *contentType = 0;
+         *contentEncoding = 0;
+         *mimeType = 0;
+         modTime = time(0);
+         // !!!! expireAt = time(0);
+         headerOnly = no;
+      }
+};
+
+//***************************************************************************
+// Tools
+//***************************************************************************
+
+double usNow();
+unsigned int getHostId();
+const char* getHostName();
+const char* getFirstIp();
+
+#ifdef USEUUID
+  const char* getUniqueId();
+#endif
+
+void removeChars(std::string& str, const char* ignore);
+void removeCharsExcept(std::string& str, const char* except);
+void removeWord(std::string& pattern, std::string word);
+void prepareCompressed(std::string& pattern);
+
+char* rTrim(char* buf);
+char* lTrim(char* buf);
+char* allTrim(char* buf);
+char* sstrcpy(char* dest, const char* src, int max);
+std::string num2Str(int num);
+std::string l2pTime(time_t t);
+std::string ms2Dur(uint64_t t);
+const char* c2s(char c, char* buf);
+int urlUnescape(char* dst, const char* src, int normalize = yes);
+
+int storeToFile(const char* filename, const char* data, int size);
+int loadFromFile(const char* infile, MemoryStruct* data);
+
+int fileExists(const char* path);
+int fileSize(const char* path);
+time_t fileModTime(const char* path);
+int createLink(const char* link, const char* dest, int force);
+int isLink(const char* path);
+const char*  suffixOf(const char* path);
+int isEmpty(const char* str);
+const char* notNull(const char* str);
+int isZero(const char* str);
+int removeFile(const char* filename);
+int chkDir(const char* path);
+int downloadFile(const char* url, int& size, MemoryStruct* data, int timeout = 30, const char* userAgent = "libcurl-agent/1.0");
+
+#ifdef USELIBXML
+  xsltStylesheetPtr loadXSLT(const char* name, const char* path, int utf8);
+#endif
+
+#ifdef USEMD5
+  typedef char md5Buf[sizeMd5+TB];
+  typedef char md5;
+  int createMd5(const char* buf, md5* md5);
+  int createMd5OfFile(const char* path, const char* name, md5* md5);
+#endif
+
+//***************************************************************************
+// Zip
+//***************************************************************************
+
+int gunzip(MemoryStruct* zippedData, MemoryStruct* unzippedData);
+int gzip(MemoryStruct* data, MemoryStruct* zippedData);
+void tellZipError(int errorCode, const char* op, const char* msg);
+
+#ifdef USELIBARCHIVE
+int unzip(const char* file, const char* filter, char*& buffer, 
+          int& size, char* entryName);
+#endif
+
+//***************************************************************************
+// cMyMutex
+//***************************************************************************
+
+class cMyMutex 
+{
+  friend class cCondVar;
+private:
+  pthread_mutex_t mutex;
+  int locked;
+public:
+  cMyMutex(void);
+  ~cMyMutex();
+  void Lock(void);
+  void Unlock(void);
+  };
+
+//***************************************************************************
+// cMyTimeMs
+//***************************************************************************
+
+class cMyTimeMs 
+{
+   private:
+
+      uint64_t begin;
+
+   public:
+
+      cMyTimeMs(int Ms = 0);
+      static uint64_t Now(void);
+      void Set(int Ms = 0);
+      bool TimedOut(void);
+      uint64_t Elapsed(void);
+};
+
+//***************************************************************************
+// Log Duration
+//***************************************************************************
+
+class LogDuration
+{
+   public:
+
+      LogDuration(const char* aMessage, int aLogLevel = 2);
+      ~LogDuration();
+
+      void show(const char* label = "");
+
+   protected:
+
+      char message[1000];
+      uint64_t durationStart;
+      int logLevel;
+};
+
+//***************************************************************************
+#endif //___COMMON_H
diff --git a/lib/config.c b/lib/config.c
new file mode 100644
index 0000000..6bb6d3d
--- /dev/null
+++ b/lib/config.c
@@ -0,0 +1,73 @@
+/*
+ * config.c:
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <string.h>
+
+#include "common.h"
+#include "config.h"
+
+cEPG2VDRConfig EPG2VDRConfig;
+
+cEPG2VDRConfig::cEPG2VDRConfig(void) 
+{
+   mainmenuVisible = yes;
+   mainmenuFullupdate = 0;
+
+   useproxy = no;
+   sstrcpy(httpproxy, "127.0.0.1:8000", sizeof(httpproxy));
+   sstrcpy(username, "", sizeof(username));
+   sstrcpy(password, "", sizeof(password));
+
+   checkInitial = yes;
+   updatetime = 6;        // hours
+   days = 8;
+   upddays = 2;
+   storeXmlToFs = no;
+   blacklist = no;
+   masterMode = 0;
+
+   getepgimages = yes;
+   maximagesperevent = 1;
+   epgImageSize = 2;
+
+   seriesEnabled = yes;
+   sstrcpy(seriesUrl, "eplists.constabel.net", sizeof(seriesUrl));
+   seriesPort = 2006;
+   storeSeriesToFs = no;
+
+   // for VDR_PLUGIN
+
+   activeOnEpgd = no;
+   scheduleBoot = no;
+
+   // for epgd
+
+   sstrcpy(cachePath, "/var/cache/epgd", sizeof(cachePath));
+   sstrcpy(httpPath, "/var/epgd/www", sizeof(httpPath));
+   sstrcpy(pluginPath, PLGDIR, sizeof(pluginPath));
+   sstrcpy(epgView, "eventsview.sql", sizeof(epgView));
+   sstrcpy(theTvDBView, "thetvdbview.sql", sizeof(epgView));
+   updateThreshold = 200;
+   maintanance = no;
+   httpPort = 9999;
+
+   // 
+
+   sstrcpy(dbHost, "localhost", sizeof(dbHost));
+   dbPort = 3306;
+   sstrcpy(dbName, "epg2vdr", sizeof(dbName));
+   sstrcpy(dbUser, "epg2vdr", sizeof(dbUser));
+   sstrcpy(dbPass, "epg", sizeof(dbPass));
+
+   logstdout = no;
+   loglevel = 1;
+
+   uuid[0] = 0;
+
+   scrapEpg = yes;
+   scrapRecordings = yes;
+} 
diff --git a/lib/config.h b/lib/config.h
new file mode 100644
index 0000000..4389c58
--- /dev/null
+++ b/lib/config.h
@@ -0,0 +1,84 @@
+/*
+ * config.h:
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id: config.h,v 1.2 2012/10/26 08:44:13 wendel Exp $
+ */
+
+#ifndef __EPG2VDR_CONFIG_H
+#define __EPG2VDR_CONFIG_H
+
+#include "common.h"
+
+//***************************************************************************
+// Config
+//***************************************************************************
+
+struct cEPG2VDRConfig
+{
+   public:
+      
+      cEPG2VDRConfig(void);
+
+      int useproxy;      
+      char httpproxy[256+TB];
+      char username[100+TB];
+      char password[100+TB];
+
+      int checkInitial;
+      int updatetime;
+      int days;
+      int upddays;
+      int storeXmlToFs;
+      int blacklist;         // to enable noepg feature
+
+      int getepgimages;
+      int maximagesperevent;
+      int epgImageSize;
+
+      int seriesEnabled;
+      char seriesUrl[500+TB];
+      int seriesPort;
+      int storeSeriesToFs;
+
+      // for VDR_PLUGIN
+
+      int activeOnEpgd;
+      int scheduleBoot;
+
+      // for epgd
+
+      char cachePath[256+TB];
+      char httpPath[256+TB];
+      char pluginPath[256+TB];
+      char epgView[100+TB];
+      char theTvDBView[100+TB];
+      int updateThreshold;
+      int maintanance;
+      int httpPort;
+
+      // 
+
+      char dbHost[100+TB];
+      int dbPort;
+      char dbName[100+TB];
+      char dbUser[100+TB];
+      char dbPass[100+TB];
+
+      int logstdout;
+      int loglevel;
+
+      int mainmenuVisible;
+      int mainmenuFullupdate;
+      int masterMode;
+      char uuid[sizeUuid+TB];
+
+      int scrapEpg;
+      int scrapRecordings;
+
+};
+
+extern cEPG2VDRConfig EPG2VDRConfig;
+
+#endif // __EPG2VDR_CONFIG_H 
diff --git a/lib/curl.c b/lib/curl.c
new file mode 100644
index 0000000..6eff24c
--- /dev/null
+++ b/lib/curl.c
@@ -0,0 +1,176 @@
+/*
+ * curl.c: 
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <curl/curl.h>
+
+#include "common.h"
+#include "config.h"
+
+//***************************************************************************
+// Callbacks
+//***************************************************************************
+
+static size_t WriteMemoryCallback(void* ptr, size_t size, size_t nmemb, void* data)
+{
+   size_t realsize = size * nmemb;
+   struct MemoryStruct* mem = (struct MemoryStruct*)data;
+   
+   if (mem->memory)
+      mem->memory = (char*)realloc(mem->memory, mem->size + realsize + 1);
+   else
+      mem->memory = (char*)malloc(mem->size + realsize + 1);
+   
+   if (mem->memory)
+   {
+      memcpy (&(mem->memory[mem->size]), ptr, realsize);
+      mem->size += realsize;
+      mem->memory[mem->size] = 0;
+   }
+
+   return realsize;
+}
+
+static size_t WriteHeaderCallback(void* ptr, size_t size, size_t nmemb, void* data)
+{
+   size_t realsize = size * nmemb;
+   struct MemoryStruct* mem = (struct MemoryStruct*)data;
+   char* p;
+
+   if (ptr)
+   {
+      // get filename
+      {
+         // Content-Disposition: attachment; filename="20140103_20140103_de_qy.zip"
+
+         const char* attribute = "Content-disposition: ";
+         
+         if ((p = strcasestr((char*)ptr, attribute)))
+         {
+            if (p = strcasestr(p, "filename="))
+            {
+               p += strlen("filename=");
+
+               tell(4, "found filename at [%s]", p);
+
+               if (*p == '"')
+                  p++;
+
+               sprintf(mem->name, "%s", p);
+               
+               if ((p = strchr(mem->name, '\n')))
+                  *p = 0;
+               
+               if ((p = strchr(mem->name, '\r')))
+                  *p = 0;
+               
+               if ((p = strchr(mem->name, '"')))
+                  *p = 0;
+
+               tell(4, "set name to '%s'", mem->name);
+            }
+         }
+      }
+      
+      // since some sources update "ETag" an "Last-Modified:" without changing the contents
+      //  we have to use "Content-Length:" to check for updates :(
+      {
+         const char* attribute = "Content-Length: ";
+         
+         if ((p = strcasestr((char*)ptr, attribute)))
+         {
+            sprintf(mem->tag, "%s", p+strlen(attribute));
+            
+            if ((p = strchr(mem->tag, '\n')))
+               *p = 0;
+            
+            if ((p = strchr(mem->tag, '\r')))
+               *p = 0;
+            
+            if ((p = strchr(mem->tag, '"')))
+               *p = 0;
+         }
+      }
+   }
+
+   return realsize;
+}
+
+//***************************************************************************
+// Download File
+//***************************************************************************
+
+int downloadFile(const char* url, int& size, MemoryStruct* data, int timeout, const char* userAgent)
+{
+   CURL* curl_handle;
+   long code;
+   CURLcode res = CURLE_OK;
+
+   size = 0;
+
+   // init curl
+
+   if (curl_global_init(CURL_GLOBAL_ALL) != 0)
+   {
+      tell(0, "Error, something went wrong with curl_global_init()");
+      
+      return fail;
+   }
+   
+   curl_handle = curl_easy_init();
+   
+   if (!curl_handle)
+   {
+      tell(0, "Error, unable to get handle from curl_easy_init()");
+      
+      return fail;
+   }
+  
+   if (EPG2VDRConfig.useproxy)
+   {
+      curl_easy_setopt(curl_handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
+      curl_easy_setopt(curl_handle, CURLOPT_PROXY, EPG2VDRConfig.httpproxy);   // Specify HTTP proxy
+   }
+
+   curl_easy_setopt(curl_handle, CURLOPT_URL, url);                            // Specify URL to get   
+   curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 0);                   // don't follow redirects
+   curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);  // Send all data to this function
+   curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void*)data);              // Pass our 'data' struct to the callback function
+   curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, WriteHeaderCallback); // Send header to this function
+   curl_easy_setopt(curl_handle, CURLOPT_WRITEHEADER, (void*)data);            // Pass some header details to this struct
+   curl_easy_setopt(curl_handle, CURLOPT_MAXFILESIZE, 100*1024*1024);          // Set maximum file size to get (bytes)
+   curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1);                       // No progress meter
+   curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);                         // No signaling
+   curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, timeout);                    // Set timeout
+   curl_easy_setopt(curl_handle, CURLOPT_NOBODY, data->headerOnly ? 1 : 0);    // 
+   curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, userAgent);                // Some servers don't like requests 
+                                                                               // that are made without a user-agent field
+   // perform http-get
+
+   if ((res = curl_easy_perform(curl_handle)) != 0)
+   {
+      data->clear();
+
+      tell(1, "Error, download failed; %s (%d)",
+           curl_easy_strerror(res), res);
+      curl_easy_cleanup(curl_handle);  // Cleanup curl stuff
+
+      return fail;
+   }
+
+   curl_easy_getinfo(curl_handle, CURLINFO_HTTP_CODE, &code); 
+   curl_easy_cleanup(curl_handle);     // cleanup curl stuff
+
+   if (code == 404)
+   {
+      data->clear();
+      return fail;
+   }
+
+   size = data->size;
+
+   return success;
+}
diff --git a/lib/db.c b/lib/db.c
new file mode 100644
index 0000000..6ee2c71
--- /dev/null
+++ b/lib/db.c
@@ -0,0 +1,1209 @@
+/*
+ * db.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <stdio.h>
+
+#include <errmsg.h>
+
+#include "db.h"
+
+// #define DEB_HANDLER
+
+//***************************************************************************
+// DB Statement
+//***************************************************************************
+
+int cDbStatement::explain = no;
+
+cDbStatement::cDbStatement(cDbTable* aTable)
+{
+   table = aTable;
+   connection = table->getConnection();
+   stmtTxt = "";
+   stmt = 0;
+   inCount = 0;
+   outCount = 0;
+   inBind = 0;
+   outBind = 0;
+   affected = 0;
+   metaResult = 0;
+   bindPrefix = 0;
+   firstExec = yes;
+
+   callsPeriod = 0;
+   callsTotal = 0;
+   duration = 0;
+
+   if (connection)
+      connection->statements.append(this);
+}
+
+cDbStatement::cDbStatement(cDbConnection* aConnection, const char* stmt)
+{
+   table = 0;
+   connection = aConnection;
+   stmtTxt = stmt;
+   stmt = 0;
+   inCount = 0;
+   outCount = 0;
+   inBind = 0;
+   outBind = 0;
+   affected = 0;
+   metaResult = 0;
+   bindPrefix = 0;
+   firstExec = yes;
+
+   callsPeriod = 0;
+   callsTotal = 0;
+   duration = 0;
+
+   if (connection)
+      connection->statements.append(this);
+}
+
+cDbStatement::~cDbStatement()  
+{ 
+   if (connection)
+      connection->statements.remove(this);
+
+   clear();
+}
+
+//***************************************************************************
+// Execute
+//***************************************************************************
+
+int cDbStatement::execute(int noResult)
+{
+   affected = 0;
+
+   if (!connection || !connection->getMySql())
+      return fail;
+
+   if (!stmt)
+      return connection->errorSql(connection, "execute(missing statement)");
+
+//    if (explain && firstExec)
+//    {
+//       firstExec = no;
+
+//       if (strstr(stmtTxt.c_str(), "select "))
+//       {
+//          MYSQL_RES* result;
+//          MYSQL_ROW row;
+//          string q = "explain " + stmtTxt;
+
+//          if (connection->query(q.c_str()) != success)
+//             connection->errorSql(connection, "explain ", 0);
+//          else if ((result = mysql_store_result(connection->getMySql())))
+//          {
+//             while ((row = mysql_fetch_row(result)))
+//             {
+//                tell(0, "EXPLAIN: %s) %s %s %s %s %s %s %s %s %s", 
+//                     row[0], row[1], row[2], row[3],
+//                     row[4], row[5], row[6], row[7], row[8], row[9]);
+//             }
+            
+//             mysql_free_result(result);
+//          }
+//       }
+//    }
+
+   // tell(0, "execute %d [%s]", stmt, stmtTxt.c_str());
+
+   double start = usNow();
+
+   if (mysql_stmt_execute(stmt))
+      return connection->errorSql(connection, "execute(stmt_execute)", stmt, stmtTxt.c_str());
+
+   duration += usNow() - start;
+   callsPeriod++;
+   callsTotal++;
+
+   // out binding - if needed
+
+   if (outCount && !noResult)
+   {
+      if (mysql_stmt_store_result(stmt))
+         return connection->errorSql(connection, "execute(store_result)", stmt, stmtTxt.c_str());
+
+      // fetch the first result - if any
+
+      if (mysql_stmt_affected_rows(stmt) > 0)
+         mysql_stmt_fetch(stmt);
+   }
+   else if (outCount)
+   {
+      mysql_stmt_store_result(stmt);
+   }
+
+   // result was stored (above) only if output (outCound) is expected, 
+   // therefore we don't need to call freeResult() after insert() or update()
+   
+   affected = mysql_stmt_affected_rows(stmt);
+
+   return success;
+}
+
+int cDbStatement::getResultCount()
+{
+   mysql_stmt_store_result(stmt);
+
+   return mysql_stmt_affected_rows(stmt);
+}
+
+int cDbStatement::find()
+{
+   if (execute() != success)
+      return fail;
+
+   return getAffected() > 0 ? yes : no;
+}
+
+int cDbStatement::fetch()
+{
+   if (!mysql_stmt_fetch(stmt))
+      return yes;
+
+   return no;
+}
+
+int cDbStatement::freeResult()
+{
+   if (metaResult)
+      mysql_free_result(metaResult);
+   
+   if (stmt)
+      mysql_stmt_free_result(stmt);
+   
+   return success;
+}
+
+//***************************************************************************
+// Build Statements - new Interface
+//***************************************************************************
+
+int cDbStatement::build(const char* format, ...)
+{
+   if (format)
+   {
+      char* tmp;
+
+      va_list more;
+      va_start(more, format);
+      vasprintf(&tmp, format, more);
+
+      stmtTxt += tmp;
+      free(tmp);
+   }
+
+   return success;
+}
+
+int cDbStatement::bind(int field, int mode, const char* delim)
+{
+   return bind(table->getRow()->getValue(field), mode, delim);
+}
+
+int cDbStatement::bind(cDbTable* aTable, int field, int mode, const char* delim)
+{
+   return bind(aTable->getRow()->getValue(field), mode, delim);
+}
+
+int cDbStatement::bind(cDbValue* value, int mode, const char* delim)
+{
+   if (!value || !value->getField())
+      return fail;
+
+   if (delim)
+      stmtTxt += delim;
+
+   if (bindPrefix)
+      stmtTxt += bindPrefix;
+
+   if (mode & bndIn)
+   {
+      if (mode & bndSet)
+         stmtTxt += value->getName() + string(" =");
+      
+      stmtTxt += " ?";
+      appendBinding(value, bndIn);
+   }
+   else if (mode & bndOut)
+   {
+      stmtTxt += value->getName();
+      appendBinding(value, bndOut);
+   }
+
+   return success;
+}
+
+int cDbStatement::bindCmp(const char* ctable, cDbValue* value, 
+                          const char* comp, const char* delim)
+{
+   if (ctable)
+      build("%s.", ctable);
+
+   build("%s%s%s %s ?", delim ? delim : "", bindPrefix ? bindPrefix : "", value->getName(), comp);
+
+   appendBinding(value, bndIn);
+
+   return success;
+}
+
+int cDbStatement::bindCmp(const char* ctable, int field, cDbValue* value, 
+                          const char* comp, const char* delim)
+{
+   cDbValue* vf = table->getRow()->getValue(field);
+   cDbValue* vv = value ? value : vf;
+
+   if (ctable)
+      build("%s.", ctable);
+
+   build("%s%s%s %s ?", delim ? delim : "", bindPrefix ? bindPrefix : "", vf->getName(), comp);
+
+   appendBinding(vv, bndIn);
+
+   return success;
+}
+
+//***************************************************************************
+// Clear
+//***************************************************************************
+
+void cDbStatement::clear()
+{
+   stmtTxt = "";
+   affected = 0;
+
+   if (inCount)
+   {
+      free(inBind);
+      inCount = 0;
+      inBind = 0;
+   }
+   
+   if (outCount)
+   {
+      free(outBind);
+      outCount = 0;
+      outBind = 0;
+   }
+   
+   if (stmt) 
+   { 
+      mysql_stmt_free_result(stmt);
+      mysql_stmt_close(stmt); 
+      stmt = 0; 
+   }
+}
+
+//***************************************************************************
+// Append Binding 
+//***************************************************************************
+
+int cDbStatement::appendBinding(cDbValue* value, BindType bt)
+{
+   int count = 0;
+   MYSQL_BIND** bindings = 0;
+   MYSQL_BIND* newBinding;
+
+   if (bt & bndIn)
+   {
+      count = ++inCount;
+      bindings = &inBind;
+   }
+   else if (bt & bndOut)
+   {
+      count = ++outCount;
+      bindings = &outBind;
+   }
+   else
+      return 0;
+
+   if (!bindings)
+      *bindings = (MYSQL_BIND*)malloc(count * sizeof(MYSQL_BIND));
+   else
+      *bindings = (MYSQL_BIND*)srealloc(*bindings, count * sizeof(MYSQL_BIND));
+
+   newBinding = &((*bindings)[count-1]);
+
+   if (value->getField()->format == ffAscii || value->getField()->format == ffText)
+   {
+      newBinding->buffer_type = MYSQL_TYPE_STRING;
+      newBinding->buffer = value->getStrValueRef();
+      newBinding->buffer_length = value->getField()->size;
+      newBinding->length = value->getStrValueSizeRef();
+      
+      newBinding->is_null = value->getNullRef();
+      newBinding->error = 0;            // #TODO
+   }
+   else if (value->getField()->format == ffMlob)
+   {
+      newBinding->buffer_type = MYSQL_TYPE_BLOB;
+      newBinding->buffer = value->getStrValueRef();
+      newBinding->buffer_length = value->getField()->size;
+      newBinding->length = value->getStrValueSizeRef();
+      
+      newBinding->is_null = value->getNullRef();
+      newBinding->error = 0;            // #TODO
+   }
+   else if (value->getField()->format == ffFloat)
+   {
+      newBinding->buffer_type = MYSQL_TYPE_FLOAT;
+      newBinding->buffer = value->getFloatValueRef();
+      
+      newBinding->length = 0;            // #TODO
+      newBinding->is_null =  value->getNullRef();
+      newBinding->error = 0;             // #TODO
+   }
+   else if (value->getField()->format == ffDateTime)
+   {
+      newBinding->buffer_type = MYSQL_TYPE_DATETIME;
+      newBinding->buffer = value->getTimeValueRef();
+      
+      newBinding->length = 0;            // #TODO
+      newBinding->is_null =  value->getNullRef();
+      newBinding->error = 0;             // #TODO
+   }
+   else
+   {
+      newBinding->buffer_type = MYSQL_TYPE_LONG;
+      newBinding->buffer = value->getIntValueRef();
+      newBinding->is_unsigned = (value->getField()->format == ffUInt);
+
+      newBinding->length = 0;
+      newBinding->is_null =  value->getNullRef();
+      newBinding->error = 0;             // #TODO
+   }
+
+   return success;
+}
+
+//***************************************************************************
+// Prepare Statement
+//***************************************************************************
+
+int cDbStatement::prepare()
+{
+   if (!stmtTxt.length() || !connection->getMySql())
+      return fail;
+   
+   stmt = mysql_stmt_init(connection->getMySql());
+   
+   // prepare statement
+   
+   if (mysql_stmt_prepare(stmt, stmtTxt.c_str(), stmtTxt.length()))
+      return connection->errorSql(connection, "prepare(stmt_prepare)", stmt, stmtTxt.c_str());
+
+   if (outBind)
+   {
+      if (mysql_stmt_bind_result(stmt, outBind))
+         return connection->errorSql(connection, "execute(bind_result)", stmt);
+   }
+
+   if (inBind)
+   {
+      if (mysql_stmt_bind_param(stmt, inBind))
+         return connection->errorSql(connection, "buildPrimarySelect(bind_param)", stmt);
+   }
+
+   tell(2, "Statement '%s' with (%ld) in parameters and (%d) out bindings prepared", 
+        stmtTxt.c_str(), mysql_stmt_param_count(stmt), outCount);
+   
+   return success;
+}
+
+//***************************************************************************
+// Show Statistic
+//***************************************************************************
+
+void cDbStatement::showStat()
+{
+   if (callsPeriod)
+   {
+      tell(0, "calls %4ld in %6.2fms; total %4ld [%s]", 
+           callsPeriod, duration/1000, callsTotal, stmtTxt.c_str());
+
+      callsPeriod = 0;
+      duration = 0;
+   }
+}
+
+//***************************************************************************
+// cDbService
+//***************************************************************************
+
+const char* cDbService::formats[] =
+{
+   "INT",
+   "INT",
+   "VARCHAR",
+   "TEXT",
+   "MEDIUMBLOB",
+   "FLOAT",
+   "DATETIME",
+
+   0
+};
+
+const char* cDbService::toString(FieldFormat t)
+{
+   return formats[t];
+}
+
+const char* cDbService::dictFormats[] =
+{
+   "int",
+   "uint",
+   "ascii",
+   "text",
+   "mlob",
+   "float",
+   "datetime",
+
+   0
+};
+
+cDbService::FieldFormat cDbService::toDictFormat(const char* format)
+{
+   for (int i = 0; i < ffCount; i++)
+      if (strcasecmp(dictFormats[i], format) == 0)
+         return (FieldFormat)i;
+
+   return ffUnknown;
+}
+
+const char* cDbService::types[] =
+{
+   "data",
+   "primary",
+   "meta",
+   "calc",
+   "autoinc",
+
+   0
+};
+
+cDbService::FieldType cDbService::toType(const char* type)
+{
+   // #TODO  !!!
+
+   for (int i = 0; i < 3; i++)
+      if (strcasecmp(types[i], type) == 0)
+         return (FieldType)i;
+
+   return ftUnknown;
+}
+
+//***************************************************************************
+// Class cDbTable
+//***************************************************************************
+
+char* cDbTable::confPath = 0;
+
+char* cDbConnection::encoding = 0;
+char* cDbConnection::dbHost = strdup("localhost");
+int   cDbConnection::dbPort = 3306;
+char* cDbConnection::dbUser = 0;
+char* cDbConnection::dbPass = 0;
+char* cDbConnection::dbName = 0;
+
+//***************************************************************************
+// Object
+//***************************************************************************
+
+cDbTable::cDbTable(cDbConnection* aConnection, FieldDef* f, IndexDef* i)
+{
+   connection = aConnection;
+   row = new cDbRow(f);
+   holdInMemory = no;
+
+   stmtSelect = 0;
+   stmtInsert = 0;
+   stmtUpdate = 0;
+
+   indices = i;
+}
+
+cDbTable::~cDbTable()
+{
+   close();
+
+   delete row;
+}
+
+//***************************************************************************
+// Open / Close
+//***************************************************************************
+
+int cDbTable::open()
+{
+   if (connection->attachConnection() != success)
+   {
+      tell(0, "Could not access database '%s:%d' (tried to open %s)", 
+           connection->getHost(), connection->getPort(), TableName());
+
+      return fail;
+   }
+   
+   return init();
+}
+
+int cDbTable::close()
+{
+   if (stmtSelect) { delete stmtSelect; stmtSelect = 0; }
+   if (stmtInsert) { delete stmtInsert; stmtInsert = 0; }
+   if (stmtUpdate) { delete stmtUpdate; stmtUpdate = 0; }
+
+   connection->detachConnection();
+
+   return success;
+}
+
+//***************************************************************************
+// Init
+//***************************************************************************
+
+int cDbTable::init()
+{
+   string str;
+
+   if (!isConnected()) return fail;
+
+   // check/create table ...
+
+   if (createTable() != success)
+      return fail;
+
+   // check/create indices
+
+   createIndices();
+
+   // ------------------------------
+   // prepare BASIC statements
+   // ------------------------------
+
+   // select by primary key ...
+
+   stmtSelect = new cDbStatement(this);
+   
+   stmtSelect->build("select ");
+   
+   for (int i = 0, n = 0; row->getField(i)->name; i++)
+   {
+      if (row->getField(i)->type & ftCalc)
+         continue;
+      
+      stmtSelect->bind(i, bndOut, n++ ? ", " : "");
+   }
+   
+   stmtSelect->build(" from %s where ", TableName());
+   
+   for (int i = 0, n = 0; row->getField(i)->name; i++)
+   {
+      if (!(row->getField(i)->type & ftPrimary))
+         continue;
+      
+      stmtSelect->bind(i, bndIn | bndSet, n++ ? " and " : "");
+   }
+   
+   stmtSelect->build(";");
+ 
+   if (stmtSelect->prepare() != success)
+      return fail;
+
+   // -----------------------------------------
+   // insert 
+
+   stmtInsert = new cDbStatement(this);
+
+   stmtInsert->build("insert into %s set ", TableName());
+
+   for (int i = 0, n = 0; row->getField(i)->name; i++)
+   {
+      // don't insert autoinc and calculated fields
+
+      if (row->getField(i)->type & ftCalc || row->getField(i)->type & ftAutoinc)
+         continue;
+
+      stmtInsert->bind(i, bndIn | bndSet, n++ ? ", " : "");
+   }
+
+   stmtInsert->build(";");
+
+   if (stmtInsert->prepare() != success)
+      return fail;
+
+   // -----------------------------------------
+   // update via primary key ...
+
+   stmtUpdate = new cDbStatement(this);
+
+   stmtUpdate->build("update %s set ", TableName());
+         
+   for (int i = 0, n = 0; row->getField(i)->name; i++)
+   {
+      // don't update PKey, autoinc and calculated fields
+
+      if (row->getField(i)->type & ftPrimary || 
+          row->getField(i)->type & ftCalc || 
+          row->getField(i)->type & ftAutoinc)
+         continue;
+      
+      if (strcmp(row->getField(i)->name, "inssp") == 0)  // don't update the insert stamp
+         continue;
+      
+      stmtUpdate->bind(i, bndIn | bndSet, n++ ? ", " : "");
+   }
+
+   stmtUpdate->build(" where ");
+
+   for (int i = 0, n = 0; row->getField(i)->name; i++)
+   {
+      if (!(row->getField(i)->type & ftPrimary))
+         continue;
+
+      stmtUpdate->bind(i, bndIn | bndSet, n++ ? " and " : "");
+   }
+
+   stmtUpdate->build(";");
+
+   if (stmtUpdate->prepare() != success)
+      return fail;
+
+   return success;
+}
+
+//***************************************************************************
+// Check Table 
+//***************************************************************************
+
+int cDbTable::exist(const char* name)
+{
+   if (!name)
+      name = TableName();
+
+   MYSQL_RES* result = mysql_list_tables(connection->getMySql(), name);
+   MYSQL_ROW tabRow = mysql_fetch_row(result);
+   mysql_free_result(result);
+
+   return tabRow ? yes : no;
+}
+
+//***************************************************************************
+// Create Table
+//***************************************************************************
+
+int cDbTable::createTable()
+{
+   string statement;
+   string aKey;
+
+   if (!isConnected())
+   {
+      if (connection->attachConnection() != success)
+      {
+         tell(0, "Could not access database '%s:%d' (tried to create %s)", 
+              connection->getHost(), connection->getPort(), TableName());
+         
+         return fail;
+      }
+   }
+
+   // table exists -> nothing to do
+
+   if (exist())
+      return done;
+
+   tell(0, "Initialy creating table '%s'", TableName());
+
+   // build 'create' statement ...
+
+   statement = string("create table ") + TableName() + string("(");
+
+   for (int i = 0; getField(i)->name; i++)
+   {
+      int size = getField(i)->size;
+      char num[10];
+
+      if (getField(i)->type & ftCalc)
+         continue;
+
+      if (i) statement += string(", ");
+      statement += string(getField(i)->name) + " " + string(toString(getField(i)->format));
+
+      if (getField(i)->format != ffMlob)
+      {
+         if (!size) size = getField(i)->format == ffAscii || getField(i)->format == ffText ? 100 : 11;
+
+         if (getField(i)->format != ffFloat)
+            sprintf(num, "%d", size);
+         else
+            sprintf(num, "%d,%d", size/10, size%10);
+
+         statement += "(" + string(num) + ")";
+
+         if (getField(i)->format == ffUInt)
+            statement += " unsigned";
+
+         if (getField(i)->type & ftAutoinc)
+            statement += " not null auto_increment";
+         else if (getField(i)->type & ftDef0)
+            statement += " default '0'";
+      }
+   }
+
+   aKey = "";
+
+   for (int i = 0, n = 0; getField(i)->name; i++)
+   {
+      if (getField(i)->type & ftPrimary)
+      {
+         if (n++) aKey += string(", ");
+         aKey += string(getField(i)->name) + " DESC";
+      }
+   }
+
+   if (aKey.length())
+   {
+      statement += string(", PRIMARY KEY(");
+      statement += aKey;
+      statement += ")";
+   }
+
+   aKey = "";
+
+   for (int i = 0, n = 0; getField(i)->name; i++)
+   {
+      if (getField(i)->type & ftAutoinc && !(getField(i)->type & ftPrimary))
+      {
+         if (n++) aKey += string(", ");
+         aKey += string(getField(i)->name) + " DESC";
+      }
+   }
+
+   if (aKey.length())
+   {
+      statement += string(", KEY(");
+      statement += aKey;
+      statement += ")";
+   }
+
+   // statement += string(") ENGINE MYISAM;");
+   statement += string(") ENGINE InnoDB;");
+
+   tell(1, "%s", statement.c_str());
+
+   if (connection->query(statement.c_str()))
+      return connection->errorSql(getConnection(), "createTable()", 
+                                  0, statement.c_str());
+
+   return success;
+}
+
+//***************************************************************************
+// Create Indices
+//***************************************************************************
+
+int cDbTable::createIndices()
+{
+   string statement;
+
+   tell(5, "Initialy checking indices for '%s'", TableName());
+
+   // check/create indexes
+
+   if (!indices)
+      return done;
+
+   for (int i = 0; getIndex(i)->name; i++)
+   {
+      IndexDef* index = getIndex(i);
+      int fCount;
+      string idxName;
+      int expectCount = 0;
+
+      for (; index->fields[expectCount] != na; expectCount++) ;
+
+      if (!expectCount)
+         continue;
+         
+      // check
+
+      idxName = "idx" + string(index->name);
+
+      checkIndex(idxName.c_str(), fCount);
+
+      if (fCount != expectCount)
+      {
+         // create index
+            
+         statement = "create index " + idxName;
+         statement += " on " + string(TableName()) + "(";
+            
+         int n = 0;
+            
+         for (int f = 0; index->fields[f] != na; f++)
+         {              
+            FieldDef* fld = getField(index->fields[f]);
+               
+            if (fld && !(fld->type & ftCalc))
+            {
+               if (n++) statement += string(", ");
+               statement += fld->name;
+            }
+         }
+            
+         if (!n) continue;
+            
+         statement += ");";
+         tell(1, "%s", statement.c_str());
+            
+         if (connection->query(statement.c_str()))
+            return connection->errorSql(getConnection(), "createIndices()", 
+                                        0, statement.c_str());
+      }
+   }
+
+   return success;
+}
+
+//***************************************************************************
+// Check Index
+//***************************************************************************
+
+int cDbTable::checkIndex(const char* idxName, int& fieldCount)
+{
+   enum IndexQueryFields
+   {
+      idTable,
+      idNonUnique,
+      idKeyName,
+      idSeqInIndex,
+      idColumnName,
+      idCollation,
+      idCardinality,
+      idSubPart,
+      idPacked,
+      idNull,
+      idIndexType,
+      idComment,
+      idIndexComment,
+      
+      idCount
+   };
+
+   MYSQL_RES* result;
+   MYSQL_ROW row;
+
+   fieldCount = 0;
+
+   if (connection->query("show index from %s", TableName()) != success)
+   {
+      connection->errorSql(getConnection(), "checkIndex()", 0);
+
+      return fail;
+   }
+
+   if ((result = mysql_store_result(connection->getMySql())))
+   {
+      while ((row = mysql_fetch_row(result)))
+      {
+         tell(5, "%s:  %-20s %s %s", 
+              row[idTable], row[idKeyName],
+              row[idSeqInIndex], row[idColumnName]);
+
+         if (strcasecmp(row[idKeyName], idxName) == 0)
+            fieldCount++;
+      }
+      
+      mysql_free_result(result);
+
+      return success;
+   }
+
+   connection->errorSql(getConnection(), "checkIndex()");
+
+   return fail;
+}
+
+//***************************************************************************
+// Copy Values
+//***************************************************************************
+
+void cDbTable::copyValues(cDbRow* r)
+{
+   for (int i = 0; i < fieldCount(); i++)
+   {
+      if (getField(i)->format == ffAscii || getField(i)->format == ffText)
+         row->setValue(i, r->getStrValue(i));
+      else
+         row->setValue(i, r->getIntValue(i));
+   }
+}
+
+//***************************************************************************
+// SQL Error 
+//***************************************************************************
+
+int cDbConnection::errorSql(cDbConnection* connection, const char* prefix, MYSQL_STMT* stmt, const char* stmtTxt)
+{
+   if (!connection || !connection->mysql)
+   {
+      tell(0, "SQL-Error in '%s'", prefix);
+      return fail;
+   }
+
+   int error = mysql_errno(connection->mysql);
+   char* conErr = 0;
+   char* stmtErr = 0;
+
+   if (error == CR_SERVER_LOST ||
+       error == CR_SERVER_GONE_ERROR ||
+       error == CR_INVALID_CONN_HANDLE ||
+       error == CR_SERVER_LOST_EXTENDED)
+      connectDropped = yes;
+
+   if (error)
+      asprintf(&conErr, "%s (%d) ", mysql_error(connection->mysql), error);
+
+   if (stmt || stmtTxt)
+      asprintf(&stmtErr, "'%s' [%s]",
+               stmt ? mysql_stmt_error(stmt) : "",
+               stmtTxt ? stmtTxt : "");
+
+   tell(0, "SQL-Error in '%s' - %s%s", prefix, 
+        conErr ? conErr : "", stmtErr ? stmtErr : "");
+
+   free(conErr);
+   free(stmtErr);
+
+   if (connectDropped)
+      tell(0, "Fatal, lost connection to mysql server, aborting pending actions");
+
+   return fail;
+}
+
+//***************************************************************************
+// Delete Where
+//***************************************************************************
+
+int cDbTable::deleteWhere(const char* where)
+{
+   string tmp;
+
+   if (!connection || !connection->getMySql())
+      return fail;
+
+   tmp = "delete from " + string(TableName()) + " where " + string(where);
+   
+   if (connection->query(tmp.c_str()))
+      return connection->errorSql(connection, "deleteWhere()", 0, tmp.c_str());
+
+   return success;
+}
+
+//***************************************************************************
+// Coiunt Where
+//***************************************************************************
+
+int cDbTable::countWhere(const char* where, int& count, const char* what)
+{
+   string tmp;
+   MYSQL_RES* res;
+   MYSQL_ROW data;
+
+   count = 0;
+   
+   if (isEmpty(what))
+      what = "count(1)";
+
+   if (!isEmpty(where))
+      tmp = "select " + string(what) + " from " + string(TableName()) + " where " + string(where);
+   else
+      tmp = "select " + string(what) + " from " + string(TableName());
+   
+   if (connection->query(tmp.c_str()))
+      return connection->errorSql(connection, "countWhere()", 0, tmp.c_str());
+
+   if (res = mysql_store_result(connection->getMySql()))
+   {
+      data = mysql_fetch_row(res);
+
+      if (data)
+         count = atoi(data[0]);
+
+      mysql_free_result(res);
+   }
+
+   return success;
+}
+
+//***************************************************************************
+// Truncate
+//***************************************************************************
+
+int cDbTable::truncate()
+{
+   string tmp;
+
+   tmp = "delete from " + string(TableName());
+
+   if (connection->query(tmp.c_str()))
+      return connection->errorSql(connection, "truncate()", 0, tmp.c_str());
+
+   return success;
+}
+
+
+//***************************************************************************
+// Store
+//***************************************************************************
+
+int cDbTable::store()
+{
+   int found;
+
+   // insert or just update ...
+
+   if (stmtSelect->execute(/*noResult =*/ yes) != success)
+   {
+      connection->errorSql(connection, "store()");
+      return no;
+   }
+
+   found = stmtSelect->getAffected() == 1;
+   stmtSelect->freeResult();
+   
+   if (found)
+      return update();
+   else
+      return insert();
+}
+
+//***************************************************************************
+// Insert
+//***************************************************************************
+
+int cDbTable::insert()
+{
+   if (!stmtInsert)
+   {
+      tell(0, "Fatal missing insert statement\n");
+      return fail;
+   }
+
+   for (int i = 0; getField(i)->name; i++)
+   {
+      if (strcmp(getField(i)->name, "updsp") == 0 || strcmp(getField(i)->name, "inssp") == 0)
+         setValue(getField(i)->index, time(0));
+   }
+
+#ifdef DEB_HANDLER
+
+   if (strcmp(TableName(), "events") == 0)
+      tell(1, "inserting vdr event %d for '%s', starttime = %ld, updflg = '%s'", 
+           getIntValue(0), getStrValue(1), getIntValue(15), getStrValue(6));
+#endif
+
+   if (stmtInsert->execute())
+      return fail;
+
+   return stmtInsert->getAffected() == 1 ? success : fail;
+}
+
+//***************************************************************************
+// Update
+//***************************************************************************
+
+int cDbTable::update()
+{
+   if (!stmtUpdate)
+   {
+      tell(0, "Fatal missing update statement\n");
+      return fail;
+   }
+
+   for (int i = 0; getField(i)->name; i++)
+   {
+      if (strcmp(getField(i)->name, "updsp") == 0)
+      {
+         setValue(getField(i)->index, time(0));
+         break;
+      }
+   }
+
+#ifdef DEB_HANDLER
+   if (strcmp(TableName(), "events") == 0)
+      tell(1, "updating vdr event %d for '%s', starttime = %ld, updflg = '%s'", 
+           getIntValue(0), getStrValue(1), getIntValue(15), getStrValue(6));
+#endif
+
+   if (stmtUpdate->execute())
+      return fail;
+
+   return stmtUpdate->getAffected() == 1 ? success : fail;
+}
+
+//***************************************************************************
+// Find
+//***************************************************************************
+
+int cDbTable::find()
+{
+   if (!stmtSelect)
+      return no;
+
+   if (stmtSelect->execute() != success)
+   {
+      connection->errorSql(connection, "find()");
+      return no;
+   }
+
+   return stmtSelect->getAffected() == 1 ? yes : no;
+}
+
+//***************************************************************************
+// Find via Statement
+//***************************************************************************
+
+int cDbTable::find(cDbStatement* stmt)
+{
+   if (!stmt)
+      return no;
+
+   if (stmt->execute() != success)
+   {
+      connection->errorSql(connection, "find(stmt)");
+      return no;
+   }
+
+   return stmt->getAffected() > 0 ? yes : no;
+}
+
+//***************************************************************************
+// Fetch
+//***************************************************************************
+
+int cDbTable::fetch(cDbStatement* stmt)
+{
+   if (!stmt)
+      return no;
+
+   return stmt->fetch();
+}
+
+//***************************************************************************
+// Reset Fetch
+//***************************************************************************
+
+void cDbTable::reset(cDbStatement* stmt)
+{
+   if (stmt)
+      stmt->freeResult();
+}
diff --git a/lib/db.h b/lib/db.h
new file mode 100644
index 0000000..5f44c14
--- /dev/null
+++ b/lib/db.h
@@ -0,0 +1,1068 @@
+/*
+ * db.h
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __DB_H
+#define __DB_H
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <mysql/mysql.h>
+
+#include <list>
+#include <sstream>
+
+#include "common.h"
+
+class cDbTable;
+class cDbConnection;
+
+using namespace std;
+
+//***************************************************************************
+// cDbService
+//***************************************************************************
+
+class cDbService
+{
+   public:
+
+      enum Misc
+      {
+         maxIndexFields = 20
+      };
+
+      enum FieldFormat
+      {
+         ffUnknown = na,
+
+         ffInt,
+         ffUInt,
+         ffAscii,      // -> VARCHAR
+         ffText,
+         ffMlob,       // -> MEDIUMBLOB
+         ffFloat,
+         ffDateTime,
+         ffCount
+      };
+
+      enum FieldType
+      {
+         ftUnknown    = na,
+
+         ftData       = 1,
+         ftPrimary    = 2,
+         ftMeta       = 4,
+         ftCalc       = 8,
+         ftAutoinc    = 16,
+         ftDef0       = 32
+      };
+
+      struct FieldDef
+      {
+         const char* name;
+         FieldFormat format;
+         int size;
+         int index;
+         int type;
+      };
+
+      enum BindType
+      {
+         bndIn  = 0x001,
+         bndOut = 0x002,
+         bndSet = 0x004
+      };
+
+      enum ProcType
+      {
+         ptProcedure, 
+         ptFunction
+      };
+
+      struct IndexDef
+      {
+         const char* name;
+         int fields[maxIndexFields+1];
+         int order;                     // not implemented yet
+      };
+
+      static const char* toString(FieldFormat t);
+      static FieldFormat toDictFormat(const char* format);
+      static const char* formats[];
+      static const char* dictFormats[];
+
+      static FieldType toType(const char* type);      
+      static const char* types[];
+};
+
+typedef cDbService cDBS;
+
+//***************************************************************************
+// cDbValue
+//***************************************************************************
+
+class cDbValue : public cDbService
+{
+   public:
+
+      cDbValue(FieldDef* f = 0)
+      {
+         field = 0;
+         strValue = 0;
+         ownField = 0;
+
+         if (f) setField(f);
+      }
+
+      cDbValue(const char* name, FieldFormat format, int size)
+      {
+         strValue = 0;
+
+         ownField = new FieldDef;
+         ownField->name = strdup(name);
+         ownField->format = format;
+         ownField->size = size;
+         ownField->type = ftData;
+
+         field = ownField;
+
+         clear();
+      }
+
+      virtual ~cDbValue()
+      {
+         free();
+      }
+
+      void free()
+      {
+         clear();
+         ::free(strValue);
+         strValue = 0;
+
+         if (ownField)
+         {
+            ::free((char*)ownField->name);  // b�ser cast ;)
+            delete ownField;
+            ownField = 0;
+         }
+
+         field = 0;
+      }
+
+      void clear()
+      {
+         if (strValue)
+            *strValue = 0;
+
+         strValueSize = 0;
+         numValue = 0;
+         floatValue = 0;
+         memset(&timeValue, 0, sizeof(timeValue));
+
+         nullValue = 1;
+         initialized = no;
+      }
+
+      virtual void setField(FieldDef* f)
+      { 
+         free();
+         field = f;
+
+         if (field)
+            strValue = (char*)calloc(field->size+TB, sizeof(char));
+      }
+
+      virtual FieldDef* getField()    { return field; }
+      virtual const char* getName()   { return field->name; }
+
+      void setValue(const char* value, int size = 0)
+      { 
+         clear();
+
+         if (field->format != ffAscii && field->format != ffText && field->format != ffMlob)
+         {
+            tell(0, "Setting invalid field format for '%s', expected ASCII or MLOB", field->name);
+            return;
+         }
+
+         if (field->format == ffMlob && !size)
+         {
+            tell(0, "Missing size for MLOB field '%s'", field->name);
+            return;
+         }
+
+         if (value && size)
+         {
+            if (size > field->size)
+            {
+               tell(0, "Warning, size of %d for '%s' exeeded, got %d bytes!",
+                    field->size, field->name, size);
+
+               size = field->size;
+            }
+
+            memcpy(strValue, value, size);
+            strValue[size] = 0;
+            strValueSize = size;
+            nullValue = 0;
+         }
+
+         else if (value)
+         {
+            if (strlen(value) > (size_t)field->size)
+               tell(0, "Warning, size of %d for '%s' exeeded [%s]", 
+                    field->size, field->name, value);
+
+            sprintf(strValue, "%.*s", field->size, value);
+            strValueSize = strlen(strValue);
+            nullValue = 0;
+         }
+      }
+
+      void setCharValue(char value)
+      {
+         char tmp[2];
+         tmp[0] = value;
+         tmp[1] = 0;
+         
+         setValue(tmp);
+      }
+
+      void setValue(int value)
+      {
+         setValue((long)value);
+      }
+
+      void setValue(long value)
+      { 
+         if (field->format == ffInt || field->format == ffUInt)
+         {
+            numValue = value;
+            nullValue = 0;
+         }
+         else if (field->format == ffDateTime)
+         {
+            struct tm tm;
+            time_t v = value;
+
+            memset(&tm, 0, sizeof(tm));
+            localtime_r(&v, &tm);
+            
+            timeValue.year = tm.tm_year + 1900;
+            timeValue.month = tm.tm_mon + 1;
+            timeValue.day = tm.tm_mday;
+            
+            timeValue.hour = tm.tm_hour;
+            timeValue.minute = tm.tm_min;
+            timeValue.second = tm.tm_sec;
+
+            nullValue = 0;
+         }
+         else
+         {
+            tell(0, "Setting invalid field format for '%s'", field->name);
+         }
+      }
+
+      void setValue(double value)
+      { 
+         if (field->format == ffInt || field->format == ffUInt)
+         {
+            numValue = value;
+            nullValue = 0;
+         }
+         else if (field->format == ffFloat)
+         {
+            floatValue = value;
+            nullValue = 0;
+         }
+         else
+         {
+            tell(0, "Setting invalid field format for '%s'", field->name);
+         }
+      }
+
+      int hasValue(long value)
+      {
+         if (field->format == ffInt || field->format == ffUInt)
+            return numValue == value;
+
+         if (field->format == ffDateTime)
+            return no; // to be implemented!
+
+         tell(0, "Setting invalid field format for '%s'", field->name);
+
+         return no;
+      }
+
+      int hasValue(double value)
+      {
+         if (field->format == ffInt || field->format == ffUInt)
+            return numValue == value;
+
+         if (field->format == ffFloat)
+            return floatValue == value;
+
+         tell(0, "Setting invalid field format for '%s'", field->name);
+
+         return no;
+      }
+
+      int hasValue(const char* value)
+      { 
+         if (!value)
+            value = "";
+
+         if (field->format != ffAscii && field->format != ffText)
+         {
+            tell(0, "Checking invalid field format for '%s', expected ASCII or MLOB", field->name);
+            return no;
+         }
+
+         return strcmp(getStrValue(), value) == 0;
+      }
+
+      time_t getTimeValue()
+      {
+         struct tm tm;
+         memset(&tm, 0, sizeof(tm));
+
+         tm.tm_isdst = -1;               // force DST auto detect
+         tm.tm_year = timeValue.year - 1900;
+         tm.tm_mon  = timeValue.month - 1;
+         tm.tm_mday  = timeValue.day;
+
+         tm.tm_hour = timeValue.hour;
+         tm.tm_min = timeValue.minute;
+         tm.tm_sec = timeValue.second;
+         
+         return mktime(&tm);
+      }
+
+      unsigned long* getStrValueSizeRef()  { return &strValueSize; }
+      unsigned long getStrValueSize()      { return strValueSize; }
+      const char* getStrValue()            { return !isNull() && strValue ? strValue : ""; }
+      long getIntValue()                   { return !isNull() ? numValue : 0; }
+      float getFloatValue()                { return !isNull() ? floatValue : 0; }
+      int isNull()                         { return nullValue; }
+
+      char* getStrValueRef()               { return strValue; }
+      long* getIntValueRef()               { return &numValue; }
+      MYSQL_TIME* getTimeValueRef()        { return &timeValue; }
+      float* getFloatValueRef()            { return &floatValue; }
+      my_bool* getNullRef()                { return &nullValue; }
+
+   private:
+
+      FieldDef* ownField;
+      FieldDef* field;
+      long numValue;
+      float floatValue;
+      MYSQL_TIME timeValue;
+      char* strValue;
+      unsigned long strValueSize;
+      my_bool nullValue;
+      int initialized;
+};
+
+//***************************************************************************
+// cDbStatement
+//***************************************************************************
+
+class cDbStatement : public cDbService
+{
+   public:
+
+      cDbStatement(cDbTable* aTable);
+      cDbStatement(cDbConnection* aConnection, const char* stmt = "");
+      virtual ~cDbStatement();
+      
+      int execute(int noResult = no);
+      int find();
+      int fetch();
+      int freeResult();
+
+      // interface
+
+      int build(const char* format, ...);
+
+      void setBindPrefix(const char* p) { bindPrefix = p; }
+      void clrBindPrefix()              { bindPrefix = 0; }
+      int bind(cDbValue* value, int mode, const char* delim = 0);
+      int bind(cDbTable* aTable, int field, int mode, const char* delim);
+      int bind(int field, int mode, const char* delim = 0);
+
+      int bindCmp(const char* table, cDbValue* value,
+                  const char* comp, const char* delim = 0);
+      int bindCmp(const char* table, int field, cDbValue* value, 
+                  const char* comp, const char* delim = 0);
+
+      // ..
+
+      int prepare();
+      int getAffected()    { return affected; }
+      int getResultCount();
+      const char* asText() { return stmtTxt.c_str(); }
+
+      void showStat();
+
+      // data
+
+      static int explain;          // debug explain
+
+   private:
+
+      void clear();
+      int appendBinding(cDbValue* value, BindType bt);
+
+      string stmtTxt;
+      MYSQL_STMT* stmt;
+      int affected;
+      cDbConnection* connection;
+      cDbTable* table;
+      int inCount;
+      MYSQL_BIND* inBind;         // to db
+      int outCount;
+      MYSQL_BIND* outBind;        // from db (result)
+      MYSQL_RES* metaResult;
+      const char* bindPrefix;
+      int firstExec;               // debug explain
+
+      unsigned long callsPeriod;
+      unsigned long callsTotal;
+      double duration;
+};
+
+//***************************************************************************
+// cDbStatements
+//***************************************************************************
+
+class cDbStatements
+{
+   public:
+
+      cDbStatements()  { statisticPeriod = time(0); }
+      ~cDbStatements() {};
+      
+      void append(cDbStatement* s)  { statements.push_back(s); }
+      void remove(cDbStatement* s)  { statements.remove(s); }
+
+      void showStat(const char* name)
+      {
+         tell(0, "Statement statistic of last %ld seconds from '%s':", time(0) - statisticPeriod, name);
+
+         for (std::list<cDbStatement*>::iterator it = statements.begin() ; it != statements.end(); ++it)
+         {
+            if (*it)
+               (*it)->showStat();
+         }
+
+         statisticPeriod = time(0);
+      }
+
+   private:
+
+      time_t statisticPeriod;
+      std::list<cDbStatement*> statements;
+};
+
+//***************************************************************************
+// Class Database Row
+//***************************************************************************
+
+class cDbRow : public cDbService
+{
+   public:
+
+      cDbRow(FieldDef* f)
+      { 
+         count = 0; 
+         fieldDef = 0;
+         useFields(f);
+
+         dbValues = new cDbValue[count];
+
+         for (int f = 0; f < count; f++)
+            dbValues[f].setField(getField(f));
+      }
+
+      virtual ~cDbRow() { delete[] dbValues; }
+
+      void clear()
+      {
+         for (int f = 0; f < count; f++)
+            dbValues[f].clear();
+      }
+
+      virtual FieldDef* getField(int f)            { return f < 0 ? 0 : fieldDef+f; }
+      virtual int fieldCount()                     { return count; }
+
+      void setValue(int f, const char* value, 
+                    int size = 0)                  { dbValues[f].setValue(value, size); }
+      void setValue(int f, int value)              { dbValues[f].setValue(value); }
+      void setValue(int f, long value)             { dbValues[f].setValue(value); }
+      void setValue(int f, double value)           { dbValues[f].setValue(value); }
+      void setCharValue(int f, char value)         { dbValues[f].setCharValue(value); }
+
+      int hasValue(int f, const char* value) const { return dbValues[f].hasValue(value); }
+      int hasValue(int f, long value)        const { return dbValues[f].hasValue(value); }
+      int hasValue(int f, double value)      const { return dbValues[f].hasValue(value); }
+
+      cDbValue* getValue(int f)                    { return &dbValues[f]; }
+
+      const char* getStrValue(int f)         const { return dbValues[f].getStrValue(); }
+      long getIntValue(int f)                const { return dbValues[f].getIntValue(); }
+      float getFloatValue(int f)             const { return dbValues[f].getFloatValue(); }
+      int isNull(int f)                      const { return dbValues[f].isNull(); }
+
+   protected:
+
+      virtual void useFields(FieldDef* f)  { fieldDef = f; for (count = 0; (fieldDef+count)->name; count++); } 
+
+      int count;           // field count
+      FieldDef* fieldDef;
+      cDbValue* dbValues;
+};
+
+//***************************************************************************
+// Connection
+//***************************************************************************
+
+class cDbConnection
+{
+   public:
+
+      cDbConnection()
+      {
+         mysql = 0;
+         attached = 0;
+         inTact = no;
+         connectDropped = yes;
+      }
+
+      virtual ~cDbConnection()
+      {
+         if (mysql) 
+         {
+            mysql_close(mysql);
+            mysql_thread_end();
+         }
+      }
+
+      int attachConnection()
+      { 
+         static int first = yes;
+
+         if (!mysql)
+         {
+            connectDropped = yes;
+
+            if (!(mysql = mysql_init(0)))
+               return errorSql(this, "attachConnection(init)");
+
+            if (!mysql_real_connect(mysql, dbHost, 
+                                    dbUser, dbPass, dbName, dbPort, 0, 0)) 
+            {
+               mysql_close(mysql);
+               mysql = 0;
+               tell(0, "Error, connecting to database at '%s' on port (%d) failed",
+                    dbHost, dbPort);
+
+               return fail;
+            }
+
+            connectDropped = no;
+
+            // init encoding 
+            
+            if (encoding && *encoding)
+            {
+               if (mysql_set_character_set(mysql, encoding))
+                  errorSql(this, "init(character_set)");
+
+               if (first)
+               {
+                  tell(0, "SQL client character now '%s'", mysql_character_set_name(mysql));
+                  first = no;
+               }
+            }
+         }
+
+         attached++; 
+
+         return success; 
+      }
+
+      void detachConnection()
+      { 
+         attached--;
+
+         if (!attached)
+         {
+            mysql_close(mysql);
+            mysql_thread_end();
+            mysql = 0;
+         }
+      }
+
+      int isConnected() { return getMySql() > 0; }
+
+      int check()
+      {
+         if (!isConnected())
+            return fail;
+
+         query("SELECT SYSDATE();");
+         queryReset();
+
+         return isConnected() ? success : fail;
+      }
+
+      virtual int query(const char* format, ...)
+      {
+         int status = 1;
+         MYSQL* h = getMySql();
+
+         if (h && format)
+         {
+            char* stmt;
+            
+            va_list more;
+            va_start(more, format);
+            vasprintf(&stmt, format, more);
+            
+            if ((status = mysql_query(h, stmt)))
+               errorSql(this, stmt);
+
+            free(stmt);
+         }
+
+         return status ? fail : success;
+      }
+
+      virtual void queryReset()
+      {
+         if (getMySql())
+         {
+            MYSQL_RES* result = mysql_use_result(getMySql());
+            mysql_free_result(result);
+         }
+      }
+
+      virtual int executeSqlFile(const char* file)
+      {
+         FILE* f;
+         int res;
+         char* buffer;
+         int size = 1000;
+         int nread = 0;
+
+         if (!getMySql())
+            return fail;
+
+         if (!(f = fopen(file, "r")))
+         {
+            tell(0, "Fatal: Can't access '%s'; %s", file, strerror(errno));
+            return fail;
+         }
+         
+         buffer = (char*)malloc(size+1);
+   
+         while (res = fread(buffer+nread, 1, 1000, f))
+         {
+            nread += res;
+            size += 1000;
+            buffer = srealloc(buffer, size+1);
+         }
+         
+         fclose(f);
+         buffer[nread] = 0;
+         
+         // execute statement
+         
+         tell(2, "Executing '%s'", buffer);
+         
+         if (query("%s", buffer))
+         {
+            free(buffer);
+            return errorSql(this, "executeSqlFile()");
+         }
+
+         free(buffer);
+
+         return success;
+      }
+
+      virtual int startTransaction() 
+      { 
+         inTact = yes;
+         return query("START TRANSACTION"); 
+      }
+
+      virtual int commit()
+      { 
+         inTact = no;
+         return query("COMMIT");
+      }
+
+      virtual int rollback() 
+      { 
+         inTact = no;
+         return query("ROLLBACK"); 
+      }
+
+      virtual int inTransaction() { return inTact; }
+
+      MYSQL* getMySql()
+      {
+         if (connectDropped && mysql)
+         {
+            mysql_close(mysql);
+            mysql_thread_end();
+            mysql = 0;
+            attached = 0;
+         }
+
+         return mysql; 
+      }
+
+      int getAttachedCount()                         { return attached; }
+
+      // --------------
+      // static stuff 
+
+      // set/get connecting data
+
+      static void setHost(const char* s)             { free(dbHost); dbHost = strdup(s); }
+      static const char* getHost()                   { return dbHost; }
+      static void setName(const char* s)             { free(dbName); dbName = strdup(s); }
+      static const char* getName()                   { return dbName; }
+      static void setUser(const char* s)             { free(dbUser); dbUser = strdup(s); }
+      static const char* getUser()                   { return dbUser; }
+      static void setPass(const char* s)             { free(dbPass); dbPass = strdup(s); }
+      static const char* getPass()                   { return dbPass; }
+      static void setPort(int port)                  { dbPort = port; }
+      static int getPort()                           { return dbPort; }
+      static void setEncoding(const char* enc)       { free(encoding); encoding = strdup(enc); }
+      static const char* getEncoding()               { return encoding; }
+
+      int errorSql(cDbConnection* mysql, const char* prefix, MYSQL_STMT* stmt = 0, const char* stmtTxt = 0);
+
+      void showStat(const char* name = "")   { statements.showStat(name); }
+
+      static int init()
+      {
+         if (mysql_library_init(0, 0, 0)) 
+         {
+            tell(0, "Error: mysql_library_init failed");
+            return fail;  // return errorSql(0, "init(library_init)");
+         }
+
+         return success;
+      }
+
+      static int exit()
+      {
+         mysql_library_end();
+         free(dbHost);
+         free(dbUser);
+         free(dbPass);
+         free(dbName);
+         free(encoding);
+
+         return done;
+      }
+
+      MYSQL* mysql;
+
+      cDbStatements statements;         // all statements of this connection
+
+   private:
+
+      int initialized;
+      int attached;
+      int inTact;
+      int connectDropped;
+
+      static char* encoding;
+
+      // connecting data
+
+      static char* dbHost;
+      static int dbPort;
+      static char* dbName;              // database name
+      static char* dbUser;
+      static char* dbPass;
+};
+
+//***************************************************************************
+// cDbTable
+//***************************************************************************
+
+class cDbTable : public cDbService
+{
+   public:
+
+      cDbTable(cDbConnection* aConnection, FieldDef* f, IndexDef* i = 0);
+      virtual ~cDbTable();
+
+      virtual const char* TableName() = 0;
+
+      virtual int open();
+      virtual int close();
+
+      virtual int find();
+      virtual void reset() { reset(stmtSelect); }
+
+      virtual int find(cDbStatement* stmt);
+      virtual int fetch(cDbStatement* stmt);
+      virtual void reset(cDbStatement* stmt);
+
+      virtual int insert();
+      virtual int update();
+      virtual int store();
+
+      virtual int deleteWhere(const char* where);
+      virtual int countWhere(const char* where, int& count, const char* what = 0);
+      virtual int truncate();
+
+      // interface to cDbRow
+
+      void clear()                                           { row->clear(); }
+      void setValue(int f, const char* value, int size = 0)  { row->setValue(f, value, size); }
+      void setValue(int f, int value)                        { row->setValue(f, value); }
+      void setValue(int f, long value)                       { row->setValue(f, value); }
+      void setValue(int f, double value)                     { row->setValue(f, value); }
+      void setCharValue(int f, char value)                   { row->setCharValue(f, value); }
+
+      int hasValue(int f, const char* value)                 { return row->hasValue(f, value); }
+      int hasValue(int f, long value)                        { return row->hasValue(f, value); }
+      int hasValue(int f, double value)                      { return row->hasValue(f, value); }
+
+      const char* getStrValue(int f)       const             { return row->getStrValue(f); }
+      long getIntValue(int f)              const             { return row->getIntValue(f); }
+      float getFloatValue(int f)           const             { return row->getFloatValue(f); }
+      int isNull(int f)                    const             { return row->isNull(f); }
+
+      FieldDef* getField(int f)                              { return row->getField(f); }
+      cDbValue* getValue(int f)                              { return row->getValue(f); }
+      int fieldCount()                                       { return row->fieldCount(); }
+      cDbRow* getRow()                                       { return row; }
+
+      cDbConnection* getConnection()                         { return connection; }
+      MYSQL* getMySql()                                      { return connection->getMySql(); }
+      int isConnected()                                      { return connection && connection->getMySql(); }
+
+      virtual IndexDef* getIndex(int i)                      { return indices+i; }
+      virtual int exist(const char* name = 0);
+      virtual int createTable();
+
+      // static stuff
+
+      static void setConfPath(const char* cpath)             { free(confPath); confPath = strdup(cpath); }
+
+   protected:
+
+      virtual int init();
+      virtual int createIndices();
+      virtual int checkIndex(const char* idxName, int& fieldCount);
+
+      virtual void copyValues(cDbRow* r);
+
+      // data
+
+      cDbRow* row;
+      int holdInMemory;        // hold table additionally in memory (not implemented yet)
+
+      IndexDef* indices;
+
+      // basic statements
+
+      cDbStatement* stmtSelect;
+      cDbStatement* stmtInsert;
+      cDbStatement* stmtUpdate;
+      cDbConnection* connection;
+
+      // statics
+
+      static char* confPath;
+};
+
+//***************************************************************************
+// cDbView
+//***************************************************************************
+
+class cDbView : public cDbService
+{
+   public:
+
+      cDbView(cDbConnection* c, const char* aName)
+      {
+         connection = c;
+         name = strdup(aName);
+      }
+
+      ~cDbView() { free(name); }
+
+      int exist()
+      {
+         if (connection->getMySql())
+         {
+            MYSQL_RES* result = mysql_list_tables(connection->getMySql(), name);
+            MYSQL_ROW tabRow = mysql_fetch_row(result);
+            mysql_free_result(result);
+         
+            return tabRow ? yes : no;
+         }
+
+         return no;
+      }
+
+      int create(const char* path, const char* sqlFile)
+      {
+         int status;
+         char* file = 0;
+         
+         asprintf(&file, "%s/%s", path, sqlFile);
+         
+         tell(0, "Creating view '%s' using definition in '%s'", 
+              name, file);
+         
+         status = connection->executeSqlFile(file);
+         
+         free(file);
+         
+         return status;
+      }
+
+      int drop()
+      {
+         tell(0, "Drop view '%s'", name);
+
+         return connection->query("drop view %s", name);
+      }
+
+   protected:
+
+      cDbConnection* connection;
+      char* name;
+};
+
+//***************************************************************************
+// cDbProcedure
+//***************************************************************************
+
+class cDbProcedure : public cDbService
+{
+   public:
+
+      cDbProcedure(cDbConnection* c, const char* aName, ProcType pt = ptProcedure)
+      {
+         connection = c;
+         type = pt;
+         name = strdup(aName);
+      }
+
+      ~cDbProcedure() { free(name); }
+
+      const char* getName() { return name; }
+
+      int call(int ll = 1)
+      {
+         if (!connection || !connection->getMySql())
+            return fail;
+
+         cDbStatement stmt(connection);
+
+         tell(ll, "Calling '%s'", name);
+
+         stmt.build("call %s", name);
+
+         if (stmt.prepare() != success || stmt.execute() != success)
+            return fail;
+
+         tell(ll, "'%s' suceeded", name);
+
+         return success;
+      }
+
+      int created()
+      {
+         if (!connection || !connection->getMySql())
+            return fail;
+
+         cDbStatement stmt(connection);
+         
+         stmt.build("show %s status where name = '%s'", 
+                    type == ptProcedure ? "procedure" : "function", name);
+         
+         if (stmt.prepare() != success || stmt.execute() != success)
+         {
+            tell(0, "%s check of '%s' failed",
+                 type == ptProcedure ? "Procedure" : "Function", name);
+            return no;
+         }
+         else
+         {  
+            if (stmt.getResultCount() != 1)
+               return no;
+         }
+
+         return yes;
+      }
+
+      int create(const char* path)
+      {
+         int status;
+         char* file = 0;
+
+         asprintf(&file, "%s/%s.sql", path, name);
+
+         tell(1, "Creating %s '%s'", 
+              type == ptProcedure ? "procedure" : "function", name);
+
+         status = connection->executeSqlFile(file);
+
+         free(file);
+
+         return status;
+      }
+
+      int drop()
+      {
+         tell(1, "Drop %s '%s'", type == ptProcedure ? "procedure" : "function", name);
+
+         return connection->query("drop %s %s", type == ptProcedure ? "procedure" : "function", name);
+      }
+
+      static int existOnFs(const char* path, const char* name)
+      {
+         int state;
+         char* file = 0;
+         
+         asprintf(&file, "%s/%s.sql", path, name);
+         state = fileExists(file);
+         
+         free(file);
+         
+         return state;
+      }
+
+   protected:
+
+      cDbConnection* connection;
+      ProcType type;
+      char* name;
+      
+};
+
+//***************************************************************************
+#endif //__DB_H
diff --git a/lib/dbdict.c b/lib/dbdict.c
new file mode 100644
index 0000000..0b2211e
--- /dev/null
+++ b/lib/dbdict.c
@@ -0,0 +1,158 @@
+/*
+ * dbdict.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "common.h"
+#include "dbdict.h"
+
+//***************************************************************************
+// cDbDict
+//***************************************************************************
+
+cDbDict::cDbDict()
+{
+   inside = no;
+}
+
+cDbDict::~cDbDict()
+{
+}
+
+//***************************************************************************
+// In
+//***************************************************************************
+
+int cDbDict::in(const char* file)
+{
+   FILE* f;
+   char* line = 0;
+   size_t size = 0;
+   char* path;
+
+   asprintf(&path, "%s", file);
+
+   f = fopen(path, "r");
+   
+   while (getline(&line, &size, f) > 0)
+   {
+      char* p = strstr(line, "//");
+
+      if (p) *p = 0;
+      
+      allTrim(line);
+      
+      if (isEmpty(line))
+         continue;
+      
+      if (atLine(line) != success)
+      {
+         tell(0, "Found unexpected definition '%s', aborting", line);
+         free(path);
+         return fail;
+      }
+   }
+
+   fclose(f);   
+   free(line);
+   free(path);
+   
+   return success;
+}
+
+//***************************************************************************
+// At Line
+//***************************************************************************
+
+int cDbDict::atLine(const char* line)
+{
+   const char* p;
+
+   if (p = strcasestr(line, "Table"))
+   {
+      char tableName[100];
+
+      p += strlen("Table");
+      strcpy(tableName, p);
+      tell(0, "Table: '%s'", tableName);
+   }
+
+   else if (strchr(line, '{'))
+      inside = yes;
+
+   else if (strchr(line, '}'))
+      inside = no;
+
+   else if (inside)
+      parseField(line);
+
+   return success;
+}
+
+//***************************************************************************
+// Get Token
+//***************************************************************************
+
+int getToken(const char*& p, char* token, int size)
+{
+   char* dest = token;
+   int num = 0;
+
+   while (*p && *p == ' ')
+      p++;
+
+   while (*p && *p != ' ' && num < size)
+   {
+      if (*p == '"')
+         p++;
+      else
+      {
+         *dest++ = *p++;
+         num++;
+      }
+   }
+
+   *dest = 0;
+
+   return success;
+}
+
+//***************************************************************************
+// Parse Field
+//***************************************************************************
+
+int cDbDict::parseField(const char* line)
+{
+   const int sizeTokenMax = 100;
+   FieldDef f;
+   char token[sizeTokenMax+TB];
+   const char* p = line;
+
+   // tell(0, "Got: '%s'", p);
+
+   for (int i = 0; i < dtCount; i++)
+   {
+      if (getToken(p, token, sizeTokenMax) != success)
+      {
+         tell(0, "Errot: can't parse line [%s]", line);
+         return fail;
+      }
+
+      switch (i)
+      {
+         case dtName:        f.name = strdup(token);         break;
+         case dtDescription: break;
+         case dtFormat:      f.format = toDictFormat(token); break;
+         case dtSize:        f.size = atoi(token);           break;
+         case dtType:        f.type = toType(token);         break;
+      }
+      
+      free((char*)f.name);  // böser cast ...
+
+      tell(0, "token %d -> '%s'", i, token);
+   }
+   
+   return success;
+}
diff --git a/lib/dbdict.h b/lib/dbdict.h
new file mode 100644
index 0000000..100c626
--- /dev/null
+++ b/lib/dbdict.h
@@ -0,0 +1,52 @@
+/*
+ * dbdict.h
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __DBDICT_H
+#define __DBDICT_H
+
+#include "db.h"
+
+//***************************************************************************
+// cDbDict
+//***************************************************************************
+
+class cDbDict : public cDbService
+{
+      
+   public:
+
+      // declarations
+
+      enum DictToken
+      {
+         dtName,
+         dtDescription,
+         dtFormat,
+         dtSize,
+         dtType,
+
+         dtCount
+      };
+
+      cDbDict();
+      virtual ~cDbDict();
+
+      int in(const char* file);
+
+   protected:
+
+      int atLine(const char* line);
+      int parseField(const char* line);
+
+      // data
+
+      int inside;
+      static FieldDef fields[];
+
+};
+
+#endif //  __DBDICT_H
diff --git a/lib/demo.c b/lib/demo.c
new file mode 100644
index 0000000..1d6ee10
--- /dev/null
+++ b/lib/demo.c
@@ -0,0 +1,280 @@
+
+
+#include "config.h"
+#include "common.h"
+
+#include "db.h"
+#include "tabledef.h"
+
+cDbConnection* connection = 0;
+const char* logPrefix = "demo";
+
+//***************************************************************************
+// Init Connection
+//***************************************************************************
+
+void initConnection()
+{
+   cDbConnection::init();
+
+   cDbConnection::setEncoding("utf8");
+   cDbConnection::setHost("localhost");
+
+   cDbConnection::setPort(3306);
+   cDbConnection::setName("epg2vdr");
+   cDbConnection::setUser("epg2vdr");
+   cDbConnection::setPass("epg");
+   cDbTable::setConfPath("/etc/epgd/");
+
+   connection = new cDbConnection();
+}
+
+void exitConnection()
+{
+   cDbConnection::exit();
+   
+   if (connection)
+      delete connection;
+}
+
+//***************************************************************************
+// 
+//***************************************************************************
+
+int demoStatement()
+{
+   int status = success;
+
+   cTableEvents* eventsDb = new cTableEvents(connection);
+
+   tell(0, "------------------- attach table ---------------");
+
+   // open table (attach)
+
+   if (eventsDb->open() != success) 
+      return fail;
+   
+   tell(0, "---------------- prepare select statement -------------");
+
+   // vorbereiten (prepare) eines statement, am besten einmal bei programmstart!
+   // ----------
+   // select eventid, compshorttext, episodepart, episodelang 
+   //   from events 
+   //     where eventid > ?
+
+   cDbStatement* selectByCompTitle = new cDbStatement(eventsDb);
+
+   status += selectByCompTitle->build("select ");
+   status += selectByCompTitle->bind(cTableEvents::fiEventId, cDBS::bndOut);
+   status += selectByCompTitle->bind(cTableEvents::fiChannelId, cDBS::bndOut, ", ");
+   status += selectByCompTitle->bind(cTableEvents::fiTitle, cDBS::bndOut, ", ");
+   status += selectByCompTitle->build(" from %s where ", eventsDb->TableName());
+   status += selectByCompTitle->bindCmp(0, cTableEvents::fiEventId, 0, ">");
+
+   status += selectByCompTitle->prepare();   // prepare statement 
+
+   if (status != success)
+   {
+      // prepare sollte oracle fehler ausgegeben haben!
+
+      delete eventsDb;
+      delete selectByCompTitle; 
+
+      return fail;
+   }
+
+   tell(0, "------------------ prepare done ----------------------");
+
+   tell(0, "------------------ create some rows  ----------------------");
+
+   eventsDb->clear();     // alle values löschen   
+
+   for (int i = 0; i < 10; i++)
+   {
+      char* title;
+      asprintf(&title, "title %d", i);
+
+      eventsDb->setValue(cTableEvents::fiEventId, 800 + i * 100);
+      eventsDb->setValue(cTableEvents::fiChannelId, "xxx-yyyy-zzz");
+      eventsDb->setValue(cTableEvents::fiTitle, title);
+      
+      eventsDb->store();                // store -> select mit anschl. update oder insert je nachdem ob dier PKey bereits vorhanden ist
+      // eventsDb->insert();            // sofern man schon weiß das es ein insert ist
+      // eventsDb->update();            // sofern man schon weiß das der Datensatz vorhanden ist
+
+      free(title);
+   }
+
+   tell(0, "------------------ done  ----------------------");
+
+   tell(0, "-------- select all where eventid > 1000 -------------");
+   
+   eventsDb->clear();     // alle values löschen
+   eventsDb->setValue(cTableEvents::fiEventId, 1000);
+
+   for (int f = selectByCompTitle->find(); f; f = selectByCompTitle->fetch())
+   {
+      tell(0, "id: %ld", eventsDb->getIntValue(cTableEvents::fiEventId));
+      tell(0, "channel: %s", eventsDb->getStrValue(cTableEvents::fiChannelId));
+      tell(0, "titel: %s", eventsDb->getStrValue(cTableEvents::fiTitle));
+   }
+
+   // freigeben der Ergebnissmenge !!
+
+   selectByCompTitle->freeResult();
+
+   // folgendes am programmende
+
+   delete eventsDb;             // implizietes close (detach)
+   delete selectByCompTitle;    // statement freigeben (auch gegen die DB)
+
+   return success;
+}
+
+//***************************************************************************
+// Join
+//***************************************************************************
+
+int joinDemo()
+{
+   int status = success;
+
+   // grundsätzlich genügt hier auch eine Tabelle, für die anderen sind cDbValue Instanzen außreichend
+   // so ist es etwas einfacher die cDbValues zu initialisieren. 
+   // Ich habe statische "virtual FieldDef* getFieldDef(int f)" Methode in der Tabellenklassen geplant
+   // um ohne Instanz der cTable ein Feld einfach initialisieren zu können
+
+   cTableEvents* eventsDb = new cTableEvents(connection);
+   cTableImageRefs* imageRefDb = new cTableImageRefs(connection);
+   cTableImages* imageDb = new cTableImages(connection);
+
+   tell(0, "------------------- attach table ---------------");
+
+   // open table (attach)
+
+   if (eventsDb->open() != success) 
+      return fail;
+
+   if (imageDb->open() != success) 
+      return fail;
+
+   if (imageRefDb->open() != success) 
+      return fail;
+   
+   tell(0, "---------------- prepare select statement -------------");
+
+   // all images
+
+   cDbStatement* selectAllImages = new cDbStatement(imageRefDb);
+
+   // prepare fields
+
+   cDbValue imageUpdSp;
+   cDbValue imageSize;
+   cDbValue masterId;
+
+   cDBS::FieldDef imageSizeDef = { "image", cDBS::ffUInt,  0, 999, cDBS::ftData };  // eine Art ein Feld zu erzeugen
+   imageSize.setField(&imageSizeDef);                                               // eine andere Art ein Feld zu erzeugen ...
+   imageUpdSp.setField(imageDb->getField(cTableImages::fiUpdSp));
+   masterId.setField(eventsDb->getField(cTableEvents::fiMasterId));
+
+   // select e.masterid, r.imagename, r.eventid, r.lfn, length(i.image)
+   //      from imagerefs r, images i, events e 
+   //      where i.imagename = r.imagename 
+   //         and e.eventid = r.eventid
+   //         and (i.updsp > ? or r.updsp > ?)
+
+   selectAllImages->build("select ");
+   selectAllImages->setBindPrefix("e.");
+   selectAllImages->bind(&masterId, cDBS::bndOut);
+   selectAllImages->setBindPrefix("r.");
+   selectAllImages->bind(cTableImageRefs::fiImgName, cDBS::bndOut, ", ");
+   selectAllImages->bind(cTableImageRefs::fiEventId, cDBS::bndOut, ", ");
+   selectAllImages->bind(cTableImageRefs::fiLfn, cDBS::bndOut, ", ");
+   selectAllImages->setBindPrefix("i.");
+   selectAllImages->build(", length(");
+   selectAllImages->bind(&imageSize, cDBS::bndOut);
+   selectAllImages->build(")");
+   selectAllImages->clrBindPrefix();
+   selectAllImages->build(" from %s r, %s i, %s e where ", 
+                          imageRefDb->TableName(), imageDb->TableName(), eventsDb->TableName());
+   selectAllImages->build("e.%s = r.%s and i.%s = r.%s and (",
+                          eventsDb->getField(cTableEvents::fiEventId)->name, 
+                          imageRefDb->getField(cTableImageRefs::fiEventId)->name,
+                          imageDb->getField(cTableImages::fiImgName)->name,
+                          imageRefDb->getField(cTableImageRefs::fiImgName)->name);
+   selectAllImages->bindCmp("i", &imageUpdSp, ">");
+   selectAllImages->build(" or ");
+   selectAllImages->bindCmp("r", cTableImageRefs::fiUpdSp, 0, ">");
+   selectAllImages->build(")");
+
+   status += selectAllImages->prepare();
+
+   if (status != success)
+   {
+      // prepare sollte oracle fehler ausgegeben haben!
+
+      delete eventsDb;
+      delete imageDb;
+      delete imageRefDb;
+      delete selectAllImages; 
+
+      return fail;
+   }
+
+   tell(0, "------------------ prepare done ----------------------");
+
+   tell(0, "------------------ select ----------------------");
+
+   time_t since = time(0) - 60 * 60;
+   imageRefDb->clear();
+   imageRefDb->setValue(cTableImageRefs::fiUpdSp, since);
+   imageUpdSp.setValue(since);
+
+   for (int res = selectAllImages->find(); res; res = selectAllImages->fetch())
+   {
+      // so kommst du an die Werte der unterschgiedlichen Tabellen
+
+      // int eventid = masterId.getIntValue();
+      // const char* imageName = imageRefDb->getStrValue(cTableImageRefs::fiImgName);
+      // int lfn = imageRefDb->getIntValue(cTableImageRefs::fiLfn);
+      // int size = imageSize.getIntValue();
+
+      
+   }
+
+   // freigeben der Ergebnissmenge !!
+
+   selectAllImages->freeResult();
+
+   // folgendes am programmende
+
+   delete eventsDb;             // implizietes close (detach)
+   delete imageDb;
+   delete imageRefDb;
+   delete selectAllImages;      // statement freigeben (auch gegen die DB)
+
+   return success;
+}
+
+
+//***************************************************************************
+// Main
+//***************************************************************************
+
+int main()
+{
+   EPG2VDRConfig.logstdout = yes;
+   EPG2VDRConfig.loglevel = 2;
+
+   initConnection();
+
+   // demoStatement();
+   // joinDemo();
+
+   tell(0, "uuid: '%s'", getUniqueId());
+
+   exitConnection();
+
+   return 0;
+}
diff --git a/lib/imgtools.c b/lib/imgtools.c
new file mode 100644
index 0000000..f7e6200
--- /dev/null
+++ b/lib/imgtools.c
@@ -0,0 +1,189 @@
+/*
+ * imgtools.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "imgtools.h"
+
+//***************************************************************************
+// Image converting stuff
+//***************************************************************************
+
+int fromJpeg(Imlib_Image& image, unsigned char* buffer, int size)
+{
+   struct jpeg_decompress_struct cinfo;
+   struct jpeg_error_mgr jerr;
+   int w, h;
+   DATA8 *ptr, *line[16], *data;
+   DATA32 *ptr2, *dest;
+   int x, y;
+
+   cinfo.err = jpeg_std_error(&jerr);
+   
+   jpeg_create_decompress(&cinfo);
+   jpeg_mem_src(&cinfo, buffer, size);
+   jpeg_read_header(&cinfo, TRUE);
+   cinfo.do_fancy_upsampling = FALSE;
+   cinfo.do_block_smoothing = FALSE;
+   
+   jpeg_start_decompress(&cinfo);
+
+   w = cinfo.output_width;
+   h = cinfo.output_height;
+
+   image = imlib_create_image(w, h);
+   imlib_context_set_image(image);
+
+   dest = ptr2 = imlib_image_get_data();
+   data = (DATA8*)malloc(w * 16 * cinfo.output_components);
+
+   for (int i = 0; i < cinfo.rec_outbuf_height; i++)
+      line[i] = data + (i * w * cinfo.output_components);
+   
+   for (int l = 0; l < h; l += cinfo.rec_outbuf_height)
+   {
+      jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height);
+      int scans = cinfo.rec_outbuf_height;
+      
+      if (h - l < scans)
+         scans = h - l;
+      
+      ptr = data;
+      
+      for (y = 0; y < scans; y++)
+      {
+         for (x = 0; x < w; x++)
+         {
+            *ptr2 = (0xff000000) | ((ptr[0]) << 16) | ((ptr[1]) << 8) | (ptr[2]);
+            ptr += cinfo.output_components;
+            ptr2++;
+         }
+      }
+   }
+
+   free(data);
+
+   imlib_image_put_back_data(dest);
+
+   jpeg_finish_decompress(&cinfo);
+   jpeg_destroy_decompress(&cinfo);
+
+   return success;
+}
+
+long toJpeg(Imlib_Image image, MemoryStruct* data, int quality)
+{
+   struct jpeg_compress_struct cinfo = { 0 };
+   struct jpeg_error_mgr jerr;
+   DATA32* ptr;
+   DATA8* buf;
+
+   imlib_context_set_image(image);
+
+   data->clear();
+   
+   cinfo.err = jpeg_std_error(&jerr);
+
+   jpeg_create_compress(&cinfo);
+   jpeg_mem_dest(&cinfo, (unsigned char**)(&data->memory), &data->size);
+
+   cinfo.image_width = imlib_image_get_width();
+   cinfo.image_height = imlib_image_get_height();
+   cinfo.input_components = 3;
+   cinfo.in_color_space = JCS_RGB;
+
+   jpeg_set_defaults(&cinfo);
+   jpeg_set_quality(&cinfo, quality, TRUE);
+   jpeg_start_compress(&cinfo, TRUE);
+
+   // get data pointer
+
+   if (!(ptr = imlib_image_get_data_for_reading_only()))
+      return 0;
+   
+   // allocate a small buffer to convert image data */
+
+   buf = (DATA8*)malloc(imlib_image_get_width() * 3 * sizeof(DATA8));
+
+   while (cinfo.next_scanline < cinfo.image_height) 
+   {
+      // convert scanline from ARGB to RGB packed
+
+      for (int j = 0, i = 0; i < imlib_image_get_width(); i++)
+      {
+         buf[j++] = ((*ptr) >> 16) & 0xff;
+         buf[j++] = ((*ptr) >>  8) & 0xff;
+         buf[j++] = ((*ptr))       & 0xff;
+
+         ptr++;
+      }
+
+      // write scanline
+
+      jpeg_write_scanlines(&cinfo, (JSAMPROW*)(&buf), 1);
+   }
+   
+   free(buf);
+   jpeg_finish_compress(&cinfo);
+   jpeg_destroy_compress(&cinfo);
+   
+   return data->size;
+}
+
+int scaleImageToJpegBuffer(Imlib_Image image, MemoryStruct* data, int width, int height)
+{
+   if (width && height)
+   {
+      Imlib_Image scaledImage;
+
+      imlib_context_set_image(image);
+
+      int imgWidth = imlib_image_get_width();
+      int imgHeight = imlib_image_get_height();
+      double ratio = (double)imgWidth / (double)imgHeight;
+      
+      if ((double)width/(double)imgWidth < (double)height/(double)imgHeight)
+         height = (int)((double)width / ratio);
+      else
+         width = (int)((double)height * ratio);
+
+      scaledImage = imlib_create_image(width, height);
+      imlib_context_set_image(scaledImage);
+
+      imlib_context_set_color(240, 240, 240, 255);
+      imlib_image_fill_rectangle(0, 0, width, height);
+
+      imlib_blend_image_onto_image(image, 0, 0, 0, 
+                                   imgWidth, imgHeight, 0, 0, 
+                                   width, height);
+      
+      toJpeg(scaledImage, data, 70);
+
+      imlib_context_set_image(scaledImage);
+      imlib_free_image();
+
+      tell(1, "Scaled image to %d/%d, now %d bytes", width, height, (int)data->size);
+   }
+   else
+   {
+      toJpeg(image, data, 70);
+   }
+
+   return success;
+}
+
+int scaleJpegBuffer(MemoryStruct* data, int width, int height)
+{
+   Imlib_Image image;
+
+   fromJpeg(image, (unsigned char*)data->memory, data->size);
+
+   scaleImageToJpegBuffer(image, data, width, height);
+   
+   imlib_context_set_image(image);
+   imlib_free_image();
+
+   return success;
+}
diff --git a/lib/imgtools.h b/lib/imgtools.h
new file mode 100644
index 0000000..e8b97af
--- /dev/null
+++ b/lib/imgtools.h
@@ -0,0 +1,30 @@
+/*
+ * imgtools.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __IMGTOOLS_H
+#define __IMGTOOLS_H
+
+#include <stdio.h>
+
+#define X_DISPLAY_MISSING 1
+
+#include <jpeglib.h>
+#include <Imlib2.h>
+
+#include "common.h"
+
+//***************************************************************************
+// Image Manipulating
+//***************************************************************************
+
+int fromJpeg(Imlib_Image& image, unsigned char* buffer, int size);
+long toJpeg(Imlib_Image image, MemoryStruct* data, int quality);
+int scaleImageToJpegBuffer(Imlib_Image image, MemoryStruct* data, int width = 0, int height = 0);
+int scaleJpegBuffer(MemoryStruct* data, int width = 0, int height = 0);
+
+//***************************************************************************
+#endif // __IMGTOOLS_H
diff --git a/lib/tabledef.c b/lib/tabledef.c
new file mode 100644
index 0000000..d9826f5
--- /dev/null
+++ b/lib/tabledef.c
@@ -0,0 +1,926 @@
+/*
+ * tabledef.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "tabledef.h"
+
+//***************************************************************************
+// cEpgdState
+//***************************************************************************
+
+const char* cEpgdState::states[] =
+{
+   "init",
+   "standby",
+   "stopped",
+
+   "busy (events)",
+   "busy (match)",
+   "busy (scraping)",
+   "busy (images)",
+
+   0
+};
+
+const char* cEpgdState::toName(cEpgdState::State s)
+{
+   if (!isValid(s))
+      return "unknown";
+
+   return states[s];
+}
+
+cEpgdState::State cEpgdState::toState(const char* name)
+{
+   for (int i = 0; i < esCount; i++)
+      if (strcmp(states[i], name) == 0)
+         return (State)i;
+
+   return esUnknown;
+}
+
+//***************************************************************************
+// Event Fields
+//***************************************************************************
+//***************************************************************************
+// Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableEvents::fields[] =
+{
+   // name               format     size  index               type
+
+   // primary key
+
+   { "eventid",          ffUInt,       0, fiEventId,          ftPrimary },
+   { "channelid",        ffAscii,     50, fiChannelId,        ftPrimary },
+
+   { "masterid",         ffUInt,       0, fiMasterId,         ftAutoinc },
+   { "useid",            ffUInt,       0, fiUseId,            ftData },
+
+   // meta                                                         
+
+   { "source",           ffAscii,     10, fiSource,           ftMeta },
+   { "fileref",          ffAscii,     50, fiFileRef,          ftMeta },
+   { "inssp",            ffInt,       10, fiInsSp,            ftMeta },
+   { "updsp",            ffInt,       10, fiUpdSp,            ftMeta },
+   { "updflg",           ffAscii,      1, fiUpdFlg,           ftMeta },
+   { "delflg",           ffAscii,      1, fiDelFlg,           ftMeta },
+                                                                   
+   // vdr event data                                               
+                                                                   
+   { "tableid",          ffInt,        2, fiTableId,          ftData },
+   { "version",          ffInt,        3, fiVersion,          ftData },
+   { "title",            ffAscii,    200, fiTitle,            ftData },
+   { "comptitle",        ffAscii,    200, fiCompTitle,        ftData },
+   { "shorttext",        ffAscii,    300, fiShortText,        ftData },
+   { "compshorttext",    ffAscii,    300, fiCompShortText,    ftData },
+   { "longdescription",  ffText,   25000, fiLongDescription,  ftData },
+   { "starttime",        ffInt,       10, fiStartTime,        ftData },
+   { "duration",         ffInt,        5, fiDuration,         ftData },
+   { "parentalrating",   ffInt,        2, fiParentalRating,   ftData },
+   { "vps",              ffInt,       10, fiVps,              ftData },
+
+   { "description",      ffText,   50000, fiDescription,      ftCalc },
+                                                                   
+   // additional external data
+                                                                   
+   { "shortdescription", ffAscii,   3000, fiShortDescription, ftData },
+   { "actor",            ffAscii,   3000, fiActor,            ftData },
+   { "audio",            ffAscii,     50, fiAudio,            ftData },
+   { "category",         ffAscii,     50, fiCategory,         ftData },
+   { "country",          ffAscii,     50, fiCountry,          ftData },
+   { "director",         ffAscii,    250, fiDirector,         ftData },
+   { "flags",            ffAscii,    100, fiFlags,            ftData },
+   { "genre",            ffAscii,    100, fiGenre,            ftData },
+   { "info",             ffText,   10000, fiInfo,             ftData },
+   { "music",            ffAscii,    250, fiMusic,            ftData },
+   { "producer",         ffText,    1000, fiProducer,         ftData },
+   { "screenplay",       ffAscii,    500, fiScreenplay,       ftData },
+   { "shortreview",      ffAscii,    500, fiShortreview,      ftData },
+   { "tipp",             ffAscii,    250, fiTipp,             ftData },
+   { "topic",            ffAscii,    500, fiTopic,            ftData },
+   { "year",             ffAscii,     10, fiYear,             ftData },
+   { "rating",           ffAscii,    250, fiRating,           ftData },
+   { "fsk",              ffAscii,      2, fiFsk,              ftData },
+   { "movieid",          ffAscii,     20, fiMovieid,          ftData },
+   { "moderator",        ffAscii,    250, fiModerator,        ftData },
+   { "other",            ffText,    2000, fiOther,            ftData },
+   { "guest",            ffText,    1000, fiGuest,            ftData },
+   { "camera",           ffText,    1000, fiCamera,           ftData },
+
+   { "extepnum",         ffInt,        4, fiExtEpNum,         ftData },
+   { "imagecount",       ffInt,        2, fiImageCount,       ftData },
+
+   // episodes (constable)
+
+   { "episode",          ffAscii,    250, fiEpisode,          ftData },
+   { "episodepart",      ffAscii,    250, fiEpisodePart,      ftData },
+   { "episodelang",      ffAscii,      3, fiEpisodeLang,      ftData },
+
+   // tv scraper
+
+   { "scrseriesid",      ffInt,       11, fiScrSeriesId,      ftData },
+   { "scrseriesepisode", ffInt,       11, fiScrSeriesEpisode, ftData },
+   { "scrmovieid",       ffInt,       11, fiScrMovieId,       ftData },
+   { "scrsp",            ffInt,       11, fiScrSp,            ftData },
+
+   { 0 }
+};
+
+cDbService::FieldDef* cTableEvents::toField(const char* name)
+{
+   for (int i = 0; i < fiCount; i++)
+      if (strcmp(fields[i].name, name) == 0)
+         return &fields[i];
+   
+   tell(0, "Request for unexpected field '%s', ignoring", name);
+
+   return 0;
+}
+
+cDbService::IndexDef cTableEvents::indices[] =
+{
+   // index             fields  
+
+   { "comptitle",               { fiCompTitle, na            }, 0 },
+   { "source",                  { fiSource, na               }, 0 },
+   { "FilerefSource",           { fiFileRef, fiSource,    na }, 0 },
+   { "channelid",               { fiChannelId, na            }, 0 },
+   { "useid",                   { fiUseId, na                }, 0 },
+   { "useidchannelid",          { fiUseId, fiChannelId,   na }, 0 },
+   { "updflgupdsp",             { fiUpdFlg, fiUpdSp,      na }, 0 },
+   { "sourcechannelid",         { fiSource, fiChannelId,  na }, 0 },
+   { "scrsp",                   { fiScrSp,  na               }, 0 },
+
+   { 0 }
+};
+
+//***************************************************************************
+// Components
+//***************************************************************************
+
+cDbService::FieldDef cTableComponents::fields[] =
+{
+   // name               format     size  index          type
+
+   { "eventid",          ffUInt,        0, fiEventId,     ftPrimary },
+   { "channelid",        ffAscii,      50, fiChannelId,   ftPrimary },
+   { "stream",           ffInt,         3, fiStream,      ftPrimary },
+   { "type",             ffInt,         3, fiType,        ftPrimary },
+   { "lang",             ffAscii,       8, fiLang,        ftPrimary },
+   { "description",      ffAscii,     100, fiDescription, ftPrimary },
+
+   { "inssp",            ffInt,         0, fiInsSp,       ftMeta },
+   { "updsp",            ffInt,         0, fiUpdSp,       ftMeta },
+
+   { 0 }
+};
+
+//***************************************************************************
+// File References
+//***************************************************************************
+
+cDbService::FieldDef cTableFileRefs::fields[] =
+{
+   // name               format     size  index          type
+
+   { "name",             ffAscii,    100, fiName,        ftPrimary },
+   { "source",           ffAscii,     10, fiSource,      ftPrimary },
+
+   { "inssp",            ffInt,        0, fiInsSp,       ftMeta },
+   { "updsp",            ffInt,        0, fiUpdSp,       ftMeta },
+
+   { "extid",            ffAscii,     10, fiExternalId,  ftData },
+   { "fileref",          ffAscii,    100, fiFileRef,     ftData },     // name + '-' + tag
+   { "tag",              ffAscii,    100, fiTag,         ftData },
+
+   { 0 }
+};
+
+cDbService::IndexDef cTableFileRefs::indices[] =
+{
+   // index              fields             
+
+   { "SourceFileref",  { fiSource, fiFileRef, na }, 0 },
+   { "Fileref",        { fiFileRef, na           }, 0 },
+
+   { 0 }
+};
+
+//***************************************************************************
+// Image Ref Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableImageRefs::fields[] =
+{
+   // name               format     size index          type
+
+   { "eventid",          ffUInt,       0, fiEventId,     ftPrimary },
+   { "lfn",              ffInt,        0, fiLfn,         ftPrimary },
+
+   { "inssp",            ffInt,        0, fiInsSp,       ftMeta   },
+   { "updsp",            ffInt,        0, fiUpdSp,       ftMeta   },
+   { "source",           ffAscii,     10, fiSource,      ftMeta },
+
+   { "fileref",          ffAscii,    100, fiFileRef,     ftData },
+   { "imagename",        ffAscii,    100, fiImgName,     ftData   },
+
+   { 0 }
+};
+
+cDbService::IndexDef cTableImageRefs::indices[] =
+{
+   // index     fields     
+
+   { "lfn",   { fiLfn, na     }, 0 },
+   { "name",  { fiImgName, na }, 0 },
+
+   { 0 }
+};
+
+//***************************************************************************
+// Image Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableImages::fields[] =
+{
+   // name               format     size index          type
+
+   { "imagename",        ffAscii,    100, fiImgName,     ftPrimary },
+
+   { "inssp",            ffInt,        0, fiInsSp,       ftMeta },
+   { "updsp",            ffInt,        0, fiUpdSp,       ftMeta },
+ 
+   { "image",            ffMlob,  200000, fiImage,       ftData },
+
+   { 0 }
+};
+
+//***************************************************************************
+// Series Episode Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableEpisodes::fields[] =
+{
+   // name               format    size  index           type
+
+   // primary key 
+
+   { "compname",          ffAscii,   100, fiCompName,     ftPrimary },   // episode name compressed
+   { "comppartname",      ffAscii,   200, fiCompPartName, ftPrimary },   // part name compressed
+   { "lang",              ffAscii,    10, fiLang,         ftPrimary }, 
+
+   { "inssp",             ffInt,       0, fiInsSp,        ftMeta },
+   { "updsp",             ffInt,       0, fiUpdSp,        ftMeta },
+   { "link",              ffInt,       0, fiLink,         ftData },
+
+   // episode data 
+
+   { "shortname",         ffAscii,   100, fiShortName,    ftData },
+   { "episodename",       ffAscii,   100, fiEpisodeName,  ftData },   // episode name
+
+   // part data
+
+   { "partname",          ffAscii,   300, fiPartName,     ftData },   // part name
+   { "season",            ffInt,       0, fiSeason,       ftData },
+   { "part",              ffInt,       0, fiPart,         ftData },
+   { "parts",             ffInt,       0, fiParts,        ftData },
+   { "number",            ffInt,       0, fiNumber,       ftData },
+
+   { "extracol1",         ffAscii,   250, fiExtraCol1,    ftData },
+   { "extracol2",         ffAscii,   250, fiExtraCol2,    ftData },
+   { "extracol3",         ffAscii,   250, fiExtraCol3,    ftData },
+
+   { "comment",           ffAscii,   250, fiComment,      ftData },
+
+   { 0 }
+};
+
+cDbService::IndexDef cTableEpisodes::indices[] =
+{
+   // index     fields
+
+   { "updsp", { fiUpdSp, na }, 0 },
+
+   { 0 }
+};
+
+//***************************************************************************
+// Channel Map Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableChannelMap::fields[] =
+{
+   // name               format     size index           type
+
+   { "extid",            ffAscii,    10, fiExternalId,   ftPrimary },
+   { "channelid",        ffAscii,    50, fiChannelId,    ftPrimary },
+   { "source",           ffAscii,    20, fiSource,       ftPrimary },
+
+   { "channelname",      ffAscii,   100, fiChannelName,  ftData },
+
+   { "vps",              ffInt,       0, fiVps,          ftData },
+   { "merge",            ffInt,       0, fiMerge,        ftData },
+   { "mergesp",          ffInt,       0, fiMergeSp,      ftData },
+
+   { "inssp",            ffInt,       0, fiInsSp,        ftMeta },
+   { "updsp",            ffInt,       0, fiUpdSp,        ftMeta },
+   { "updflg",           ffAscii,     1, fiUpdFlg,       ftMeta },
+
+   { 0 }
+};
+
+cDbService::IndexDef cTableChannelMap::indices[] =
+{
+   // index                fields 
+
+   { "sourceExtid",        { fiSource, fiExternalId, na }, 0 },
+   { "source",             { fiSource, na               }, 0 },
+   { "updflg",             { fiUpdFlg, na               }, 0 },
+   { "sourcechannelid",    { fiSource, fiChannelId,  na }, 0 },
+   { "mergesp",            { fiMergeSp, na              }, 0 },
+   { "channelid",          { fiChannelId, na            }, 0 },
+
+   { 0 }
+};
+
+//***************************************************************************
+// VDRs Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableVdrs::fields[] =
+{
+   // name               format     size index           type
+
+   { "uuid",             ffAscii,    40, fiUuid,         ftPrimary },
+
+   { "inssp",            ffInt,       0, fiInsSp,        ftMeta },
+   { "updsp",            ffInt,       0, fiUpdSp,        ftMeta },
+
+   { "name",             ffAscii,   100, fiName,         ftData },
+   { "version",          ffAscii,   100, fiVersion,      ftData },
+   { "dbapi",            ffUInt,      0, fiDbApi,        ftData },
+   { "lastupd",          ffInt,       0, fiLastUpdate,   ftData },
+   { "nextupd",          ffInt,       0, fiNextUpdate,   ftData },
+   { "state",            ffAscii,    20, fiState,        ftData },
+   { "master",           ffAscii,     1, fiMaster,       ftData },
+   { "ip",               ffAscii,    20, fiIp,           ftData },
+
+   { 0 }
+};
+
+cDbService::IndexDef cTableVdrs::indices[] =
+{
+   // index           fields 
+
+   { "state",        { fiState, na              }, 0 },
+
+   { 0 }
+};
+
+//***************************************************************************
+// Parameter Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableParameters::fields[] =
+{
+   // name               format     size index           type
+
+   { "owner",            ffAscii,    40, fiOwner,        ftPrimary },
+   { "name",             ffAscii,    40, fiName,         ftPrimary },
+
+   { "inssp",            ffInt,       0, fiInsSp,        ftMeta },
+   { "updsp",            ffInt,       0, fiUpdSp,        ftMeta },
+
+   { "value",            ffAscii,   100, fiValue,        ftData },
+
+   { 0 }
+};
+
+//***************************************************************************
+// Analyse
+//***************************************************************************
+
+cDbService::FieldDef cTableAnalyse::fields[] =
+{
+   // name               format     size  index            type
+
+   { "channelid",        ffAscii,     50, fiChannelId,     ftPrimary },
+   { "vdr_masterid",     ffUInt,       0, fiVdrMasterId,   ftData },
+   { "vdr_eventid",      ffUInt,       0, fiVdrEventId,    ftPrimary },
+
+   { "vdr_starttime",    ffInt,       10, fiVdrStartTime,  ftData },
+   { "vdr_duration",     ffInt,        5, fiVdrDuration,   ftData },
+   { "vdr_title",        ffAscii,    200, fiVdrTitle,      ftData },
+   { "vdr_shorttext",    ffAscii,    300, fiVdrShortText,  ftData },
+
+   { "ext_masterid",     ffUInt,       0, fiExtMasterId,   ftData },
+   { "ext_eventid",      ffUInt,       0, fiExtEventId,    ftData },
+   { "ext_starttime",    ffInt,       10, fiExtStartTime,  ftData },
+   { "ext_duration",     ffInt,        5, fiExtDuration,   ftData },
+   { "ext_title",        ffAscii,    200, fiExtTitle,      ftData },
+   { "ext_shorttext",    ffAscii,    300, fiExtShortText,  ftData },
+   { "ext_episode",      ffAscii,      1, fiExtEpisode,    ftData },
+   { "ext_merge",        ffInt,       11, fiExtMerge,      ftData },
+   { "ext_images",       ffAscii,      1, fiExiImages,     ftData },
+
+   { "lvmin",            ffInt,        3, fiLvMin,         ftData },
+   { "rank",             ffInt,        5, fiRank,          ftData },
+
+   { 0 }
+};
+
+cDbService::IndexDef cTableAnalyse::indices[] =
+{
+   // index            fields 
+
+   { "vdr_masterid", { fiVdrMasterId, na }, 0 },
+
+   { 0 }
+};
+
+//***************************************************************************
+// Snapshot
+//***************************************************************************
+
+cDbService::FieldDef cTableSnapshot::fields[] =
+{
+   // name            format     size  index             type
+
+   { "channelid",     ffAscii,     50, fiChannelId,      ftData },
+   { "source",        ffAscii,     10, fiSource,         ftData },
+
+   { "masterid",      ffUInt,       0, fiVdrMasterId,    ftData },
+   { "eventid",       ffUInt,       0, fiEventId,        ftData },
+   { "useid",         ffUInt,       0, fiUseId,          ftData },
+
+   { "starttime",     ffInt,       10, fiStartTime,      ftData },
+   { "duration",      ffInt,        5, fiDuration,       ftData },
+   { "title",         ffAscii,    200, fiTitle,          ftData },
+   { "comptitle",     ffAscii,    200, fiCompTitle,      ftData },
+   { "shorttext",     ffAscii,    300, fiShortText,      ftData },
+   { "compshorttext", ffAscii,    300, fiCompShortText,  ftData },
+
+   { "updsp",         ffInt,       10, fiUpdsp,          ftData },
+
+   { "episode",       ffAscii,      1, fiEpisode,        ftData },
+   { "merge",         ffInt,        0, fiMerge,          ftData },
+   { "images",        ffAscii,      1, fiImages,         ftData },
+
+   { 0 }
+};
+
+cDbService::IndexDef cTableSnapshot::indices[] =
+{
+   // index            fields 
+
+   { "channelid",       { fiChannelId, na }, 0 },
+   { "starttimeSource", { fiStartTime, fiSource, na }, 0 },
+
+   { 0 }
+};
+
+//***************************************************************************
+// Timers
+//***************************************************************************
+
+cDbService::FieldDef cTableTimers::fields[] =
+{
+   // name               format     size  index          type
+
+   { "eventid",          ffUInt,        0, fiEventId,     ftPrimary },
+   { "channelid",        ffAscii,      50, fiChannelId,   ftPrimary },
+   { "vdruuid",          ffAscii,      40, fiVdrUuid,     ftPrimary },
+
+   { "inssp",            ffInt,         0, fiInsSp,       ftMeta },
+   { "updsp",            ffInt,         0, fiUpdSp,       ftMeta },
+
+   { "state",            ffAscii,       1, fiState,       ftData },       // { 'D'eleted, 'N'ew, 'A'ssigned, pease 'R'eassign }
+   { "starttime",        ffInt,        10, fiStartTime,   ftData },
+   { "endtime",          ffInt,        10, fiEndTime,     ftData },
+
+   { 0 }
+};
+
+//***************************************************************************
+// Series Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableSeries::fields[] =
+{
+   // name                     format     size  index                  type
+
+   // primary key
+
+   { "series_id",              ffUInt,       0, fiSeriesId,            ftPrimary },
+
+   // data
+
+   { "series_name",            ffAscii,    200, fiSeriesName,          ftData },
+   { "series_last_scraped",    ffUInt,       0, fiSeriesLastScraped,   ftData },
+   { "series_last_updated",    ffUInt,       0, fiSeriesLastUpdated,   ftData },
+   { "series_overview",        ffText,   10000, fiSeriesOverview,      ftData },
+   { "series_firstaired",      ffAscii,     50, fiSeriesFirstAired,    ftData },
+   { "series_network",         ffAscii,    100, fiSeriesNetwork,       ftData },
+   { "series_imdb_id",         ffAscii,     20, fiSeriesIMDBId,        ftData },
+   { "series_genre",           ffAscii,    100, fiSeriesGenre,         ftData },
+   { "series_rating",          ffFloat,     31, fiSeriesRating,        ftData },
+   { "series_status",          ffAscii,     50, fiSeriesStatus,        ftData },
+
+   { 0 }
+};
+
+cDbService::FieldDef* cTableSeries::toField(const char* name)
+{
+   for (int i = 0; i < fiCount; i++)
+      if (strcmp(fields[i].name, name) == 0)
+         return &fields[i];
+   
+   tell(0, "Request for unexpected field '%s', ignoring", name);
+
+   return 0;
+}
+
+cDbService::IndexDef cTableSeries::indices[] =
+{
+   // index               fields  
+
+   { "seriesname",        { fiSeriesName, na }, 0 },
+
+   { 0 }
+};
+
+
+//***************************************************************************
+// SeriesEpisode Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableSeriesEpisode::fields[] =
+{
+   // name                        format     size  index                  type
+
+   // primary key
+
+   { "episode_id",                  ffUInt,       0, fiEpisodeId,           ftPrimary },
+
+   // data
+
+   { "episode_number",              ffUInt,       0, fiEpisodeNumber,       ftData },
+   { "season_number",               ffUInt,       0, fiSeasonNumber,        ftData },
+   { "episode_name",                ffAscii,    300, fiEpisodeName,         ftData },
+   { "episode_overview",            ffText,   10000, fiEpisodeOverview,     ftData },
+   { "episode_firstaired",          ffAscii,     20, fiEpisodeFirstAired,   ftData },
+   { "episode_gueststars",          ffAscii,   1000, fiEpisodeGuestStars,   ftData },
+   { "episode_rating",              ffFloat,     31, fiEpisodeRating,       ftData },
+   { "episode_last_updated",        ffUInt,       0, fiEpisodeLastUpdated,  ftData },
+   { "series_id",                   ffUInt,       0, fiSeriesId,            ftData },
+   
+   { 0 }
+};
+
+cDbService::FieldDef* cTableSeriesEpisode::toField(const char* name)
+{
+   for (int i = 0; i < fiCount; i++)
+      if (strcmp(fields[i].name, name) == 0)
+         return &fields[i];
+   
+   tell(0, "Request for unexpected field '%s', ignoring", name);
+
+   return 0;
+}
+
+cDbService::IndexDef cTableSeriesEpisode::indices[] =
+{
+   // index               fields  
+   { "series_id",         { fiSeriesId, na }, 0 },
+
+   { 0 }
+};
+
+//***************************************************************************
+// SeriesMedia Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableSeriesMedia::fields[] =
+{
+   // name                        format     size  index                    type
+
+   // primary key
+
+   { "series_id",                  ffUInt,       0, fiSeriesId,             ftPrimary },
+   { "season_number",              ffUInt,       0, fiSeasonNumber,         ftPrimary },
+   { "episode_id",                 ffUInt,       0, fiEpisodeId,            ftPrimary },
+   { "actor_id",                   ffUInt,       0, fiActorId,              ftPrimary },
+   { "media_type",                 ffUInt,       0, fiMediaType,            ftPrimary },
+
+   // data
+
+   { "media_url",                  ffAscii,    100, fiMediaUrl,             ftData },
+   { "media_width",                ffUInt,       0, fiMediaWidth,           ftData },
+   { "media_height",               ffUInt,       0, fiMediaHeight,          ftData },
+   { "media_rating",               ffFloat,     31, fiMediaRating,          ftData },
+   { "media_content",              ffMlob, 1000000, fiMediaContent,         ftData },
+   
+   { 0 }
+};
+
+cDbService::FieldDef* cTableSeriesMedia::toField(const char* name)
+{
+   for (int i = 0; i < fiCount; i++)
+      if (strcmp(fields[i].name, name) == 0)
+         return &fields[i];
+   
+   tell(0, "Request for unexpected field '%s', ignoring", name);
+
+   return 0;
+}
+
+cDbService::IndexDef cTableSeriesMedia::indices[] =
+{
+   // index               fields  
+
+   { "series_id",         { fiSeriesId, na }, 0 },
+   { "season_number",     { fiSeasonNumber, na }, 0 },
+   { "episode_id",        { fiEpisodeId, na }, 0 },
+   { "actor_id",          { fiActorId, na }, 0 },
+
+   { 0 }
+};
+
+//***************************************************************************
+// SeriesActor Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableSeriesActor::fields[] =
+{
+   // name                        format     size  index                    type
+
+   // primary key
+
+   { "actor_id",                  ffUInt,       0, fiActorId,              ftPrimary },
+
+   // data
+
+   { "actor_name",                ffAscii,    100, fiActorName,            ftData },
+   { "actor_role",                ffAscii,    500, fiActorRole,            ftData },
+   { "actor_sortorder",           ffUInt,       0, fiSortOrder,            ftData },
+   
+   { 0 }
+};
+
+cDbService::FieldDef* cTableSeriesActor::toField(const char* name)
+{
+   for (int i = 0; i < fiCount; i++)
+      if (strcmp(fields[i].name, name) == 0)
+         return &fields[i];
+   
+   tell(0, "Request for unexpected field '%s', ignoring", name);
+
+   return 0;
+}
+
+cDbService::IndexDef cTableSeriesActor::indices[] =
+{
+   // index               fields  
+
+   { 0 }
+};
+
+//***************************************************************************
+// Movie Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableMovies::fields[] =
+{
+   // name                        format     size  index                    type
+
+   // primary key
+
+   { "movie_id",                   ffUInt,       0, fiMovieId,              ftPrimary },
+
+   // data
+
+   { "movie_title",                ffAscii,    300, fiTitle,                ftData },
+   { "movie_original_title",       ffAscii,    300, fiOriginalTitle,        ftData },
+   { "movie_tagline",              ffAscii,   1000, fiTagline,              ftData },
+   { "movie_overview",             ffText,    5000, fiOverview,             ftData },
+   { "movie_adult",                ffUInt,       0, fiIsAdult,              ftData },
+   { "movie_collection_id",        ffUInt,       0, fiCollectionId,         ftData },
+   { "movie_collection_name",      ffAscii,    300, fiCollectionName,       ftData },
+   { "movie_budget",               ffUInt,       0, fiBudget,               ftData },
+   { "movie_revenue",              ffUInt,       0, fiRevenue,              ftData },
+   { "movie_genres",               ffAscii,    500, fiGenres,               ftData },
+   { "movie_homepage",             ffAscii,    300, fiHomepage,             ftData },
+   { "movie_release_date",         ffAscii,     20, fiReleaaseDate,         ftData },
+   { "movie_runtime",              ffUInt,       0, fiRuntime,              ftData },
+   { "movie_popularity",           ffFloat,     31, fiPopularity,           ftData },
+   { "movie_vote_average",         ffFloat,     31, fiVoteAverage,          ftData },
+   
+   { 0 }
+};
+
+cDbService::FieldDef* cTableMovies::toField(const char* name)
+{
+   for (int i = 0; i < fiCount; i++)
+      if (strcmp(fields[i].name, name) == 0)
+         return &fields[i];
+   
+   tell(0, "Request for unexpected field '%s', ignoring", name);
+
+   return 0;
+}
+
+cDbService::IndexDef cTableMovies::indices[] =
+{
+   // index              fields  
+
+   { "movie_id",         { fiMovieId, na }, 0 },
+   { "movietitle",       { fiTitle, na }, 0 },
+
+   { 0 }
+};
+
+//***************************************************************************
+// MovieActor Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableMovieActor::fields[] =
+{
+   // name                        format     size  index                    type
+
+   // primary key
+
+   { "actor_id",                   ffUInt,       0, fiActorId,              ftPrimary },
+
+   // data
+
+   { "actor_name",                 ffAscii,    300, fiActorName,            ftData },
+   
+   { 0 }
+};
+
+cDbService::FieldDef* cTableMovieActor::toField(const char* name)
+{
+   for (int i = 0; i < fiCount; i++)
+      if (strcmp(fields[i].name, name) == 0)
+         return &fields[i];
+   
+   tell(0, "Request for unexpected field '%s', ignoring", name);
+
+   return 0;
+}
+
+cDbService::IndexDef cTableMovieActor::indices[] =
+{
+   // index               fields  
+
+   { "actor_id",         { fiActorId, na }, 0 },
+   
+   { 0 }
+};
+
+//***************************************************************************
+// MovieActors Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableMovieActors::fields[] =
+{
+   // name                        format     size  index                    type
+
+   // primary key
+
+   { "movie_id",                   ffUInt,       0, fiMovieId,              ftPrimary },
+   { "actor_id",                   ffUInt,       0, fiActorId,              ftPrimary },
+
+   // data
+
+   { "actor_role",                 ffAscii,    300, fiRole,                 ftData },
+   
+   { 0 }
+};
+
+cDbService::FieldDef* cTableMovieActors::toField(const char* name)
+{
+   for (int i = 0; i < fiCount; i++)
+      if (strcmp(fields[i].name, name) == 0)
+         return &fields[i];
+   
+   tell(0, "Request for unexpected field '%s', ignoring", name);
+
+   return 0;
+}
+
+cDbService::IndexDef cTableMovieActors::indices[] =
+{
+   // index               fields  
+
+   { "movie_id",         { fiMovieId, na }, 0 },
+   { "actor_id",         { fiActorId, na }, 0 },
+   
+   { 0 }
+};
+
+//***************************************************************************
+// cTableMovieMedia Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableMovieMedia::fields[] =
+{
+   // name                        format     size  index                    type
+
+   // primary key
+
+   { "movie_id",                   ffUInt,       0, fiMovieId,              ftPrimary },
+   { "actor_id",                   ffUInt,       0, fiActorId,              ftPrimary },
+   { "media_type",                 ffUInt,       0, fiMediaType,            ftPrimary },
+
+   // data
+
+   { "media_url",                  ffAscii,    100, fiMediaUrl,             ftData },
+   { "media_width",                ffUInt,       0, fiMediaWidth,           ftData },
+   { "media_height",               ffUInt,       0, fiMediaHeight,          ftData },
+   { "media_content",              ffMlob, 1000000, fiMediaContent,         ftData },
+   
+   { 0 }
+};
+
+cDbService::FieldDef* cTableMovieMedia::toField(const char* name)
+{
+   for (int i = 0; i < fiCount; i++)
+      if (strcmp(fields[i].name, name) == 0)
+         return &fields[i];
+   
+   tell(0, "Request for unexpected field '%s', ignoring", name);
+
+   return 0;
+}
+
+cDbService::IndexDef cTableMovieMedia::indices[] =
+{
+   // index               fields  
+
+   { "movie_id",          { fiMovieId, na }, 0 },
+   { "actor_id",          { fiActorId, na }, 0 },
+
+   { 0 }
+};
+
+//***************************************************************************
+// cTableRecordings Fields
+//***************************************************************************
+
+cDbService::FieldDef cTableRecordings::fields[] =
+{
+   // name                        format     size  index                    type
+
+   // primary key
+
+   { "uuid",                       ffAscii,     40, fiUuid,                 ftPrimary },
+   { "rec_path",                   ffAscii,    200, fiRecPath,              ftPrimary },
+   { "rec_start",                  ffUInt,       0, fiRecStart,             ftPrimary },
+   
+   // data
+
+   { "event_id",                   ffUInt,       0, fiEventId,              ftData },
+   { "channel_id",                 ffAscii,     50, fiChannelId,            ftData },
+   { "scrapinfo_movie_id",         ffUInt,       0, fiScrapInfoMovieId,     ftData },
+   { "scrapinfo_series_id",        ffUInt,       0, fiScrapInfoSeriesId,    ftData },
+   { "scrapinfo_episode_id",       ffUInt,       0, fiScrapInfoEpisodeId,   ftData },
+   { "scrap_new",                  ffUInt,       0, fiScrapNew,             ftData },
+   { "rec_title",                  ffAscii,    200, fiRecTitle,             ftData },
+   { "rec_subtitle",               ffAscii,    500, fiRecSubTitle,          ftData },
+   { "rec_duration",               ffUInt,       0, fiRecDuration,          ftData },
+   { "movie_id",                   ffUInt,       0, fiMovieId,              ftData },
+   { "series_id",                  ffUInt,       0, fiSeriesId,             ftData },
+   { "episode_id",                 ffUInt,       0, fiEpisodeId,            ftData },
+   
+   { 0 }
+};
+
+cDbService::FieldDef* cTableRecordings::toField(const char* name)
+{
+   for (int i = 0; i < fiCount; i++)
+      if (strcmp(fields[i].name, name) == 0)
+         return &fields[i];
+   
+   tell(0, "Request for unexpected field '%s', ignoring", name);
+
+   return 0;
+}
+
+cDbService::IndexDef cTableRecordings::indices[] =
+{
+   // index                   fields  
+ 
+   { "uuid",                  { fiUuid, na     }, 0 },
+   { "rec_path",              { fiRecPath, na  }, 0 },
+   { "rec_start",             { fiRecStart, na }, 0 },
+   { "scrap_new",             { fiScrapNew, na }, 0 },
+
+
+   { 0 }
+};
diff --git a/lib/tabledef.h b/lib/tabledef.h
new file mode 100644
index 0000000..9936f4e
--- /dev/null
+++ b/lib/tabledef.h
@@ -0,0 +1,867 @@
+/*
+ * tabledef.h
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __TABLEDEF_H
+#define __TABLEDEF_H
+
+#include "db.h"
+
+//***************************************************************************
+// cEpgdState
+//***************************************************************************
+
+class cEpgdState
+{
+   public:
+
+      enum State
+      {
+         esUnknown = na,
+
+         esInit,
+         esStandby,
+         esStopped,
+
+         // handler pause on this states!
+
+         esBusy,
+         esBusyEvents = esBusy,
+         esBusyMatch,
+         esBusyScraping,
+
+         // handler don't pause on this states!
+
+         esBusyImages,
+
+         esCount
+      };
+
+      static const char* toName(State s);
+      static State toState(const char* name);
+      static int isValid(State s) { return s > esUnknown && s < esCount; }
+
+      static const char* states[];
+};
+
+typedef cEpgdState Es;
+
+//***************************************************************************
+// cUpdateState
+//***************************************************************************
+
+class cUpdateState
+{
+   public:
+
+      enum State
+      {
+         // add to VDRs EPG
+         
+         usActive      = 'A',
+         usLink        = 'L',
+         usPassthrough = 'P',
+         
+         // remove from VDRs EPG
+         
+         usChanged     = 'C',
+         usDelete      = 'D', 
+         usRemove      = 'R',
+         
+         // don't care for VDRs EPG
+         
+         usInactive    = 'I',
+         usTarget      = 'T'
+      };
+
+      // get lists for SQL 'in' statements
+
+      static const char* getDeletable()      { return "'A','L','P','R','I'"; }
+      static const char* getNeeded()         { return "'A','L','P','C','D','R'"; }
+      static const char* getVisible()        { return "'A','L','P'"; }
+
+      // checks fpr c++ code
+
+      static int isNeeded(char c)            { return strchr("ALPCDR", c) != 0; }
+      static int isRemove(char c)            { return strchr("CDR", c) != 0; }
+      
+};
+
+typedef cUpdateState Us;
+
+//***************************************************************************
+// class cTableFileRef
+//***************************************************************************
+
+class cTableFileRefs : public cDbTable
+{
+   public:
+
+      cTableFileRefs(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "fileref"; }
+      
+      enum FieldIndex
+      {
+         fiName,
+         fiSource,
+
+         fiInsSp,
+         fiUpdSp,
+
+         fiExternalId,
+         fiFileRef,
+         fiTag,
+
+         fiCount
+      };
+
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// class cTableImageRef
+//***************************************************************************
+
+class cTableImageRefs : public cDbTable
+{
+   public:
+
+      cTableImageRefs(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "imagerefs"; }
+
+      enum FieldIndex
+      {
+         fiEventId,
+         fiLfn,
+
+         fiInsSp,
+         fiUpdSp,
+         fiSource,
+         fiFileRef,
+
+         fiImgName,
+
+         fiCount
+      };
+
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// class cTableImage
+//***************************************************************************
+
+class cTableImages : public cDbTable
+{
+   public:
+
+      cTableImages(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields) { }
+
+      virtual const char* TableName()    { return "images"; }
+
+      enum FieldIndex
+      {
+         fiImgName,
+
+         fiInsSp,
+         fiUpdSp,
+         fiImage,
+
+         fiCount
+      };
+
+      static FieldDef fields[];
+};
+
+//***************************************************************************
+// class cTableEvent
+//***************************************************************************
+
+class cTableEvents : public cDbTable
+{
+   public:
+
+      cTableEvents(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "events"; }
+
+      enum FieldIndex
+      {
+         fiEventId,
+         fiChannelId,
+
+         fiMasterId,
+         fiUseId,
+
+         fiSource,
+         fiFileRef,
+         fiInsSp,
+         fiUpdSp,
+         fiUpdFlg,           // update flag
+         fiDelFlg,           // deletion flag
+
+         fiTableId,
+         fiVersion,
+         fiTitle,
+         fiCompTitle,        // compressed (without whitespace and special characters)
+         fiShortText,
+         fiCompShortText,    // compressed (without whitespace and special characters)
+         fiLongDescription,
+         fiStartTime,
+         fiDuration,
+         fiParentalRating,
+         fiVps,
+         fiDescription,      // view field, not stored!
+
+         fiShortDescription,
+         fiActor,
+         fiAudio,
+         fiCategory,
+         fiCountry,
+         fiDirector,
+         fiFlags,
+         fiGenre,
+         fiInfo,
+         fiMusic,
+         fiProducer,
+         fiScreenplay,
+         fiShortreview,
+         fiTipp,
+         fiTopic,
+         fiYear,
+         fiRating,
+         fiFsk,
+         fiMovieid,
+         fiModerator,
+         fiOther,
+         fiGuest,
+         fiCamera,
+
+         fiExtEpNum,
+         fiImageCount,
+
+         fiEpisode,
+         fiEpisodePart,
+         fiEpisodeLang,
+
+         fiScrSeriesId,
+         fiScrSeriesEpisode,
+         fiScrMovieId,
+         fiScrSp,
+
+         fiCount
+      };
+
+      static FieldDef* toField(const char* name);      
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// class cTableComponent
+//***************************************************************************
+
+class cTableComponents : public cDbTable
+{
+   public:
+
+      cTableComponents(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields) { }
+
+      virtual const char* TableName()    { return "components"; }
+
+      enum FieldIndex
+      {
+         fiEventId,
+         fiChannelId,
+         fiStream,
+         fiType,
+         fiLang,
+         fiDescription,
+
+         fiInsSp,
+         fiUpdSp
+      };
+
+      static FieldDef fields[];
+};
+
+//***************************************************************************
+// class cTableEpisode
+//***************************************************************************
+
+class cTableEpisodes : public cDbTable
+{
+   public:
+
+      cTableEpisodes(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "episodes"; }
+
+
+      enum FieldIndex
+      {
+         // primary key 
+
+         fiCompName,      // compressed name (without whitespace and special characters)
+         fiCompPartName,  //      "      "       "
+         fiLang,          // "de", "en", ...
+
+         fiInsSp,
+         fiUpdSp,
+         fiLink,
+
+         // episode data 
+
+         fiShortName,
+         fiEpisodeName,   // episode name (fielname without path and suffix)
+
+         // part data
+
+         fiPartName,      // part name
+         fiSeason,
+         fiPart,
+         fiParts,
+         fiNumber,
+
+         fiExtraCol1,
+         fiExtraCol2,
+         fiExtraCol3,
+         fiComment,
+
+         fiCount
+      };
+
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// class cTableChannelMap
+//***************************************************************************
+
+class cTableChannelMap : public cDbTable
+{
+   public:
+
+      cTableChannelMap(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "channelmap"; }
+
+      enum FieldIndex
+      {
+         fiExternalId,   // 
+         fiChannelId,    // 
+         fiSource,
+
+         fiChannelName,
+
+         fiVps,
+         fiMerge,
+         fiMergeSp,
+
+         fiInsSp,
+         fiUpdSp,
+         fiUpdFlg,
+
+         fiCount
+      };
+
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// class cTableVdr
+//***************************************************************************
+
+class cTableVdrs : public cDbTable
+{
+   public:
+
+      cTableVdrs(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "vdrs"; }
+
+      enum FieldIndex
+      {
+         fiUuid,
+
+         fiInsSp,
+         fiUpdSp,
+
+         fiName,
+         fiVersion,
+         fiDbApi,
+         fiLastUpdate,
+         fiNextUpdate,
+         fiState,
+         fiMaster,
+         fiIp,
+
+         fiCount
+      };
+
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// class cTableParameters
+//***************************************************************************
+
+class cTableParameters : public cDbTable
+{
+   public:
+
+      cTableParameters(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields) { }
+
+      virtual const char* TableName()    { return "parameters"; }
+
+      enum FieldIndex
+      {
+         fiOwner,
+         fiName,
+
+         fiInsSp,
+         fiUpdSp,
+
+         fiValue,
+
+         fiCount
+      };
+
+      static FieldDef fields[];
+};
+
+//***************************************************************************
+// cTableAnalyse
+//***************************************************************************
+
+class cTableAnalyse : public cDbTable
+{
+   public:
+
+      cTableAnalyse(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "analyse"; }
+
+      enum FieldIndex
+      {
+         fiChannelId,   
+         fiVdrMasterId, 
+         fiVdrEventId,  
+
+         fiVdrStartTime,
+         fiVdrDuration, 
+         fiVdrTitle,    
+         fiVdrShortText,
+         
+         fiExtMasterId, 
+         fiExtEventId,  
+         fiExtStartTime,
+         fiExtDuration, 
+         fiExtTitle,    
+         fiExtShortText,
+         fiExtEpisode,
+         fiExtMerge,
+         fiExiImages,
+
+         fiLvMin,
+         fiRank,
+
+         fiCount
+      };
+
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// cTableSnapshot
+//***************************************************************************
+
+class cTableSnapshot : public cDbTable
+{
+   public:
+
+      cTableSnapshot(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "snapshot"; }
+
+      enum FieldIndex
+      {
+         fiChannelId,    
+         fiSource,
+         fiVdrMasterId,  
+         fiEventId,      
+         fiUseId,        
+         fiStartTime,    
+         fiDuration,     
+         fiTitle,        
+         fiCompTitle,    
+         fiShortText,
+         fiCompShortText,
+         fiUpdsp,
+         fiEpisode,
+         fiMerge,
+         fiImages,
+         
+         fiCount
+      };
+
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// class cTableTimers
+//***************************************************************************
+
+class cTableTimers : public cDbTable
+{
+   public:
+
+      cTableTimers(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields) { }
+
+      virtual const char* TableName()    { return "timers"; }
+
+      enum FieldIndex
+      {
+         fiEventId,
+         fiChannelId,
+         fiVdrUuid,
+         
+         fiInsSp,
+         fiUpdSp,
+         
+         fiState,
+         fiStartTime,
+         fiEndTime
+      };
+
+      static FieldDef fields[];
+};
+
+//***************************************************************************
+// class cTableSeries
+//***************************************************************************
+
+class cTableSeries : public cDbTable
+{
+   public:
+
+      cTableSeries(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "series"; }
+
+      enum FieldIndex
+      {
+         fiSeriesId,
+
+         fiSeriesName,
+         fiSeriesLastScraped,
+         fiSeriesLastUpdated,
+         fiSeriesOverview,
+         fiSeriesFirstAired,
+         fiSeriesNetwork,
+         fiSeriesIMDBId,
+         fiSeriesGenre,
+         fiSeriesRating,
+         fiSeriesStatus,
+
+         fiCount
+      };
+
+      static FieldDef* toField(const char* name);      
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// class cTableSeriesEpisode
+//***************************************************************************
+
+class cTableSeriesEpisode : public cDbTable
+{
+   public:
+
+      cTableSeriesEpisode(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "series_episode"; }
+
+      enum FieldIndex
+      {
+         fiEpisodeId,
+
+         fiEpisodeNumber,
+         fiSeasonNumber,
+         fiEpisodeName,
+         fiEpisodeOverview,
+         fiEpisodeFirstAired,
+         fiEpisodeGuestStars,
+         fiEpisodeRating,
+         fiEpisodeLastUpdated,
+         fiSeriesId,   
+
+         fiCount
+      };
+
+      static FieldDef* toField(const char* name);      
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// class cTableSeriesMedia
+//***************************************************************************
+
+class cTableSeriesMedia : public cDbTable
+{
+   public:
+
+      cTableSeriesMedia(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "series_media"; }
+
+      enum FieldIndex
+      {
+         fiSeriesId,
+         fiSeasonNumber,
+         fiEpisodeId,
+         fiActorId,
+         fiMediaType,
+
+         fiMediaUrl,
+         fiMediaWidth,
+         fiMediaHeight,
+         fiMediaRating,
+         fiMediaContent,
+
+         fiCount
+      };
+
+      static FieldDef* toField(const char* name);      
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// class cTableSeriesActor
+//***************************************************************************
+
+class cTableSeriesActor : public cDbTable
+{
+   public:
+
+      cTableSeriesActor(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "series_actor"; }
+
+      enum FieldIndex
+      {
+         fiActorId,
+
+         fiActorName,
+         fiActorRole,
+         fiSortOrder,
+
+         fiCount
+      };
+
+      static FieldDef* toField(const char* name);      
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// class cTableMovies
+//***************************************************************************
+
+class cTableMovies : public cDbTable
+{
+   public:
+
+      cTableMovies(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "movie"; }
+
+      enum FieldIndex
+      {
+         fiMovieId,
+
+         fiTitle,
+         fiOriginalTitle,
+         fiTagline,
+         fiOverview,
+         fiIsAdult,
+         fiCollectionId,
+         fiCollectionName,
+         fiBudget,
+         fiRevenue,
+         fiGenres,
+         fiHomepage,
+         fiReleaaseDate,
+         fiRuntime,
+         fiPopularity,
+         fiVoteAverage,
+
+         fiCount
+      };
+
+      static FieldDef* toField(const char* name);      
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// class cTableMovieActor
+//***************************************************************************
+
+class cTableMovieActor : public cDbTable
+{
+   public:
+
+      cTableMovieActor(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "movie_actor"; }
+
+      enum FieldIndex
+      {
+         fiActorId,
+
+         fiActorName,
+
+         fiCount
+      };
+
+      static FieldDef* toField(const char* name);      
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// class cTableMovieActors
+//***************************************************************************
+
+class cTableMovieActors : public cDbTable
+{
+   public:
+
+      cTableMovieActors(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "movie_actors"; }
+
+      enum FieldIndex
+      {
+         fiMovieId,
+         fiActorId,
+
+         fiRole,
+
+         fiCount
+      };
+
+      static FieldDef* toField(const char* name);      
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// class cTableMovieMedia
+//***************************************************************************
+
+class cTableMovieMedia : public cDbTable
+{
+   public:
+
+      cTableMovieMedia(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "movie_media"; }
+
+      enum FieldIndex
+      {
+         fiMovieId,
+         fiActorId,
+         fiMediaType,
+
+         fiMediaUrl,
+         fiMediaWidth,
+         fiMediaHeight,
+         fiMediaContent,
+
+         fiCount
+      };
+
+      static FieldDef* toField(const char* name);      
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+//***************************************************************************
+// class cTableRecordings
+//***************************************************************************
+
+class cTableRecordings : public cDbTable
+{
+   public:
+
+      cTableRecordings(cDbConnection* aConnection)
+         : cDbTable(aConnection, fields, indices) { }
+
+      virtual const char* TableName()    { return "recordings"; }
+
+      enum FieldIndex
+      {
+         fiUuid,
+         fiRecPath,
+         fiRecStart,
+
+         fiEventId,
+         fiChannelId,
+         fiScrapInfoMovieId,
+         fiScrapInfoSeriesId,
+         fiScrapInfoEpisodeId,
+         fiScrapNew,
+         fiRecTitle,
+         fiRecSubTitle,
+         fiRecDuration,
+         fiMovieId,
+         fiSeriesId,
+         fiEpisodeId,
+
+         fiCount
+      };
+
+      static FieldDef* toField(const char* name);      
+      static FieldDef fields[];
+      static IndexDef indices[];
+};
+
+#endif //__TABLEDEF_H
diff --git a/lib/test.c b/lib/test.c
new file mode 100644
index 0000000..fa85c70
--- /dev/null
+++ b/lib/test.c
@@ -0,0 +1,377 @@
+
+#include <stdint.h>   // uint_64_t
+#include <sys/time.h>
+#include <time.h>
+
+#include <stdio.h>
+#include <string>
+
+#include "config.h"
+#include "common.h"
+#include "db.h"
+#include "tabledef.h"
+#include "dbdict.h"
+
+cDbConnection* connection = 0;
+const char* logPrefix = "test";
+
+//***************************************************************************
+// Init Connection
+//***************************************************************************
+
+void initConnection()
+{
+   cDbConnection::init();
+
+   cDbConnection::setEncoding("utf8");
+   cDbConnection::setHost("localhost");
+   cDbConnection::setPort(EPG2VDRConfig.dbPort);
+   cDbConnection::setName(EPG2VDRConfig.dbName);
+   cDbConnection::setUser(EPG2VDRConfig.dbUser);
+   cDbConnection::setPass(EPG2VDRConfig.dbPass);
+   cDbTable::setConfPath("/etc/epgd/");
+
+   connection = new cDbConnection();
+}
+
+void exitConnection()
+{
+   cDbConnection::exit();
+   
+   if (connection)
+      delete connection;
+}
+
+//***************************************************************************
+// 
+//***************************************************************************
+
+void chkCompress()
+{
+   std::string s = "_+*!#?=&%$< Hallo TEIL Hallo Folge ";
+
+   printf("'%s'\n", s.c_str());
+   prepareCompressed(s);
+   printf("'%s'\n", s.c_str());
+
+   s = "Place Vendôme - Heiße Diamanten";
+   printf("'%s'\n", s.c_str());
+   prepareCompressed(s);
+   printf("'%s'\n", s.c_str());
+
+   s = "Halöö älter";
+   printf("'%s'\n", s.c_str());
+   prepareCompressed(s);
+   printf("'%s'\n", s.c_str());
+}
+
+//***************************************************************************
+// 
+//***************************************************************************
+
+void chkStatement1()
+{
+   cDbTable* epgDb = new cTableEvents(connection);
+
+   if (epgDb->open() != success)
+   { 
+      tell(0, "Could not access database '%s:%d' (%s)", 
+           cDbConnection::getHost(), cDbConnection::getPort(), epgDb->TableName());
+
+      return ;
+   }
+
+   tell(0, "---------------------------------------------------");
+
+   // prepare statement to mark wasted DVB events
+   
+   cDbValue* endTime = new cDbValue("starttime+duration", cDBS::ffInt, 10);
+   cDbStatement* updateDelFlg = new cDbStatement(epgDb);
+
+   // update events set delflg = ?, updsp = ? 
+   //   where channelid = ? and source = ? 
+   //      and starttime+duration > ? 
+   //      and starttime < ? 
+   //      and (tableid > ? or (tableid = ? and version <> ?))
+
+   updateDelFlg->build("update %s set ", epgDb->TableName());
+   updateDelFlg->bind(cTableEvents::fiDelFlg, cDBS::bndIn | cDBS::bndSet);
+   updateDelFlg->bind(cTableEvents::fiUpdSp, cDBS::bndIn | cDBS::bndSet, ", ");
+   updateDelFlg->build(" where ");
+   updateDelFlg->bind(cTableEvents::fiChannelId, cDBS::bndIn | cDBS::bndSet);
+   updateDelFlg->bind(cTableEvents::fiSource, cDBS::bndIn | cDBS::bndSet, " and ");
+   
+   updateDelFlg->bindCmp(0, endTime, ">", " and ");
+   
+   updateDelFlg->bindCmp(0, cTableEvents::fiStartTime, 0, "<" ,  " and ");
+   updateDelFlg->bindCmp(0, cTableEvents::fiTableId,   0, ">" ,  " and (");
+   updateDelFlg->bindCmp(0, cTableEvents::fiTableId,   0, "=" ,  " or (");
+   updateDelFlg->bindCmp(0, cTableEvents::fiVersion,   0, "<>" , " and ");
+   updateDelFlg->build("));");
+
+   updateDelFlg->prepare();
+
+   tell(0, "---------------------------------------------------");
+}
+
+//***************************************************************************
+// 
+//***************************************************************************
+
+void chkStatement2()
+{
+   cDbTable* imageRefDb = new cTableImageRefs(connection);
+   cDbTable* imageDb = new cTableImages(connection);
+
+   if (imageRefDb->open() != success)
+      return ;
+
+   if (imageDb->open() != success)
+      return ;
+
+   tell(0, "---------------------------------------------------");
+ 
+   cDbStatement* selectAllImages = new cDbStatement(imageRefDb);
+
+   cDbValue imageData; 
+   imageData.setField(imageDb->getField(cTableImages::fiImage));
+
+   // select r.imagename, r.eventid, r.lfn, i.image from imagerefs r, images i 
+   //    where r.imagename = i.imagename and i.image is not null;
+
+   selectAllImages->build("select ");
+   selectAllImages->setBindPrefix("r.");
+   selectAllImages->bind(cTableImageRefs::fiImgName, cDBS::bndOut);
+   selectAllImages->bind(cTableImageRefs::fiEventId, cDBS::bndOut, ", ");
+   selectAllImages->bind(cTableImageRefs::fiLfn, cDBS::bndOut, ", ");
+   selectAllImages->setBindPrefix("i.");
+   selectAllImages->bind(&imageData, cDBS::bndOut, ",");
+   selectAllImages->clrBindPrefix();
+   selectAllImages->build(" from %s r, %s i where ", imageRefDb->TableName(), imageDb->TableName());
+   selectAllImages->build("r.%s = i.%s and i.%s is not null;",
+            imageRefDb->getField(cTableImageRefs::fiImgName)->name,
+            imageDb->getField(cTableImages::fiImgName)->name,
+            imageDb->getField(cTableImages::fiImage)->name);
+
+   selectAllImages->prepare();
+
+
+   tell(0, "---------------------------------------------------");
+
+   //delete s;
+   delete imageRefDb;
+   delete imageDb;
+}
+
+//***************************************************************************
+// 
+//***************************************************************************
+
+void chkStatement3()
+{
+   int count = 0;
+   int lcount = 0;
+
+   cDbTable* epgDb = new cTableEvents(connection);
+   cDbTable* mapDb = new cTableChannelMap(connection);
+
+   if (epgDb->open() != success)
+      return ;
+
+   if (mapDb->open() != success)
+      return ;
+
+   tell(0, "---------------------------------------------------");
+ 
+   cDbStatement* s = new cDbStatement(epgDb);
+
+   s->build("select ");
+   s->setBindPrefix("e.");
+   s->bind(cTableEvents::fiEventId, cDBS::bndOut);
+   s->bind(cTableEvents::fiChannelId, cDBS::bndOut, ", ");
+   s->bind(cTableEvents::fiSource, cDBS::bndOut, ", ");
+   s->bind(cTableEvents::fiDelFlg, cDBS::bndOut, ", ");
+   s->bind(cTableEvents::fiFileRef, cDBS::bndOut, ", ");
+   s->bind(cTableEvents::fiTableId, cDBS::bndOut, ", ");
+   s->bind(cTableEvents::fiVersion, cDBS::bndOut, ", ");
+   s->bind(cTableEvents::fiTitle, cDBS::bndOut, ", ");
+   s->bind(cTableEvents::fiShortText, cDBS::bndOut, ", ");
+   s->bind(cTableEvents::fiStartTime, cDBS::bndOut, ", ");
+   s->bind(cTableEvents::fiDuration, cDBS::bndOut, ", ");
+   s->bind(cTableEvents::fiParentalRating, cDBS::bndOut, ", ");
+   s->bind(cTableEvents::fiVps,  cDBS::bndOut, ", ");
+   s->bind(cTableEvents::fiDescription, cDBS::bndOut, ", ");
+   s->clrBindPrefix();
+   s->build(" from eventsview e, %s m where ", mapDb->TableName());
+   s->build("e.%s = m.%s and e.%s = m.%s and ",
+                          epgDb->getField(cTableEvents::fiChannelId)->name,
+                          mapDb->getField(cTableChannelMap::fiChannelName)->name,
+                          epgDb->getField(cTableEvents::fiSource)->name,
+                          mapDb->getField(cTableChannelMap::fiSource)->name);
+   s->bindCmp("e", cTableEvents::fiUpdSp, 0, ">");
+   s->build(" order by m.%s;", mapDb->getField(cTableChannelMap::fiChannelName)->name);
+
+   s->prepare();
+
+   epgDb->clear();
+   epgDb->setValue(cTableEvents::fiUpdSp, (double)0);
+   epgDb->setValue(cTableEvents::fiSource, "vdr");                             // used by selectUpdEventsByChannel
+   epgDb->setValue(cTableEvents::fiChannelId, "xxxxxxxxxxxxx");    // used by selectUpdEventsByChannel
+
+   int channels = 0;
+   char chan[100]; *chan = 0;
+
+   tell(0, "---------------------------------------------------");
+
+   for (int found = s->find(); found; found = s->fetch())
+   {
+      if (!*chan || strcmp(chan, epgDb->getStrValue(cTableEvents::fiChannelId)) != 0)
+      {
+         if (*chan)
+            tell(0, "processed %-20s with %d events", chan, count - lcount);
+
+         lcount = count;
+         channels++;
+         strcpy(chan, epgDb->getStrValue(cTableEvents::fiChannelId));
+
+         tell(0, "processing %-20s now", chan);
+      }
+
+      tell(0, "-> '%s' - (%ld)", epgDb->getStrValue(cTableEvents::fiChannelId),
+           epgDb->getIntValue(cTableEvents::fiEventId));
+
+
+      count++;
+   }
+
+   s->freeResult();
+
+   tell(0, "---------------------------------------------------");
+   tell(0, "updated %d channels and %d events", channels, count);
+   tell(0, "---------------------------------------------------");
+
+   delete s;
+   delete epgDb;
+   delete mapDb;
+}
+
+//***************************************************************************
+// 
+//***************************************************************************
+
+void chkStatement4()
+{
+   cDbTable* eventDb = new cTableEvents(connection);
+   if (eventDb->open() != success) return;
+
+   cDbTable* imageRefDb = new cTableImageRefs(connection);
+   if (imageRefDb->open() != success) return;
+
+   cDbTable* imageDb = new cTableImages(connection);
+   if (imageDb->open() != success) return;
+
+   // select e.masterid, r.imagename, r.eventid, r.lfn, i.image 
+   //      from imagerefs r, images i, events e 
+   //      where r.imagename = i.imagename 
+   //         and e.eventid = r.eventid,
+   //         and i.image is not null 
+   //         and (i.updsp > ? or r.updsp > ?);
+
+   cDBS::FieldDef masterFld = { "masterid", cDBS::ffUInt,  0, 999, cDBS::ftData };
+   cDbValue masterId;
+   cDbValue imageData;
+   cDbValue imageUpdSp;
+
+   masterId.setField(&masterFld);
+   imageData.setField(imageDb->getField(cTableImages::fiImage));
+   imageUpdSp.setField(imageDb->getField(cTableImages::fiUpdSp));
+
+   cDbStatement* selectAllImages = new cDbStatement(imageRefDb);
+
+   selectAllImages->build("select ");
+   selectAllImages->setBindPrefix("e.");
+   selectAllImages->bind(&masterId, cDBS::bndOut);
+   selectAllImages->setBindPrefix("r.");
+   selectAllImages->bind(cTableImageRefs::fiImgName, cDBS::bndOut, ", ");
+   selectAllImages->bind(cTableImageRefs::fiEventId, cDBS::bndOut, ", ");
+   selectAllImages->bind(cTableImageRefs::fiLfn, cDBS::bndOut, ", ");
+   selectAllImages->setBindPrefix("i.");
+   selectAllImages->bind(&imageData, cDBS::bndOut, ", ");
+   selectAllImages->clrBindPrefix();
+   selectAllImages->build(" from %s r, %s i, %s e where ", 
+                          imageRefDb->TableName(), imageDb->TableName(), eventDb->TableName());
+   selectAllImages->build("e.%s = r.%s and i.%s = r.%s and i.%s is not null and (",
+                          eventDb->getField(cTableEvents::fiEventId)->name, 
+                          imageRefDb->getField(cTableImageRefs::fiEventId)->name,
+                          imageDb->getField(cTableImageRefs::fiImgName)->name,
+                          imageRefDb->getField(cTableImageRefs::fiImgName)->name,
+                          imageDb->getField(cTableImages::fiImage)->name);
+   selectAllImages->bindCmp("i", &imageUpdSp, ">");
+   selectAllImages->build(" or ");
+   selectAllImages->bindCmp("r", cTableImageRefs::fiUpdSp, 0, ">");
+   selectAllImages->build(");");
+
+   selectAllImages->prepare();
+
+   imageRefDb->clear();
+   imageRefDb->setValue(cTableImageRefs::fiUpdSp, 1377733333L);
+   imageUpdSp.setValue(1377733333L);
+   
+   int count = 0;
+   for (int res = selectAllImages->find(); res; res = selectAllImages->fetch())
+   {
+      count ++;
+   }
+   tell(0,"%d", count);
+}
+
+//***************************************************************************
+// Main
+//***************************************************************************
+
+int main()
+{
+   EPG2VDRConfig.logstdout = yes;
+   EPG2VDRConfig.loglevel = 2;
+
+   setlocale(LC_CTYPE, "");
+   char* lang = setlocale(LC_CTYPE, 0);
+   
+   if (lang)
+   {
+      tell(0, "Set locale to '%s'", lang);
+      
+      if ((strcasestr(lang, "UTF-8") != 0) || (strcasestr(lang, "UTF8") != 0))
+         tell(0, "detected UTF-8");
+      else
+         tell(0, "no UTF-8");
+   }
+   else
+   {
+      tell(0, "Reseting locale for LC_CTYPE failed.");
+   }
+
+   
+   cDbDict* dict = new cDbDict();
+
+   dict->in("../epg.dat");
+
+   delete dict;
+
+   return 0;
+
+   initConnection();
+
+   chkCompress();
+
+   tell(0, "duration was: '%s'", ms2Dur(2340).c_str());
+
+   // chkStatement1();
+   // chkStatement2();
+   // chkStatement3();
+   // chkStatement4();
+  
+   exitConnection();
+
+   return 0;
+}
diff --git a/patches/epghandler-segment-transfer.patch b/patches/epghandler-segment-transfer.patch
new file mode 100644
index 0000000..8374a66
--- /dev/null
+++ b/patches/epghandler-segment-transfer.patch
@@ -0,0 +1,65 @@
+--- ../vdr-2.0.2.plain//eit.c	2012-12-04 12:10:10.000000000 +0100
++++ eit.c	2013-05-22 16:49:37.635027462 +0200
+@@ -46,6 +46,8 @@
+      return;
+      }
+ 
++  EpgHandlers.BeginSegmentTransfer(channel, OnlyRunningStatus);
++
+   bool handledExternally = EpgHandlers.HandledExternally(channel);
+   cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(channel, true);
+ 
+@@ -310,6 +312,7 @@
+      Schedules->SetModified(pSchedule);
+      }
+   Channels.Unlock();
++  EpgHandlers.EndSegmentTransfer(Modified, OnlyRunningStatus);
+ }
+ 
+ // --- cTDT ------------------------------------------------------------------
+--- ../vdr-2.0.2.plain//epg.c	2013-02-17 15:12:07.000000000 +0100
++++ epg.c	2013-05-22 16:50:29.043029281 +0200
+@@ -1537,3 +1537,19 @@
+       }
+   Schedule->DropOutdated(SegmentStart, SegmentEnd, TableID, Version);
+ }
++
++void cEpgHandlers::BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus)
++{
++  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
++      if (eh->BeginSegmentTransfer(Channel, OnlyRunningStatus))
++         return;
++      }
++}
++
++void cEpgHandlers::EndSegmentTransfer(bool Modified, bool OnlyRunningStatus)
++{
++  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
++      if (eh->EndSegmentTransfer(Modified, OnlyRunningStatus))
++         return;
++      }
++}
+--- ../vdr-2.0.2.plain//epg.h	2012-09-24 14:53:53.000000000 +0200
++++ epg.h	2013-05-22 16:50:16.867028850 +0200
+@@ -273,6 +273,12 @@
+   virtual bool DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version) { return false; }
+           ///< Takes a look at all EPG events between SegmentStart and SegmentEnd and
+           ///< drops outdated events.
++  virtual bool BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus) { return false; }
++          ///< called directly after IgnoreChannel before any other handler method called
++          ///< designed to give handlers the ossibility to prepare a transaction 
++  virtual bool EndSegmentTransfer(bool Modified, bool OnlyRunningStatus) { return false; }
++          ///< called at last after the segment data is processed
++          ///< at this oint handlers should close/commt/rollback their transactions
+   };
+ 
+ class cEpgHandlers : public cList<cEpgHandler> {
+@@ -295,6 +301,8 @@
+   void HandleEvent(cEvent *Event);
+   void SortSchedule(cSchedule *Schedule);
+   void DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version);
++  void BeginSegmentTransfer(const cChannel *Channel, bool OnlyRunningStatus);
++  void EndSegmentTransfer(bool Modified, bool OnlyRunningStatus);
+   };
+ 
+ extern cEpgHandlers EpgHandlers;
diff --git a/patches/vdr-1.7.27-to-epghandler-of-1.7.31.patch b/patches/vdr-1.7.27-to-epghandler-of-1.7.31.patch
new file mode 100644
index 0000000..7be1802
--- /dev/null
+++ b/patches/vdr-1.7.27-to-epghandler-of-1.7.31.patch
@@ -0,0 +1,219 @@
+--- /home/wendel/vdr-1.7.27.plain//eit.c	2012-03-14 11:11:15.000000000 +0100
++++ eit.c	2012-10-01 09:38:51.526839349 +0200
+@@ -45,6 +45,7 @@
+      return;
+      }
+ 
++  bool handledExternally = EpgHandlers.HandledExternally(channel);
+   cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(channel, true);
+ 
+   bool Empty = true;
+@@ -70,14 +71,18 @@
+       cEvent *newEvent = NULL;
+       cEvent *rEvent = NULL;
+       cEvent *pEvent = (cEvent *)pSchedule->GetEvent(SiEitEvent.getEventId(), StartTime);
+-      if (!pEvent) {
++      if (!pEvent || handledExternally) {
+          if (OnlyRunningStatus)
+             continue;
++         if (handledExternally)
++            if (!EpgHandlers.IsUpdate(SiEitEvent.getEventId(), StartTime, Tid, getVersionNumber()))
++               continue;
+          // If we don't have that event yet, we create a new one.
+          // Otherwise we copy the information into the existing event anyway, because the data might have changed.
+          pEvent = newEvent = new cEvent(SiEitEvent.getEventId());
+          newEvent->SetStartTime(StartTime);
+          newEvent->SetDuration(Duration);
++         if (!handledExternally)
+          pSchedule->AddEvent(newEvent);
+          }
+       else {
+@@ -290,6 +295,9 @@
+          channel->SetLinkChannels(LinkChannels);
+       Modified = true;
+       EpgHandlers.HandleEvent(pEvent);
++
++        if (handledExternally)
++         delete pEvent;
+       }
+   if (Tid == 0x4E) {
+      if (Empty && getSectionNumber() == 0)
+--- /home/wendel/vdr-1.7.27.plain//epg.c	2012-03-10 14:14:27.000000000 +0100
++++ epg.c	2012-10-01 09:41:35.010845128 +0200
+@@ -18,6 +18,7 @@
+ #include "timers.h"
+ 
+ #define RUNNINGSTATUSTIMEOUT 30 // seconds before the running status is considered unknown
++#define EPGDATAWRITEDELTA   600 // seconds between writing the epg.data file
+ 
+ // --- tComponent ------------------------------------------------------------
+ 
+@@ -1109,6 +1110,47 @@
+   return false;
+ }
+ 
++// --- cEpgDataWriter ---------------------------------------------------------
++
++class cEpgDataWriter : public cThread {
++private:
++  cMutex mutex;
++protected:
++  virtual void Action(void);
++public:
++  cEpgDataWriter(void);
++  void Perform(void);
++  };
++
++cEpgDataWriter::cEpgDataWriter(void)
++:cThread("epg data writer")
++{
++}
++
++void cEpgDataWriter::Action(void)
++{
++  SetPriority(19);
++  SetIOPriority(7);
++  Perform();
++}
++
++void cEpgDataWriter::Perform(void)
++{
++  cMutexLock MutexLock(&mutex); // to make sure fore- and background calls don't cause parellel dumps!
++  {
++    cSchedulesLock SchedulesLock(true, 1000);
++    cSchedules *s = (cSchedules *)cSchedules::Schedules(SchedulesLock);
++    if (s) {
++       time_t now = time(NULL);
++       for (cSchedule *p = s->First(); p; p = s->Next(p))
++           p->Cleanup(now);
++       }
++  }
++  cSchedules::Dump();
++}
++
++static cEpgDataWriter EpgDataWriter;
++
+ // --- cSchedulesLock --------------------------------------------------------
+ 
+ cSchedulesLock::cSchedulesLock(bool WriteLock, int TimeoutMs)
+@@ -1152,28 +1194,13 @@
+   if (Force)
+      lastDump = 0;
+   time_t now = time(NULL);
+-  struct tm tm_r;
+-  struct tm *ptm = localtime_r(&now, &tm_r);
+-  if (now - lastCleanup > 3600) {
+-     isyslog("cleaning up schedules data");
+-     cSchedulesLock SchedulesLock(true, 1000);
+-     cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
+-     if (s) {
+-        for (cSchedule *p = s->First(); p; p = s->Next(p))
+-            p->Cleanup(now);
+-        }
+-     lastCleanup = now;
+-     if (ptm->tm_hour == 5)
+-        ReportEpgBugFixStats(true);
+-     }
+-  if (epgDataFileName && now - lastDump > 600) {
+-     cSafeFile f(epgDataFileName);
+-     if (f.Open()) {
+-        Dump(f);
+-        f.Close();
++  if (now - lastDump > EPGDATAWRITEDELTA) {
++     if (epgDataFileName) {
++        if (Force)
++           EpgDataWriter.Perform();
++        else if (!EpgDataWriter.Active())
++           EpgDataWriter.Start();
+         }
+-     else
+-        LOG_ERROR;
+      lastDump = now;
+      }
+ }
+@@ -1207,8 +1234,23 @@
+   cSchedulesLock SchedulesLock;
+   cSchedules *s = (cSchedules *)Schedules(SchedulesLock);
+   if (s) {
++     cSafeFile *sf = NULL;
++     if (!f) {
++        sf = new cSafeFile(epgDataFileName);
++        if (sf->Open())
++           f = *sf;
++        else {
++           LOG_ERROR;
++           delete sf;
++           return false;
++           }
++        }
+      for (cSchedule *p = s->First(); p; p = s->Next(p))
+          p->Dump(f, Prefix, DumpMode, AtTime);
++     if (sf) {
++        sf->Close();
++        delete sf;
++        }
+      return true;
+      }
+   return false;
+@@ -1329,6 +1371,24 @@
+          return true;
+       }
+   return false;
++}
++
++bool cEpgHandlers::HandledExternally(const cChannel *Channel)
++{
++  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
++      if (eh->HandledExternally(Channel))
++         return true;
++      }
++  return false;
++}
++
++bool cEpgHandlers::IsUpdate(tEventID EventID, time_t StartTime, uchar TableID, uchar Version)
++{
++  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
++      if (eh->IsUpdate(EventID, StartTime, TableID, Version))
++         return true;
++      }
++  return false;
+ }
+ 
+ void cEpgHandlers::SetEventID(cEvent *Event, tEventID EventID)
+--- /home/wendel/vdr-1.7.27.plain//epg.h	2012-03-10 14:50:10.000000000 +0100
++++ epg.h	2012-10-01 09:43:28.162849134 +0200
+@@ -207,7 +207,7 @@
+   static void Cleanup(bool Force = false);
+   static void ResetVersions(void);
+   static bool ClearAll(void);
+-  static bool Dump(FILE *f, const char *Prefix = "", eDumpMode DumpMode = dmAll, time_t AtTime = 0);
++  static bool Dump(FILE *f = NULL, const char *Prefix = "", eDumpMode DumpMode = dmAll, time_t AtTime = 0);
+   static bool Read(FILE *f = NULL);
+   cSchedule *AddSchedule(tChannelID ChannelID);
+   const cSchedule *GetSchedule(tChannelID ChannelID) const;
+@@ -244,6 +244,16 @@
+           ///< EPG handlers are queried to see if any of them would like to do the
+           ///< complete processing by itself. TableID and Version are from the
+           ///< incoming section data.
++  virtual bool HandledExternally(const cChannel *Channel) { return false; }
++          ///< If any EPG handler returns true in this function, it is assumed that
++          ///< the EPG for the given Channel is handled completely from some external
++          ///< source. Incoming EIT data is processed as usual, but any new EPG event
++          ///< will not be added to the respective schedule. It's up to the EPG
++          ///< handler to take care of this.
++  virtual bool IsUpdate(tEventID EventID, time_t StartTime, uchar TableID, uchar Version) { return false; }
++          ///<  VDR can't perform the update check (version, tid) for external handled events 
++          ///<  therefore the handle have to take care. Otherwise the parsing of 'non' updates will 
++          ///<  take a lot of resources
+   virtual bool SetEventID(cEvent *Event, tEventID EventID) { return false; }
+   virtual bool SetTitle(cEvent *Event, const char *Title) { return false; }
+   virtual bool SetShortText(cEvent *Event, const char *ShortText) { return false; }
+@@ -269,6 +279,8 @@
+ public:
+   bool IgnoreChannel(const cChannel *Channel);
+   bool HandleEitEvent(cSchedule *Schedule, const SI::EIT::Event *EitEvent, uchar TableID, uchar Version);
++  bool HandledExternally(const cChannel *Channel);
++  bool IsUpdate(tEventID EventID, time_t StartTime, uchar TableID, uchar Version);
+   void SetEventID(cEvent *Event, tEventID EventID);
+   void SetTitle(cEvent *Event, const char *Title);
+   void SetShortText(cEvent *Event, const char *ShortText);
diff --git a/patches/vdr-1.7.28-epghandledexternally.diff b/patches/vdr-1.7.28-epghandledexternally.diff
new file mode 100644
index 0000000..52dfab6
--- /dev/null
+++ b/patches/vdr-1.7.28-epghandledexternally.diff
@@ -0,0 +1,118 @@
+--- ./eit.c	2012/06/02 14:05:22	2.17
++++ ./eit.c	2012/06/04 10:10:11
+@@ -45,6 +45,7 @@
+      return;
+      }
+ 
++  bool handledExternally = EpgHandlers.HandledExternally(channel);
+   cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(channel, true);
+ 
+   bool Empty = true;
+@@ -70,7 +71,7 @@
+       cEvent *newEvent = NULL;
+       cEvent *rEvent = NULL;
+       cEvent *pEvent = (cEvent *)pSchedule->GetEvent(SiEitEvent.getEventId(), StartTime);
+-      if (!pEvent) {
++      if (!pEvent || handledExternally) {
+          if (OnlyRunningStatus)
+             continue;
+          // If we don't have that event yet, we create a new one.
+@@ -78,7 +79,8 @@
+          pEvent = newEvent = new cEvent(SiEitEvent.getEventId());
+          newEvent->SetStartTime(StartTime);
+          newEvent->SetDuration(Duration);
+-         pSchedule->AddEvent(newEvent);
++         if (!handledExternally)
++            pSchedule->AddEvent(newEvent);
+          }
+       else {
+          // We have found an existing event, either through its event ID or its start time.
+@@ -290,11 +292,8 @@
+          channel->SetLinkChannels(LinkChannels);
+       Modified = true;
+       EpgHandlers.HandleEvent(pEvent);
+-
+-      if (EpgHandlers.DeleteEvent(pEvent)) {
+-         pSchedule->DelEvent(pEvent);
+-         pEvent = NULL;
+-         }
++      if (handledExternally)
++         delete pEvent;
+       }
+   if (Tid == 0x4E) {
+      if (Empty && getSectionNumber() == 0)
+--- ./epg.c	2012/06/02 14:08:12	2.14
++++ ./epg.c	2012/06/04 10:06:22
+@@ -1331,6 +1331,15 @@
+   return false;
+ }
+ 
++bool cEpgHandlers::HandledExternally(const cChannel *Channel)
++{
++  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
++      if (eh->HandledExternally(Channel))
++         return true;
++      }
++  return false;
++}
++
+ void cEpgHandlers::SetEventID(cEvent *Event, tEventID EventID)
+ {
+   for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
+@@ -1429,15 +1438,6 @@
+       }
+ }
+ 
+-bool cEpgHandlers::DeleteEvent(const cEvent *Event)
+-{
+-  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
+-      if (eh->DeleteEvent(Event))
+-         return true;
+-      }
+-  return false;
+-}
+-
+ void cEpgHandlers::SortSchedule(cSchedule *Schedule)
+ {
+   for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
+--- ./epg.h	2012/06/02 14:07:51	2.10
++++ ./epg.h	2012/06/04 10:05:21
+@@ -244,6 +244,12 @@
+           ///< EPG handlers are queried to see if any of them would like to do the
+           ///< complete processing by itself. TableID and Version are from the
+           ///< incoming section data.
++  virtual bool HandledExternally(const cChannel *Channel) { return false; }
++          ///< If any EPG handler returns true in this function, it is assumed that
++          ///< the EPG for the given Channel is handled completely from some external
++          ///< source. Incoming EIT data is processed as usual, but any new EPG event
++          ///< will not be added to the respective schedule. It's up to the EPG
++          ///< handler to take care of this.
+   virtual bool SetEventID(cEvent *Event, tEventID EventID) { return false; }
+   virtual bool SetTitle(cEvent *Event, const char *Title) { return false; }
+   virtual bool SetShortText(cEvent *Event, const char *ShortText) { return false; }
+@@ -258,9 +264,6 @@
+   virtual bool HandleEvent(cEvent *Event) { return false; }
+           ///< After all modifications of the Event have been done, the EPG handler
+           ///< can take a final look at it.
+-  virtual bool DeleteEvent(const cEvent *Event) { return false; }
+-          ///< After the complete processing of the Event is finished, an EPG handler
+-          ///< can decide that this Event shall be deleted from its schedule.
+   virtual bool SortSchedule(cSchedule *Schedule) { return false; }
+           ///< Sorts the Schedule after the complete table has been processed.
+   virtual bool DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version) { return false; }
+@@ -272,6 +275,7 @@
+ public:
+   bool IgnoreChannel(const cChannel *Channel);
+   bool HandleEitEvent(cSchedule *Schedule, const SI::EIT::Event *EitEvent, uchar TableID, uchar Version);
++  bool HandledExternally(const cChannel *Channel);
+   void SetEventID(cEvent *Event, tEventID EventID);
+   void SetTitle(cEvent *Event, const char *Title);
+   void SetShortText(cEvent *Event, const char *ShortText);
+@@ -283,7 +287,6 @@
+   void SetVps(cEvent *Event, time_t Vps);
+   void FixEpgBugs(cEvent *Event);
+   void HandleEvent(cEvent *Event);
+-  bool DeleteEvent(const cEvent *Event);
+   void SortSchedule(cSchedule *Schedule);
+   void DropOutdated(cSchedule *Schedule, time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version);
+   };
diff --git a/patches/vdr-1.7.29-epgIsUpdate.diff b/patches/vdr-1.7.29-epgIsUpdate.diff
new file mode 100644
index 0000000..61549ca
--- /dev/null
+++ b/patches/vdr-1.7.29-epgIsUpdate.diff
@@ -0,0 +1,52 @@
+--- ../vdr-1.7.29.plain//eit.c  2012-06-04 12:26:10.000000000 +0200
++++ eit.c       2012-07-30 10:19:34.841894485 +0200
+@@ -74,6 +74,9 @@
+       if (!pEvent || handledExternally) {
+          if (OnlyRunningStatus)
+             continue;
++         if (handledExternally)
++            if (!EpgHandlers.IsUpdate(SiEitEvent.getEventId(), StartTime, Tid, getVersionNumber()))
++               continue;
+          // If we don't have that event yet, we create a new one.
+          // Otherwise we copy the information into the existing event anyway, because the data might have changed.
+          pEvent = newEvent = new cEvent(SiEitEvent.getEventId());
+--- ../vdr-1.7.29.plain//epg.c  2012-06-04 12:26:10.000000000 +0200
++++ epg.c       2012-07-30 10:21:51.153899306 +0200
+@@ -1340,6 +1340,15 @@
+   return false;
+ }
+ 
++bool cEpgHandlers::IsUpdate(tEventID EventID, time_t StartTime, uchar TableID, uchar Version)
++{
++  for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
++      if (eh->IsUpdate(EventID, StartTime, TableID, Version))
++         return true;
++      }
++  return false;
++}
++
+ void cEpgHandlers::SetEventID(cEvent *Event, tEventID EventID)
+ {
+   for (cEpgHandler *eh = First(); eh; eh = Next(eh)) {
+--- ../vdr-1.7.29.plain//epg.h  2012-06-04 12:26:10.000000000 +0200
++++ epg.h       2012-07-30 10:20:15.705895929 +0200
+@@ -250,6 +250,10 @@
+           ///< source. Incoming EIT data is processed as usual, but any new EPG event
+           ///< will not be added to the respective schedule. It's up to the EPG
+           ///< handler to take care of this.
++  virtual bool IsUpdate(tEventID EventID, time_t StartTime, uchar TableID, uchar Version) { return false; }
++          ///<  VDR can't perform the update check (version, tid) for external handled events 
++          ///<  therefore the handle have to take care. Otherwise the parsing of 'non' updates will 
++          ///<  take a lot of resources
+   virtual bool SetEventID(cEvent *Event, tEventID EventID) { return false; }
+   virtual bool SetTitle(cEvent *Event, const char *Title) { return false; }
+   virtual bool SetShortText(cEvent *Event, const char *ShortText) { return false; }
+@@ -277,6 +281,7 @@
+   bool IgnoreChannel(const cChannel *Channel);
+   bool HandleEitEvent(cSchedule *Schedule, const SI::EIT::Event *EitEvent, uchar TableID, uchar Version);
+   bool HandledExternally(const cChannel *Channel);
++  bool IsUpdate(tEventID EventID, time_t StartTime, uchar TableID, uchar Version);
+   void SetEventID(cEvent *Event, tEventID EventID);
+   void SetTitle(cEvent *Event, const char *Title);
+   void SetShortText(cEvent *Event, const char *ShortText);
+
diff --git a/po/de_DE.po b/po/de_DE.po
new file mode 100644
index 0000000..2596418
--- /dev/null
+++ b/po/de_DE.po
@@ -0,0 +1,86 @@
+# VDR plugin language source file.
+# Copyright (C) 2007 Klaus Schmidinger <kls at cadsoft.de>
+# This file is distributed under the same license as the VDR package.
+# Klaus Schmidinger <kls at cadsoft.de>, 2000
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: VDR 1.5.7\n"
+"Report-Msgid-Bugs-To: <vdr at jwendel.de>\n"
+"POT-Creation-Date: 2014-02-05 09:31+0100\n"
+"PO-Revision-Date: 2009-08-27 21:40+0200\n"
+"Last-Translator: Klaus Schmidinger <kls at cadsoft.de>\n"
+"Language-Team: <vdr at linuxtv.org>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "Reload"
+msgstr "Neu laden"
+
+#, fuzzy
+msgid "Update"
+msgstr "Automatisches Update"
+
+msgid "Reload EPG"
+msgstr "EPG komplett neu einlesen"
+
+#, fuzzy
+msgid "Update EPG"
+msgstr "Automatisches Update"
+
+#, fuzzy
+msgid "EPG Update"
+msgstr "Automatisches Update"
+
+msgid "Show In Main Menu"
+msgstr "Im Hauptmenü anzeigen"
+
+msgid "Update DVB EPG Database"
+msgstr "DVB/EPG in Datenbank eintragen"
+
+msgid "Load Images"
+msgstr "Bilder laden"
+
+msgid "Prohibit Shutdown On Busy 'epgd'"
+msgstr "Herunterfahren erlauben, auch wenn 'epgd' beschäftigt ist"
+
+msgid "Schedule Boot For Update"
+msgstr "Booten für geplante Updates"
+
+msgid "MySQL"
+msgstr ""
+
+msgid "Host"
+msgstr ""
+
+msgid "Port"
+msgstr ""
+
+msgid "Database Name"
+msgstr "Name der Datenbank"
+
+msgid "User"
+msgstr "Benutzer"
+
+msgid "Password"
+msgstr "Passwort"
+
+msgid "NO EPG"
+msgstr ""
+
+msgid "Blacklist not configured Channels"
+msgstr "Nicht konfigurierte Kanäle 'blacklisten'"
+
+msgid "Technical Stuff"
+msgstr "Technisches"
+
+msgid "Log level"
+msgstr "Protokollierungs Stufe"
+
+msgid "EPG2VDR Waiting on epgd"
+msgstr ""
+
+#~ msgid "Updatetime (hours)"
+#~ msgstr "Updatezeit (Stunden)"
diff --git a/po/it_IT.po b/po/it_IT.po
new file mode 100644
index 0000000..035819e
--- /dev/null
+++ b/po/it_IT.po
@@ -0,0 +1,92 @@
+# VDR plugin language source file.
+# Copyright (C) 2007 Klaus Schmidinger <kls at cadsoft.de>
+# This file is distributed under the same license as the VDR package.
+# Alberto Carraro <bertocar at tin.it>, 2001
+# Antonio Ospite <ospite at studenti.unina.it>, 2003
+# Sean Carlos <seanc at libero.it>, 2005
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: VDR 1.5.7\n"
+"Report-Msgid-Bugs-To: <vdr at jwendel.de>\n"
+"POT-Creation-Date: 2014-02-05 09:31+0100\n"
+"PO-Revision-Date: 2009-08-27 21:45+0100\n"
+"Last-Translator: Diego Pierotto <vdr-italian at tiscali.it>\n"
+"Language-Team:  <vdr at linuxtv.org>\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Italian\n"
+"X-Poedit-Country: ITALY\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+msgid "Reload"
+msgstr ""
+
+#, fuzzy
+msgid "Update"
+msgstr "Aggiorn. automatico"
+
+msgid "Reload EPG"
+msgstr ""
+
+#, fuzzy
+msgid "Update EPG"
+msgstr "Aggiorn. automatico"
+
+#, fuzzy
+msgid "EPG Update"
+msgstr "Aggiorn. automatico"
+
+msgid "Show In Main Menu"
+msgstr "Mostra nel menu principale"
+
+msgid "Update DVB EPG Database"
+msgstr ""
+
+msgid "Load Images"
+msgstr ""
+
+msgid "Prohibit Shutdown On Busy 'epgd'"
+msgstr ""
+
+msgid "Schedule Boot For Update"
+msgstr ""
+
+msgid "MySQL"
+msgstr ""
+
+msgid "Host"
+msgstr ""
+
+msgid "Port"
+msgstr ""
+
+msgid "Database Name"
+msgstr ""
+
+msgid "User"
+msgstr ""
+
+msgid "Password"
+msgstr ""
+
+msgid "NO EPG"
+msgstr ""
+
+msgid "Blacklist not configured Channels"
+msgstr ""
+
+msgid "Technical Stuff"
+msgstr ""
+
+msgid "Log level"
+msgstr "xxx #AS# "
+
+msgid "EPG2VDR Waiting on epgd"
+msgstr ""
+
+#, fuzzy
+#~ msgid "Updatetime (hours)"
+#~ msgstr "Tempo aggiorn. (min)"
diff --git a/timer.c b/timer.c
new file mode 100644
index 0000000..4aed3d5
--- /dev/null
+++ b/timer.c
@@ -0,0 +1,127 @@
+/*
+ * timer.c: EPG2VDR plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <vdr/tools.h>
+
+#include "update.h"
+
+//***************************************************************************
+// Update Timer
+//***************************************************************************
+
+int cUpdate::updateTimer()
+{
+   cMutexLock lock(&timerMutex);
+
+   tell(1, "Updating timer");
+
+   timerDb->deleteWhere("STATE = 'D'");
+
+   // remove deleted timers
+
+   timerDb->clear();
+   timerDb->setValue(cTableTimers::fiVdrUuid, EPG2VDRConfig.uuid);
+
+   for (int f = selectMyTimer->find(); f; f = selectMyTimer->fetch())
+   {
+      int exist = no;
+
+      for (cTimer* t = Timers.First(); t; t = Timers.Next(t)) 
+      {
+         if (t->StartTime() == timerDb->getIntValue(cTableTimers::fiStartTime) &&
+             strcmp(t->Channel()->GetChannelID().ToString(), timerDb->getStrValue(cTableTimers::fiChannelId)) == 0)
+         {
+            exist = yes;
+            break;
+         }
+      }
+
+      if (!exist)
+      {
+         timerDb->setValue(cTableTimers::fiState, "D");
+         timerDb->update();
+      }
+   }
+
+   // update timers
+
+   for (cTimer* t = Timers.First(); t; t = Timers.Next(t)) 
+   {
+      int insert;
+      
+      if (!t->Event())
+      {
+         // tell(0, "Fatal: Found timer without event!");
+         continue;
+      }
+
+      cString channelId = t->Event()->ChannelID().ToString();
+
+      timerDb->clear();
+      timerDb->setValue(cTableTimers::fiEventId, (long)t->Event()->EventID());
+      timerDb->setValue(cTableTimers::fiChannelId, channelId);
+      timerDb->setValue(cTableTimers::fiVdrUuid, EPG2VDRConfig.uuid);
+      
+      insert = !timerDb->find();
+      
+      timerDb->setValue(cTableTimers::fiState, "A");
+      timerDb->setValue(cTableTimers::fiStartTime, t->StartTime());
+      timerDb->setValue(cTableTimers::fiEndTime, t->StopTime());
+
+      if (insert)
+         timerDb->insert();
+      else
+         timerDb->update();
+
+      tell(1, "%s timer for event %u", insert ? "Insert" : "Update", t->Event()->EventID());
+   }
+
+   timerUpdateTriggered = no;
+
+   return success;
+}
+
+//***************************************************************************
+// Notifications from VDRs Status Interface
+//***************************************************************************
+//***************************************************************************
+// Timers Change Notification
+//***************************************************************************
+
+void cUpdate::TimerChange(const cTimer* Timer, eTimerChange Change)
+{
+   cMutexLock lock(&timerMutex);
+
+   if (Change == tcMod)
+   {
+      tell(2, "Timer changed, updating");
+      timerUpdateTriggered = yes;
+   }
+//    else if (Change == tcDel)
+//    {
+//       if (Timer->Event())
+//       {
+//          TimerId tid;
+//
+//          tell(2, "Timer of %u/%s deleted", Timer->Event()->EventID(), 
+//               (const char*)Timer->Event()->ChannelID().ToString());
+         
+//          tid.eventId = Timer->Event()->EventID();
+//          strcpy(tid.channelId, (const char*)Timer->Event()->ChannelID().ToString());
+//          deletedTimers.push_back(tid);
+//       }
+//    }
+}
+
+//***************************************************************************
+// Channel Switch Notification
+//***************************************************************************
+
+void cUpdate::ChannelSwitch(const cDevice *Device, int ChannelNumber, bool LiveView)
+{
+   // to be implemented ...
+}
diff --git a/update.c b/update.c
new file mode 100644
index 0000000..85ae1bd
--- /dev/null
+++ b/update.c
@@ -0,0 +1,1243 @@
+/*
+ * update.c: EPG2VDR plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <locale.h>
+
+#include <vdr/videodir.h>
+#include <vdr/tools.h>
+
+#include "epg2vdr.h"
+#include "update.h"
+#include "handler.h"
+
+//***************************************************************************
+// Class Update
+//***************************************************************************
+
+cUpdate::cUpdate(cPluginEPG2VDR* plugin)
+   : cThread("update thread started")
+{
+   char* pdir;
+   char* lang;
+
+   // thread / update control
+
+   connection = 0;
+   loopActive = no;
+   updateTriggered = no;
+   timerUpdateTriggered = yes;
+   fullreload = no;
+   nextEpgdUpdateAt = 0;
+   lastUpdateAt = 0;
+   lastEventsUpdateAt = 0;
+   epgdBusy = yes;
+   epgdUpdating = no;
+   eventRefreshOnly = no;
+   epgdState = cEpgdState::esUnknown;
+
+   // 
+
+   compDb = 0;
+   eventsDb = 0;
+   fileDb = 0;
+   imageDb = 0;
+   imageRefDb = 0;
+   episodeDb = 0;
+   vdrDb = 0;
+   mapDb = 0;
+   timerDb = 0;
+
+   selectMasterVdr = 0;
+   selectAllImages = 0;
+   selectUpdEvents = 0;
+   selectAllChannels = 0;
+   selectAllVdrChannels = 0;
+   selectComponentsOf = 0;
+   deleteTimer = 0;
+   selectMyTimer = 0;
+
+   // 
+
+   epgimagedir = 0;
+   withutf8 = no;
+   epgHandler = 0;
+
+   lang = setlocale(LC_CTYPE, 0);
+
+   if (lang)
+   {
+      tell(0, "Set locale to '%s'", lang);
+
+      if ((strcasestr(lang, "UTF-8") != 0) || (strcasestr(lang, "UTF8") != 0))
+      {
+         tell(0, "detected UTF-8");
+         withutf8 = yes;
+      }
+   }
+   else
+   {
+      tell(0, "Reseting locale for LC_CTYPE failed.");
+   }
+  
+   strcpy(imageExtension, "jpg");
+   asprintf(&epgimagedir, "%s/epgimages", EPG2VDR_DATA_DIR);
+   asprintf(&pdir, "%s/images", epgimagedir);
+
+   if (!(DirectoryOk(pdir) || MakeDirs(pdir, true)))
+      tell(0, "could not access or create Directory %s", pdir);
+  
+   free(pdir);
+
+   // check uuid
+
+   if (isEmpty(EPG2VDRConfig.uuid))
+   {
+      sstrcpy(EPG2VDRConfig.uuid, getUniqueId(), sizeof(EPG2VDRConfig.uuid));
+      plugin->SetupStore("Uuid", EPG2VDRConfig.uuid);
+      Setup.Save();
+
+      tell(0, "Initially created uuid '%s'", EPG2VDRConfig.uuid);
+   }
+
+   // init database ...
+
+   cDbConnection::setEncoding(withutf8 ? "utf8": "latin1"); // mysql uses latin1 for ISO8851-1
+   cDbConnection::setHost(EPG2VDRConfig.dbHost);
+   cDbConnection::setPort(EPG2VDRConfig.dbPort);
+   cDbConnection::setName(EPG2VDRConfig.dbName);
+   cDbConnection::setUser(EPG2VDRConfig.dbUser);
+   cDbConnection::setPass(EPG2VDRConfig.dbPass);
+
+   cDbTable::setConfPath(cPlugin::ConfigDirectory("epg2vdr/"));
+
+   // init epg handler
+
+   epgHandler = new cEpg2VdrEpgHandler(this);
+
+   // open tables ..
+
+   if (initDb() != success)
+      exitDb();
+}
+
+//***************************************************************************
+// 
+//***************************************************************************
+
+cUpdate::~cUpdate()
+{
+   if (loopActive)
+      Stop();
+   
+   free(epgimagedir);
+   
+   exitDb();
+}
+
+//***************************************************************************
+// Init/Exit Database Connections
+//***************************************************************************
+
+int cUpdate::initDb()
+{
+   int status = success;
+
+   if (!connection)
+      connection = new cDbConnection();
+
+   vdrDb = new cTableVdrs(connection);
+   if (vdrDb->open() != success) return fail;
+      
+   // DB-API check 
+
+   vdrDb->clear();
+   vdrDb->setValue(cTableVdrs::fiUuid, EPGDNAME);
+
+   if (!vdrDb->find())
+   {
+      tell(0, "Can't lookup epgd information, start epgd to create the tables first! Aborting now.");
+      return fail;
+   }
+
+   vdrDb->reset();
+
+   if (vdrDb->getIntValue(cTableVdrs::fiDbApi) != DB_API)
+   {
+      tell(0, "Found dbapi %d, expected %d, please alter the tables first! Aborting now.",  
+           (int)vdrDb->getIntValue(cTableVdrs::fiDbApi), DB_API);
+      return fail;
+   }
+
+   // open tables ..
+
+   mapDb = new cTableChannelMap(connection);
+   if (mapDb->open() != success) return fail;
+
+   fileDb = new cTableFileRefs(connection);
+   if (fileDb->open() != success) return fail;
+
+   imageDb = new cTableImages(connection);
+   if (imageDb->open() != success) return fail;
+
+   imageRefDb = new cTableImageRefs(connection);
+   if (imageRefDb->open() != success) return fail;
+
+   episodeDb = new cTableEpisodes(connection);
+   if (episodeDb->open() != success) return fail;
+
+   eventsDb = new cTableEvents(connection);
+   if (eventsDb->open() != success) return fail;
+
+   compDb = new cTableComponents(connection);
+   if (compDb->open() != success) return fail;
+
+   timerDb = new cTableTimers(connection);
+   if (timerDb->open() != success) return fail;
+
+   // -------------------------------------------
+   // init statements
+
+   selectMasterVdr = new cDbStatement(vdrDb);
+
+   // select uuid, name from vdrs
+   //    where upper(master) = 'Y' and uuid <>  ?;
+
+   selectMasterVdr->build("select ");
+   selectMasterVdr->bind(cTableVdrs::fiUuid, cDBS::bndOut);
+   selectMasterVdr->bind(cTableVdrs::fiName, cDBS::bndOut, ", ");
+   selectMasterVdr->build(" from %s where upper(master) = 'Y' and ", vdrDb->TableName());
+   selectMasterVdr->bindCmp(0, cTableVdrs::fiUuid, 0, "<>");
+
+   status += selectMasterVdr->prepare();
+
+   // all images
+
+   selectAllImages = new cDbStatement(imageRefDb);
+
+   // prepare fields
+
+   cDBS::FieldDef imageSizeDef = { "image", cDBS::ffUInt,  0, 999, cDBS::ftData };
+   imageSize.setField(&imageSizeDef);
+   imageUpdSp.setField(imageDb->getField(cTableImages::fiUpdSp));
+   masterId.setField(eventsDb->getField(cTableEvents::fiMasterId));
+
+   // select e.masterid, r.imagename, r.eventid, r.lfn, length(i.image)
+   //      from imagerefs r, images i, events e 
+   //      where i.imagename = r.imagename 
+   //         and e.eventid = r.eventid
+   //         and (i.updsp > ? or r.updsp > ?)
+
+   selectAllImages->build("select ");
+   selectAllImages->setBindPrefix("e.");
+   selectAllImages->bind(&masterId, cDBS::bndOut);
+   selectAllImages->setBindPrefix("r.");
+   selectAllImages->bind(cTableImageRefs::fiImgName, cDBS::bndOut, ", ");
+   selectAllImages->bind(cTableImageRefs::fiEventId, cDBS::bndOut, ", ");
+   selectAllImages->bind(cTableImageRefs::fiLfn, cDBS::bndOut, ", ");
+   selectAllImages->setBindPrefix("i.");
+   selectAllImages->build(", length(");
+   selectAllImages->bind(&imageSize, cDBS::bndOut);
+   selectAllImages->build(")");
+   selectAllImages->clrBindPrefix();
+   selectAllImages->build(" from %s r, %s i, %s e where ", 
+                          imageRefDb->TableName(), imageDb->TableName(), eventsDb->TableName());
+   selectAllImages->build("e.%s = r.%s and i.%s = r.%s and (",
+                          eventsDb->getField(cTableEvents::fiEventId)->name, 
+                          imageRefDb->getField(cTableImageRefs::fiEventId)->name,
+                          imageDb->getField(cTableImages::fiImgName)->name,
+                          imageRefDb->getField(cTableImageRefs::fiImgName)->name);
+   selectAllImages->bindCmp("i", &imageUpdSp, ">");
+   selectAllImages->build(" or ");
+   selectAllImages->bindCmp("r", cTableImageRefs::fiUpdSp, 0, ">");
+   selectAllImages->build(")");
+
+   status += selectAllImages->prepare();
+
+   // select distinct channelid, channelname 
+   //   from channelmap;
+
+   selectAllChannels = new cDbStatement(mapDb);
+
+   selectAllChannels->build("select distinct ");
+   selectAllChannels->bind(cTableChannelMap::fiChannelId, cDBS::bndOut);
+   selectAllChannels->bind(cTableChannelMap::fiChannelName, cDBS::bndOut, ", ");
+   selectAllChannels->build(" from %s;", mapDb->TableName());
+
+   status += selectAllChannels->prepare();
+
+   // select channelid, source 
+   //   from channlemap 
+   //    where source = ?;
+
+   selectAllVdrChannels = new cDbStatement(mapDb);
+
+   selectAllVdrChannels->build("select ");
+   selectAllVdrChannels->bind(cTableChannelMap::fiChannelId, cDBS::bndOut);
+   selectAllVdrChannels->bind(cTableChannelMap::fiChannelName, cDBS::bndOut, ", ");
+   selectAllVdrChannels->bind(cTableChannelMap::fiSource, cDBS::bndOut, ", ");
+   selectAllVdrChannels->build(" from %s where ", mapDb->TableName());
+   selectAllVdrChannels->bind(cTableChannelMap::fiSource, cDBS::bndIn | cDBS::bndSet);
+
+   status += selectAllVdrChannels->prepare();
+
+   // select changed events
+
+   selectUpdEvents = new cDbStatement(eventsDb);
+
+   // select useid, eventid, source, delflg, updflg, fileref, 
+   //        tableid, version, title, shorttext, starttime, 
+   //        duration, parentalrating, vps, description
+   //    from eventsview
+   //      where 
+   //        channelid = ?
+   //        and updsp > ?
+   //        and updflg in (.....)
+
+   selectUpdEvents->build("select ");
+   selectUpdEvents->bind(cTableEvents::fiUseId, cDBS::bndOut);
+//   selectUpdEvents->bind(cTableEvents::fiMasterId, cDBS::bndOut, ", ");
+   selectUpdEvents->bind(cTableEvents::fiEventId, cDBS::bndOut, ", ");
+   selectUpdEvents->bind(cTableEvents::fiSource, cDBS::bndOut, ", ");
+   selectUpdEvents->bind(cTableEvents::fiDelFlg, cDBS::bndOut, ", ");
+   selectUpdEvents->bind(cTableEvents::fiUpdFlg, cDBS::bndOut, ", ");
+   selectUpdEvents->bind(cTableEvents::fiFileRef, cDBS::bndOut, ", ");
+   selectUpdEvents->bind(cTableEvents::fiTableId, cDBS::bndOut, ", ");
+   selectUpdEvents->bind(cTableEvents::fiVersion, cDBS::bndOut, ", ");
+   selectUpdEvents->bind(cTableEvents::fiTitle, cDBS::bndOut, ", ");
+   selectUpdEvents->bind(cTableEvents::fiShortText, cDBS::bndOut, ", ");
+   selectUpdEvents->bind(cTableEvents::fiStartTime, cDBS::bndOut, ", ");
+   selectUpdEvents->bind(cTableEvents::fiDuration, cDBS::bndOut, ", ");
+   selectUpdEvents->bind(cTableEvents::fiParentalRating, cDBS::bndOut, ", ");
+   selectUpdEvents->bind(cTableEvents::fiVps,  cDBS::bndOut, ", ");
+   selectUpdEvents->bind(cTableEvents::fiDescription, cDBS::bndOut, ", ");
+   selectUpdEvents->build(" from eventsview where ");
+   selectUpdEvents->bind(cTableEvents::fiChannelId, cDBS::bndIn | cDBS::bndSet);
+   selectUpdEvents->bindCmp(0, cTableEvents::fiUpdSp, 0, ">", " and ");
+   selectUpdEvents->build(" and updflg in (%s)", Us::getNeeded());
+
+   status += selectUpdEvents->prepare();
+
+   // ...
+  
+   // select stream, type, lang, description 
+   //  from components where 
+   // eventid = ?;
+   // channelid = ?;
+
+   selectComponentsOf = new cDbStatement(compDb);
+   
+   selectComponentsOf->build("select ");
+   selectComponentsOf->bind(cTableComponents::fiStream, cDBS::bndOut);
+   selectComponentsOf->bind(cTableComponents::fiType, cDBS::bndOut, ",");
+   selectComponentsOf->bind(cTableComponents::fiLang, cDBS::bndOut, ",");
+   selectComponentsOf->bind(cTableComponents::fiDescription, cDBS::bndOut, ",");
+   selectComponentsOf->build(" from %s where ", compDb->TableName());
+   selectComponentsOf->bind(cTableComponents::fiEventId, cDBS::bndIn |cDBS::bndSet);
+   selectComponentsOf->bind(cTableComponents::fiChannelId, cDBS::bndIn |cDBS::bndSet, " and ");
+   
+   status += selectComponentsOf->prepare();
+   
+   // select eventid, channelid, starttime, state, endtime
+   //   from timers where 
+   //     vdruuid = ?
+
+   selectMyTimer = new cDbStatement(timerDb);
+   
+   selectMyTimer->build("select ");
+   selectMyTimer->bind(cTableTimers::fiEventId, cDBS::bndOut);
+   selectMyTimer->bind(cTableTimers::fiChannelId, cDBS::bndOut, ",");
+   selectMyTimer->bind(cTableTimers::fiStartTime, cDBS::bndOut, ",");
+   selectMyTimer->bind(cTableTimers::fiState, cDBS::bndOut, ",");
+   selectMyTimer->bind(cTableTimers::fiEndTime, cDBS::bndOut, ",");
+   selectMyTimer->build(" from %s where ", timerDb->TableName());
+   selectMyTimer->bind(cTableTimers::fiVdrUuid, cDBS::bndIn |cDBS::bndSet);
+   
+   status += selectMyTimer->prepare();
+
+   // delete from timers where 
+
+   deleteTimer = new cDbStatement(timerDb);
+
+   deleteTimer->build("delete from %s where ", timerDb->TableName());
+   deleteTimer->bind(cTableTimers::fiEventId, cDBS::bndIn |cDBS::bndSet);
+   deleteTimer->bind(cTableTimers::fiChannelId, cDBS::bndIn |cDBS::bndSet, " and ");
+   deleteTimer->bind(cTableTimers::fiVdrUuid, cDBS::bndIn |cDBS::bndSet, " and ");
+
+   status += deleteTimer->prepare();
+
+   if (status == success)
+   {
+      // -------------------------------------------
+      // lookback -> get last stamp
+      
+      vdrDb->clear();
+      vdrDb->setValue(cTableVdrs::fiUuid, EPG2VDRConfig.uuid);
+      
+      if (vdrDb->find())
+      {
+         char buf[50+TB];
+
+         lastUpdateAt = vdrDb->getIntValue(cTableVdrs::fiLastUpdate);
+         lastEventsUpdateAt = lastUpdateAt;
+
+         strftime(buf, 50, "%y.%m.%d %H:%M:%S", localtime(&lastUpdateAt));
+         tell(0, "Info: Last update was at '%s'", buf);
+      }
+      
+      // register me to the vdrs table
+      
+      char* v;
+
+      asprintf(&v, "vdr %s epg2vdr %s (%s)", VDRVERSION, VERSION, VERSION_DATE);
+      vdrDb->setValue(cTableVdrs::fiUuid, EPG2VDRConfig.uuid);
+      vdrDb->setValue(cTableVdrs::fiIp, getFirstIp());
+      vdrDb->setValue(cTableVdrs::fiName, getHostName());
+      vdrDb->setValue(cTableVdrs::fiDbApi, DB_API);
+      vdrDb->setValue(cTableVdrs::fiVersion, v);
+      vdrDb->setValue(cTableVdrs::fiState, "attached");
+      vdrDb->setValue(cTableVdrs::fiMaster, "n");
+
+      // set svdrp port if uninitialized, we cant query ther actual port from VDR
+
+//       if (vdrDb->getIntValue(cTableVdrs::fiSvdrp) == 0)
+//          vdrDb->setValue(cTableVdrs::fiSvdrp, 6419);
+
+      vdrDb->store();
+      free(v);
+   }
+
+   if (status == success)
+   {
+      status += epgHandler->updateExternalIdsMap(mapDb);
+      checkHandlerRole();
+   }
+
+   return status;
+}
+
+int cUpdate::exitDb()
+{
+   // de-register me at the vdrs table
+
+   if (vdrDb && vdrDb->isConnected())
+   {
+      vdrDb->setValue(cTableVdrs::fiUuid, EPG2VDRConfig.uuid);
+      vdrDb->find();
+      vdrDb->setValue(cTableVdrs::fiMaster, "n");
+      vdrDb->setValue(cTableVdrs::fiState, "detached");
+      vdrDb->store();
+   }
+
+   delete selectAllImages;      selectAllImages = 0;
+   delete selectUpdEvents;      selectUpdEvents = 0;
+   delete selectAllChannels;    selectAllChannels = 0;
+   delete selectAllVdrChannels; selectAllVdrChannels = 0;
+   delete selectComponentsOf;   selectComponentsOf = 0;
+   delete selectMasterVdr;      selectMasterVdr = 0;
+   delete deleteTimer;          deleteTimer = 0;
+   delete selectMyTimer;        selectMyTimer = 0;
+
+   delete eventsDb;    eventsDb = 0;
+   delete compDb;     compDb = 0;
+   delete fileDb;     fileDb = 0;
+   delete imageDb;    imageDb = 0;
+   delete imageRefDb; imageRefDb = 0;
+   delete episodeDb;  episodeDb = 0;
+   delete vdrDb;      vdrDb = 0;
+   delete mapDb;      mapDb = 0;
+   delete timerDb;    timerDb = 0;
+
+   delete connection; connection = 0;
+
+   return done;
+}
+
+//***************************************************************************
+// Get Timer Of Event
+//***************************************************************************
+
+cTimer* cUpdate::getTimerOf(const cEvent* event) const
+{
+   for (cTimer* t = Timers.First(); t; t = Timers.Next(t)) 
+      if (t->Event() == event)
+         return t;
+
+   return 0;
+}
+
+//***************************************************************************
+// Check Connection
+//***************************************************************************
+
+int cUpdate::checkConnection(int& timeout)
+{
+   static int retry = 0;
+
+   timeout = retry < 5 ? 10 : 60;
+
+   // check connection
+
+   if (!dbConnected(yes))
+   {
+      // try to connect
+
+      tell(0, "Trying to re-connect to database!");
+      retry++;
+
+      if (initDb() != success)
+      {
+         tell(0, "Retry #%d failed, retrying in %d seconds!", retry, timeout);
+         exitDb();
+
+         return fail;
+      }
+
+      retry = 0;         
+      tell(0, "Connection established successfull!");
+   }
+
+   return success;
+}
+
+//***************************************************************************
+// Check Handler Role
+//***************************************************************************
+
+void cUpdate::checkHandlerRole()
+{
+   char buf[1+TB];
+   int active;
+   char flag = 0;
+
+/*
+  wenn no    - mich ausschalten und feritg (db 'n' und handler off)
+  wenn auto  - aushandeln wie gehabt
+  wenn yes   - (db 'Y' und handler on)
+
+*/
+
+   if (EPG2VDRConfig.masterMode == mmAuto)
+   {
+      tell(3, "Auto check master role");
+
+      // select where "uuid <> ? and upper(master) = 'Y'"
+
+      vdrDb->clear();
+      vdrDb->setValue(cTableVdrs::fiUuid, EPG2VDRConfig.uuid);
+      
+      active = !selectMasterVdr->find();
+
+      if (!active)
+         tell(3, "Master found, uuid '%s' (%s)", 
+              vdrDb->getStrValue(cTableVdrs::fiUuid), 
+              vdrDb->getStrValue(cTableVdrs::fiName));
+      
+      selectMasterVdr->freeResult();
+      
+      flag = active ? 'y' : 'n';
+   }
+   else if (EPG2VDRConfig.masterMode == mmYes)
+   {
+      flag = 'Y';
+      active = yes;
+   }
+   else
+   {
+      flag = 'n';
+      active = no;
+   }
+
+   // write again to force update the updsp
+
+   vdrDb->clear();
+   vdrDb->setValue(cTableVdrs::fiUuid, EPG2VDRConfig.uuid);
+   vdrDb->find();
+
+   vdrDb->setValue(cTableVdrs::fiState, "attached");
+   vdrDb->setValue(cTableVdrs::fiMaster, c2s(flag, buf));
+   vdrDb->store();
+
+   if (!epgdBusy && epgHandler->getActive() != active)
+   {
+      tell(2, "Change handler state to '%s'", active ? "active" : "standby");
+      epgHandler->setActive(active);
+   }
+}
+
+//***************************************************************************
+// Updates Pending
+//***************************************************************************
+
+int cUpdate::updatesPending(int timeout)
+{
+   int pending = no;
+
+   epgdBusy = no;
+   epgdUpdating = no;
+
+   // check epgd state
+
+   vdrDb->clear();
+   vdrDb->setValue(cTableVdrs::fiUuid, EPGDNAME);
+
+   if (vdrDb->find())
+   {
+      nextEpgdUpdateAt = vdrDb->getIntValue(cTableVdrs::fiNextUpdate);
+      epgdState = cEpgdState::toState(vdrDb->getStrValue(cTableVdrs::fiState));
+
+      if (epgdState >= cEpgdState::esBusy && epgdState < cEpgdState::esBusyImages)
+      {
+         epgdBusy = yes;
+
+         if (epgHandler->getActive())
+            tell(2, "Change handler state to 'standby'");
+
+         epgHandler->setActive(no);
+
+         pending = epgdState == Es::esBusyMatch || epgdState == Es::esBusyEvents;
+
+//         if (pending)
+         {
+            char buf[50+TB];
+            time_t updsp = vdrDb->getIntValue(cTableVdrs::fiUpdSp);
+
+            strftime(buf, 50, "%y.%m.%d %H:%M:%S", localtime(&updsp));
+            tell(2, "Info: Updates pending but epgd '%s' since '%s', retrying in %d seconds", 
+                 Es::toName(epgdState), buf, timeout);
+            
+            // set but don't reset here!
+            
+            if (epgdState == Es::esBusyMatch)
+               eventRefreshOnly = yes;
+            
+            if (epgdState == cEpgdState::esBusyEvents)
+               epgdUpdating = yes;
+         }
+      }
+
+      // foce pending if ..
+
+      if (updateTriggered || vdrDb->getIntValue(cTableVdrs::fiLastUpdate) > lastUpdateAt)
+         pending = yes;
+   }
+
+   vdrDb->reset();
+
+   return pending;
+}
+
+//***************************************************************************
+// Trigger Update
+//***************************************************************************
+
+void cUpdate::triggerUpdate(int reload)
+{
+   if (!loopActive)
+   {
+      tell(0, "Thread not running, try to recover ...");
+      Cancel(3);
+      Start();
+   }
+
+   fullreload = reload;
+   updateTriggered = yes;
+   waitCondition.Broadcast();    // wakeup thread
+}
+
+//***************************************************************************
+// Stop Thread
+//***************************************************************************
+
+void cUpdate::Stop()
+{   
+   loopActive = no;
+   waitCondition.Broadcast();    // wakeup thread
+
+   Cancel(20);                   // wait up to 20 seconds for thread was stopping
+}
+
+//***************************************************************************
+// Action
+//***************************************************************************
+
+void cUpdate::Action()
+{
+   time_t nextAt = time(0) + 60;
+
+   tell(0, "Update thread started (pid=%d)", getpid());
+   
+   mutex.Lock();         // mutex gets ONLY unlocked when sleeping
+   loopActive = yes;
+
+   while (loopActive && Running())
+   {      
+      int reconnectTimeout;  // set by checkConnection()
+      int wait;
+
+      // calc wait time, we sleep at most 10 seconds or (if shorter) 
+      // to the next update time
+
+      wait = nextAt - time(0);
+      wait = wait > 0 && wait < 10 ? wait : 10;
+
+      // wait time in ms
+
+      waitCondition.TimedWait(mutex, wait*1000);
+
+      // we pass here at least once per 10 seconds ..
+
+      if (checkConnection(reconnectTimeout) != success)
+      {
+         nextAt = time(0) + reconnectTimeout;
+         continue;
+      }
+
+      // update timer
+
+      if (timerUpdateTriggered)
+         updateTimer();
+
+      // update EPG
+      
+      updateTriggered = updatesPending(10);
+      checkHandlerRole();
+
+      // if triggered externally or updates pending
+
+      if (!updateTriggered)
+         continue;
+
+      if (epgdBusy)
+      {
+         nextAt = time(0) + 10;
+         continue;
+      }
+
+      if (dbConnected(yes))
+      {
+         if (fullreload)
+            eventRefreshOnly = no;
+         
+         tell(eventRefreshOnly ? 2 : 0, "--- EPG %s started ---", fullreload ? "reload" : eventRefreshOnly ? "refresh" : "update");
+         
+         if (eventRefreshOnly)
+         {
+            refreshEpg();
+            lastEventsUpdateAt = time(0);
+            cSchedules::Cleanup(true);   // force VDR to store of epg.data to filesystem
+         }
+         else
+         {
+            // cleanup unreferenced image-files
+            
+            if (cleanupPictures() != success)
+               continue;
+            
+            if (refreshEpg() != success)
+               continue;
+            
+            // force VDR to store of epg.data to filesystem
+
+            cSchedules::Cleanup(true);
+            
+            // get pictures from database and copy to local FS
+            
+            if (storePicturesToFs() != success)
+               continue;
+            
+            // update lookback information (notice) 
+            
+            lastUpdateAt = time(0);
+            lastEventsUpdateAt = lastUpdateAt;
+
+            vdrDb->clear();
+            vdrDb->setValue(cTableVdrs::fiUuid, EPG2VDRConfig.uuid);
+            vdrDb->find();
+            vdrDb->setValue(cTableVdrs::fiLastUpdate, lastUpdateAt);
+            vdrDb->store();
+         }
+
+         tell(eventRefreshOnly ? 2 : 0, "--- EPG %s finished ---", fullreload ? "reload" : eventRefreshOnly ? "refresh" : "update");
+
+         if (EPG2VDRConfig.loglevel > 2)
+            connection->showStat("update");
+
+         eventRefreshOnly = no;
+         fullreload = no;
+         updateTriggered = no;
+      }
+   } 
+
+   loopActive = no;
+
+   tell(0, "Update thread ended (pid=%d)", getpid());
+}
+
+//***************************************************************************
+// Refresh Epg
+//***************************************************************************
+
+int cUpdate::refreshEpg()
+{
+   const cEvent* event;
+   int total = 0;
+   int dels = 0;
+   int channels = 0;
+   uint64_t start = cTimeMs::Now();
+   time_t updateSince;
+
+   if (fullreload)
+   {
+      tell(1, "Removing all events from epg");
+
+      while (!cSchedules::ClearAll())
+         tell(0, "Warning: Clear EPG failed, can't get lock. Retrying ...");
+
+      lastUpdateAt = 0;
+   }
+
+   updateSince = eventRefreshOnly  ? lastEventsUpdateAt : lastUpdateAt;
+
+   if (updateSince)
+      tell(eventRefreshOnly ? 2 : 1, "Update EPG, loading changes since %s", l2pTime(updateSince).c_str());
+   else
+      tell(1, "Update EPG, reloading all events");
+   
+   // 
+   // iterate over all channels in channelmap
+   // 
+
+   mapDb->clear();
+   mapDb->setValue(cTableChannelMap::fiSource, "vdr"); // only used from selectAllVdrChannels
+
+   for (int f = selectAllChannels->find(); f; f = selectAllChannels->fetch())
+   {
+      int count = 0;
+      cSchedulesLock* schedulesLock = new cSchedulesLock(true);
+      cSchedules* ss = 0;
+      cSchedule* s = 0;
+      tChannelID channelId = tChannelID::FromString(mapDb->getStrValue(cTableChannelMap::fiChannelId));
+
+      channels++;
+
+      eventsDb->clear();
+      eventsDb->setValue(cTableEvents::fiUpdSp, updateSince);
+      eventsDb->setValue(cTableEvents::fiChannelId, mapDb->getStrValue(cTableChannelMap::fiChannelId));
+
+      // lock holen
+
+      if (!(ss = (cSchedules*)cSchedules::Schedules(*schedulesLock)))
+      {
+         delete schedulesLock;
+         tell(0, "Error, can't get lock on schedules, aborting refresh!");
+         selectAllChannels->freeResult();
+         
+         return fail;  // #TODO -> break!?
+      }
+
+      // get schedule (channel)
+      
+      if (!(s = (cSchedule*)ss->GetSchedule(channelId)))
+         s = ss->AddSchedule(channelId);
+
+      //
+      // iterate over all events of this channel
+      // 
+
+      for (int found = selectUpdEvents->find(); found; found = selectUpdEvents->fetch())
+      {
+         cTimer* timer = 0;
+         char updFlg = toupper(eventsDb->getStrValue(cTableEvents::fiUpdFlg)[0]);
+
+         // fix missing flag
+
+         updFlg = updFlg == 0 ? 'P' : updFlg;
+
+         // ignore unneded event rows ..
+
+         if (!Us::isNeeded(updFlg))
+            continue;
+
+         // get event / timer 
+         
+         if (event = s->GetEvent(eventsDb->getIntValue(cTableEvents::fiUseId)))
+         {
+            if (Us::isRemove(updFlg))
+               tell(2, "Remove event %uld of channel '%s' due to updflg %c", 
+                    event->EventID(),  (const char*)event->ChannelID().ToString(), updFlg);
+
+            timer = getTimerOf(event);
+            s->DelEvent((cEvent*)event);
+         }
+
+         if (!Us::isRemove(updFlg))
+            event = s->AddEvent(createEventFromRow(eventsDb->getRow()));
+         else if (event)
+         {
+            event = 0;
+            dels++;
+         }
+         
+         if (timer && event)
+            timer->SetEvent(event);
+         
+         count++;
+
+         if (!dbConnected())
+            break;
+      }
+
+      selectUpdEvents->freeResult();
+
+      // Kanal fertig machen ..
+
+      s->Sort();
+      ss->SetModified(s);
+      
+      // lock freigeben 
+      
+      if (schedulesLock)
+         delete schedulesLock;
+                
+      tell(2, "Processed channel '%s' - '%s' with %d updates",
+           eventsDb->getStrValue(cTableEvents::fiChannelId),
+           mapDb->getStrValue(cTableChannelMap::fiChannelName),
+           count);
+
+      total += count;
+
+      if (!dbConnected(yes))
+         break;
+   }
+
+   selectAllChannels->freeResult();
+
+   if (updateSince)
+      tell(1, "Updated changes since '%s'; %d channels, %d events (%d deletions) in %s", 
+           l2pTime(updateSince).c_str(),
+           channels, total, dels, ms2Dur(cTimeMs::Now()-start).c_str());
+   else
+      tell(0, "Updated all %d channels, %d events (%d deletions) in %s", 
+           channels, total, dels, ms2Dur(cTimeMs::Now()-start).c_str());
+   
+   return dbConnected(yes) ? success : fail;
+}
+
+//***************************************************************************
+// To/From Row
+//***************************************************************************
+
+cEvent* cUpdate::createEventFromRow(const cDbRow* row)
+{
+   cEvent* e = new cEvent(row->getIntValue(cTableEvents::fiUseId));
+
+   e->SetTableID(row->getIntValue(cTableEvents::fiTableId));
+   e->SetVersion(row->getIntValue(cTableEvents::fiVersion));
+   e->SetTitle(row->getStrValue(cTableEvents::fiTitle));
+   e->SetShortText(row->getStrValue(cTableEvents::fiShortText));
+   e->SetStartTime(row->getIntValue(cTableEvents::fiStartTime));
+   e->SetDuration(row->getIntValue(cTableEvents::fiDuration));
+   e->SetParentalRating(row->getIntValue(cTableEvents::fiParentalRating));
+   e->SetVps(row->getIntValue(cTableEvents::fiVps));
+   e->SetDescription(row->getStrValue(cTableEvents::fiDescription));
+   e->SetComponents(0);
+
+   // components
+
+   if (row->hasValue(cTableEvents::fiSource, "vdr"))
+   {
+      cComponents* components = new cComponents;
+      
+      compDb->clear();
+      compDb->setValue(cTableComponents::fiEventId, row->getIntValue(cTableEvents::fiEventId));
+      compDb->setValue(cTableComponents::fiChannelId, row->getStrValue(cTableEvents::fiChannelId));
+      
+      for (int f = selectComponentsOf->find(); f; f = selectComponentsOf->fetch())
+      {
+         components->SetComponent(components->NumComponents(),
+                                  compDb->getIntValue(cTableComponents::fiStream),
+                                  compDb->getIntValue(cTableComponents::fiType),
+                                  compDb->getStrValue(cTableComponents::fiLang),
+                                  compDb->getStrValue(cTableComponents::fiDescription));
+      }
+      
+      selectComponentsOf->freeResult();
+      
+      if (components->NumComponents())
+         e->SetComponents(components);      // event take ownership of components!
+      else
+         delete components;
+   }
+
+   return e;
+}
+
+//***************************************************************************
+// Store Pictures to local Filesystem
+//***************************************************************************
+
+int cUpdate::storePicturesToFs()
+{
+   int count = 0;
+   int updated = 0;
+   char* path = 0;
+   time_t start = time(0);
+
+   if (!EPG2VDRConfig.getepgimages)
+      return done;
+
+   asprintf(&path, "%s", epgimagedir);
+   chkDir(path);
+   free(path);
+   asprintf(&path, "%s/images", epgimagedir);
+   chkDir(path);
+   free(path);
+
+   tell(0, "Load images from database");
+
+   imageRefDb->clear();
+   imageRefDb->setValue(cTableImageRefs::fiUpdSp, lastUpdateAt);
+   imageUpdSp.setValue(lastUpdateAt);
+   
+   for (int res = selectAllImages->find(); res; res = selectAllImages->fetch())
+   {
+      int eventid = masterId.getIntValue();
+      const char* imageName = imageRefDb->getStrValue(cTableImageRefs::fiImgName);
+      int lfn = imageRefDb->getIntValue(cTableImageRefs::fiLfn);
+      char* newpath;
+      char* linkdest = 0;
+      char* destfile = 0;
+      int forceLink = no;
+      int size = imageSize.getIntValue();
+
+      asprintf(&destfile, "%s/images/%s", epgimagedir, imageName);
+      
+      // check target ... image changed?
+
+      if (!fileExists(destfile) || fileSize(destfile) != size)
+      {
+         // get image 
+         
+         imageDb->clear();
+         imageDb->setValue(cTableImages::fiImgName, imageName);
+         
+         if (imageDb->find() && !imageDb->getRow()->getValue(cTableImages::fiImage)->isNull())
+         {
+            count++;
+
+            // remove existing target
+            
+            if (fileExists(destfile))
+            {
+               updated++;
+               removeFile(destfile);
+            }
+            
+            forceLink = yes;
+            tell(2, "Store image '%s' with %d bytes", destfile, size);
+            
+            if (FILE* fh1 = fopen(destfile, "w"))
+            {
+               fwrite(imageDb->getStrValue(cTableImages::fiImage), 1, size, fh1);
+               fclose(fh1);
+            }
+            else
+            {
+               tell(1, "Can't write image to '%s', error was '%m'", destfile);
+            }
+         }
+
+         imageDb->reset();
+      }
+
+      free(destfile);
+     
+      // create links ...
+
+      asprintf(&linkdest, "./images/%s", imageName);
+
+#ifdef _IMG_LINK
+      if (!lfn)
+      {
+         // for lfn 0 create additional link without "_?"
+
+         asprintf(&newpath, "%s/%d.%s", epgimagedir, eventid, imageExtension);
+         createLink(newpath, linkdest, forceLink);
+         free(newpath);
+      }
+#endif
+
+      // create link with index
+
+      asprintf(&newpath, "%s/%d_%d.%s", epgimagedir, eventid, lfn, imageExtension);
+      createLink(newpath, linkdest, forceLink);
+      free(newpath);
+
+      // ...
+      
+      free(linkdest);
+
+      if (!dbConnected())
+         break;
+   }
+
+   selectAllImages->freeResult();
+
+   tell(0, "Got %d images from database in %ld seconds (%d updates, %d new)", 
+        count, time(0) - start, updated, count-updated);
+ 
+   return dbConnected(yes) ? success : fail;
+}
+
+//***************************************************************************
+// Remove Pictures
+//***************************************************************************
+
+int cUpdate::cleanupPictures()
+{
+   const char* ext = ".jpg";
+   struct dirent* dirent;   
+   DIR* dir;
+   char* pdir;
+   int iCount = 0;
+   int lCount = 0;
+
+   imageRefDb->countWhere("", iCount);
+
+   if (iCount < 100)   // less than 100 image lines are suspicious ;)
+   {
+      tell(0, "Exit image cleanup to avoid deleting of all images on empty imagerefs table");
+      return done;
+   }
+
+   // -----------------------
+   // remove unused images
+
+   tell(1, "Starting cleanup of images in '%s'", epgimagedir);
+ 
+   // -----------------------
+   // cleanup 'images' directory 
+
+   cDbStatement* stmt = new cDbStatement(imageRefDb);
+   
+   stmt->build("select ");
+   stmt->bind(cTableImageRefs::fiFileRef, cDBS::bndOut);
+   stmt->build(" from %s where ", imageRefDb->TableName());
+   stmt->bind(cTableImageRefs::fiImgName, cDBS::bndIn | cDBS::bndSet);
+
+   if (stmt->prepare() != success)
+   {
+      delete stmt;
+      return fail;
+   }
+
+   iCount = 0;
+
+   // open directory
+
+   asprintf(&pdir, "%s/images", epgimagedir);
+
+   if (!(dir = opendir(pdir)))
+   {
+      tell(1, "Can't open directory '%s', '%m'", pdir);
+
+      free(pdir);
+
+      return done;
+   }
+
+   free(pdir);
+
+   while (dbConnected() && (dirent = readdir(dir)))
+   {
+      // check extension
+
+      if (strncmp(dirent->d_name + strlen(dirent->d_name) - strlen(ext), ext, strlen(ext)) != 0)
+         continue;
+
+      imageRefDb->clear();
+      imageRefDb->setValue(cTableImageRefs::fiImgName, dirent->d_name);
+     
+      if (!stmt->find())
+      {
+         asprintf(&pdir, "%s/images/%s", epgimagedir, dirent->d_name);
+
+         if (!removeFile(pdir))
+            iCount++;
+         
+         free(pdir);
+      }
+
+      stmt->freeResult();
+   }
+
+   delete stmt;
+   closedir(dir);
+
+   if (!dbConnected(yes))
+      return fail;
+
+   // -----------------------
+   // remove wasted symlinks
+   
+   if (!(dir = opendir(epgimagedir)))
+   {
+      tell(1, "Can't open directory '%s', '%m'", epgimagedir);
+      return done;
+   }
+
+   tell(1, "Remove %s symlinks", fullreload ? "all" : "old");
+   
+   while ((dirent = readdir(dir)))
+   {
+      // check extension
+      
+      if (strncmp(dirent->d_name + strlen(dirent->d_name) - strlen(ext), ext, strlen(ext)) != 0)
+         continue;
+      
+      asprintf(&pdir, "%s/%s", epgimagedir, dirent->d_name);
+      
+      // fileExists use access() which dereference links!
+      
+      if (isLink(pdir) && (fullreload || !fileExists(pdir)))
+      {
+         if (!removeFile(pdir))
+            lCount++;
+      }
+      
+      free(pdir);
+   }
+   
+   closedir(dir);
+   tell(1, "Cleanup finished, removed (%d) images and (%d) symlinks", iCount, lCount);
+   
+   return success;
+}
+
+//***************************************************************************
+// Link Needed
+//***************************************************************************
+
+int cUpdate::pictureLinkNeeded(const char* linkName)
+{
+   int found;
+
+   if (!dbConnected())
+      return yes;
+
+   // we don't need to patch the linkname "123456_0.jpg"
+   // since atoi() stops at the first non numerical character ...
+
+   imageRefDb->clear();
+   imageRefDb->setValue(cTableImageRefs::fiLfn, 0L);
+   imageRefDb->setValue(cTableImageRefs::fiEventId, atoi(linkName));
+   
+   found = imageRefDb->find();
+   imageRefDb->reset();
+
+   return found;
+}
diff --git a/update.h b/update.h
new file mode 100644
index 0000000..6c75c87
--- /dev/null
+++ b/update.h
@@ -0,0 +1,150 @@
+/*
+ * update.h: EPG2VDR plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __UPDATE_H
+#define __UPDATE_H
+
+#include <mysql/mysql.h>
+#include <map>
+#include <vector>
+
+#include <vdr/status.h>
+
+#include "lib/common.h"
+#include "lib/db.h"
+#include "lib/tabledef.h"
+
+#include "epg2vdr.h"
+
+#define EPGDNAME "epgd"
+
+class cEpg2VdrEpgHandler;
+
+//***************************************************************************
+// Update
+//***************************************************************************
+
+class cUpdate : public cThread, public cStatus
+{
+   public:
+
+      enum MasterMode
+      {
+         mmAuto,
+         mmYes,
+         mmNo,
+         
+         mmCount
+      };
+      
+      cUpdate(cPluginEPG2VDR* plugin);
+      ~cUpdate();
+
+      // interface
+
+      void Stop();
+      int isUpdateActive()         { return updateTriggered; }
+      int isEpgdBusy()             { return epgdBusy; }
+      int isEpgdUpdating()         { return epgdUpdating; }
+      time_t getNextEpgdUpdateAt() { return nextEpgdUpdateAt; }
+      void triggerUpdate(int reload = no);
+
+   protected:
+
+      virtual void TimerChange(const cTimer* Timer, eTimerChange Change);   // notification from VDRs status interface
+      virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber, bool LiveView);
+
+   private:
+
+      struct TimerId
+      {
+         unsigned int eventId;
+         char channelId[100];
+      };
+
+      // functions
+
+      int initDb();
+      int exitDb();
+
+      void Action(void);
+      void checkHandlerRole();
+      int updatesPending(int timeout);
+      int dbConnected(int force = no) { return connection && (!force || connection->check() == success); }
+      int checkConnection(int& timeout);
+      int updateTimer();
+      int refreshEpg();
+      cEvent* createEventFromRow(const cDbRow* row);
+      int lookupVdrEventOf(int eId, const char* cId);
+      cTimer* getTimerOf(const cEvent* event) const;
+      int storePicturesToFs();
+      int cleanupPictures();
+      int pictureLinkNeeded(const char* linkName);
+
+      tChannelID toChanID(const char* chanIdStr)
+      {
+         if (isEmpty(chanIdStr))
+            return tChannelID::InvalidID;
+         
+         return tChannelID::FromString(chanIdStr);
+      }
+
+      // data
+
+      cDbConnection* connection;
+      int loopActive;
+      time_t nextEpgdUpdateAt;
+      time_t lastUpdateAt;
+      time_t lastEventsUpdateAt;
+      char* epgimagedir;
+      int withutf8;
+      cCondVar waitCondition;
+      cMutex mutex;
+      cMutex timerMutex;
+      int updateTriggered;
+      int timerUpdateTriggered;
+      int fullreload;
+      char imageExtension[3+TB];
+      int epgdBusy;
+      int epgdUpdating;
+      Es::State epgdState;
+      int eventRefreshOnly;
+
+      cTableEvents* eventsDb;
+      cTableFileRefs* fileDb;
+      cTableImages* imageDb;
+      cTableImageRefs* imageRefDb;
+      cTableEpisodes* episodeDb;
+      cTableChannelMap* mapDb;
+      cTableTimers* timerDb;
+      cTableVdrs* vdrDb;
+      cTableComponents* compDb;
+
+      cDbStatement* selectMasterVdr;
+      cDbStatement* selectAllImages;
+      cDbStatement* selectUpdEvents;
+      cDbStatement* selectAllChannels;
+      cDbStatement* selectAllVdrChannels;
+      cDbStatement* selectComponentsOf;
+      cDbStatement* deleteTimer;
+      cDbStatement* selectMyTimer;
+
+      cDbValue vdrEvtId;
+      cDbValue extEvtId;
+      cDbValue vdrStartTime;
+      cDbValue extChannelId;
+      cDbValue imageUpdSp;
+      cDbValue imageSize;
+      cDbValue masterId;
+
+      cEpg2VdrEpgHandler* epgHandler;
+
+      vector<TimerId> deletedTimers;
+};
+
+//***************************************************************************
+#endif //__UPDATE_H

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-vdr-dvb/vdr-plugin-epg2vdr.git



More information about the pkg-vdr-dvb-changes mailing list