summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--COPYING340
-rw-r--r--HISTORY6
-rw-r--r--Makefile132
-rw-r--r--README79
-rw-r--r--config.c56
-rw-r--r--config.h31
-rw-r--r--lib/Makefile7
-rw-r--r--lib/common.c812
-rw-r--r--lib/common.h179
-rw-r--r--lib/config.c63
-rw-r--r--lib/config.h73
-rw-r--r--lib/db.c1086
-rw-r--r--lib/db.h1030
-rw-r--r--lib/tabledef.c856
-rw-r--r--lib/tabledef.h832
-rw-r--r--moviedbmovie.c130
-rw-r--r--moviedbmovie.h98
-rw-r--r--po/de_DE.po65
-rw-r--r--scraper2vdr.c249
-rw-r--r--scraper2vdr.h57
-rw-r--r--scrapmanager.c503
-rw-r--r--scrapmanager.h81
-rw-r--r--services.h194
-rw-r--r--setup.c78
-rw-r--r--setup.h25
-rw-r--r--tools.c170
-rw-r--r--tools.h30
-rw-r--r--tvdbseries.c289
-rw-r--r--tvdbseries.h143
-rw-r--r--update.c1323
-rw-r--r--update.h87
31 files changed, 9104 insertions, 0 deletions
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..706074f
--- /dev/null
+++ b/HISTORY
@@ -0,0 +1,6 @@
+VDR Plugin 'scraper2vdr' Revision History
+-----------------------------------------
+
+2014-03-02: Version 0.0.1
+
+- Initial revision.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..209effe
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,132 @@
+#
+# Makefile for a Video Disk Recorder plugin
+#
+# $Id$
+
+# External image lib to use: imagemagick, graphicsmagick
+IMAGELIB = imagemagick
+
+PLUGIN = scraper2vdr
+
+### 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)
+
+### The version number of VDR's plugin API:
+
+APIVERSION = $(call PKGCFG,apiversion)
+
+### Allow user defined options to overwrite defaults:
+
+-include $(PLGCFG)
+
+LIBS = -lmysqlclient_r -luuid
+
+### 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 +=
+
+DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
+DEFINES += -DVDR_PLUGIN -DUSEUUID
+
+ifeq ($(IMAGELIB), imagemagick)
+ INCLUDES += $(shell pkg-config --cflags Magick++)
+ LIBS += $(shell pkg-config --libs Magick++)
+else ifeq ($(IMAGELIB), graphicsmagick)
+ INCLUDES += $(shell pkg-config --cflags GraphicsMagick++)
+ LIBS += $(shell pkg-config --libs GraphicsMagick++)
+endif
+
+### The object files (add further files here):
+
+OBJS = $(PLUGIN).o config.o setup.o update.o scrapmanager.o tvdbseries.o moviedbmovie.o tools.o lib/db.o lib/tabledef.o lib/common.o lib/config.o
+
+### The main target:
+
+all: $(SOFILE) i18n
+
+### 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='<see README>' -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): $(OBJS)
+ $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) $(LIBS) -o $@
+
+install-lib: $(SOFILE)
+ install -D -m644 $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION)
+
+install: install-lib install-i18n
+
+dist: $(I18Npo) 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* *~
diff --git a/README b/README
new file mode 100644
index 0000000..efa52e1
--- /dev/null
+++ b/README
@@ -0,0 +1,79 @@
+This is a "plugin" for the Video Disk Recorder (VDR).
+
+Written by: Louis Braun <louis.braun@gmx.de>
+
+Project's homepage: http://projects.vdr-developer.org/projects/plg-scraper2vdr
+
+Latest version available at: http://projects.vdr-developer.org/git/vdr-plugin-scraper2vdr.git/
+
+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.
+See the file COPYING for more information.
+
+Description
+-----------
+
+scraper2vdr acts as client and provides scraped metadata for tvshows and
+movies from epgd to other plugins via its service interface. The plugin
+cares about caching the images locally and also cleans up the images if
+not longer needed.
+
+epgd itself uses the thetvdb.com API for collecting series metadata and
+themoviedb.org API for movies. Check the websites of both services for
+the terms of use.
+
+Requirements
+------------
+
+To run the plugin the following libaries have to be installed:
+
+ - VDR 1.7.x
+ - libmysql >= 5.07
+ - uuid-dev
+ - imagemagick or graphicksmagick
+
+Installation and configuration
+------------------------------
+
+Just install the plugin depending on your used distribution. During VDR
+startup the following options can be set:
+
+-i <IMAGEDIR>, --imagedir=<IMAGEDIR> Set directory where images are stored
+-m <MODE>, --mode=<MODE> mode can be client or headless.
+
+Each running scraper2vdr Plugin reports his recordings to the epgd
+database, the epgd then checks these entries and tries to find
+appropriate scraping information. epgd performs first a lookup for a
+event in the database which belongs to the recording. If this fails, epgd
+checks if another client has already reported this recording to the database.
+After that, the scrapinfo file in the recording directory (if existing)
+is checked. If nothing is successfull, a new scrap process for the name
+of the recording is done. If in this case the length of the recording
+is less than 70 minutes, a series recording is assumed, otherwise
+the scraper searches for a movie.
+
+In client mode both live epg and recordings metadata is loaded from the
+database. In headless mode only recording metadata is loaded. This mode
+is useful for headless VDRs so that recordings which are done from this
+VDR during no other VDR client with running scraper2vdr Plugin is active
+are not missed. The recording information is then written to the database
+in time before the related and already reliably scraped event entry is
+deleted from the database.
+
+Service Interface
+-----------------
+
+Other Plugins can and should request information about meta data from
+scraper2vdr via a call to the provided service interface.
+
+First the service "GetEventType" which expects a pointer to a cEvent or
+a cRecording object as input variable has to be called. This call provides
+the type of the event or recording (tSeries, tMovie, tNone) and the seriesId,
+episodeId and movieId. If type is tSeries, movieId is 0 and vice versa.
+With that then a second call to GetSeries or GetMovie with the appropriate IDs
+provides all stored information for the series or movie in form of a cSeries
+or cMovie object.
+
+For further information just check the self explanatory services.h file.
diff --git a/config.c b/config.c
new file mode 100644
index 0000000..b3812be
--- /dev/null
+++ b/config.c
@@ -0,0 +1,56 @@
+#include "lib/common.h"
+#include "config.h"
+
+cScraper2VdrConfig::cScraper2VdrConfig() {
+ mainMenuEntry = 1;
+ headless = false;
+ uuid = "";
+ imgDirSet = false;
+ mysqlHost = "localhost";
+ mysqlPort = 3306;
+ mysqlDBName = "epg2vdr";
+ mysqlDBUser = "epg2vdr";
+ mysqlDBPass = "epg";
+ recScrapInfoName = "scrapinfo";
+}
+
+cScraper2VdrConfig::~cScraper2VdrConfig() {
+}
+
+void cScraper2VdrConfig::SetUuid(cPlugin *plug) {
+ if (uuid.size() == 0) {
+ uuid = getUniqueId();
+ plug->SetupStore("uuid", uuid.c_str());
+ }
+ tell(0, "epgd uuid: %s", uuid.c_str());
+}
+
+void cScraper2VdrConfig::SetImageDir(cString dir) {
+ imageDir = *dir;
+ imgDirSet = true;
+}
+
+void cScraper2VdrConfig::SetDefaultImageDir(void) {
+ if (!imgDirSet) {
+ imageDir = cPlugin::CacheDirectory(PLUGIN_NAME_I18N);
+ }
+ tell (0, "using image directory %s", imageDir.c_str());
+}
+
+void cScraper2VdrConfig::SetMode(string mode) {
+ if (!mode.compare("headless"))
+ headless = true;
+}
+
+bool cScraper2VdrConfig::SetupParse(const char *Name, const char *Value) {
+ if (strcmp(Name, "uuid") == 0) uuid = Value;
+ else if (strcmp(Name, "mainMenuEntry") == 0) mainMenuEntry = atoi(Value);
+ else if (strcmp(Name, "mysqlHost") == 0) mysqlHost = Value;
+ else if (strcmp(Name, "mysqlPort") == 0) mysqlPort = atoi(Value);
+ else if (strcmp(Name, "mysqlDBName") == 0) mysqlDBName = Value;
+ else if (strcmp(Name, "mysqlDBUser") == 0) mysqlDBUser = Value;
+ else if (strcmp(Name, "mysqlDBPass") == 0) mysqlDBPass = Value;
+ else
+ return false;
+ return true;
+}
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..c81a8ef
--- /dev/null
+++ b/config.h
@@ -0,0 +1,31 @@
+#ifndef __SCRAPER2VDR_CONFIG_H
+#define __SCRAPER2VDR_CONFIG_H
+
+#include <string>
+#include <vdr/plugin.h>
+
+using namespace std;
+
+class cScraper2VdrConfig {
+ private:
+ public:
+ cScraper2VdrConfig();
+ ~cScraper2VdrConfig();
+ bool SetupParse(const char *Name, const char *Value);
+ void SetUuid(cPlugin *plug);
+ void SetImageDir(cString dir);
+ void SetDefaultImageDir(void);
+ void SetMode(string mode);
+ int mainMenuEntry;
+ bool headless;
+ string uuid;
+ bool imgDirSet;
+ string imageDir;
+ string mysqlHost;
+ int mysqlPort;
+ string mysqlDBName;
+ string mysqlDBUser;
+ string mysqlDBPass;
+ string recScrapInfoName;
+};
+#endif //__SCRAPER2VDR_CONFIG_H
diff --git a/lib/Makefile b/lib/Makefile
new file mode 100644
index 0000000..f27782b
--- /dev/null
+++ b/lib/Makefile
@@ -0,0 +1,7 @@
+
+
+all:
+ g++ -ggdb -DPLGDIR='"."' test.c common.c config.c db.c tabledef.c -lrt -lz -lmysqlclient -o t
+
+clean:
+ rm -f *.o *.a *~ core
diff --git a/lib/common.c b/lib/common.c
new file mode 100644
index 0000000..f3a0436
--- /dev/null
+++ b/lib/common.c
@@ -0,0 +1,812 @@
+/*
+ * common.c:
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <sys/stat.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>
+
+#ifdef USELIBARCHIVE
+# include <archive.h>
+# include <archive_entry.h>
+#endif
+
+#ifdef VDR_PLUGIN
+# include <vdr/thread.h>
+#endif
+
+#include "common.h"
+#include "config.h"
+
+#ifdef VDR_PLUGIN
+ cMutex logMutex;
+#endif
+
+//***************************************************************************
+// Debug
+//***************************************************************************
+
+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;
+
+#ifdef VDR_PLUGIN
+ cMutexLock lock(&logMutex);
+#endif
+
+ va_start(ap, format);
+
+#ifdef VDR_PLUGIN
+ snprintf(t, sizeBuffer, "scraper2vdr: ");
+#endif
+
+ vsnprintf(t+strlen(t), sizeBuffer-strlen(t), format, ap);
+
+ if (EPG2VDRConfig.logstdout)
+ {
+ char buf[50+TB];
+ time_t now;
+ time(&now);
+ strftime(buf, 50, "%y.%m.%d %H:%M:%S", localtime(&now));
+ printf("%s %s\n", buf, t);
+ }
+ else
+ syslog(LOG_ERR, "%s", t);
+
+ va_end(ap);
+}
+
+//***************************************************************************
+// 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);
+}
+
+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;
+}
+
+//***************************************************************************
+// TOOLS
+//***************************************************************************
+
+int isEmpty(const char* str)
+{
+ return !str || !*str;
+}
+
+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 '%m'", path);
+
+ return false;
+}
+
+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 '%m'", path);
+
+ return false;
+}
+
+
+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 '%m'", link);
+ 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', '%m'", filename);
+
+ 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 '%m'");
+ 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;
+}
+
+//*************************************************************************
+// 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 unzip file%s!\n", op); return;
+ case Z_BUF_ERROR: tell(0, "Error: Couldn't 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 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 - %m", file);
+ 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
+
+//***************************************************************************
+// Class LogDuration
+//***************************************************************************
+
+#ifdef VDR_PLUGIN
+
+# include <vdr/plugin.h>
+
+LogDuration::LogDuration(const char* aMessage, int aLogLevel)
+{
+ logLevel = aLogLevel;
+ strcpy(message, aMessage);
+
+ // at last !
+
+ durationStart = cTimeMs::Now();
+}
+
+LogDuration::~LogDuration()
+{
+ tell(logLevel, "duration '%s' was (%dms)",
+ message, cTimeMs::Now() - durationStart);
+}
+
+void LogDuration::show(const char* label)
+{
+ tell(logLevel, "elapsed '%s' at '%s' was (%dms)",
+ message, label, cTimeMs::Now() - durationStart);
+}
+
+#endif
+
+//***************************************************************************
+// 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'; %m", file);
+ 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
diff --git a/lib/common.h b/lib/common.h
new file mode 100644
index 0000000..614dfe6
--- /dev/null
+++ b/lib/common.h
@@ -0,0 +1,179 @@
+/*
+ * 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>
+
+#include <openssl/md5.h> // MD5_*
+
+#ifdef VDR_PLUGIN
+# include <vdr/tools.h>
+#endif
+
+#ifdef USELIBXML
+# include <libxslt/transform.h>
+# include <libxslt/xsltutils.h>
+# include <libexslt/exslt.h>
+#endif
+
+//***************************************************************************
+//
+//***************************************************************************
+
+#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
+};
+
+//***************************************************************************
+// Tell
+//***************************************************************************
+
+void tell(int eloquence, const char* format, ...);
+
+//***************************************************************************
+// MemoryStruct for curl callbacks
+//***************************************************************************
+
+struct MemoryStruct
+{
+ MemoryStruct() { memory = 0; clear(); }
+ ~MemoryStruct() { clear(); }
+
+ // data
+
+ char* memory;
+ size_t size;
+
+ // tag attribute
+
+ char tag[100]; // the tag to be compared
+ char name[100]; // content name (filename)
+ int headerOnly;
+
+ int isEmpty() { return memory == 0; }
+
+ void clear()
+ {
+ free(memory);
+ memory = 0;
+ size = 0;
+ *tag = 0;
+ *name = 0;
+ headerOnly = no;
+ }
+};
+
+//***************************************************************************
+// Tools
+//***************************************************************************
+
+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 fileExists(const char* path);
+int fileSize(const char* path);
+int createLink(const char* link, const char* dest, int force);
+int isLink(const char* path);
+int isEmpty(const char* str);
+int removeFile(const char* filename);
+int chkDir(const char* path);
+
+#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);
+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
+
+#ifdef VDR_PLUGIN
+
+//***************************************************************************
+// 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
+
+//***************************************************************************
+#endif //___COMMON_H
diff --git a/lib/config.c b/lib/config.c
new file mode 100644
index 0000000..9ca25f1
--- /dev/null
+++ b/lib/config.c
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+#ifdef VDR_PLUGIN
+ activeOnEpgd = no;
+ scheduleBoot = no;
+#else
+ sstrcpy(cachePath, "/var/cache/epgd", sizeof(cachePath));
+ sstrcpy(pluginPath, PLGDIR, sizeof(pluginPath));
+ sstrcpy(epgView, "eventsview.sql", sizeof(epgView));
+ updateThreshold = 200;
+ maintanance = no;
+#endif
+
+ 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;
+}
diff --git a/lib/config.h b/lib/config.h
new file mode 100644
index 0000000..e0c729c
--- /dev/null
+++ b/lib/config.h
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+#ifdef VDR_PLUGIN
+ int activeOnEpgd;
+ int scheduleBoot;
+#else
+ char cachePath[256+TB];
+ char pluginPath[256+TB];
+ char epgView[100+TB];
+ int updateThreshold;
+ int maintanance;
+#endif
+
+ 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];
+};
+
+extern cEPG2VDRConfig EPG2VDRConfig;
+
+#endif // __EPG2VDR_CONFIG_H
diff --git a/lib/db.c b/lib/db.c
new file mode 100644
index 0000000..17e679c
--- /dev/null
+++ b/lib/db.c
@@ -0,0 +1,1086 @@
+/*
+ * db.c
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <stdio.h>
+
+#include <mysql/errmsg.h>
+
+#include "db.h"
+
+// #define DEB_HANDLER
+
+//***************************************************************************
+// DB Statement
+//***************************************************************************
+
+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;
+}
+
+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;
+}
+
+//***************************************************************************
+// Execute
+//***************************************************************************
+
+int cDbStatement::execute(int noResult)
+{
+ affected = 0;
+
+ if (!connection || !connection->getMySql())
+ return fail;
+
+ if (!stmt)
+ return connection->errorSql(connection, "execute(missing statement)");
+
+ // tell(0, "execute %d [%s]", stmt, stmtTxt.c_str());
+
+ if (mysql_stmt_execute(stmt))
+ return connection->errorSql(connection, "execute(stmt_execute)", stmt, stmtTxt.c_str());
+
+ // 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(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 ?", delim ? delim : "", 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 ?", delim ? delim : "", 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*)realloc(*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 (%d) in parameters and (%d) out bindings prepared",
+ stmtTxt.c_str(), mysql_stmt_param_count(stmt), outCount);
+
+ return success;
+}
+
+//***************************************************************************
+// cDbService
+//***************************************************************************
+
+const char* cDbService::formats[] =
+{
+ "INT",
+ "INT",
+ "VARCHAR",
+ "TEXT",
+ "MEDIUMBLOB",
+ "FLOAT",
+ "DATETIME",
+
+ 0
+};
+
+const char* cDbService::toString(FieldFormat t)
+{
+ return formats[t];
+}
+
+//***************************************************************************
+// 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;
+
+ // ------------------------------
+ // 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());
+
+ // create indices
+
+ createIndices();
+
+ 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..522226e
--- /dev/null
+++ b/lib/db.h
@@ -0,0 +1,1030 @@
+/*
+ * 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 <sstream>
+
+#include <mysql/mysql.h>
+
+#include "common.h"
+
+class cDbTable;
+class cDbConnection;
+
+using namespace std;
+
+//***************************************************************************
+// cDbService
+//***************************************************************************
+
+class cDbService
+{
+ public:
+
+ enum Misc
+ {
+ maxIndexFields = 20
+ };
+
+ enum FieldFormat
+ {
+ ffInt,
+ ffUInt,
+ ffAscii, // -> VARCHAR
+ ffText,
+ ffMlob, // -> MEDIUMBLOB
+ ffFloat,
+ ffDateTime,
+ ffCount
+ };
+
+ enum FieldType
+ {
+ 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 const char* formats[];
+};
+
+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(float value)
+// {
+// if (field->format != ffFloat)
+// {
+// tell(0, "Checking invalid field format for '%s', expected FLOAT", field->name);
+// return no;
+// }
+
+// return floatValue == value;
+// }
+
+// int hasValue(long value)
+// {
+// if (field->format != ffInt && field->format != ffUInt)
+// {
+// tell(0, "Checking invalid field format for '%s', expected INT", field->name);
+// return no;
+// }
+
+// return numValue == value;
+// }
+
+ 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_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() { clear(); }
+
+ 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(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(); }
+
+ 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;
+};
+
+//***************************************************************************
+// 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'; %m", file);
+ return fail;
+ }
+
+ buffer = (char*)malloc(size+1);
+
+ while (res = fread(buffer+nread, 1, 1000, f))
+ {
+ nread += res;
+ size += 1000;
+ buffer = (char*)realloc(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);
+
+ 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;
+
+ 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); }
+ 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/tabledef.c b/lib/tabledef.c
new file mode 100644
index 0000000..d4bb6a9
--- /dev/null
+++ b/lib/tabledef.c
@@ -0,0 +1,856 @@
+/*
+ * 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 (images)",
+ "busy (scraping)",
+
+ 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 },
+ { "idxsourcechannelid", { fiSource, fiChannelId, 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 },
+ { "idxsourcechannelid", { fiSource, 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 }
+};
+
+//***************************************************************************
+// 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 }
+};
+
+
+//***************************************************************************
+// 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
+
+ { 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 },
+
+ { 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 },
+
+ { 0 }
+}; \ No newline at end of file
diff --git a/lib/tabledef.h b/lib/tabledef.h
new file mode 100644
index 0000000..bd2b043
--- /dev/null
+++ b/lib/tabledef.h
@@ -0,0 +1,832 @@
+/*
+ * 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,
+
+ esBusy,
+ esBusyEvents = esBusy,
+ esBusyMatch,
+
+ esBusyImages,
+
+ esBusyScraping,
+
+ 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'"; }
+
+ // 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) { }
+
+ virtual const char* TableName() { return "vdrs"; }
+
+ enum FieldIndex
+ {
+ fiUuid,
+
+ fiInsSp,
+ fiUpdSp,
+
+ fiName,
+ fiVersion,
+ fiDbApi,
+ fiLastUpdate,
+ fiNextUpdate,
+ fiState,
+ fiMaster,
+ fiIp,
+
+ fiCount
+ };
+
+ static FieldDef fields[];
+};
+
+//***************************************************************************
+// 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 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/moviedbmovie.c b/moviedbmovie.c
new file mode 100644
index 0000000..c1485c3
--- /dev/null
+++ b/moviedbmovie.c
@@ -0,0 +1,130 @@
+#define __STL_CONFIG_H
+#include "lib/common.h"
+#include "moviedbmovie.h"
+
+using namespace std;
+
+cMovieDbMovie::cMovieDbMovie(void) {
+ title = "";
+ originalTitle = "";
+ tagline = "";
+ overview = "";
+ adult = false;
+ collectionID = 0;
+ collectionName = "";
+ budget = 0;
+ revenue = 0;
+ genres = "";
+ homepage = "";
+ imdbid = "";
+ releaseDate = "";
+ runtime = 0;
+ popularity = 0.0;
+ voteAverage = 0.0;
+}
+
+cMovieDbMovie::~cMovieDbMovie() {
+ for (map<int, cMovieActor*>::iterator it = actors.begin(); it != actors.end(); it++) {
+ cMovieActor *a = (cMovieActor*)it->second;
+ delete a;
+ }
+ for (map<int, cMovieMedia*>::iterator it = medias.begin(); it != medias.end(); it++) {
+ cMovieMedia *m = (cMovieMedia*)it->second;
+ delete m;
+ }
+}
+
+void cMovieDbMovie::InsertMedia(cMovieMedia *media) {
+ medias.insert(pair<int, cMovieMedia*>(media->mediaType, media));
+}
+
+void cMovieDbMovie::InsertActor(cMovieActor *actor) {
+ cMovieMedia *m = new cMovieMedia();
+ actor->actorThumb = m;
+ actors.insert(pair<int, cMovieActor*>(actor->id, actor));
+}
+
+vector<int> cMovieDbMovie::GetActorIDs(void) {
+ vector<int> IDs;
+ for (map<int, cMovieActor*>::iterator it = actors.begin(); it != actors.end(); it++) {
+ cMovieActor *a = it->second;
+ IDs.push_back(a->id);
+ }
+ return IDs;
+}
+
+void cMovieDbMovie::SetActorThumbSize(int actorId, int imgWidth, int imgHeight) {
+ map<int, cMovieActor*>::iterator hit = actors.find(actorId);
+ if (hit != actors.end()) {
+ cMovieActor *a = hit->second;
+ if (!a->actorThumb)
+ return;
+ cMovieMedia *thumb = a->actorThumb;
+ thumb->width = imgWidth;
+ thumb->height = imgHeight;
+ thumb->mediaType = mmActorThumb;
+ }
+}
+
+void cMovieDbMovie::SetActorPath(int actorId, string path) {
+ map<int, cMovieActor*>::iterator hit = actors.find(actorId);
+ if (hit != actors.end()) {
+ cMovieActor *a = hit->second;
+ if (!a->actorThumb)
+ return;
+ a->actorThumb->path = path;
+ }
+}
+
+bool cMovieDbMovie::GetMedia(mediaMovies mediatype, cTvMedia *p) {
+ map<int, cMovieMedia*>::iterator hit = medias.find(mediatype);
+ if (hit == medias.end())
+ return false;
+ cMovieMedia *pStored = hit->second;
+ p->path = pStored->path;
+ p->width = pStored->width;
+ p->height = pStored->height;
+ return true;
+}
+
+void cMovieDbMovie::GetActors(vector<cActor> *a) {
+ for (map<int, cMovieActor*>::iterator it = actors.begin(); it != actors.end(); it++) {
+ cMovieActor *aStored = it->second;
+ cActor act;
+ act.name = aStored->name;
+ act.role = aStored->role;
+ if (aStored->actorThumb) {
+ act.actorThumb.width = aStored->actorThumb->width;
+ act.actorThumb.height = aStored->actorThumb->height;
+ act.actorThumb.path = aStored->actorThumb->path;
+ }
+ a->push_back(act);
+ }
+}
+
+void cMovieDbMovie::Dump(void) {
+ tell(0, "--------------------------- Movie Info ----------------------------------");
+ tell(0, "title %s, ID: %d", title.c_str(), id);
+ tell(0, "Orig. Title: %s", originalTitle.c_str());
+ tell(0, "Tagline: %s", tagline.c_str());
+ tell(0, "Overview: %s", overview.c_str());
+ tell(0, "Collection: %s", collectionName.c_str());
+ tell(0, "Genre: %s", genres.c_str());
+ tell(0, "Popularity: %f", popularity);
+ tell(0, "--------------------------- Actors ----------------------------------");
+ for (map<int, cMovieActor*>::iterator it = actors.begin(); it != actors.end(); it++) {
+ cMovieActor *a = it->second;
+ tell(0, "Actor %d, Name: %s, Role %s", a->id, a->name.c_str(), a->role.c_str());
+ if (a->actorThumb) {
+ tell(0, "thmbWidth %d, thmbHeight %d", a->actorThumb->width, a->actorThumb->height);
+ tell(0, "Path %s", a->actorThumb->path.c_str());
+ }
+ }
+ tell(0, "--------------------------- Media ----------------------------------");
+ for (map<int, cMovieMedia*>::iterator it = medias.begin(); it != medias.end(); it++) {
+ cMovieMedia *m = it->second;
+ tell(0, "Media %d", m->mediaType);
+ tell(0, "width %d, height %d", m->width, m->height);
+ tell(0, "Path %s", m->path.c_str());
+ }
+} \ No newline at end of file
diff --git a/moviedbmovie.h b/moviedbmovie.h
new file mode 100644
index 0000000..d53f7b0
--- /dev/null
+++ b/moviedbmovie.h
@@ -0,0 +1,98 @@
+#ifndef __TVSCRAPER_MOVIEDBMOVIE_H
+#define __TVSCRAPER_MOVIEDBMOVIE_H
+
+#include <iostream>
+#include <string>
+#include <vector>
+#include <map>
+#include <set>
+#include <utility>
+#include <algorithm>
+#include "services.h"
+
+using namespace std;
+
+enum mediaMovies {
+ mmPoster,
+ mmFanart,
+ mmCollectionPoster,
+ mmCollectionFanart,
+ mmActorThumb,
+ mmPosterThumb,
+};
+
+// --- cMovieMedia -------------------------------------------------------------
+class cMovieMedia {
+public:
+ cMovieMedia(void) {
+ path = "";
+ mediaType = mmPoster;
+ width = 0;
+ height = 0;
+ };
+ ~cMovieMedia(void) {
+ };
+ string path;
+ int mediaType;
+ int width;
+ int height;
+};
+
+// --- cMovieActor -------------------------------------------------------------
+class cMovieActor {
+public:
+ cMovieActor(void) {
+ id = 0;
+ name = "";
+ role = "";
+ actorThumb = NULL;
+ };
+ ~cMovieActor(void) {
+ if (actorThumb)
+ delete actorThumb;
+ };
+ int id;
+ string name;
+ string role;
+ cMovieMedia *actorThumb;
+};
+
+// --- cMovieDbMovie -------------------------------------------------------------
+
+class cMovieDbMovie {
+private:
+ map<int, cMovieActor*> actors;
+ map<int, cMovieMedia*> medias;
+public:
+ cMovieDbMovie(void);
+ virtual ~cMovieDbMovie(void);
+ int id;
+ string title;
+ string originalTitle;
+ string tagline;
+ string overview;
+ bool adult;
+ int collectionID;
+ string collectionName;
+ int budget;
+ int revenue;
+ string genres;
+ string homepage;
+ string imdbid;
+ string releaseDate;
+ int runtime;
+ float popularity;
+ float voteAverage;
+ void InsertActor(cMovieActor *actor);
+ void InsertMedia(cMovieMedia *media);
+ vector<int> GetActorIDs(void);
+ void SetActorThumbSize(int actorId, int imgWidth, int imgHeight);
+ void SetActorPath(int actorId, string path);
+ //Getter for Serivice Calls
+ bool GetMedia(mediaMovies mediatype, cTvMedia *p);
+ void GetActors(vector<cActor> *a);
+ void Dump();
+};
+
+
+#endif //__TVSCRAPER_TVDBSERIES_H
diff --git a/po/de_DE.po b/po/de_DE.po
new file mode 100644
index 0000000..ed0652d
--- /dev/null
+++ b/po/de_DE.po
@@ -0,0 +1,65 @@
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: vdr-scraper2vdr 0.0.1\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2014-03-30 14:37+0200\n"
+"PO-Revision-Date: 2014-03-30 18:30+0100\n"
+"Last-Translator: 3PO\n"
+"Language-Team: German <vdr@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 "Update Scraper Information from Database"
+msgstr "Filme und Serien aus Datenbank aktualisieren"
+
+msgid "Update Scraper Recordings Information from Database"
+msgstr "Aufnahmen aus Datenbank aktualisieren"
+
+msgid "Scan for new recordings in video directory"
+msgstr "Videoverzeichnis nach neuen Aufnahmen scannen"
+
+msgid "Scan for new or updated scrapinfo files"
+msgstr "Nach neuen oder geänderten scrapinfo Dateien suchen"
+
+msgid "Cleanup Recordings in Database"
+msgstr "Aufnahmen in Datenbank bereinigen"
+
+msgid "Updating Scraper EPG Information from Database"
+msgstr "Filme und Serien werden aus Datenbank aktualisieren"
+
+msgid "Updating Scraper Recordings Information from Database"
+msgstr "Aufnahmen werden aus Datenbank aktualisieren"
+
+msgid "Scanning for new recordings in video directory"
+msgstr "Scanne Videoverzeichnis nach neuen Aufnahmen"
+
+msgid "Scanning for new or updated scrapinfo files"
+msgstr "Suche nach neuen oder geänderten scrapinfo Dateien"
+
+msgid "Cleaning up Recordings in Database"
+msgstr "Bereinige Aufnahmen in Datenbank"
+
+msgid "Show Main Menu Entry"
+msgstr "Hauptmenüeintrag anzeigen"
+
+msgid "MySQL Host"
+msgstr "MySQL Host"
+
+msgid "MySQL Port"
+msgstr "MySQL Port"
+
+msgid "MySQL Database Name"
+msgstr "MySQL Datenbannk Name"
+
+msgid "MySQL User"
+msgstr "MySQL Benutzer"
+
+msgid "MySQL Password"
+msgstr "MySQL Passwort"
+
diff --git a/scraper2vdr.c b/scraper2vdr.c
new file mode 100644
index 0000000..332c3df
--- /dev/null
+++ b/scraper2vdr.c
@@ -0,0 +1,249 @@
+/*
+ * scraper2vdr.c: A plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include "scraper2vdr.h"
+
+#if defined (APIVERSNUM) && (APIVERSNUM < 10600)
+# error VDR API versions < 1.6.0 are not supported !
+#endif
+
+//***************************************************************************
+// Plugin Main Menu
+//***************************************************************************
+
+class cScraper2VdrPluginMenu : public cOsdMenu {
+ public:
+ cScraper2VdrPluginMenu(const char* title, cUpdate *update);
+ virtual ~cScraper2VdrPluginMenu() { };
+ virtual eOSState ProcessKey(eKeys key);
+ protected:
+ cUpdate *update;
+};
+
+cScraper2VdrPluginMenu::cScraper2VdrPluginMenu(const char* title, cUpdate *update) : cOsdMenu(title) {
+ this->update = update;
+ Clear();
+ cOsdMenu::Add(new cOsdItem(tr("Update Scraper Information from Database")));
+ cOsdMenu::Add(new cOsdItem(tr("Update Scraper Recordings Information from Database")));
+ cOsdMenu::Add(new cOsdItem(tr("Scan for new recordings in video directory")));
+ cOsdMenu::Add(new cOsdItem(tr("Scan for new or updated scrapinfo files")));
+ cOsdMenu::Add(new cOsdItem(tr("Cleanup Recordings in Database")));
+ SetHelp(0, 0, 0,0);
+ Display();
+}
+
+//***************************************************************************
+// Process Key
+//***************************************************************************
+
+eOSState cScraper2VdrPluginMenu::ProcessKey(eKeys key) {
+ eOSState state = cOsdMenu::ProcessKey(key);
+
+ if (state != osUnknown)
+ return state;
+
+ switch (key) {
+ case kOk: {
+ if (Current() == 0) {
+ Skins.Message(mtInfo, tr("Updating Scraper EPG Information from Database"));
+ update->ForceUpdate();
+ } else if (Current() == 1) {
+ Skins.Message(mtInfo, tr("Updating Scraper Recordings Information from Database"));
+ update->ForceRecordingUpdate();
+ } else if (Current() == 2) {
+ Skins.Message(mtInfo, tr("Scanning for new recordings in video directory"));
+ update->ForceVideoDirUpdate();
+ } else if (Current() == 3) {
+ Skins.Message(mtInfo, tr("Scanning for new or updated scrapinfo files"));
+ update->ForceScrapInfoUpdate();
+ } else if (Current() == 4) {
+ Skins.Message(mtInfo, tr("Cleaning up Recordings in Database"));
+ update->TriggerCleanRecordingsDB();
+ }
+ return osEnd;
+ }
+
+ default:
+ break;
+ }
+ return state;
+}
+
+//***************************************************************************
+// cPluginScraper2vdr
+//***************************************************************************
+
+cPluginScraper2vdr::cPluginScraper2vdr(void) {
+ cDbConnection::init();
+}
+
+cPluginScraper2vdr::~cPluginScraper2vdr() {
+ delete update;
+ delete scrapManager;
+ cDbConnection::exit();
+}
+
+const char *cPluginScraper2vdr::CommandLineHelp(void) {
+ return
+ " -i <IMAGEDIR>, --imagedir=<IMAGEDIR> Set directory where images are stored\n"
+ " -m <MODE>, --mode=<MODE> mode can be client or headless, see README\n";
+}
+
+bool cPluginScraper2vdr::ProcessArgs(int argc, char *argv[]) {
+ static const struct option long_options[] = {
+ { "imagedir", required_argument, NULL, 'i' },
+ { "mode", required_argument, NULL, 'm' },
+ { 0, 0, 0, 0 }
+ };
+ int c;
+ while ((c = getopt_long(argc, argv, "i:m:", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'i':
+ config.SetImageDir(optarg);
+ break;
+ case 'm':
+ config.SetMode(optarg);
+ break;
+ default:
+ return false;
+ }
+ }
+ return true;
+}
+
+bool cPluginScraper2vdr::Initialize(void) {
+ config.SetUuid(this);
+ config.SetDefaultImageDir();
+ scrapManager = new cScrapManager();
+ update = new cUpdate(scrapManager);
+ return true;
+}
+
+bool cPluginScraper2vdr::Start(void) {
+ update->Start();
+ return true;
+}
+
+void cPluginScraper2vdr::Stop(void) {
+ update->Stop();
+}
+
+void cPluginScraper2vdr::Housekeeping(void) {
+}
+
+void cPluginScraper2vdr::MainThreadHook(void) {
+}
+
+cString cPluginScraper2vdr::Active(void) {
+ return NULL;
+}
+
+time_t cPluginScraper2vdr::WakeupTime(void) {
+ return 0;
+}
+
+cOsdObject *cPluginScraper2vdr::MainMenuAction(void) {
+ if (config.mainMenuEntry == 1)
+ return new cScraper2VdrPluginMenu("Scraper2Vdr", update);
+ return NULL;
+}
+
+cMenuSetupPage *cPluginScraper2vdr::SetupMenu(void) {
+ return new cScraper2VdrSetup(update);
+}
+
+bool cPluginScraper2vdr::SetupParse(const char *Name, const char *Value) {
+ return config.SetupParse(Name, Value);
+}
+
+bool cPluginScraper2vdr::Service(const char *Id, void *Data) {
+ if (Data == NULL)
+ return false;
+ if (strcmp(Id, "GetEventType") == 0) {
+ ScraperGetEventType* call = (ScraperGetEventType*) Data;
+ if (!call->event && !call->recording)
+ return false;
+ return scrapManager->GetEventType(call);
+ }
+
+ if (strcmp(Id, "GetSeries") == 0) {
+ cSeries* call = (cSeries*) Data;
+ if (call->seriesId == 0)
+ return false;
+ return scrapManager->GetSeries(call);
+ }
+
+ if (strcmp(Id, "GetMovie") == 0) {
+ cMovie* call = (cMovie*) Data;
+ if (call->movieId == 0)
+ return false;
+ return scrapManager->GetMovie(call);
+ }
+
+ if (strcmp(Id, "GetPosterBanner") == 0) {
+ ScraperGetPosterBanner* call = (ScraperGetPosterBanner*) Data;
+ if (!call->event)
+ return false;
+ return scrapManager->GetPosterBanner(call);
+ }
+
+ if (strcmp(Id, "GetPoster") == 0) {
+ ScraperGetPoster* call = (ScraperGetPoster*) Data;
+ if (!call->event && !call->recording)
+ return false;
+ return scrapManager->GetPoster(call);
+ }
+
+ if (strcmp(Id, "GetPosterThumb") == 0) {
+ ScraperGetPosterThumb* call = (ScraperGetPosterThumb*) Data;
+ if (!call->event && !call->recording)
+ return false;
+ return scrapManager->GetPosterThumb(call);
+ }
+
+ return false;
+}
+
+const char **cPluginScraper2vdr::SVDRPHelpPages(void) {
+ static const char *HelpPages[] = {
+ "UPDT\n"
+ " Load all series and movies for events from database.",
+ "UPDR\n"
+ " Load recordings from database.",
+ "SCVD\n"
+ " Trigger scan fornew recordings in video directory.",
+ "SCSI\n"
+ " Trigger scan for scrapinfo files in video directory.",
+ "CRDB\n"
+ " Trigger cleanup of recordings database.",
+ 0
+ };
+ return HelpPages;
+}
+
+cString cPluginScraper2vdr::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) {
+ if (strcasecmp(Command, "UPDT") == 0) {
+ update->ForceUpdate();
+ return "SCRAPER2VDR full update from database forced.";
+ } else if (strcasecmp(Command, "UPDR") == 0) {
+ update->ForceRecordingUpdate();
+ return "SCRAPER2VDR scanning of recordings in database triggered.";
+ } else if (strcasecmp(Command, "SCVD") == 0) {
+ update->ForceVideoDirUpdate();
+ return "SCRAPER2VDR scan for new recordings in video dir triggered.";
+ } else if (strcasecmp(Command, "SCSI") == 0) {
+ update->ForceScrapInfoUpdate();
+ return "SCRAPER2VDR scan for new or updated scrapinfo files triggered.";
+ } else if (strcasecmp(Command, "CRDB") == 0) {
+ update->TriggerCleanRecordingsDB();
+ return "SCRAPER2VDR cleanup of recording DB triggered.";
+ }
+ return NULL;
+}
+
+VDRPLUGINCREATOR(cPluginScraper2vdr); // Don't touch this!
diff --git a/scraper2vdr.h b/scraper2vdr.h
new file mode 100644
index 0000000..b955a59
--- /dev/null
+++ b/scraper2vdr.h
@@ -0,0 +1,57 @@
+#ifndef __SCRAPER2VDR_H
+#define __SCRAPER2VDR_H
+
+#include <getopt.h>
+#include <vdr/plugin.h>
+#include "lib/common.h"
+#include "config.h"
+#include "setup.h"
+#include "scrapmanager.h"
+#include "update.h"
+#include "services.h"
+
+//***************************************************************************
+// Constants
+//***************************************************************************
+static const char *VERSION = "0.0.1";
+static const char *DESCRIPTION = "'scraper2vdr' plugin";
+static const char *MAINMENUENTRY = "Scraper2Vdr";
+
+//***************************************************************************
+// Globals
+//***************************************************************************
+cScraper2VdrConfig config;
+
+//***************************************************************************
+// cPluginScraper2vdr
+//***************************************************************************
+
+class cPluginScraper2vdr : public cPlugin {
+private:
+ cScrapManager *scrapManager;
+ cUpdate *update;
+public:
+ cPluginScraper2vdr(void);
+ virtual ~cPluginScraper2vdr();
+ virtual const char *Version(void) { return VERSION; }
+ virtual const char *Description(void) { return DESCRIPTION; }
+ virtual const char *CommandLineHelp(void);
+ virtual bool ProcessArgs(int argc, char *argv[]);
+ virtual bool Initialize(void);
+ virtual bool Start(void);
+ virtual void Stop(void);
+ virtual void Housekeeping(void);
+ virtual void MainThreadHook(void);
+ virtual cString Active(void);
+ virtual time_t WakeupTime(void);
+ virtual const char *MainMenuEntry(void) { return (config.mainMenuEntry)?MAINMENUENTRY:NULL; }
+ virtual cOsdObject *MainMenuAction(void);
+ virtual cMenuSetupPage *SetupMenu(void);
+ virtual bool SetupParse(const char *Name, const char *Value);
+ virtual bool Service(const char *Id, void *Data = NULL);
+ virtual const char **SVDRPHelpPages(void);
+ virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
+};
+
+//***************************************************************************
+#endif // __SCRAPER2VDR_H \ No newline at end of file
diff --git a/scrapmanager.c b/scrapmanager.c
new file mode 100644
index 0000000..b446c17
--- /dev/null
+++ b/scrapmanager.c
@@ -0,0 +1,503 @@
+#define __STL_CONFIG_H
+#include <vdr/recording.h>
+#include "tools.h"
+#include "scrapmanager.h"
+
+using namespace std;
+
+bool operator<(const sEventsKey& l, const sEventsKey& r) {
+ if (l.eventId != r.eventId)
+ return (l.eventId < r.eventId);
+ int comp = l.channelId.compare(r.channelId);
+ if (comp < 0)
+ return true;
+ return false;
+}
+
+bool operator<(const sRecordingsKey& l, const sRecordingsKey& r) {
+ if (l.recStart != r.recStart)
+ return (l.recStart < r.recStart);
+ int comp = l.recPath.compare(r.recPath);
+ if (comp < 0)
+ return true;
+ return false;
+}
+
+cScrapManager::cScrapManager(void) {
+
+}
+
+cScrapManager::~cScrapManager(void) {
+ for (map<int, cTVDBSeries*>::iterator it = series.begin(); it != series.end(); it++) {
+ cTVDBSeries *s = (cTVDBSeries*)it->second;
+ delete s;
+ }
+ series.clear();
+ for (map<int, cMovieDbMovie*>::iterator it = movies.begin(); it != movies.end(); it++) {
+ cMovieDbMovie *m = (cMovieDbMovie*)it->second;
+ delete m;
+ }
+ movies.clear();
+}
+
+void cScrapManager::InitIterator(bool isRec) {
+ if (!isRec)
+ eventsIterator = events.begin();
+ else
+ recIterator = recordings.begin();
+}
+
+sEventsValue cScrapManager::GetEventInformation(int eventId, string channelId) {
+ sEventsKey k;
+ k.eventId = eventId;
+ k.channelId = channelId;
+ sEventsValue emptyVal;
+ emptyVal.seriesId = 0;
+ emptyVal.episodeId = 0;
+ emptyVal.movieId = 0;
+ emptyVal.isNew = false;
+ map<sEventsKey, sEventsValue>::iterator hit = events.find(k);
+ if (hit != events.end())
+ return hit->second;
+ return emptyVal;
+}
+
+
+void cScrapManager::AddEvent(int eventId, string channelId, int seriesId, int episodeId, int movieId) {
+ sEventsKey k;
+ k.eventId = eventId;
+ k.channelId = channelId;
+ sEventsValue v;
+ v.seriesId = seriesId;
+ v.episodeId = episodeId;
+ v.movieId = movieId;
+ v.isNew = true;
+ events.insert(pair<sEventsKey, sEventsValue>(k, v));
+}
+
+bool cScrapManager::GetNextSeries(bool isRec, int &seriesId, int &episodeId) {
+ bool next = false;
+ if (!isRec) {
+ while (eventsIterator != events.end()) {
+ next = true;
+ sEventsValue ev = eventsIterator->second;
+ if (ev.isNew && (ev.seriesId > 0)) {
+ seriesId = ev.seriesId;
+ episodeId = ev.episodeId;
+ eventsIterator->second.isNew = false;
+ eventsIterator++;
+ break;
+ }
+ eventsIterator++;
+ next = false;
+ }
+ } else {
+ while (recIterator != recordings.end()) {
+ next = true;
+ sEventsValue ev = recIterator->second;
+ if (ev.isNew && (ev.seriesId > 0)) {
+ seriesId = ev.seriesId;
+ episodeId = ev.episodeId;
+ recIterator->second.isNew = false;
+ recIterator++;
+ break;
+ }
+ recIterator++;
+ next = false;
+ }
+
+ }
+ return next;
+}
+
+bool cScrapManager::GetNextMovie(bool isRec, int &movieId) {
+ bool next = false;
+ if (!isRec) {
+ while (eventsIterator != events.end()) {
+ next = true;
+ sEventsValue ev = eventsIterator->second;
+ if (ev.isNew && (ev.movieId > 0)) {
+ movieId = ev.movieId;
+ eventsIterator->second.isNew = false;
+ eventsIterator++;
+ break;
+ }
+ eventsIterator++;
+ next = false;
+ }
+ } else {
+ while (recIterator != recordings.end()) {
+ next = true;
+ sEventsValue ev = recIterator->second;
+ if (ev.isNew && (ev.movieId > 0)) {
+ movieId = ev.movieId;
+ recIterator->second.isNew = false;
+ recIterator++;
+ break;
+ }
+ recIterator++;
+ next = false;
+ }
+ }
+ return next;
+}
+
+cTVDBSeries *cScrapManager::GetSeries(int seriesId) {
+ map<int, cTVDBSeries*>::iterator hit = series.find(seriesId);
+ if (hit == series.end())
+ return NULL;
+ return hit->second;
+}
+
+cMovieDbMovie *cScrapManager::GetMovie(int movieId) {
+ map<int, cMovieDbMovie*>::iterator hit = movies.find(movieId);
+ if (hit == movies.end())
+ return NULL;
+ return hit->second;
+}
+
+cTVDBSeries *cScrapManager::AddSeries(cTableSeries* tSeries) {
+ cTVDBSeries *s = new cTVDBSeries();
+ s->id = tSeries->getIntValue(cTableSeries::fiSeriesId);
+ s->name = tSeries->getStrValue(cTableSeries::fiSeriesName);
+ s->overview = tSeries->getStrValue(cTableSeries::fiSeriesOverview);
+ s->firstAired = tSeries->getStrValue(cTableSeries::fiSeriesFirstAired);
+ s->network = tSeries->getStrValue(cTableSeries::fiSeriesNetwork);
+ string genre = replaceString(tSeries->getStrValue(cTableSeries::fiSeriesGenre), "|", ", ");
+ s->genre = genre;
+ s->rating = tSeries->getFloatValue(cTableSeries::fiSeriesRating);
+ s->status = tSeries->getStrValue(cTableSeries::fiSeriesStatus);
+ series.insert(pair<int, cTVDBSeries*>(tSeries->getIntValue(cTableSeries::fiSeriesId), s));
+ return s;
+}
+
+cMovieDbMovie *cScrapManager::AddMovie(cTableMovies* tMovies) {
+ cMovieDbMovie *m = new cMovieDbMovie();
+ m->id = tMovies->getIntValue(cTableMovies::fiMovieId);
+ m->title = tMovies->getStrValue(cTableMovies::fiTitle);
+ m->originalTitle = tMovies->getStrValue(cTableMovies::fiOriginalTitle);
+ m->tagline = tMovies->getStrValue(cTableMovies::fiTagline);
+ m->overview = tMovies->getStrValue(cTableMovies::fiOverview);
+ m->adult = tMovies->getIntValue(cTableMovies::fiIsAdult);
+ m->collectionName = tMovies->getStrValue(cTableMovies::fiCollectionName);
+ m->budget = tMovies->getIntValue(cTableMovies::fiBudget);
+ m->revenue = tMovies->getIntValue(cTableMovies::fiRevenue);
+ string genre = replaceString(tMovies->getStrValue(cTableMovies::fiGenres), "|", ",");
+ m->genres = genre;
+ m->homepage = tMovies->getStrValue(cTableMovies::fiHomepage);
+ m->releaseDate = tMovies->getStrValue(cTableMovies::fiReleaaseDate);
+ m->runtime = tMovies->getIntValue(cTableMovies::fiRuntime);
+ m->popularity = tMovies->getFloatValue(cTableMovies::fiPopularity);
+ m->voteAverage = tMovies->getFloatValue(cTableMovies::fiVoteAverage);
+ movies.insert(pair<int, cMovieDbMovie*>(tMovies->getIntValue(cTableMovies::fiMovieId), m));
+ return m;
+}
+
+void cScrapManager::AddSeriesEpisode(cTVDBSeries *series, cTableSeriesEpisode* tEpisodes) {
+ cTVDBEpisode *e = new cTVDBEpisode();
+ e->id = tEpisodes->getIntValue(cTableSeriesEpisode::fiEpisodeId);
+ e->name = tEpisodes->getStrValue(cTableSeriesEpisode::fiEpisodeName);
+ e->number = tEpisodes->getIntValue(cTableSeriesEpisode::fiEpisodeNumber);
+ e->season = tEpisodes->getIntValue(cTableSeriesEpisode::fiSeasonNumber);
+ e->overview = tEpisodes->getStrValue(cTableSeriesEpisode::fiEpisodeOverview);
+ e->firstAired = tEpisodes->getStrValue(cTableSeriesEpisode::fiEpisodeFirstAired);
+ string guestStars = replaceString(tEpisodes->getStrValue(cTableSeriesEpisode::fiEpisodeGuestStars), "|", ", ");
+ e->guestStars = guestStars;
+ e->rating = tEpisodes->getFloatValue(cTableSeriesEpisode::fiEpisodeRating);
+ series->InsertEpisode(e);
+}
+
+void cScrapManager::AddSeriesActor(cTVDBSeries *series, cTableSeriesActor* tActors) {
+ cTVDBActor *a = new cTVDBActor();
+ a->id = tActors->getIntValue(cTableSeriesActor::fiActorId);
+ a->name = tActors->getStrValue(cTableSeriesActor::fiActorName);
+ a->role = tActors->getStrValue(cTableSeriesActor::fiActorRole);
+ series->InsertActor(a);
+}
+
+void cScrapManager::AddMovieMedia(cMovieDbMovie *movie, cTableMovieMedia* tMovieMedia, string path) {
+ cMovieMedia *m = new cMovieMedia();
+ m->mediaType = tMovieMedia->getIntValue(cTableMovieMedia::fiMediaType);
+ m->width = tMovieMedia->getIntValue(cTableMovieMedia::fiMediaWidth);
+ m->height = tMovieMedia->getIntValue(cTableMovieMedia::fiMediaHeight);
+ m->path = path;
+ movie->InsertMedia(m);
+}
+
+
+void cScrapManager::AddMovieActor(cMovieDbMovie *movie, cTableMovieActor* tActor, string role) {
+ cMovieActor *a = new cMovieActor();
+ a->id = tActor->getIntValue(cTableMovieActor::fiActorId);
+ a->name = tActor->getStrValue(cTableMovieActor::fiActorName);
+ a->role = role;
+ movie->InsertActor(a);
+}
+
+bool cScrapManager::AddRecording(int recStart, string recPath, int seriesId, int episodeId, int movieId) {
+ sRecordingsKey k;
+ k.recStart = recStart;
+ k.recPath = recPath;
+ //check if recording already exists
+ map<sRecordingsKey, sEventsValue>::iterator hit = recordings.find(k);
+ if (hit != recordings.end()) {
+ sEventsValue v = hit->second;
+ if ((v.seriesId == seriesId) && (v.episodeId == episodeId) && (v.movieId == movieId))
+ return false;
+ else
+ recordings.erase(hit);
+ }
+ sEventsValue v;
+ v.seriesId = seriesId;
+ v.episodeId = episodeId;
+ v.movieId = movieId;
+ v.isNew = true;
+ recordings.insert(pair<sRecordingsKey, sEventsValue>(k, v));
+ return true;
+}
+
+bool cScrapManager::RecordingExists(int recStart, string recPath) {
+ sRecordingsKey k;
+ k.recStart = recStart;
+ k.recPath = recPath;
+ map<sRecordingsKey, sEventsValue>::iterator hit = recordings.find(k);
+ if (hit != recordings.end())
+ return true;
+ return false;
+}
+
+bool cScrapManager::SeriesInUse(int seriesId) {
+ map<int, cTVDBSeries*>::iterator hit = series.find(seriesId);
+ if (hit != series.end())
+ return true;
+ return false;
+}
+
+bool cScrapManager::MovieInUse(int movieId) {
+ map<int, cMovieDbMovie*>::iterator hit = movies.find(movieId);
+ if (hit != movies.end())
+ return true;
+ return false;
+}
+
+void cScrapManager::DumpSeries(int num) {
+ int i=0;
+ tell(0, "Dumping first %d series", num);
+ for (map<int, cTVDBSeries*>::iterator it = series.begin(); it != series.end(); it++) {
+ cTVDBSeries *s = it->second;
+ s->Dump();
+ if (i == num)
+ break;
+ i++;
+ }
+}
+
+void cScrapManager::DumpMovies(int num) {
+ int i=0;
+ tell(0, "Dumping first %d movies", num);
+ for (map<int, cMovieDbMovie*>::iterator it = movies.begin(); it != movies.end(); it++) {
+ cMovieDbMovie *m = it->second;
+ m->Dump();
+ if (i == num)
+ break;
+ i++;
+ }
+}
+
+void cScrapManager::DumpRecordings(int num) {
+ tell(0, "Dumping first %d recordings", num);
+ for (map<sRecordingsKey, sEventsValue>::iterator it = recordings.begin(); it != recordings.end(); it++) {
+ sRecordingsKey key = it->first;
+ sEventsValue val = it->second;
+ tell(0, "recStart %d, recPath %s, seriesId %d, episodeId %d, movieId %d", key.recStart, key.recPath.c_str(), val.seriesId, val.episodeId, val.movieId);
+ }
+}
+
+bool cScrapManager::GetEventType(ScraperGetEventType *call) {
+ sEventsValue v;
+ if (call->event) {
+ sEventsKey k;
+ k.eventId = call->event->EventID();
+ k.channelId = *(call->event->ChannelID().ToString());
+ map<sEventsKey, sEventsValue>::iterator hit = events.find(k);
+ if (hit == events.end())
+ return false;
+ v = hit->second;
+ } else if (call->recording) {
+ sRecordingsKey k;
+ k.recStart = call->recording->Start();
+ k.recPath = call->recording->FileName();
+ map<sRecordingsKey, sEventsValue>::iterator hit = recordings.find(k);
+ if (hit == recordings.end())
+ return false;
+ v = hit->second;
+ }
+ if (v.seriesId > 0) {
+ call->type = tSeries;
+ } else if (v.movieId > 0) {
+ call->type = tMovie;
+ } else {
+ call->type = tNone;
+ return false;
+ }
+ call->seriesId = v.seriesId;
+ call->episodeId = v.episodeId;
+ call->movieId = v.movieId;
+ return true;
+}
+
+bool cScrapManager::GetSeries(cSeries *s) {
+ map<int, cTVDBSeries*>::iterator hit = series.find(s->seriesId);
+ if (hit == series.end())
+ return false;
+ cTVDBSeries *sStored = hit->second;
+ s->name = sStored->name;
+ s->overview = sStored->overview;
+ s->firstAired = sStored->firstAired;
+ s->network = sStored->network;
+ s->genre = sStored->genre;
+ s->rating = sStored->rating;
+ s->status = sStored->status;
+ //Episode
+ if (s->episodeId > 0) {
+ sStored->GetEpisode(s->episodeId, &s->episode);
+ sStored->GetSeasonPoster(s->episodeId, &s->seasonPoster);
+ }
+ //Media
+ sStored->GetPosters(&s->posters);
+ sStored->GetBanners(&s->banners);
+ sStored->GetFanart(&s->fanarts);
+ //Actors
+ sStored->GetActors(&s->actors);
+ return true;
+}
+
+bool cScrapManager::GetMovie(cMovie *m) {
+ map<int, cMovieDbMovie*>::iterator hit = movies.find(m->movieId);
+ if (hit == movies.end())
+ return false;
+ cMovieDbMovie *mStored = hit->second;
+ m->title = mStored->title;
+ m->originalTitle = mStored->originalTitle;
+ m->tagline = mStored->tagline;
+ m->overview = mStored->overview;
+ m->adult = mStored->adult;
+ m->collectionName = mStored->collectionName;
+ m->budget = mStored->budget;
+ m->revenue = mStored->revenue;
+ m->genres = mStored->genres;
+ m->homepage = mStored->homepage;
+ m->releaseDate = mStored->releaseDate;
+ m->runtime = mStored->runtime;
+ m->popularity = mStored->popularity;
+ m->voteAverage = mStored->voteAverage;
+ //Media
+ mStored->GetMedia(mmPoster, &m->poster);
+ mStored->GetMedia(mmFanart, &m->fanart);
+ mStored->GetMedia(mmCollectionPoster, &m->collectionPoster);
+ mStored->GetMedia(mmCollectionFanart, &m->collectionFanart);
+ //Actors
+ mStored->GetActors(&m->actors);
+ return true;
+}
+
+bool cScrapManager::GetPosterBanner(ScraperGetPosterBanner *call) {
+ sEventsKey k;
+ k.eventId = call->event->EventID();
+ k.channelId = *(call->event->ChannelID().ToString());
+ map<sEventsKey, sEventsValue>::iterator hit = events.find(k);
+ if (hit == events.end())
+ return false;
+ sEventsValue v = hit->second;
+ if (v.seriesId > 0) {
+ call->type = tSeries;
+ map<int, cTVDBSeries*>::iterator hitSeries = series.find(v.seriesId);
+ if (hitSeries == series.end())
+ return false;
+ cTVDBSeries *s = hitSeries->second;
+ bool found = s->GetRandomBanner(&call->banner);
+ if (v.episodeId > 0) {
+ s->GetSeasonPoster(v.episodeId, &call->poster);
+ }
+ return found;
+ } else if (v.movieId > 0) {
+ call->type = tMovie;
+ map<int, cMovieDbMovie*>::iterator hitMovies = movies.find(v.movieId);
+ if (hitMovies == movies.end())
+ return false;
+ cMovieDbMovie *m = hitMovies->second;
+ return m->GetMedia(mmPoster, &call->poster);
+ } else {
+ call->type = tNone;
+ }
+ return false;
+}
+
+bool cScrapManager::GetPoster(ScraperGetPoster *call) {
+ sEventsValue v;
+ if (call->event) {
+ sEventsKey k;
+ k.eventId = call->event->EventID();
+ k.channelId = *(call->event->ChannelID().ToString());
+ map<sEventsKey, sEventsValue>::iterator hit = events.find(k);
+ if (hit == events.end())
+ return false;
+ v = hit->second;
+ } else if (call->recording) {
+ sRecordingsKey k;
+ k.recStart = call->recording->Start();
+ k.recPath = call->recording->FileName();
+ map<sRecordingsKey, sEventsValue>::iterator hit = recordings.find(k);
+ if (hit == recordings.end())
+ return false;
+ v = hit->second;
+ }
+ if (v.seriesId > 0) {
+ map<int, cTVDBSeries*>::iterator hitSeries = series.find(v.seriesId);
+ if (hitSeries == series.end())
+ return false;
+ cTVDBSeries *s = hitSeries->second;
+ return s->GetPoster(&call->poster);
+ } else if (v.movieId > 0) {
+ map<int, cMovieDbMovie*>::iterator hitMovies = movies.find(v.movieId);
+ if (hitMovies == movies.end())
+ return false;
+ cMovieDbMovie *m = hitMovies->second;
+ return m->GetMedia(mmPoster, &call->poster);
+ }
+
+}
+
+bool cScrapManager::GetPosterThumb(ScraperGetPosterThumb *call) {
+ sEventsValue v;
+ if (call->event) {
+ sEventsKey k;
+ k.eventId = call->event->EventID();
+ k.channelId = *(call->event->ChannelID().ToString());
+ map<sEventsKey, sEventsValue>::iterator hit = events.find(k);
+ if (hit == events.end())
+ return false;
+ v = hit->second;
+ } else if (call->recording) {
+ sRecordingsKey k;
+ k.recStart = call->recording->Start();
+ k.recPath = call->recording->FileName();
+ map<sRecordingsKey, sEventsValue>::iterator hit = recordings.find(k);
+ if (hit == recordings.end())
+ return false;
+ v = hit->second;
+ }
+ if (v.seriesId > 0) {
+ map<int, cTVDBSeries*>::iterator hitSeries = series.find(v.seriesId);
+ if (hitSeries == series.end())
+ return false;
+ cTVDBSeries *s = hitSeries->second;
+ return s->GetPosterThumb(&call->poster);
+ } else if (v.movieId > 0) {
+ map<int, cMovieDbMovie*>::iterator hitMovies = movies.find(v.movieId);
+ if (hitMovies == movies.end())
+ return false;
+ cMovieDbMovie *m = hitMovies->second;
+ return m->GetMedia(mmPosterThumb, &call->poster);
+ }
+ return false;
+}
diff --git a/scrapmanager.h b/scrapmanager.h
new file mode 100644
index 0000000..682bd65
--- /dev/null
+++ b/scrapmanager.h
@@ -0,0 +1,81 @@
+#ifndef __SCRAPMANAGER_H
+#define __SCRAPMANAGER_H
+
+#include <vector>
+#include <map>
+#include <set>
+#include <utility>
+#include <algorithm>
+
+#include "lib/common.h"
+#include "lib/db.h"
+#include "lib/tabledef.h"
+
+#include "services.h"
+#include "tvdbseries.h"
+#include "moviedbmovie.h"
+
+using namespace std;
+
+struct sEventsKey {
+ int eventId;
+ string channelId;
+};
+
+struct sEventsValue {
+ int seriesId;
+ int episodeId;
+ int movieId;
+ bool isNew;
+};
+
+struct sRecordingsKey {
+ int recStart;
+ string recPath;
+};
+
+class cScrapManager {
+ private:
+ map<sEventsKey, sEventsValue> events;
+ map<sEventsKey, sEventsValue>::iterator eventsIterator;
+ map<sRecordingsKey, sEventsValue> recordings;
+ map<sRecordingsKey, sEventsValue>::iterator recIterator;
+ map<int, cTVDBSeries*> series;
+ map<int, cMovieDbMovie*> movies;
+ public:
+ cScrapManager(void);
+ virtual ~cScrapManager(void);
+ //Series and Movies Handling
+ void AddEvent(int eventId, string channelId, int seriesId, int episodeId, int movieId);
+ void InitIterator(bool isRec);
+ int GetNumSeries(void) { return series.size(); };
+ int GetNumMovies(void) { return movies.size(); };
+ sEventsValue GetEventInformation(int eventId, string channelId);
+ bool GetNextSeries(bool isRec, int &seriesId, int &episodeId);
+ bool GetNextMovie(bool isRec, int &movieId);
+ cTVDBSeries *GetSeries(int seriesId);
+ cMovieDbMovie *GetMovie(int movieId);
+ cTVDBSeries *AddSeries(cTableSeries* tSeries);
+ cMovieDbMovie *AddMovie(cTableMovies* tMovies);
+ void AddSeriesEpisode(cTVDBSeries *series, cTableSeriesEpisode* tEpisodes);
+ void AddSeriesActor(cTVDBSeries *series, cTableSeriesActor* tActors);
+ void AddMovieActor(cMovieDbMovie *movie, cTableMovieActor* tActor, string role);
+ void AddMovieMedia(cMovieDbMovie *movie, cTableMovieMedia* tMovieMedia, string path);
+ //Recording Handling
+ bool AddRecording(int recStart, string recPath, int seriesId, int episodeId, int movieId);
+ bool RecordingExists(int recStart, string recPath);
+ bool SeriesInUse(int seriesId);
+ bool MovieInUse(int movieId);
+ //Debug
+ void DumpSeries(int num);
+ void DumpMovies(int num);
+ void DumpRecordings(int num);
+ //Service Calls
+ bool GetEventType(ScraperGetEventType *call);
+ bool GetSeries(cSeries *series);
+ bool GetMovie(cMovie *movie);
+ bool GetPosterBanner(ScraperGetPosterBanner *call);
+ bool GetPoster(ScraperGetPoster *call);
+ bool GetPosterThumb(ScraperGetPosterThumb *call);
+};
+#endif //__SCRAPMANAGER_H
diff --git a/services.h b/services.h
new file mode 100644
index 0000000..703d077
--- /dev/null
+++ b/services.h
@@ -0,0 +1,194 @@
+#ifndef __SCRAPER2VDRSERVICES_H
+#define __SCRAPER2VDRSERVICES_H
+
+#include <vdr/epg.h>
+#include <vdr/recording.h>
+
+enum tvType {
+ tSeries,
+ tMovie,
+ tNone,
+};
+
+/*********************************************************************
+* Helper Structures
+*********************************************************************/
+class cTvMedia {
+public:
+ cTvMedia(void) {
+ path = "";
+ width = height = 0;
+ };
+ std::string path;
+ int width;
+ int height;
+};
+
+class cEpisode {
+public:
+ cEpisode(void) {
+ number = 0;
+ season = 0;
+ name = "";
+ firstAired = "";
+ guestStars = "";
+ overview = "";
+ rating = 0.0;
+ };
+ int number;
+ int season;
+ std::string name;
+ std::string firstAired;
+ std::string guestStars;
+ std::string overview;
+ float rating;
+ cTvMedia episodeImage;
+};
+
+class cActor {
+public:
+ cActor(void) {
+ name = "";
+ role = "";
+ };
+ std::string name;
+ std::string role;
+ cTvMedia actorThumb;
+};
+
+/*********************************************************************
+* Data Structures for Service Calls
+*********************************************************************/
+
+// Data structure for service "GetEventType"
+class ScraperGetEventType {
+public:
+ ScraperGetEventType(void) {
+ event = NULL;
+ recording = NULL;
+ type = tNone;
+ movieId = 0;
+ seriesId = 0;
+ episodeId = 0;
+ };
+// in
+ const cEvent *event; // check type for this event
+ const cRecording *recording; // or for this recording
+//out
+ tvType type; //typeSeries or typeMovie
+ int movieId;
+ int seriesId;
+ int episodeId;
+};
+
+//Data structure for full series and episode information
+class cMovie {
+public:
+ cMovie(void) {
+ title = "";
+ originalTitle = "";
+ tagline = "";
+ overview = "";
+ adult = false;
+ collectionName = "";
+ budget = 0;
+ revenue = 0;
+ genres = "";
+ homepage = "";
+ releaseDate = "";
+ runtime = 0;
+ popularity = 0.0;
+ voteAverage = 0.0;
+ };
+//IN
+ int movieId; // movieId fetched from ScraperGetEventType
+//OUT
+ std::string title;
+ std::string originalTitle;
+ std::string tagline;
+ std::string overview;
+ bool adult;
+ std::string collectionName;
+ int budget;
+ int revenue;
+ std::string genres;
+ std::string homepage;
+ std::string releaseDate;
+ int runtime;
+ float popularity;
+ float voteAverage;
+ cTvMedia poster;
+ cTvMedia fanart;
+ cTvMedia collectionPoster;
+ cTvMedia collectionFanart;
+ std::vector<cActor> actors;
+};
+
+//Data structure for full series and episode information
+class cSeries {
+public:
+ cSeries(void) {
+ seriesId = 0;
+ episodeId = 0;
+ name = "";
+ overview = "";
+ firstAired = "";
+ network = "";
+ genre = "";
+ rating = 0.0;
+ status = "";
+ };
+//IN
+ int seriesId; // seriesId fetched from ScraperGetEventType
+ int episodeId; // episodeId fetched from ScraperGetEventType
+//OUT
+ std::string name;
+ std::string overview;
+ std::string firstAired;
+ std::string network;
+ std::string genre;
+ float rating;
+ std::string status;
+ cEpisode episode;
+ std::vector<cActor> actors;
+ std::vector<cTvMedia> posters;
+ std::vector<cTvMedia> banners;
+ std::vector<cTvMedia> fanarts;
+ cTvMedia seasonPoster;
+};
+
+// Data structure for service "GetPosterBanner"
+class ScraperGetPosterBanner {
+public:
+ ScraperGetPosterBanner(void) {
+ type = tNone;
+ };
+// in
+ const cEvent *event; // check type for this event
+//out
+ tvType type; //typeSeries or typeMovie
+ cTvMedia poster;
+ cTvMedia banner;
+};
+
+// Data structure for service "GetPoster"
+class ScraperGetPoster {
+public:
+// in
+ const cEvent *event; // check type for this event
+ const cRecording *recording; // or for this recording
+//out
+ cTvMedia poster;
+};
+
+// Data structure for service "GetPosterThumb"
+class ScraperGetPosterThumb {
+public:
+// in
+ const cEvent *event; // check type for this event
+ const cRecording *recording; // or for this recording
+//out
+ cTvMedia poster;
+};
+
+#endif //__SCRAPER2VDRSERVICES_H \ No newline at end of file
diff --git a/setup.c b/setup.c
new file mode 100644
index 0000000..3e9a1e2
--- /dev/null
+++ b/setup.c
@@ -0,0 +1,78 @@
+#include "setup.h"
+
+extern cScraper2VdrConfig config;
+
+cScraper2VdrSetup::cScraper2VdrSetup(cUpdate *update) {
+ this->update = update;
+ tmpConfig = config;
+ strn0cpy(host, tmpConfig.mysqlHost.c_str(), sizeof(host));
+ strn0cpy(dbname, tmpConfig.mysqlDBName.c_str(), sizeof(dbname));
+ strn0cpy(user, tmpConfig.mysqlDBUser.c_str(), sizeof(user));
+ strn0cpy(password, tmpConfig.mysqlDBPass.c_str(), sizeof(password));
+ Setup();
+}
+
+cScraper2VdrSetup::~cScraper2VdrSetup() {
+}
+
+
+void cScraper2VdrSetup::Setup(void) {
+ int currentItem = Current();
+ Clear();
+
+ Add(new cMenuEditBoolItem(tr("Show Main Menu Entry"), &tmpConfig.mainMenuEntry));
+ Add(new cMenuEditStrItem(tr("MySQL Host"), host, sizeof(host), tr(FileNameChars)));
+ Add(new cMenuEditIntItem(tr("MySQL Port"), &tmpConfig.mysqlPort, 1, 99999));
+ Add(new cMenuEditStrItem(tr("MySQL Database Name"), dbname, sizeof(dbname), tr(FileNameChars)));
+ Add(new cMenuEditStrItem(tr("MySQL User"), user, sizeof(user), tr(FileNameChars)));
+ Add(new cMenuEditStrItem(tr("MySQL Password"), password, sizeof(password), tr(FileNameChars)));
+
+ Add(new cOsdItem(tr("Update Scraper Information from Database")));
+ Add(new cOsdItem(tr("Update Scraper Recordings Information from Database")));
+ Add(new cOsdItem(tr("Scan for new recordings in video directory")));
+ Add(new cOsdItem(tr("Scan for new or updated scrapinfo files")));
+ Add(new cOsdItem(tr("Cleanup Recordings in Database")));
+
+ SetCurrent(Get(currentItem));
+ Display();
+}
+
+eOSState cScraper2VdrSetup::ProcessKey(eKeys Key) {
+ bool hadSubMenu = HasSubMenu();
+ eOSState state = cMenuSetupPage::ProcessKey(Key);
+ if (Key == kOk) {
+ tmpConfig.mysqlHost = host;
+ tmpConfig.mysqlDBName = dbname;
+ tmpConfig.mysqlDBUser = user;
+ tmpConfig.mysqlDBPass = password;
+ Store();
+ if (Current() == 6) {
+ Skins.Message(mtInfo, tr("Updating Scraper EPG Information from Database"));
+ update->ForceUpdate();
+ } else if (Current() == 7) {
+ Skins.Message(mtInfo, tr("Updating Scraper Recordings Information from Database"));
+ update->ForceRecordingUpdate();
+ } else if (Current() == 8) {
+ Skins.Message(mtInfo, tr("Scanning for new recordings in video directory"));
+ update->ForceVideoDirUpdate();
+ } else if (Current() == 9) {
+ Skins.Message(mtInfo, tr("Scanning for new or updated scrapinfo files"));
+ update->ForceScrapInfoUpdate();
+ } else if (Current() == 10) {
+ Skins.Message(mtInfo, tr("Cleaning up Recordings in Database"));
+ update->TriggerCleanRecordingsDB();
+ }
+ return osEnd;
+ }
+ return state;
+}
+
+void cScraper2VdrSetup::Store(void) {
+ config = tmpConfig;
+ SetupStore("mainMenuEntry", tmpConfig.mainMenuEntry);
+ SetupStore("mysqlHost", tmpConfig.mysqlHost.c_str());
+ SetupStore("mysqlPort", tmpConfig.mysqlPort);
+ SetupStore("mysqlDBName", tmpConfig.mysqlDBName.c_str());
+ SetupStore("mysqlDBUser", tmpConfig.mysqlDBUser.c_str());
+ SetupStore("mysqlDBPass", tmpConfig.mysqlDBPass.c_str());
+}
diff --git a/setup.h b/setup.h
new file mode 100644
index 0000000..baf417f
--- /dev/null
+++ b/setup.h
@@ -0,0 +1,25 @@
+#ifndef __SCRAPER2VDR_SETUP_H
+#define __SCRAPER2VDR_SETUP_H
+
+#include <vdr/menuitems.h>
+#include "update.h"
+#include "config.h"
+
+class cScraper2VdrSetup : public cMenuSetupPage {
+ public:
+ cScraper2VdrSetup(cUpdate *update);
+ virtual ~cScraper2VdrSetup();
+ private:
+ cUpdate *update;
+ cScraper2VdrConfig tmpConfig;
+ char host[256];
+ char dbname[256];
+ char user[256];
+ char password[256];
+ void Setup(void);
+ protected:
+ virtual eOSState ProcessKey(eKeys Key);
+ virtual void Store(void);
+
+};
+#endif //__SCRAPER2VDR_SETUP_H \ No newline at end of file
diff --git a/tools.c b/tools.c
new file mode 100644
index 0000000..ea0e41e
--- /dev/null
+++ b/tools.c
@@ -0,0 +1,170 @@
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <dirent.h>
+#include <fstream>
+#include <algorithm>
+#include <functional>
+#include <cctype>
+#include <locale>
+#include <Magick++.h>
+#include <vdr/plugin.h>
+#include "lib/common.h"
+#include "tools.h"
+
+using namespace std;
+using namespace Magick;
+
+bool CreateDirectory(string dir) {
+ mkdir(dir.c_str(), 0775);
+ //check if successfull
+ DIR *pDir;
+ bool exists = false;
+ pDir = opendir(dir.c_str());
+ if (pDir != NULL) {
+ exists = true;
+ closedir(pDir);
+ }
+ return exists;
+}
+
+bool FileExists(string filename, bool isImage) {
+ ifstream ifile(filename.c_str());
+ if (ifile) {
+ //a valid image should be larger then 500 bytes
+ ifile.seekg (0, ifile.end);
+ int length = ifile.tellg();
+ int minimumLength = isImage ? 500 : 1;
+ if (length > minimumLength)
+ return true;
+ }
+ return false;
+}
+
+bool CheckDirExists(const char* dirName) {
+ struct statfs statfsbuf;
+ if (statfs(dirName,&statfsbuf)==-1) return false;
+ if ((statfsbuf.f_type!=0x01021994) && (statfsbuf.f_type!=0x28cd3d45)) return false;
+ if (access(dirName,R_OK|W_OK)==-1) return false;
+ return true;
+
+}
+
+void DeleteFile(string filename) {
+ remove(filename.c_str());
+}
+
+void DeleteDirectory(string dirname) {
+ DIR *dir;
+ struct dirent *entry;
+ if ((dir = opendir (dirname.c_str())) != NULL) {
+ while ((entry = readdir (dir)) != NULL) {
+ string file = entry->d_name;
+ if (!file.compare("."))
+ continue;
+ if (!file.compare(".."))
+ continue;
+ string delFile = dirname + "/" + file;
+ DeleteFile(delFile);
+ }
+ closedir (dir);
+ }
+ rmdir(dirname.c_str());
+}
+
+string TwoFoldersHigher(string folder) {
+ unsigned found = folder.find_last_of("/");
+ if (found != string::npos) {
+ string firstDirRemoved = folder.substr(0,found);
+ unsigned found2 = firstDirRemoved.find_last_of("/");
+ if (found2 != string::npos) {
+ return firstDirRemoved.substr(0,found2);
+ }
+ }
+ return "";
+}
+
+
+// trim from start
+string &ltrim(string &s) {
+ s.erase(s.begin(), find_if(s.begin(), s.end(), not1(ptr_fun<int, int>(isspace))));
+ return s;
+}
+
+// trim from end
+string &rtrim(string &s) {
+ s.erase(find_if(s.rbegin(), s.rend(), not1(ptr_fun<int, int>(isspace))).base(), s.end());
+ return s;
+}
+
+// trim from both ends
+string &trim(string &s) {
+ return ltrim(rtrim(s));
+}
+
+void toLower(string &s) {
+ transform(s.begin(), s.end(), s.begin(), ::tolower);
+}
+
+bool isNumber(const string& s) {
+ string::const_iterator it = s.begin();
+ while (it != s.end() && isdigit(*it)) ++it;
+ return !s.empty() && it == s.end();
+}
+
+string replaceString(string content, string search, string repl) {
+ size_t pos = 0;
+ while((pos = content.find(search, pos)) != std::string::npos) {
+ if (pos > 3 && pos < content.size() - 2) {
+ content.replace(pos, search.length(), repl);
+ } else {
+ content.replace(pos, search.length(), "");
+ }
+ pos += repl.length();
+ }
+ return content;
+}
+
+
+/****************************************************************************************
+* SPLTSTRING
+****************************************************************************************/
+// split: receives a char delimiter; returns a vector of strings
+// By default ignores repeated delimiters, unless argument rep == 1.
+vector<string>& splitstring::split(char delim, int rep) {
+ if (!flds.empty()) flds.clear(); // empty vector if necessary
+ string work = data();
+ string buf = "";
+ int i = 0;
+ while (i < work.length()) {
+ if (work[i] != delim)
+ buf += work[i];
+ else if (rep == 1) {
+ flds.push_back(buf);
+ buf = "";
+ } else if (buf.length() > 0) {
+ flds.push_back(buf);
+ buf = "";
+ }
+ i++;
+ }
+ if (!buf.empty())
+ flds.push_back(buf);
+ return flds;
+}
+
+void CreateThumbnail(string sourcePath, string destPath, int origWidth, int origHeight, int shrinkFactor) {
+ if (sourcePath.size() < 5 || destPath.size() < 5 || shrinkFactor < 2)
+ return;
+
+ int thumbWidth = origWidth / shrinkFactor;
+ int thumbHeight = origHeight / shrinkFactor;
+
+ InitializeMagick(NULL);
+ Image buffer;
+ try {
+ buffer.read(sourcePath.c_str());
+ buffer.sample(Geometry(thumbWidth, thumbHeight));
+ buffer.write(destPath.c_str());
+ } catch( ... ) {}
+} \ No newline at end of file
diff --git a/tools.h b/tools.h
new file mode 100644
index 0000000..64e8103
--- /dev/null
+++ b/tools.h
@@ -0,0 +1,30 @@
+#include <vector>
+#include <string>
+
+using namespace std;
+
+//Filesystem Functions
+bool CreateDirectory(string dir);
+bool FileExists(string filename, bool isImage = true);
+bool CheckDirExists(const char* dirName);
+void DeleteFile(string filename);
+void DeleteDirectory(string dirname);
+string TwoFoldersHigher(string folder);
+
+//String Functions
+string &ltrim(string &s);
+string &rtrim(string &s);
+string &trim(string &s);
+void toLower(string &s);
+bool isNumber(const string& s);
+string replaceString(string content, string search, string repl);
+
+class splitstring : public string {
+ vector<string> flds;
+public:
+ splitstring(const char *s) : string(s) { };
+ vector<string>& split(char delim, int rep=0);
+};
+
+//Image Functions
+void CreateThumbnail(string sourcePath, string destPath, int origWidth, int origHeight, int shrinkFactor); \ No newline at end of file
diff --git a/tvdbseries.c b/tvdbseries.c
new file mode 100644
index 0000000..96bde06
--- /dev/null
+++ b/tvdbseries.c
@@ -0,0 +1,289 @@
+#define __STL_CONFIG_H
+#include "lib/common.h"
+#include "tvdbseries.h"
+
+using namespace std;
+
+cTVDBSeries::cTVDBSeries(void) {
+ id = 0;
+ name = "";
+ overview = "";
+ firstAired = "";
+ network = "";
+ genre = "";
+ rating = 0.0;
+ status = "";
+ posterThumb = NULL;
+}
+
+cTVDBSeries::~cTVDBSeries() {
+ for (map<int, cTVDBActor*>::iterator it = actors.begin(); it != actors.end(); it++) {
+ cTVDBActor *a = (cTVDBActor*)it->second;
+ delete a;
+ }
+ for (map<int, cTVDBEpisode*>::iterator it = episodes.begin(); it != episodes.end(); it++) {
+ cTVDBEpisode *e = (cTVDBEpisode*)it->second;
+ delete e;
+ }
+ for (vector<cTVDBMedia*>::iterator it = posters.begin(); it != posters.end(); it++) {
+ cTVDBMedia *p = *it;
+ delete p;
+ }
+ for (vector<cTVDBMedia*>::iterator it = banners.begin(); it != banners.end(); it++) {
+ cTVDBMedia *b = *it;
+ delete b;
+ }
+ for (vector<cTVDBMedia*>::iterator it = fanart.begin(); it != fanart.end(); it++) {
+ cTVDBMedia *f = *it;
+ delete f;
+ }
+ for (map<int, cTVDBMedia*>::iterator it = seasonPosters.begin(); it != seasonPosters.end(); it++) {
+ cTVDBMedia *s = (cTVDBMedia*)it->second;
+ delete s;
+ }
+ for (map<int, cTVDBMedia*>::iterator it = seasonPosterThumbs.begin(); it != seasonPosterThumbs.end(); it++) {
+ cTVDBMedia *s = (cTVDBMedia*)it->second;
+ delete s;
+ }
+ if (posterThumb)
+ delete posterThumb;
+}
+
+void cTVDBSeries::InsertEpisode(cTVDBEpisode *episode) {
+ map<int, cTVDBEpisode*>::iterator hit = episodes.find(episode->id);
+ if (hit != episodes.end())
+ delete episode;
+ else
+ episodes.insert(pair<int, cTVDBEpisode*>(episode->id, episode));
+}
+
+void cTVDBSeries::InsertEpisodeImage(int episodeId, int width, int height, string path) {
+ map<int, cTVDBEpisode*>::iterator hit = episodes.find(episodeId);
+ if (hit != episodes.end()) {
+ cTVDBEpisode *e = hit->second;
+ cTVDBMedia *m = new cTVDBMedia();
+ m->width = width;
+ m->height = height;
+ m->path = path;
+ m->mediaType = msEpisodePic;
+ e->episodeImage = m;
+ }
+}
+
+void cTVDBSeries::InsertActor(cTVDBActor *actor) {
+ actors.insert(pair<int, cTVDBActor*>(actor->id, actor));
+}
+
+void cTVDBSeries::InsertActorThumb(int actorId, int imgWidth, int imgHeight, string path) {
+ map<int, cTVDBActor*>::iterator hit = actors.find(actorId);
+ if (hit != actors.end()) {
+ cTVDBActor *a = hit->second;
+ cTVDBMedia *m = new cTVDBMedia();
+ m->width = imgWidth;
+ m->height = imgHeight;
+ m->path = path;
+ m->mediaType = msActorThumb;
+ a->actorThumb = m;
+ }
+}
+
+void cTVDBSeries::InsertMedia(int mediaType, int imgWidth, int imgHeight, string path, int season) {
+ cTVDBMedia *media = new cTVDBMedia();
+ media->width = imgWidth;
+ media->height = imgHeight;
+ media->path = path;
+ media->mediaType = mediaType;
+ switch (mediaType) {
+ case msPoster1:
+ case msPoster2:
+ case msPoster3:
+ posters.push_back(media);
+ break;
+ case msFanart1:
+ case msFanart2:
+ case msFanart3:
+ fanart.push_back(media);
+ break;
+ case msBanner1:
+ case msBanner2:
+ case msBanner3:
+ banners.push_back(media);
+ case msSeasonPoster:
+ seasonPosters.insert(pair<int, cTVDBMedia*>(season, media));
+ break;
+ case msPosterThumb:
+ posterThumb = media;
+ break;
+ case msSeasonPosterThumb:
+ seasonPosterThumbs.insert(pair<int, cTVDBMedia*>(season, media));
+ break;
+ default:
+ break;
+ }
+}
+
+void cTVDBSeries::GetEpisode(int episodeId, cEpisode *e) {
+ map<int, cTVDBEpisode*>::iterator hit = episodes.find(episodeId);
+ if (hit == episodes.end())
+ return;
+ cTVDBEpisode *eStored = hit->second;
+ e->number = eStored->number;
+ e->season = eStored->season;
+ e->name = eStored->name;
+ e->firstAired = eStored->firstAired;
+ e->guestStars = eStored->guestStars;
+ e->overview = eStored->overview;
+ e->rating = eStored->rating;
+ if (eStored->episodeImage) {
+ e->episodeImage.path = eStored->episodeImage->path;
+ e->episodeImage.width = eStored->episodeImage->width;
+ e->episodeImage.height = eStored->episodeImage->height;
+ }
+}
+
+void cTVDBSeries::GetPosters(vector<cTvMedia> *p) {
+ for (vector<cTVDBMedia*>::iterator it = posters.begin(); it != posters.end(); it++) {
+ cTVDBMedia *mStored = *it;
+ cTvMedia m;
+ m.path = mStored->path;
+ m.width = mStored->width;
+ m.height = mStored->height;
+ p->push_back(m);
+ }
+}
+
+bool cTVDBSeries::GetPoster(cTvMedia *p) {
+ if (posters.size() > 0) {
+ p->path = posters[0]->path;
+ p->width = posters[0]->width;
+ p->height = posters[0]->height;
+ return true;
+ }
+ return false;
+}
+
+bool cTVDBSeries::GetPosterThumb(cTvMedia *p) {
+ if (posterThumb) {
+ p->path = posterThumb->path;
+ p->width = posterThumb->width;
+ p->height = posterThumb->height;
+ return true;
+ }
+ return false;
+}
+
+void cTVDBSeries::GetBanners(vector<cTvMedia> *b) {
+ for (vector<cTVDBMedia*>::iterator it = banners.begin(); it != banners.end(); it++) {
+ cTVDBMedia *bStored = *it;
+ cTvMedia m;
+ m.path = bStored->path;
+ m.width = bStored->width;
+ m.height = bStored->height;
+ b->push_back(m);
+ }
+}
+
+bool cTVDBSeries::GetRandomBanner(cTvMedia *b) {
+ int numBanners = banners.size();
+ if (numBanners == 0)
+ return false;
+ srand((unsigned)time(NULL));
+ int banner = rand()%numBanners;
+ cTVDBMedia *bStored = banners[banner];
+ b->path = bStored->path;
+ b->width = bStored->width;
+ b->height = bStored->height;
+ return true;
+}
+
+void cTVDBSeries::GetFanart(vector<cTvMedia> *f) {
+ for (vector<cTVDBMedia*>::iterator it = fanart.begin(); it != fanart.end(); it++) {
+ cTVDBMedia *fStored = *it;
+ cTvMedia m;
+ m.path = fStored->path;
+ m.width = fStored->width;
+ m.height = fStored->height;
+ f->push_back(m);
+ }
+}
+
+void cTVDBSeries::GetSeasonPoster(int episodeId, cTvMedia *sp) {
+ map<int, cTVDBEpisode*>::iterator hit = episodes.find(episodeId);
+ if (hit == episodes.end())
+ return;
+ cTVDBEpisode *e = hit->second;
+ map<int, cTVDBMedia*>::iterator hit2 = seasonPosters.find(e->season);
+ if (hit2 == seasonPosters.end())
+ return;
+ cTVDBMedia *spStored = hit2->second;
+ sp->width = spStored->width;
+ sp->height = spStored->height;
+ sp->path = spStored->path;
+}
+
+void cTVDBSeries::GetActors(vector<cActor> *a) {
+ for (map<int, cTVDBActor*>::iterator it = actors.begin(); it != actors.end(); it++) {
+ cTVDBActor *aStored = it->second;
+ cActor act;
+ act.name = aStored->name;
+ act.role = aStored->role;
+ if (aStored->actorThumb) {
+ act.actorThumb.width = aStored->actorThumb->width;
+ act.actorThumb.height = aStored->actorThumb->height;
+ act.actorThumb.path = aStored->actorThumb->path;
+ }
+ a->push_back(act);
+ }
+}
+
+void cTVDBSeries::Dump(void) {
+ tell(0, "--------------------------- Series Info ----------------------------------");
+ tell(0, "series %s, ID: %d", name.c_str(), id);
+ tell(0, "Overview: %s", overview.c_str());
+ tell(0, "FirstAired: %s", firstAired.c_str());
+ tell(0, "Network: %s", network.c_str());
+ tell(0, "Status: %s", status.c_str());
+ tell(0, "Genre: %s", genre.c_str());
+ tell(0, "Rating: %f", rating);
+ tell(0, "--------------------------- Media ----------------------------------");
+ for (vector<cTVDBMedia*>::iterator it = posters.begin(); it != posters.end(); it++) {
+ cTVDBMedia *m = *it;
+ tell(0, "Poster %d, Path: %s", m->mediaType, m->path.c_str());
+ tell(0, "width %d, height %d", m->width, m->height);
+ }
+ for (vector<cTVDBMedia*>::iterator it = banners.begin(); it != banners.end(); it++) {
+ cTVDBMedia *m = *it;
+ tell(0, "Banner %d, Path: %s", m->mediaType, m->path.c_str());
+ tell(0, "width %d, height %d", m->width, m->height);
+ }
+ for (vector<cTVDBMedia*>::iterator it = fanart.begin(); it != fanart.end(); it++) {
+ cTVDBMedia *m = *it;
+ tell(0, "Fanart %d, Path: %s", m->mediaType, m->path.c_str());
+ tell(0, "width %d, height %d", m->width, m->height);
+ }
+ tell(0, "--------------------------- Episodes ----------------------------------");
+ for (map<int, cTVDBEpisode*>::iterator it = episodes.begin(); it != episodes.end(); it++) {
+ cTVDBEpisode *e = it->second;
+ tell(0, "Episode %d, Name: %s", e->id, e->name.c_str());
+ if (e->episodeImage) {
+ tell(0, "Episode Image: %d x %d, Path: %s", e->episodeImage->width, e->episodeImage->height, e->episodeImage->path.c_str());
+ }
+ }
+ tell(0, "--------------------------- Season Posters ----------------------------------");
+ for (map<int, cTVDBMedia*>::iterator it = seasonPosters.begin(); it != seasonPosters.end(); it++) {
+ int season = it->first;
+ cTVDBMedia *m = it->second;
+ tell(0, "Season %d, %d x %d, Path: %s", season, m->width, m->height, m->path.c_str());
+ }
+ tell(0, "--------------------------- Actors ----------------------------------");
+ for (map<int, cTVDBActor*>::iterator it = actors.begin(); it != actors.end(); it++) {
+ cTVDBActor *a = it->second;
+ tell(0, "Actor %d, Name: %s, Role %s", a->id, a->name.c_str(), a->role.c_str());
+ if (a->actorThumb) {
+ tell(0, "Thumb: %d x %d, Path: %s", a->actorThumb->width, a->actorThumb->height, a->actorThumb->path.c_str());
+ }
+ }
+ if (posterThumb) {
+ tell(0, "posterThumb path %s, width %d, height %d", posterThumb->path.c_str(), posterThumb->width, posterThumb->height);
+ }
+}
diff --git a/tvdbseries.h b/tvdbseries.h
new file mode 100644
index 0000000..7d44929
--- /dev/null
+++ b/tvdbseries.h
@@ -0,0 +1,143 @@
+#ifndef __TVSCRAPER_TVDBSERIES_H
+#define __TVSCRAPER_TVDBSERIES_H
+
+#include <iostream>
+#include <string>
+#include <vector>
+#include <map>
+#include <set>
+#include <utility>
+#include <algorithm>
+#include "services.h"
+
+using namespace std;
+
+enum mediaSeries {
+ msBanner1,
+ msBanner2,
+ msBanner3,
+ msPoster1,
+ msPoster2,
+ msPoster3,
+ msSeasonPoster,
+ msFanart1,
+ msFanart2,
+ msFanart3,
+ msEpisodePic,
+ msActorThumb,
+ msPosterThumb,
+ msSeasonPosterThumb,
+};
+
+// --- cTVDBMedia -------------------------------------------------------------
+class cTVDBMedia {
+public:
+ cTVDBMedia(void) {
+ path = "";
+ mediaType = msBanner1;
+ width = 0;
+ height = 0;
+ };
+ ~cTVDBMedia(void) {
+ };
+ string path;
+ int mediaType;
+ int width;
+ int height;
+};
+
+// --- cTVDBEpisode -------------------------------------------------------------
+class cTVDBEpisode {
+public:
+ cTVDBEpisode(void) {
+ id = 0;
+ number = 0;
+ season = 0;
+ name = "";
+ firstAired = "";
+ guestStars = "";
+ overview = "";
+ rating = 0.0;
+ episodeImage = NULL;
+ };
+ ~cTVDBEpisode(void) {
+ if (episodeImage)
+ delete episodeImage;
+ };
+ int id;
+ int number;
+ int season;
+ string name;
+ string firstAired;
+ string guestStars;
+ string overview;
+ float rating;
+ cTVDBMedia *episodeImage;
+};
+
+// --- cTVDBActor -------------------------------------------------------------
+class cTVDBActor {
+public:
+ cTVDBActor(void) {
+ id = 0;
+ name = "";
+ role = "";
+ thumbWidth = 0;
+ thumbHeight = 0;
+ actorThumb = NULL;
+ };
+ ~cTVDBActor(void) {
+ if (actorThumb)
+ delete actorThumb;
+ };
+ int id;
+ string name;
+ string role;
+ int thumbWidth;
+ int thumbHeight;
+ cTVDBMedia *actorThumb;
+};
+
+// --- cTVDBSeries -------------------------------------------------------------
+
+class cTVDBSeries {
+private:
+ map<int, cTVDBEpisode*> episodes;
+ map<int, cTVDBActor*> actors;
+ vector<cTVDBMedia*> posters;
+ vector<cTVDBMedia*> banners;
+ vector<cTVDBMedia*> fanart;
+ map<int, cTVDBMedia*> seasonPosters;
+ map<int, cTVDBMedia*> seasonPosterThumbs;
+ cTVDBMedia *posterThumb;
+public:
+ cTVDBSeries(void);
+ virtual ~cTVDBSeries(void);
+ int id;
+ string name;
+ string overview;
+ string firstAired;
+ string network;
+ string genre;
+ float rating;
+ string status;
+ void InsertEpisode(cTVDBEpisode *episode);
+ void InsertEpisodeImage(int episodeId, int width, int height, string path);
+ void InsertActor(cTVDBActor *actor);
+ void InsertActorThumb(int actorId, int imgWidth, int imgHeight, string path);
+ void InsertMedia(int mediaType, int imgWidth, int imgHeight, string path, int season = 0);
+ //Getter for Serivice Calls
+ void GetEpisode(int episodeId, cEpisode *e);
+ void GetPosters(vector<cTvMedia> *p);
+ bool GetPoster(cTvMedia *p);
+ bool GetPosterThumb(cTvMedia *p);
+ void GetBanners(vector<cTvMedia> *b);
+ bool GetRandomBanner(cTvMedia *b);
+ void GetFanart(vector<cTvMedia> *f);
+ void GetSeasonPoster(int episodeId, cTvMedia *sp);
+ void GetActors(vector<cActor> *a);
+ void Dump(void);
+};
+
+
+#endif //__TVSCRAPER_TVDBSERIES_H
diff --git a/update.c b/update.c
new file mode 100644
index 0000000..8c56055
--- /dev/null
+++ b/update.c
@@ -0,0 +1,1323 @@
+#define __STL_CONFIG_H
+#include <locale.h>
+
+#include <vdr/videodir.h>
+#include <vdr/tools.h>
+#include <vdr/plugin.h>
+
+#include "config.h"
+#include "tools.h"
+#include "update.h"
+
+extern cScraper2VdrConfig config;
+
+cUpdate::cUpdate(cScrapManager *manager) : cThread("update thread started") {
+ connection = NULL;
+ vdrDb = NULL;
+ tEvents = NULL;
+ tSeries = NULL;
+ tEpisodes = NULL;
+ tSeriesMedia = NULL;
+ tSeriesActors = NULL;
+ tMovies = NULL;
+ tMovieActor = NULL;
+ tMovieActors = NULL;
+ tMovieMedia = NULL;
+ tRecordings = NULL;
+ scrapManager = manager;
+ imgPathSeries = config.imageDir + "/series";
+ imgPathMovies = config.imageDir + "/movies";
+ lastScrap = 0;
+ forceUpdate = false;
+ forceRecordingUpdate = false;
+ forceVideoDirUpdate = false;
+ forceScrapInfoUpdate = false;
+ forceCleanupRecordingDb = false;
+ char* lang;
+ 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.");
+ }
+ cDbConnection::setEncoding(withutf8 ? "utf8": "latin1");
+ cDbConnection::setHost(config.mysqlHost.c_str());
+ cDbConnection::setPort(config.mysqlPort);
+ cDbConnection::setName(config.mysqlDBName.c_str());
+ cDbConnection::setUser(config.mysqlDBUser.c_str());
+ cDbConnection::setPass(config.mysqlDBPass.c_str());
+ cDbTable::setConfPath(cPlugin::ConfigDirectory("epg2vdr/"));
+}
+
+cUpdate::~cUpdate() {
+ if (loopActive)
+ Stop();
+ if (vdrDb)
+ delete vdrDb;
+ if (tEvents)
+ delete tEvents;
+ if (tSeries)
+ delete tSeries;
+ if (tEpisodes)
+ tEpisodes;
+ if (tSeriesMedia)
+ delete tSeriesMedia;
+ if (tSeriesActors)
+ delete tSeriesActors;
+ if (tMovies)
+ delete tMovies;
+ if (tMovieActor)
+ delete tMovieActor;
+ if (tMovieActors)
+ delete tMovieActors;
+ if (tMovieMedia)
+ delete tMovieMedia;
+ if (tRecordings)
+ delete tRecordings;
+ exitDb();
+}
+
+int cUpdate::initDb() {
+ int status = success;
+ if (!connection)
+ connection = new cDbConnection();
+ if (!connection)
+ return fail;
+ vdrDb = new cTableVdrs(connection);
+ if (vdrDb->open() != success)
+ return fail;
+ tEvents = new cTableEvents(connection);
+ if (tEvents->open() != success)
+ return fail;
+ tSeries = new cTableSeries(connection);
+ if (tSeries->open() != success)
+ return fail;
+ tEpisodes = new cTableSeriesEpisode(connection);
+ if (tEpisodes->open() != success)
+ return fail;
+ tSeriesMedia = new cTableSeriesMedia(connection);
+ if (tSeriesMedia->open() != success)
+ return fail;
+ tSeriesActors = new cTableSeriesActor(connection);
+ if (tSeriesActors->open() != success)
+ return fail;
+ tMovies = new cTableMovies(connection);
+ if (tMovies->open() != success)
+ return fail;
+ tMovieActor = new cTableMovieActor(connection);
+ if (tMovieActor->open() != success)
+ return fail;
+ tMovieActors = new cTableMovieActors(connection);
+ if (tMovieActors->open() != success)
+ return fail;
+ tMovieMedia = new cTableMovieMedia(connection);
+ if (tMovieMedia->open() != success)
+ return fail;
+ tRecordings = new cTableRecordings(connection);
+ if (tRecordings->open() != success)
+ return fail;
+ return status;
+}
+
+int cUpdate::exitDb() {
+ delete connection;
+ connection = 0;
+ return done;
+}
+
+void cUpdate::Stop() {
+ loopActive = false;
+ waitCondition.Broadcast();
+ Cancel(3);
+}
+
+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;
+}
+
+bool cUpdate::CheckEpgdBusy(void) {
+ vdrDb->clear();
+ vdrDb->setValue(cTableVdrs::fiUuid, EPGDNAME);
+ if (vdrDb->find()) {
+ Es::State epgdState = cEpgdState::toState(vdrDb->getStrValue(cTableVdrs::fiState));
+ if (epgdState >= cEpgdState::esBusy)
+ return true;
+ }
+ return false;
+}
+
+int cUpdate::ReadScrapedEvents(void) {
+ int status = success;
+ cDbStatement *select = new cDbStatement(tEvents);
+ select->build("select ");
+ select->bind(cTableEvents::fiEventId, cDBS::bndOut);
+ select->bind(cTableEvents::fiChannelId, cDBS::bndOut, ", ");
+ select->bind(cTableEvents::fiUseId, cDBS::bndOut, ", ");
+ select->bind(cTableEvents::fiScrSeriesId, cDBS::bndOut, ", ");
+ select->bind(cTableEvents::fiScrSeriesEpisode, cDBS::bndOut, ", ");
+ select->bind(cTableEvents::fiScrMovieId, cDBS::bndOut, ", ");
+ select->bind(cTableEvents::fiScrSp, cDBS::bndOut, ", ");
+ select->build(" from %s where ", tEvents->TableName());
+ select->build(" ((%s is not null and %s > 0) ",
+ tEvents->getField(cTableEvents::fiScrSeriesId)->name,
+ tEvents->getField(cTableEvents::fiScrSeriesId)->name);
+ select->build(" or (%s is not null and %s > 0)) ",
+ tEvents->getField(cTableEvents::fiScrMovieId)->name,
+ tEvents->getField(cTableEvents::fiScrMovieId)->name);
+ if (lastScrap > 0) {
+ select->build(" and %s > %d",
+ tEvents->getField(cTableEvents::fiScrSp)->name,
+ lastScrap);
+ }
+ status += select->prepare();
+ if (status != success) {
+ delete select;
+ return 0;
+ }
+
+ int eventId = 0;
+ int seriesId = 0;
+ int episodeId = 0;
+ int movieId = 0;
+ string channelId = "";
+
+ int numNew = 0;
+ for (int res = select->find(); res; res = select->fetch()) {
+ eventId = tEvents->getIntValue(cTableEvents::fiUseId);
+ channelId = tEvents->getStrValue(cTableEvents::fiChannelId);
+ seriesId = tEvents->getIntValue(cTableEvents::fiScrSeriesId);
+ episodeId = tEvents->getIntValue(cTableEvents::fiScrSeriesEpisode);
+ movieId = tEvents->getIntValue(cTableEvents::fiScrMovieId);
+ scrapManager->AddEvent(eventId, channelId, seriesId, episodeId, movieId);
+ lastScrap = max(lastScrap, (int)tEvents->getIntValue(cTableEvents::fiScrSp));
+ numNew++;
+ }
+ select->freeResult();
+ delete select;
+ return numNew;
+}
+
+//***************************************************************************
+// SERIES
+//***************************************************************************
+
+int cUpdate::ReadSeries(bool isRec) {
+ scrapManager->InitIterator(isRec);
+ int seriesId = 0;
+ int episodeId = 0;
+
+ if (!CreateDirectory(config.imageDir))
+ return 0;
+ if (!CreateDirectory(imgPathSeries))
+ return 0;
+
+ bool isNew = false;
+ int numNew = 0;
+ while (scrapManager->GetNextSeries(isRec, seriesId, episodeId)) {
+ cTVDBSeries *series = scrapManager->GetSeries(seriesId);
+ if (!series) {
+ tSeries->clear();
+ tSeries->setValue(cTableSeries::fiSeriesId, seriesId);
+ int res = tSeries->find();
+ if (res) {
+ series = scrapManager->AddSeries(tSeries);
+ }
+ isNew = true;
+ } else {
+ isNew = false;
+ }
+ if (series) {
+ stringstream sPath;
+ sPath << imgPathSeries << "/" << seriesId;
+ string seriesPath = sPath.str();
+ if (episodeId) {
+ ReadEpisode(episodeId, series, seriesPath);
+ }
+ if (isNew) {
+ ReadSeriesActors(series, seriesPath);
+ LoadSeriesMedia(series, seriesPath);
+ }
+ }
+ numNew++;
+ }
+ return numNew;
+}
+
+void cUpdate::ReadEpisode(int episodeId, cTVDBSeries *series, string path) {
+ int status = success;
+ tEpisodes->clear();
+ tEpisodes->setValue(cTableSeriesEpisode::fiEpisodeId, episodeId);
+ cDbStatement *selectEpisode = new cDbStatement(tEpisodes);
+ selectEpisode->build("select ");
+ selectEpisode->bind(cTableSeriesEpisode::fiEpisodeName, cDBS::bndOut);
+ selectEpisode->bind(cTableSeriesEpisode::fiEpisodeNumber, cDBS::bndOut, ", ");
+ selectEpisode->bind(cTableSeriesEpisode::fiSeasonNumber, cDBS::bndOut, ", ");
+ selectEpisode->bind(cTableSeriesEpisode::fiEpisodeOverview, cDBS::bndOut, ", ");
+ selectEpisode->bind(cTableSeriesEpisode::fiEpisodeFirstAired, cDBS::bndOut, ", ");
+ selectEpisode->bind(cTableSeriesEpisode::fiEpisodeGuestStars, cDBS::bndOut, ", ");
+ selectEpisode->bind(cTableSeriesEpisode::fiEpisodeRating, cDBS::bndOut, ", ");
+ selectEpisode->build(" from %s where ", tEpisodes->TableName());
+ selectEpisode->bind(cTableSeriesEpisode::fiEpisodeId, cDBS::bndIn | cDBS::bndSet);
+ status += selectEpisode->prepare();
+ if (status != success) {
+ delete selectEpisode;
+ return;
+ }
+ int res = selectEpisode->find();
+ if (res) {
+ scrapManager->AddSeriesEpisode(series, tEpisodes);
+ LoadEpisodeImage(series, episodeId, path);
+ int season = tEpisodes->getIntValue(cTableSeriesEpisode::fiSeasonNumber);
+ if (season > 0)
+ LoadSeasonPoster(series, season, path);
+ }
+ selectEpisode->freeResult();
+ delete selectEpisode;
+ return;
+}
+
+void cUpdate::LoadEpisodeImage(cTVDBSeries *series, int episodeId, string path) {
+ int status = success;
+ stringstream iPath;
+ iPath << path << "/" << "episode_" << episodeId << ".jpg";
+ string imgPath = iPath.str();
+ bool imgExists = FileExists(imgPath);
+ if (!CreateDirectory(path))
+ return;
+ tSeriesMedia->clear();
+ cDbValue imageSize;
+ cDBS::FieldDef imageSizeDef = { "media_content", cDBS::ffUInt, 0, 999, cDBS::ftData };
+ imageSize.setField(&imageSizeDef);
+ cDbStatement *selectImg = new cDbStatement(tSeriesMedia);
+ selectImg->build("select ");
+ selectImg->bind(cTableSeriesMedia::fiMediaWidth, cDBS::bndOut);
+ selectImg->bind(cTableSeriesMedia::fiMediaHeight, cDBS::bndOut, ", ");
+ if (!imgExists) {
+ selectImg->bind(cTableSeriesMedia::fiMediaContent, cDBS::bndOut, ", ");
+ selectImg->build(", length(");
+ selectImg->bind(&imageSize, cDBS::bndOut);
+ selectImg->build(")");
+ }
+ selectImg->build(" from %s where ", tSeriesMedia->TableName());
+ selectImg->bind(cTableSeriesMedia::fiSeriesId, cDBS::bndIn | cDBS::bndSet);
+ selectImg->bind(cTableSeriesMedia::fiEpisodeId, cDBS::bndIn | cDBS::bndSet, " and ");
+ status += selectImg->prepare();
+ if (status != success) {
+ delete selectImg;
+ return;
+ }
+ tSeriesMedia->setValue(cTableSeriesMedia::fiSeriesId, series->id);
+ tSeriesMedia->setValue(cTableSeriesMedia::fiEpisodeId, episodeId);
+ int res = selectImg->find();
+ if (res) {
+ if (!imgExists) {
+ int size = imageSize.getIntValue();
+ if (FILE* fh = fopen(imgPath.c_str(), "w")) {
+ fwrite(tSeriesMedia->getStrValue(cTableSeriesMedia::fiMediaContent), 1, size, fh);
+ fclose(fh);
+ }
+ }
+ int imgWidth = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaWidth);
+ int imgHeight = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaHeight);
+ series->InsertEpisodeImage(episodeId, imgWidth, imgHeight, imgPath);
+ }
+ selectImg->freeResult();
+ delete selectImg;
+}
+
+void cUpdate::LoadSeasonPoster(cTVDBSeries *series, int season, string path) {
+ int status = success;
+ stringstream iPath;
+ iPath << path << "/" << "season_" << season << ".jpg";
+ stringstream tPath;
+ tPath << path << "/" << "season_" << season << "_thumb.jpg";
+ string imgPath = iPath.str();
+ string thumbPath = tPath.str();
+ bool imgExists = FileExists(imgPath);
+ if (!CreateDirectory(path))
+ return;
+ tSeriesMedia->clear();
+ cDbValue imageSize;
+ cDBS::FieldDef imageSizeDef = { "media_content", cDBS::ffUInt, 0, 999, cDBS::ftData };
+ imageSize.setField(&imageSizeDef);
+ cDbStatement *selectImg = new cDbStatement(tSeriesMedia);
+ selectImg->build("select ");
+ selectImg->bind(cTableSeriesMedia::fiMediaWidth, cDBS::bndOut);
+ selectImg->bind(cTableSeriesMedia::fiMediaHeight, cDBS::bndOut, ", ");
+ if (!imgExists) {
+ selectImg->bind(cTableSeriesMedia::fiMediaContent, cDBS::bndOut, ", ");
+ selectImg->build(", length(");
+ selectImg->bind(&imageSize, cDBS::bndOut);
+ selectImg->build(")");
+ }
+ selectImg->build(" from %s where ", tSeriesMedia->TableName());
+ selectImg->bind(cTableSeriesMedia::fiSeriesId, cDBS::bndIn | cDBS::bndSet);
+ selectImg->bind(cTableSeriesMedia::fiSeasonNumber, cDBS::bndIn | cDBS::bndSet, " and ");
+ selectImg->bind(cTableSeriesMedia::fiMediaType, cDBS::bndIn | cDBS::bndSet, " and ");
+ status += selectImg->prepare();
+ if (status != success) {
+ delete selectImg;
+ return;
+ }
+ tSeriesMedia->setValue(cTableSeriesMedia::fiSeriesId, series->id);
+ tSeriesMedia->setValue(cTableSeriesMedia::fiSeasonNumber, season);
+ tSeriesMedia->setValue(cTableSeriesMedia::fiMediaType, msSeasonPoster);
+ int res = selectImg->find();
+ if (res) {
+ if (!imgExists) {
+ int size = imageSize.getIntValue();
+ if (FILE* fh = fopen(imgPath.c_str(), "w")) {
+ fwrite(tSeriesMedia->getStrValue(cTableSeriesMedia::fiMediaContent), 1, size, fh);
+ fclose(fh);
+ }
+ }
+ int imgWidth = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaWidth);
+ int imgHeight = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaHeight);
+ if (!FileExists(thumbPath)) {
+ CreateThumbnail(imgPath, thumbPath, imgWidth, imgHeight, 2);
+ }
+ series->InsertMedia(msSeasonPoster, imgWidth, imgHeight, imgPath, season);
+ series->InsertMedia(msSeasonPosterThumb, imgWidth/2, imgHeight/2, thumbPath, season);
+ }
+ selectImg->freeResult();
+ delete selectImg;
+}
+
+void cUpdate::ReadSeriesActors(cTVDBSeries *series, string path) {
+ int status = success;
+ tSeriesActors->clear();
+ cDbValue series_id;
+ series_id.setField(tSeriesMedia->getField(cTableSeriesMedia::fiSeriesId));
+ series_id.setValue(series->id);
+ cDbStatement *selectActors = new cDbStatement(tSeriesActors);
+ selectActors->build("select ");
+ selectActors->setBindPrefix("series_actor.");
+ selectActors->bind(cTableSeriesActor::fiActorId, cDBS::bndOut);
+ selectActors->bind(cTableSeriesActor::fiActorName, cDBS::bndOut, ", ");
+ selectActors->bind(cTableSeriesActor::fiActorRole, cDBS::bndOut, ", ");
+ selectActors->clrBindPrefix();
+ selectActors->build(" from %s, %s where ", tSeriesActors->TableName(), tSeriesMedia->TableName());
+ selectActors->build(" %s.%s = %s.%s ", tSeriesActors->TableName(),
+ tSeriesActors->getField(cTableSeriesActor::fiActorId)->name,
+ tSeriesMedia->TableName(),
+ tSeriesMedia->getField(cTableSeriesMedia::fiActorId)->name);
+ selectActors->setBindPrefix("series_media.");
+ selectActors->bind(&series_id, cDBS::bndIn | cDBS::bndSet, " and ");
+ selectActors->build(" order by %s, %s asc", tSeriesActors->getField(cTableSeriesActor::fiSortOrder)->name,
+ tSeriesActors->getField(cTableSeriesActor::fiActorRole)->name);
+ status += selectActors->prepare();
+ if (status != success) {
+ delete selectActors;
+ return;
+ }
+ for (int res = selectActors->find(); res; res = selectActors->fetch()) {
+ scrapManager->AddSeriesActor(series, tSeriesActors);
+ LoadSeriesActorThumb(series, tSeriesActors->getIntValue(cTableSeriesActor::fiActorId), path);
+ }
+ selectActors->freeResult();
+ delete selectActors;
+}
+
+void cUpdate::LoadSeriesActorThumb(cTVDBSeries *series, int actorId, string path) {
+ int status = success;
+ stringstream iPath;
+ iPath << path << "/" << "actor_" << actorId << ".jpg";
+ string imgPath = iPath.str();
+ bool imgExists = FileExists(imgPath);
+ if (!CreateDirectory(path))
+ return;
+ cDbValue imageSize;
+ cDBS::FieldDef imageSizeDef = { "media_content", cDBS::ffUInt, 0, 999, cDBS::ftData };
+ imageSize.setField(&imageSizeDef);
+ tSeriesMedia->clear();
+ cDbStatement *selectActorThumbs = new cDbStatement(tSeriesMedia);
+ selectActorThumbs->build("select ");
+ selectActorThumbs->bind(cTableSeriesMedia::fiMediaWidth, cDBS::bndOut);
+ selectActorThumbs->bind(cTableSeriesMedia::fiMediaHeight, cDBS::bndOut, ", ");
+ if (!imgExists) {
+ selectActorThumbs->bind(cTableSeriesMedia::fiMediaContent, cDBS::bndOut, ", ");
+ selectActorThumbs->build(", length(");
+ selectActorThumbs->bind(&imageSize, cDBS::bndOut);
+ selectActorThumbs->build(")");
+ }
+ selectActorThumbs->build(" from %s where ", tSeriesMedia->TableName());
+ selectActorThumbs->bind(cTableSeriesMedia::fiActorId, cDBS::bndIn | cDBS::bndSet);
+ status += selectActorThumbs->prepare();
+ if (status != success) {
+ delete selectActorThumbs;
+ return;
+ }
+ tSeriesMedia->setValue(cTableSeriesMedia::fiActorId, actorId);
+ int res = selectActorThumbs->find();
+ if (res) {
+ if (!imgExists) {
+ int size = imageSize.getIntValue();
+ if (FILE* fh = fopen(imgPath.c_str(), "w")) {
+ fwrite(tSeriesMedia->getStrValue(cTableSeriesMedia::fiMediaContent), 1, size, fh);
+ fclose(fh);
+ }
+ }
+ int tmbWidth = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaWidth);
+ int tmbHeight = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaHeight);
+ series->InsertActorThumb(actorId, tmbWidth, tmbHeight, imgPath);
+ }
+ selectActorThumbs->freeResult();
+ delete selectActorThumbs;
+}
+
+void cUpdate::LoadSeriesMedia(cTVDBSeries *series, string path) {
+ int status = success;
+ tSeriesMedia->clear();
+ cDbStatement *selectImg = new cDbStatement(tSeriesMedia);
+ selectImg->build("select ");
+ selectImg->bind(cTableSeriesMedia::fiMediaWidth, cDBS::bndOut);
+ selectImg->bind(cTableSeriesMedia::fiMediaHeight, cDBS::bndOut, ", ");
+ selectImg->bind(cTableSeriesMedia::fiMediaType, cDBS::bndOut, ", ");
+ selectImg->build(" from %s where ", tSeriesMedia->TableName());
+ selectImg->bind(cTableSeriesMedia::fiSeriesId, cDBS::bndIn | cDBS::bndSet);
+ selectImg->build(" and %s in (%d, %d, %d, %d, %d, %d, %d, %d, %d)",
+ tSeriesMedia->getField(cTableSeriesMedia::fiMediaType)->name,
+ msPoster1, msPoster2, msPoster3,
+ msFanart1, msFanart2, msFanart3,
+ msBanner1, msBanner2, msBanner3);
+ status += selectImg->prepare();
+ if (status != success) {
+ delete selectImg;
+ return;
+ }
+ tSeriesMedia->setValue(cTableSeriesMedia::fiSeriesId, series->id);
+ for (int res = selectImg->find(); res; res = selectImg->fetch()) {
+ int mediaType = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaType);
+ int mediaWidth = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaWidth);
+ int mediaHeight = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaHeight);
+ string mediaPath = LoadMediaSeries(series->id, mediaType, path, mediaWidth, mediaHeight);
+ series->InsertMedia(mediaType, mediaWidth, mediaHeight, mediaPath);
+ if (mediaType == msPoster1) {
+ string thumbPath = path + "/poster_thumb.jpg";
+ series->InsertMedia(msPosterThumb, mediaWidth/5, mediaHeight/5, thumbPath);
+ }
+ }
+ selectImg->freeResult();
+ delete selectImg;
+}
+
+string cUpdate::LoadMediaSeries(int seriesId, int mediaType, string path, int width, int height) {
+ int status = success;
+ stringstream iPath;
+ iPath << path << "/";
+ bool createThumb = false;
+ stringstream tPath;
+ tPath << path << "/";
+ switch (mediaType) {
+ case msPoster1:
+ iPath << "poster1.jpg";
+ createThumb = true;
+ tPath << "poster_thumb.jpg";
+ break;
+ case msPoster2:
+ iPath << "poster2.jpg";
+ break;
+ case msPoster3:
+ iPath << "poster3.jpg";
+ break;
+ case msFanart1:
+ iPath << "fanart1.jpg";
+ break;
+ case msFanart2:
+ iPath << "fanart2.jpg";
+ break;
+ case msFanart3:
+ iPath << "fanart3.jpg";
+ break;
+ case msBanner1:
+ iPath << "banner1.jpg";
+ break;
+ case msBanner2:
+ iPath << "banner2.jpg";
+ break;
+ case msBanner3:
+ iPath << "banner3.jpg";
+ break;
+ default:
+ break;
+ }
+ string imgPath = iPath.str();
+ string thumbPath = tPath.str();
+ if (FileExists(imgPath)) {
+ if (createThumb && !FileExists(thumbPath)) {
+ CreateThumbnail(imgPath, thumbPath, width, height, 5);
+ }
+ return imgPath;
+ }
+ if (!CreateDirectory(path))
+ return "";
+ tSeriesMedia->clear();
+ cDbValue imageSize;
+ cDBS::FieldDef imageSizeDef = { "media_content", cDBS::ffUInt, 0, 999, cDBS::ftData };
+ imageSize.setField(&imageSizeDef);
+ cDbStatement *selectImg = new cDbStatement(tSeriesMedia);
+ selectImg->build("select ");
+ selectImg->bind(cTableSeriesMedia::fiMediaContent, cDBS::bndOut);
+ selectImg->build(", length(");
+ selectImg->bind(&imageSize, cDBS::bndOut);
+ selectImg->build(")");
+ selectImg->build(" from %s where ", tSeriesMedia->TableName());
+ selectImg->bind(cTableSeriesMedia::fiSeriesId, cDBS::bndIn | cDBS::bndSet);
+ selectImg->bind(cTableSeriesMedia::fiMediaType, cDBS::bndIn | cDBS::bndSet, " and ");
+ status += selectImg->prepare();
+ if (status != success) {
+ delete selectImg;
+ return "";
+ }
+ tSeriesMedia->setValue(cTableSeriesMedia::fiSeriesId, seriesId);
+ tSeriesMedia->setValue(cTableSeriesMedia::fiMediaType, mediaType);
+ int res = selectImg->find();
+ if (res) {
+ int size = imageSize.getIntValue();
+ if (FILE* fh = fopen(imgPath.c_str(), "w")) {
+ fwrite(tSeriesMedia->getStrValue(cTableSeriesMedia::fiMediaContent), 1, size, fh);
+ fclose(fh);
+ }
+ if (createThumb && !FileExists(thumbPath)) {
+ CreateThumbnail(imgPath, thumbPath, width, height, 5);
+ }
+ }
+ selectImg->freeResult();
+ delete selectImg;
+ return imgPath;
+}
+
+//***************************************************************************
+// MOVIES
+//***************************************************************************
+
+int cUpdate::ReadMovies(bool isRec) {
+ scrapManager->InitIterator(isRec);
+ int movieId = 0;
+ int i=0;
+
+ if (!CreateDirectory(config.imageDir))
+ return 0;
+ if (!CreateDirectory(imgPathMovies))
+ return 0;
+
+ int numNew = 0;
+ while (scrapManager->GetNextMovie(isRec, movieId)) {
+ cMovieDbMovie *movie = scrapManager->GetMovie(movieId);
+ if (movie)
+ continue;
+ tMovies->clear();
+ tMovies->setValue(cTableMovies::fiMovieId, movieId);
+ int res = tMovies->find();
+ if (!res)
+ continue;
+ movie = scrapManager->AddMovie(tMovies);
+ stringstream mPath;
+ mPath << imgPathMovies << "/" << movieId;
+ string moviePath = mPath.str();
+ ReadMovieActors(movie);
+ LoadMovieActorThumbs(movie);
+ LoadMovieMedia(movie, moviePath);
+ numNew++;
+ }
+ return numNew;
+}
+
+void cUpdate::ReadMovieActors(cMovieDbMovie *movie) {
+ int status = success;
+ cDbValue actorRole;
+ cDbValue actorMovie;
+ cDbValue thbWidth;
+ cDbValue thbHeight;
+ actorRole.setField(tMovieActors->getField(cTableMovieActors::fiRole));
+ actorMovie.setField(tMovieActors->getField(cTableMovieActors::fiMovieId));
+ thbWidth.setField(tMovieMedia->getField(cTableMovieMedia::fiMediaWidth));
+ thbHeight.setField(tMovieMedia->getField(cTableMovieMedia::fiMediaHeight));
+ cDbStatement *selectActors = new cDbStatement(tMovieActor);
+ selectActors->build("select ");
+ selectActors->setBindPrefix("act.");
+ selectActors->bind(cTableMovieActor::fiActorId, cDBS::bndOut);
+ selectActors->bind(cTableMovieActor::fiActorName, cDBS::bndOut, ", ");
+ selectActors->setBindPrefix("role.");
+ selectActors->bind(&actorRole, cDBS::bndOut, ", ");
+ selectActors->setBindPrefix("thumb.");
+ selectActors->bind(&thbWidth, cDBS::bndOut, ", ");
+ selectActors->bind(&thbHeight, cDBS::bndOut, ", ");
+ selectActors->clrBindPrefix();
+ selectActors->build(" from %s act, %s role, %s thumb where ",
+ tMovieActor->TableName(), tMovieActors->TableName(), tMovieMedia->TableName());
+ selectActors->build("act.%s = role.%s ",
+ tMovieActor->getField(cTableMovieActor::fiActorId)->name,
+ tMovieActors->getField(cTableMovieActors::fiActorId)->name);
+ selectActors->build(" and role.%s = thumb.%s ",
+ tMovieActors->getField(cTableMovieActors::fiActorId)->name,
+ tMovieMedia->getField(cTableMovieMedia::fiActorId)->name);
+ selectActors->setBindPrefix("role.");
+ selectActors->bind(&actorMovie, cDBS::bndIn | cDBS::bndSet, " and ");
+ status += selectActors->prepare();
+ if (status != success) {
+ delete selectActors;
+ return;
+ }
+ actorMovie.setValue(movie->id);
+ for (int res = selectActors->find(); res; res = selectActors->fetch()) {
+ scrapManager->AddMovieActor(movie, tMovieActor, actorRole.getStrValue());
+ int tmbWidth = thbWidth.getIntValue();
+ int tmbHeight = thbHeight.getIntValue();
+ movie->SetActorThumbSize(tMovieActor->getIntValue(cTableMovieActor::fiActorId), tmbWidth, tmbHeight);
+ }
+ selectActors->freeResult();
+ delete selectActors;
+}
+
+void cUpdate::LoadMovieActorThumbs(cMovieDbMovie *movie) {
+ int status = success;
+ cDbValue imageSize;
+ cDBS::FieldDef imageSizeDef = { "media_content", cDBS::ffUInt, 0, 999, cDBS::ftData };
+ imageSize.setField(&imageSizeDef);
+ tMovieMedia->clear();
+ cDbStatement *selectActorThumbs = new cDbStatement(tMovieMedia);
+ selectActorThumbs->build("select ");
+ selectActorThumbs->bind(cTableMovieMedia::fiMediaContent, cDBS::bndOut);
+ selectActorThumbs->build(", length(");
+ selectActorThumbs->bind(&imageSize, cDBS::bndOut);
+ selectActorThumbs->build(")");
+ selectActorThumbs->build(" from %s where ", tMovieMedia->TableName());
+ selectActorThumbs->bind(cTableMovieMedia::fiActorId, cDBS::bndIn | cDBS::bndSet);
+ status += selectActorThumbs->prepare();
+ if (status != success) {
+ delete selectActorThumbs;
+ return;
+ }
+ string movieActorsPath = imgPathMovies + "/actors";
+ if (!CreateDirectory(movieActorsPath))
+ return;
+
+ vector<int> IDs = movie->GetActorIDs();
+ for (vector<int>::iterator it = IDs.begin(); it != IDs.end(); it++) {
+ int actorId = (int)*it;
+ stringstream tName;
+ tName << "actor_" << actorId << ".jpg";
+ string thumbName = tName.str();
+ string thumbFullPath = movieActorsPath + "/" + thumbName;
+ if (!FileExists(thumbFullPath)) {
+ tMovieMedia->setValue(cTableMovieMedia::fiActorId, actorId);
+ int res = selectActorThumbs->find();
+ if (res) {
+ int size = imageSize.getIntValue();
+ if (FILE* fh = fopen(thumbFullPath.c_str(), "w")) {
+ fwrite(tMovieMedia->getStrValue(cTableMovieMedia::fiMediaContent), 1, size, fh);
+ fclose(fh);
+ }
+ movie->SetActorPath(actorId, thumbFullPath);
+ }
+ } else {
+ movie->SetActorPath(actorId, thumbFullPath);
+ }
+ }
+ selectActorThumbs->freeResult();
+ delete selectActorThumbs;
+}
+
+void cUpdate::LoadMovieMedia(cMovieDbMovie *movie, string moviePath) {
+ int status = success;
+ tMovieMedia->clear();
+ cDbStatement *selectImg = new cDbStatement(tMovieMedia);
+ selectImg->build("select ");
+ selectImg->bind(cTableMovieMedia::fiMediaWidth, cDBS::bndOut);
+ selectImg->bind(cTableMovieMedia::fiMediaHeight, cDBS::bndOut, ", ");
+ selectImg->bind(cTableMovieMedia::fiMediaType, cDBS::bndOut, ", ");
+ selectImg->build(" from %s where ", tMovieMedia->TableName());
+ selectImg->bind(cTableMovieMedia::fiMovieId, cDBS::bndIn | cDBS::bndSet);
+ selectImg->build(" and %s in (%d, %d, %d, %d)",
+ tMovieMedia->getField(cTableMovieMedia::fiMediaType)->name,
+ mmPoster,
+ mmFanart,
+ mmCollectionPoster,
+ mmCollectionFanart);
+ status += selectImg->prepare();
+ if (status != success) {
+ delete selectImg;
+ return;
+ }
+ tMovieMedia->setValue(cTableMovieMedia::fiMovieId, movie->id);
+ for (int res = selectImg->find(); res; res = selectImg->fetch()) {
+ int mediaType = tMovieMedia->getIntValue(cTableMovieMedia::fiMediaType);
+ int mediaWidth = tMovieMedia->getIntValue(cTableMovieMedia::fiMediaWidth);
+ int mediaHeight = tMovieMedia->getIntValue(cTableMovieMedia::fiMediaHeight);
+ string imgPath = LoadMediaMovie(movie->id, mediaType, moviePath, mediaWidth, mediaHeight);
+ if (imgPath.size() > 0)
+ scrapManager->AddMovieMedia(movie, tMovieMedia, imgPath);
+ if (mediaType == mmPoster) {
+ cMovieMedia *m = new cMovieMedia();
+ m->mediaType = mmPosterThumb;
+ m->width = mediaWidth/4;
+ m->height = mediaHeight/4;
+ m->path = moviePath + "/poster_thumb.jpg";
+ movie->InsertMedia(m);
+ }
+
+ }
+ selectImg->freeResult();
+ delete selectImg;
+}
+
+string cUpdate::LoadMediaMovie(int movieId, int mediaType, string path, int width, int height) {
+ int status = success;
+ stringstream iPath;
+ iPath << path << "/";
+ bool createThumb = false;
+ stringstream tPath;
+ tPath << path << "/";
+ switch (mediaType) {
+ case mmPoster:
+ iPath << "poster.jpg";
+ createThumb = true;
+ tPath << "poster_thumb.jpg";
+ break;
+ case mmFanart:
+ iPath << "fanart.jpg";
+ break;
+ case mmCollectionPoster:
+ iPath << "collectionPoster.jpg";
+ break;
+ case mmCollectionFanart:
+ iPath << "collectionFanart.jpg";
+ break;
+ default:
+ break;
+ }
+ string imgPath = iPath.str();
+ string thumbPath = tPath.str();
+ if (FileExists(imgPath)) {
+ if (createThumb && !FileExists(thumbPath)) {
+ CreateThumbnail(imgPath, thumbPath, width, height, 4);
+ }
+ return imgPath;
+ }
+ if (!CreateDirectory(path))
+ return imgPath;
+ tMovieMedia->clear();
+ cDbValue imageSize;
+ cDBS::FieldDef imageSizeDef = { "media_content", cDBS::ffUInt, 0, 999, cDBS::ftData };
+ imageSize.setField(&imageSizeDef);
+ cDbStatement *selectImg = new cDbStatement(tMovieMedia);
+ selectImg->build("select ");
+ selectImg->bind(cTableMovieMedia::fiMediaContent, cDBS::bndOut);
+ selectImg->build(", length(");
+ selectImg->bind(&imageSize, cDBS::bndOut);
+ selectImg->build(")");
+ selectImg->build(" from %s where ", tMovieMedia->TableName());
+ selectImg->bind(cTableMovieMedia::fiMovieId, cDBS::bndIn | cDBS::bndSet);
+ selectImg->bind(cTableMovieMedia::fiMediaType, cDBS::bndIn | cDBS::bndSet, " and ");
+ status += selectImg->prepare();
+ if (status != success) {
+ delete selectImg;
+ return "";
+ }
+ tMovieMedia->setValue(cTableMovieMedia::fiMovieId, movieId);
+ tMovieMedia->setValue(cTableMovieMedia::fiMediaType, mediaType);
+ int res = selectImg->find();
+ if (res) {
+ int size = imageSize.getIntValue();
+ if (FILE* fh = fopen(imgPath.c_str(), "w")) {
+ fwrite(tMovieMedia->getStrValue(cTableMovieMedia::fiMediaContent), 1, size, fh);
+ fclose(fh);
+ }
+ if (createThumb && !FileExists(thumbPath)) {
+ CreateThumbnail(imgPath, thumbPath, width, height, 4);
+ }
+ }
+ selectImg->freeResult();
+ delete selectImg;
+ return imgPath;
+}
+
+//***************************************************************************
+// RECORDINGS
+//***************************************************************************
+int cUpdate::ReadRecordings(void) {
+ int status = success;
+ cDbStatement *select = new cDbStatement(tRecordings);
+ select->build("select ");
+ select->bind(cTableRecordings::fiRecPath, cDBS::bndOut);
+ select->bind(cTableRecordings::fiRecStart, cDBS::bndOut, ", ");
+ select->bind(cTableRecordings::fiMovieId, cDBS::bndOut, ", ");
+ select->bind(cTableRecordings::fiSeriesId, cDBS::bndOut, ", ");
+ select->bind(cTableRecordings::fiEpisodeId, cDBS::bndOut, ", ");
+ select->build(" from %s where ", tRecordings->TableName());
+ select->bind(cTableRecordings::fiUuid, cDBS::bndIn | cDBS::bndSet);
+ select->build(" and %s = 0", tRecordings->getField(cTableRecordings::fiScrapNew)->name);
+
+ status += select->prepare();
+ if (status != success) {
+ delete select;
+ return 0;
+ }
+
+ tRecordings->clear();
+ tRecordings->setValue(cTableRecordings::fiUuid, config.uuid.c_str());
+ int numRecs = 0;
+ for (int res = select->find(); res; res = select->fetch()) {
+ int recStart = tRecordings->getIntValue(cTableRecordings::fiRecStart);
+ string recPath = tRecordings->getStrValue(cTableRecordings::fiRecPath);
+ int movieId = tRecordings->getIntValue(cTableRecordings::fiMovieId);
+ int seriesId = tRecordings->getIntValue(cTableRecordings::fiSeriesId);
+ int episodeId = tRecordings->getIntValue(cTableRecordings::fiEpisodeId);
+ bool isNew = scrapManager->AddRecording(recStart, recPath, seriesId, episodeId, movieId);
+ if (isNew)
+ numRecs++;
+ }
+ select->freeResult();
+ delete select;
+ return numRecs;
+}
+
+int cUpdate::ScanVideoDir(void) {
+ int newRecs = 0;
+ for (cRecording *rec = Recordings.First(); rec; rec = Recordings.Next(rec)) {
+ string recPath = rec->FileName();
+ int recStart = rec->Start();
+ if (!scrapManager->RecordingExists(recStart, recPath)) {
+ newRecs++;
+ int scrapInfoMovieID = 0;
+ int scrapInfoSeriesID = 0;
+ int scrapInfoEpisodeID = 0;
+ ReadScrapInfo(rec->FileName(), scrapInfoMovieID, scrapInfoSeriesID, scrapInfoEpisodeID);
+ int eventId = 0;
+ string channelId = "";
+ string title = *(rec->BaseName());
+ string subTitle = "";
+ const cRecordingInfo *recInfo = rec->Info();
+ if (recInfo) {
+ const cEvent *recEvent = recInfo->GetEvent();
+ if (recEvent) {
+ eventId = recEvent->EventID();
+ channelId = *(recInfo->ChannelID().ToString());
+ subTitle = (recInfo->ShortText())?(recInfo->ShortText()):"";
+ }
+ }
+ tRecordings->clear();
+ tRecordings->setValue(cTableRecordings::fiUuid, config.uuid.c_str());
+ tRecordings->setValue(cTableRecordings::fiRecPath, recPath.c_str());
+ tRecordings->setValue(cTableRecordings::fiRecStart, recStart);
+
+ tRecordings->setValue(cTableRecordings::fiEventId, eventId);
+ tRecordings->setValue(cTableRecordings::fiChannelId, channelId.c_str());
+ tRecordings->setValue(cTableRecordings::fiScrapInfoMovieId, scrapInfoMovieID);
+ tRecordings->setValue(cTableRecordings::fiScrapInfoSeriesId, scrapInfoSeriesID);
+ tRecordings->setValue(cTableRecordings::fiScrapInfoEpisodeId, scrapInfoEpisodeID);
+ tRecordings->setValue(cTableRecordings::fiScrapNew, 1);
+ tRecordings->setValue(cTableRecordings::fiRecTitle, title.c_str());
+ tRecordings->setValue(cTableRecordings::fiRecSubTitle, subTitle.c_str());
+ tRecordings->setValue(cTableRecordings::fiRecDuration, rec->LengthInSeconds()/60);
+ tRecordings->store();
+ }
+ }
+ return newRecs;
+}
+
+int cUpdate::ScanVideoDirScrapInfo(void) {
+ int numUpdated = 0;
+ for (cRecording *rec = Recordings.First(); rec; rec = Recordings.Next(rec)) {
+ int recStart = rec->Start();
+ string recPath = rec->FileName();
+ bool recExists = LoadRecording(recStart, recPath);
+ int scrapInfoMovieID = 0;
+ int scrapInfoSeriesID = 0;
+ int scrapInfoEpisodeID = 0;
+ ReadScrapInfo(rec->FileName(), scrapInfoMovieID, scrapInfoSeriesID, scrapInfoEpisodeID);
+ if (ScrapInfoChanged(scrapInfoMovieID, scrapInfoSeriesID, scrapInfoEpisodeID)) {
+ tRecordings->setValue(cTableRecordings::fiScrapNew, 1);
+ tRecordings->setValue(cTableRecordings::fiScrapInfoMovieId, scrapInfoMovieID);
+ tRecordings->setValue(cTableRecordings::fiScrapInfoSeriesId, scrapInfoSeriesID);
+ tRecordings->setValue(cTableRecordings::fiScrapInfoEpisodeId, scrapInfoEpisodeID);
+ tRecordings->update();
+ numUpdated++;
+ }
+ }
+ return numUpdated;
+}
+
+bool cUpdate::LoadRecording(int recStart, string recPath) {
+ tRecordings->clear();
+ tRecordings->setValue(cTableRecordings::fiUuid, config.uuid.c_str());
+ tRecordings->setValue(cTableRecordings::fiRecStart, recStart);
+ tRecordings->setValue(cTableRecordings::fiRecPath, recPath.c_str());
+ int found = tRecordings->find();
+ if (found == yes) {
+ return true;
+ }
+ return false;
+}
+
+bool cUpdate::ScrapInfoChanged(int scrapInfoMovieID, int scrapInfoSeriesID, int scrapInfoEpisodeID) {
+ int movieIdCurrent = tRecordings->getIntValue(cTableRecordings::fiScrapInfoMovieId);
+ int seriesIdCurrent = tRecordings->getIntValue(cTableRecordings::fiScrapInfoSeriesId);
+ int episodeIdCurrent = tRecordings->getIntValue(cTableRecordings::fiScrapInfoEpisodeId);
+ if ((movieIdCurrent != scrapInfoMovieID) ||
+ (seriesIdCurrent != scrapInfoSeriesID) ||
+ (episodeIdCurrent != scrapInfoEpisodeID))
+ return true;
+ return false;
+}
+
+void cUpdate::ReadScrapInfo(string recDir, int &scrapInfoMovieID, int &scrapInfoSeriesID, int &scrapInfoEpisodeID) {
+ stringstream sInfoName;
+ sInfoName << recDir << "/" << config.recScrapInfoName;
+ string scrapInfoName = sInfoName.str();
+ if (!FileExists(scrapInfoName, false)) {
+ string twoHigher = TwoFoldersHigher(recDir);
+ if (twoHigher.size() > 0) {
+ stringstream sInfoNameAlt;
+ sInfoNameAlt << twoHigher << "/" << config.recScrapInfoName;
+ scrapInfoName = sInfoNameAlt.str();
+ if (!FileExists(scrapInfoName, false)) {
+ return;
+ }
+ } else
+ return;
+ }
+ vector<string> scrapInfoLines;
+ FILE *f = fopen(scrapInfoName.c_str(), "r");
+ if (!f)
+ return;
+ cReadLine ReadLine;
+ char *line;
+ while ((line = ReadLine.Read(f)) != NULL) {
+ scrapInfoLines.push_back(line);
+ }
+ fclose(f);
+ int numLines = scrapInfoLines.size();
+ if (numLines < 2) {
+ tell(0, "invalid scrapinfo file in %s", recDir.c_str());
+ return;
+ }
+ for (int line=0; line < numLines; line++) {
+ scrapInfoLines[line] = trim(scrapInfoLines[line]);
+ toLower(scrapInfoLines[line]);
+ }
+ bool isMovie = false;
+ bool isSeries = false;
+ if (!scrapInfoLines[0].compare("movie"))
+ isMovie = true;
+ else if (!scrapInfoLines[0].compare("series"))
+ isSeries = true;
+ else
+ tell(0, "invalid scrapinfo file in %s", recDir.c_str());
+
+ int id1 = 0, id2 = 0;
+ string key = "", value = "";
+
+ splitstring s(scrapInfoLines[1].c_str());
+ vector<string> flds = s.split('=');
+ if (flds.size() == 2) {
+ key = trim(flds[0]);
+ value = trim(flds[1]);
+ } else {
+ tell(0, "invalid scrapinfo file in %s", recDir.c_str());
+ }
+ if (!key.compare("id")) {
+ id1 = atoi(value.c_str());
+ if (numLines > 2) {
+ splitstring s2(scrapInfoLines[2].c_str());
+ vector<string> flds2 = s2.split('=');
+ if (flds2.size() == 2) {
+ key = trim(flds2[0]);
+ toLower(key);
+ value = trim(flds2[1]);
+ }
+ if (!key.compare("episode")) {
+ id2 = atoi(value.c_str());
+ }
+ }
+ } else
+ tell(0, "invalid scrapinfo file in %s", recDir.c_str());
+ if (isSeries) {
+ scrapInfoSeriesID = id1;
+ scrapInfoEpisodeID = id2;
+ }
+ if (isMovie) {
+ scrapInfoMovieID = id1;
+ }
+}
+
+//***************************************************************************
+// Cleanup
+//***************************************************************************
+
+int cUpdate::CleanupSeries(void) {
+ //read existing series in file system
+ vector<string> storedSeries;
+ DIR *dir;
+ struct dirent *entry;
+ if ((dir = opendir (imgPathSeries.c_str())) != NULL) {
+ while ((entry = readdir (dir)) != NULL) {
+ string dirName = entry->d_name;
+ if (isNumber(dirName)) {
+ storedSeries.push_back(dirName);
+ }
+ }
+ closedir (dir);
+ } else return 0;
+ int deletedSeries = 0;
+ //aviod complete delete if no series are available
+ int numSeriesAvailable = scrapManager->GetNumSeries();
+ if (numSeriesAvailable == 0)
+ return 0;
+ for (vector<string>::iterator seriesDir = storedSeries.begin(); seriesDir != storedSeries.end(); seriesDir++) {
+ int seriesId = atoi(((string)*seriesDir).c_str());
+ if (!scrapManager->SeriesInUse(seriesId)) {
+ string delDir = imgPathSeries + "/" + ((string)*seriesDir);
+ DeleteDirectory(delDir);
+ deletedSeries++;
+ }
+ }
+ return deletedSeries;
+}
+
+int cUpdate::CleanupMovies(void) {
+ //read existing movies in file system
+ vector<string> storedMovies;
+ DIR *dir;
+ struct dirent *entry;
+ if ((dir = opendir (imgPathMovies.c_str())) != NULL) {
+ while ((entry = readdir (dir)) != NULL) {
+ string dirName = entry->d_name;
+ if (isNumber(dirName)) {
+ storedMovies.push_back(dirName);
+ }
+ }
+ closedir (dir);
+ } else return 0;
+ int deletedMovies = 0;
+ //aviod complete delete if no movies are available
+ int numMoviesAvailable = scrapManager->GetNumMovies();
+ if (numMoviesAvailable == 0)
+ return 0;
+ for (vector<string>::iterator movieDir = storedMovies.begin(); movieDir != storedMovies.end(); movieDir++) {
+ int movieId = atoi(((string)*movieDir).c_str());
+ if (!scrapManager->MovieInUse(movieId)) {
+ string delDir = imgPathMovies + "/" + ((string)*movieDir);
+ DeleteDirectory(delDir);
+ deletedMovies++;
+ }
+ }
+ return deletedMovies;
+}
+
+int cUpdate::CleanupRecordings(void) {
+ //delete all not anymore existing recordings in database
+ int status = success;
+ cDbStatement *select = new cDbStatement(tRecordings);
+ select->build("select ");
+ select->bind(cTableRecordings::fiRecPath, cDBS::bndOut);
+ select->bind(cTableRecordings::fiRecStart, cDBS::bndOut, ", ");
+ select->build(" from %s where ", tRecordings->TableName());
+ select->bind(cTableRecordings::fiUuid, cDBS::bndIn | cDBS::bndSet);
+
+ status += select->prepare();
+ if (status != success) {
+ delete select;
+ return 0;
+ }
+
+ tRecordings->clear();
+ tRecordings->setValue(cTableRecordings::fiUuid, config.uuid.c_str());
+ int numRecsDeleted = 0;
+ for (int res = select->find(); res; res = select->fetch()) {
+ int recStart = tRecordings->getIntValue(cTableRecordings::fiRecStart);
+ string recPath = tRecordings->getStrValue(cTableRecordings::fiRecPath);
+ if (!Recordings.GetByName(recPath.c_str())) {
+ stringstream delWhere;
+ delWhere << "uuid = '" << config.uuid << "' and rec_path = '" << recPath << "' and rec_start = " << recStart;
+ tRecordings->deleteWhere(delWhere.str().c_str());
+ numRecsDeleted++;
+ }
+ }
+ select->freeResult();
+ delete select;
+ return numRecsDeleted;
+}
+
+
+//***************************************************************************
+// Action
+//***************************************************************************
+
+void cUpdate::Action() {
+ tell(0, "Update thread started (pid=%d)", getpid());
+ mutex.Lock();
+ loopActive = yes;
+ int sleep = 10;
+ int scanFreq = 60;
+ int scanNewRecFreq = 60 * 5;
+ int scanNewRecDBFreq = 60 * 5;
+ int cleanUpFreq = 60 * 5;
+ forceUpdate = true;
+ forceRecordingUpdate = true;
+ time_t lastScan = time(0);
+ time_t lastScanNewRec = time(0);
+ time_t lastScanNewRecDB = time(0);
+ time_t lastCleanup = time(0);
+ bool init = true;
+ while (loopActive && Running()) {
+ int reconnectTimeout; //set by checkConnection
+ if (CheckConnection(reconnectTimeout) != success) {
+ waitCondition.TimedWait(mutex, reconnectTimeout*1000);
+ continue;
+ }
+ //Update Events
+ if (!config.headless && (forceUpdate || (time(0) - lastScan > scanFreq))) {
+ if (!init && CheckEpgdBusy())
+ continue;
+ int numNewEvents = ReadScrapedEvents();
+ if (numNewEvents > 0) {
+ tell(0, "Loaded %d new scraped Events from Database", numNewEvents);
+ } else {
+ lastScan = time(0);
+ forceUpdate = false;
+ init = false;
+ continue;
+ }
+ tell(0, "Loading new Series and Episodes from Database...");
+ time_t now = time(0);
+ int numNewSeries = ReadSeries(false);
+ int dur = time(0) - now;
+ tell(0, "Loaded %d new Series and Episodes in %ds from Database", numNewSeries, dur);
+ //scrapManager->DumpSeries(5);
+
+ tell(0, "Loading new Movies from Database...");
+ now = time(0);
+ int numNewMovies = ReadMovies(false);
+ dur = time(0) - now;
+ tell(0, "Loaded %d new Movies in %ds from Database", numNewMovies, dur);
+ //scrapManager->DumpMovies(5);
+ lastScan = time(0);
+ forceUpdate = false;
+ }
+ //Update Recordings from Database
+ if (forceRecordingUpdate || (time(0) - lastScanNewRecDB > scanNewRecDBFreq)) {
+ if (!init && CheckEpgdBusy())
+ continue;
+ int numNewRecs = ReadRecordings();
+ if (numNewRecs > 0) {
+ int numSeries = ReadSeries(true);
+ int numMovies = ReadMovies(true);
+ tell(0, "Loaded %d new Recordings from Database, %d series, %d movies", numNewRecs, numSeries, numMovies);
+ }
+ forceRecordingUpdate = false;
+ }
+
+ //Scan new recordings
+ if (init || forceVideoDirUpdate || (time(0) - lastScanNewRec > scanNewRecFreq)) {
+ if (CheckEpgdBusy()) {
+ waitCondition.TimedWait(mutex, 1000);
+ continue;
+ }
+ static int recState = 0;
+ if (Recordings.StateChanged(recState)) {
+ tell(0, "Searching for new recordings because of Recordings State Change...");
+ int newRecs = ScanVideoDir();
+ tell(0, "found %d new recordings", newRecs);
+ }
+ lastScanNewRec = time(0);
+ forceVideoDirUpdate = false;
+ }
+
+ init = false;
+
+ //Scan Video dir for scrapinfo files
+ if (forceScrapInfoUpdate) {
+ if (CheckEpgdBusy()) {
+ tell(0, "epgd busy, try again in 1s...");
+ waitCondition.TimedWait(mutex, 1000);
+ continue;
+ }
+ tell(0, "Checking for new or updated scrapinfo files in recordings...");
+ int numUpdated = ScanVideoDirScrapInfo();
+ tell(0, "found %d new or updated scrapinfo files", numUpdated);
+ forceScrapInfoUpdate = false;
+ }
+
+ //Cleanup
+ if (time(0) - lastCleanup > cleanUpFreq) {
+ if (CheckEpgdBusy()) {
+ waitCondition.TimedWait(mutex, 1000);
+ continue;
+ }
+ int seriesDeleted = CleanupSeries();
+ int moviesDeleted = CleanupMovies();
+ if (seriesDeleted > 0 || moviesDeleted > 0) {
+ tell(0, "Deleted %d outdated series image folders", seriesDeleted);
+ tell(0, "Deleted %d outdated movie image folders", moviesDeleted);
+ }
+ lastCleanup = time(0);
+ }
+
+ //Cleanup Recording DB
+ if (forceCleanupRecordingDb) {
+ if (CheckEpgdBusy()) {
+ waitCondition.TimedWait(mutex, 1000);
+ continue;
+ }
+ tell(0, "Cleaning up recordings in database...");
+ int recsDeleted = CleanupRecordings();
+ tell(0, "Deleted %d not anymore existing recordings in database", recsDeleted);
+ forceCleanupRecordingDb = false;
+ }
+
+ waitCondition.TimedWait(mutex, sleep*1000);
+
+ }
+
+ loopActive = no;
+ tell(0, "Update thread ended (pid=%d)", getpid());
+}
+
+//***************************************************************************
+// External trigggering of Actions
+//***************************************************************************
+void cUpdate::ForceUpdate(void) {
+ tell(0, "full update from database forced");
+ forceUpdate = true;
+}
+
+void cUpdate::ForceRecordingUpdate(void) {
+ tell(0, "scanning of recordings in database triggered");
+ forceRecordingUpdate = true;
+}
+
+void cUpdate::ForceVideoDirUpdate(void) {
+ tell(0, "scanning for new recordings in video directory triggered");
+ forceVideoDirUpdate = true;
+}
+
+void cUpdate::ForceScrapInfoUpdate(void) {
+ tell(0, "scanning of recording scrapinfo files triggered");
+ forceScrapInfoUpdate = true;
+}
+
+void cUpdate::TriggerCleanRecordingsDB(void) {
+ tell(0, "cleanup of recording DB triggered");
+ forceCleanupRecordingDb = true;
+}
diff --git a/update.h b/update.h
new file mode 100644
index 0000000..c529fda
--- /dev/null
+++ b/update.h
@@ -0,0 +1,87 @@
+#ifndef __UPDATE_H
+#define __UPDATE_H
+
+#include <mysql/mysql.h>
+#include <map>
+
+#include <vdr/thread.h>
+#include "lib/common.h"
+#include "lib/db.h"
+#include "lib/tabledef.h"
+#include "scrapmanager.h"
+
+#define EPGDNAME "epgd"
+
+class cUpdate : public cThread {
+ private:
+ cScrapManager *scrapManager;
+ string imgPathSeries;
+ string imgPathMovies;
+ bool withutf8;
+ bool loopActive;
+ cDbConnection* connection;
+ cTableVdrs* vdrDb;
+ cTableEvents* tEvents;
+ cTableSeries* tSeries;
+ cTableSeriesEpisode* tEpisodes;
+ cTableSeriesMedia* tSeriesMedia;
+ cTableSeriesActor* tSeriesActors;
+ cTableMovies* tMovies;
+ cTableMovieActor* tMovieActor;
+ cTableMovieActors* tMovieActors;
+ cTableMovieMedia* tMovieMedia;
+ cTableRecordings* tRecordings;
+ int lastScrap;
+ cCondVar waitCondition;
+ cMutex mutex;
+ bool forceUpdate;
+ bool forceRecordingUpdate;
+ bool forceVideoDirUpdate;
+ bool forceScrapInfoUpdate;
+ bool forceCleanupRecordingDb;
+ int exitDb();
+ int dbConnected(int force = no) { return connection && (!force || connection->check() == success); };
+ int CheckConnection(int& timeout);
+ bool CheckEpgdBusy(void);
+ void Action(void);
+ int ReadScrapedEvents(void);
+ //SERIES
+ int ReadSeries(bool isRec);
+ void ReadEpisode(int episodeId, cTVDBSeries *series, string path);
+ void LoadEpisodeImage(cTVDBSeries *series, int episodeId, string path);
+ void LoadSeasonPoster(cTVDBSeries *series, int season, string path);
+ void ReadSeriesActors(cTVDBSeries *series, string path);
+ void LoadSeriesMedia(cTVDBSeries *series, string path);
+ string LoadMediaSeries(int seriesId, int mediaType, string path, int width, int height);
+ void LoadSeriesActorThumb(cTVDBSeries *series, int actorId, string path);
+ //MOVIES
+ int ReadMovies(bool isRec);
+ void ReadMovieActors(cMovieDbMovie *movie);
+ void LoadMovieActorThumbs(cMovieDbMovie *movie);
+ void LoadMovieMedia(cMovieDbMovie *movie, string moviePath);
+ string LoadMediaMovie(int movieId, int mediaType, string path, int width, int height);
+ //RECORDINGS
+ int ReadRecordings(void);
+ int ScanVideoDir(void);
+ int ScanVideoDirScrapInfo(void);
+ bool LoadRecording(int eventId, string recName);
+ bool ScrapInfoChanged(int scrapInfoMovieID, int scrapInfoSeriesID, int scrapInfoEpisodeID);
+ void ReadScrapInfo(string recDir, int &scrapInfoMovieID, int &scrapInfoSeriesID, int &scrapInfoEpisodeID);
+ //CLEANUP
+ int CleanupSeries(void);
+ int CleanupMovies(void);
+ int CleanupRecordings(void);
+ public:
+ cUpdate(cScrapManager *manager);
+ virtual ~cUpdate(void);
+ int initDb();
+ void Stop(void);
+ void ForceUpdate(void);
+ void ForceRecordingUpdate(void);
+ void ForceVideoDirUpdate(void);
+ void ForceScrapInfoUpdate(void);
+ void TriggerCleanRecordingsDB(void);
+};
+
+//***************************************************************************
+#endif //__UPDATE_H