From 801610d2595720208ed344cdb8ae2cb997d97e71 Mon Sep 17 00:00:00 2001 From: louis Date: Sat, 12 Apr 2014 17:10:43 +0200 Subject: [PATCH] initial commit --- COPYING | 340 +++++++++++++ HISTORY | 6 + Makefile | 132 +++++ README | 79 +++ config.c | 56 ++ config.h | 31 ++ lib/Makefile | 7 + lib/common.c | 812 +++++++++++++++++++++++++++++ lib/common.h | 179 +++++++ lib/config.c | 63 +++ lib/config.h | 73 +++ lib/db.c | 1086 +++++++++++++++++++++++++++++++++++++++ lib/db.h | 1030 +++++++++++++++++++++++++++++++++++++ lib/tabledef.c | 856 +++++++++++++++++++++++++++++++ lib/tabledef.h | 832 ++++++++++++++++++++++++++++++ moviedbmovie.c | 130 +++++ moviedbmovie.h | 98 ++++ po/de_DE.po | 65 +++ scraper2vdr.c | 249 +++++++++ scraper2vdr.h | 57 +++ scrapmanager.c | 503 ++++++++++++++++++ scrapmanager.h | 81 +++ services.h | 194 +++++++ setup.c | 78 +++ setup.h | 25 + tools.c | 170 +++++++ tools.h | 30 ++ tvdbseries.c | 289 +++++++++++ tvdbseries.h | 143 ++++++ update.c | 1323 ++++++++++++++++++++++++++++++++++++++++++++++++ update.h | 87 ++++ 31 files changed, 9104 insertions(+) create mode 100644 COPYING create mode 100644 HISTORY create mode 100644 Makefile create mode 100644 README create mode 100644 config.c create mode 100644 config.h create mode 100644 lib/Makefile create mode 100644 lib/common.c create mode 100644 lib/common.h create mode 100644 lib/config.c create mode 100644 lib/config.h create mode 100644 lib/db.c create mode 100644 lib/db.h create mode 100644 lib/tabledef.c create mode 100644 lib/tabledef.h create mode 100644 moviedbmovie.c create mode 100644 moviedbmovie.h create mode 100644 po/de_DE.po create mode 100644 scraper2vdr.c create mode 100644 scraper2vdr.h create mode 100644 scrapmanager.c create mode 100644 scrapmanager.h create mode 100644 services.h create mode 100644 setup.c create mode 100644 setup.h create mode 100644 tools.c create mode 100644 tools.h create mode 100644 tvdbseries.c create mode 100644 tvdbseries.h create mode 100644 update.c create mode 100644 update.h 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. + + + Copyright (C) + + 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. + + , 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='' -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 + +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= Set directory where images are stored +-m , --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 +#include + +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 + +#ifdef USEUUID +# include +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef USELIBARCHIVE +# include +# include +#endif + +#ifdef VDR_PLUGIN +# include +#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 +#include +#include + +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 and get data of first content which name matches +//*************************************************************************** + +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 + +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 // uint_64_t +#include +#include + +#include // MD5_* + +#ifdef VDR_PLUGIN +# include +#endif + +#ifdef USELIBXML +# include +# include +# include +#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 + +#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 + +#include + +#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 +#include +#include +#include + +#include + +#include + +#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::iterator it = actors.begin(); it != actors.end(); it++) { + cMovieActor *a = (cMovieActor*)it->second; + delete a; + } + for (map::iterator it = medias.begin(); it != medias.end(); it++) { + cMovieMedia *m = (cMovieMedia*)it->second; + delete m; + } +} + +void cMovieDbMovie::InsertMedia(cMovieMedia *media) { + medias.insert(pair(media->mediaType, media)); +} + +void cMovieDbMovie::InsertActor(cMovieActor *actor) { + cMovieMedia *m = new cMovieMedia(); + actor->actorThumb = m; + actors.insert(pair(actor->id, actor)); +} + +vector cMovieDbMovie::GetActorIDs(void) { + vector IDs; + for (map::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::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::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::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 *a) { + for (map::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::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::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 +#include +#include +#include +#include +#include +#include +#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 actors; + map 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 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 *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 , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: vdr-scraper2vdr 0.0.1\n" +"Report-Msgid-Bugs-To: \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 \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= Set directory where images are stored\n" + " -m , --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 +#include +#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 +#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::iterator it = series.begin(); it != series.end(); it++) { + cTVDBSeries *s = (cTVDBSeries*)it->second; + delete s; + } + series.clear(); + for (map::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::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(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::iterator hit = series.find(seriesId); + if (hit == series.end()) + return NULL; + return hit->second; +} + +cMovieDbMovie *cScrapManager::GetMovie(int movieId) { + map::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(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(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::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(k, v)); + return true; +} + +bool cScrapManager::RecordingExists(int recStart, string recPath) { + sRecordingsKey k; + k.recStart = recStart; + k.recPath = recPath; + map::iterator hit = recordings.find(k); + if (hit != recordings.end()) + return true; + return false; +} + +bool cScrapManager::SeriesInUse(int seriesId) { + map::iterator hit = series.find(seriesId); + if (hit != series.end()) + return true; + return false; +} + +bool cScrapManager::MovieInUse(int movieId) { + map::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::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::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::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::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::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::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::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::iterator hit = events.find(k); + if (hit == events.end()) + return false; + sEventsValue v = hit->second; + if (v.seriesId > 0) { + call->type = tSeries; + map::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::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::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::iterator hit = recordings.find(k); + if (hit == recordings.end()) + return false; + v = hit->second; + } + if (v.seriesId > 0) { + map::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::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::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::iterator hit = recordings.find(k); + if (hit == recordings.end()) + return false; + v = hit->second; + } + if (v.seriesId > 0) { + map::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::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 +#include +#include +#include +#include + +#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 events; + map::iterator eventsIterator; + map recordings; + map::iterator recIterator; + map series; + map 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 +#include + +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 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 actors; + std::vector posters; + std::vector banners; + std::vector 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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 <rim(string &s) { + s.erase(s.begin(), find_if(s.begin(), s.end(), not1(ptr_fun(isspace)))); + return s; +} + +// trim from end +string &rtrim(string &s) { + s.erase(find_if(s.rbegin(), s.rend(), not1(ptr_fun(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& 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 +#include + +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 <rim(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 flds; +public: + splitstring(const char *s) : string(s) { }; + vector& 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::iterator it = actors.begin(); it != actors.end(); it++) { + cTVDBActor *a = (cTVDBActor*)it->second; + delete a; + } + for (map::iterator it = episodes.begin(); it != episodes.end(); it++) { + cTVDBEpisode *e = (cTVDBEpisode*)it->second; + delete e; + } + for (vector::iterator it = posters.begin(); it != posters.end(); it++) { + cTVDBMedia *p = *it; + delete p; + } + for (vector::iterator it = banners.begin(); it != banners.end(); it++) { + cTVDBMedia *b = *it; + delete b; + } + for (vector::iterator it = fanart.begin(); it != fanart.end(); it++) { + cTVDBMedia *f = *it; + delete f; + } + for (map::iterator it = seasonPosters.begin(); it != seasonPosters.end(); it++) { + cTVDBMedia *s = (cTVDBMedia*)it->second; + delete s; + } + for (map::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::iterator hit = episodes.find(episode->id); + if (hit != episodes.end()) + delete episode; + else + episodes.insert(pair(episode->id, episode)); +} + +void cTVDBSeries::InsertEpisodeImage(int episodeId, int width, int height, string path) { + map::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(actor->id, actor)); +} + +void cTVDBSeries::InsertActorThumb(int actorId, int imgWidth, int imgHeight, string path) { + map::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(season, media)); + break; + case msPosterThumb: + posterThumb = media; + break; + case msSeasonPosterThumb: + seasonPosterThumbs.insert(pair(season, media)); + break; + default: + break; + } +} + +void cTVDBSeries::GetEpisode(int episodeId, cEpisode *e) { + map::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 *p) { + for (vector::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 *b) { + for (vector::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 *f) { + for (vector::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::iterator hit = episodes.find(episodeId); + if (hit == episodes.end()) + return; + cTVDBEpisode *e = hit->second; + map::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 *a) { + for (map::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::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::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::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::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::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::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 +#include +#include +#include +#include +#include +#include +#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 episodes; + map actors; + vector posters; + vector banners; + vector fanart; + map seasonPosters; + map 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 *p); + bool GetPoster(cTvMedia *p); + bool GetPosterThumb(cTvMedia *p); + void GetBanners(vector *b); + bool GetRandomBanner(cTvMedia *b); + void GetFanart(vector *f); + void GetSeasonPoster(int episodeId, cTvMedia *sp); + void GetActors(vector *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 + +#include +#include +#include + +#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 IDs = movie->GetActorIDs(); + for (vector::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 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 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 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 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::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 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::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 +#include + +#include +#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