diff options
-rw-r--r-- | COPYING | 340 | ||||
-rw-r--r-- | HISTORY | 6 | ||||
-rw-r--r-- | Makefile | 132 | ||||
-rw-r--r-- | README | 79 | ||||
-rw-r--r-- | config.c | 56 | ||||
-rw-r--r-- | config.h | 31 | ||||
-rw-r--r-- | lib/Makefile | 7 | ||||
-rw-r--r-- | lib/common.c | 812 | ||||
-rw-r--r-- | lib/common.h | 179 | ||||
-rw-r--r-- | lib/config.c | 63 | ||||
-rw-r--r-- | lib/config.h | 73 | ||||
-rw-r--r-- | lib/db.c | 1086 | ||||
-rw-r--r-- | lib/db.h | 1030 | ||||
-rw-r--r-- | lib/tabledef.c | 856 | ||||
-rw-r--r-- | lib/tabledef.h | 832 | ||||
-rw-r--r-- | moviedbmovie.c | 130 | ||||
-rw-r--r-- | moviedbmovie.h | 98 | ||||
-rw-r--r-- | po/de_DE.po | 65 | ||||
-rw-r--r-- | scraper2vdr.c | 249 | ||||
-rw-r--r-- | scraper2vdr.h | 57 | ||||
-rw-r--r-- | scrapmanager.c | 503 | ||||
-rw-r--r-- | scrapmanager.h | 81 | ||||
-rw-r--r-- | services.h | 194 | ||||
-rw-r--r-- | setup.c | 78 | ||||
-rw-r--r-- | setup.h | 25 | ||||
-rw-r--r-- | tools.c | 170 | ||||
-rw-r--r-- | tools.h | 30 | ||||
-rw-r--r-- | tvdbseries.c | 289 | ||||
-rw-r--r-- | tvdbseries.h | 143 | ||||
-rw-r--r-- | update.c | 1323 | ||||
-rw-r--r-- | update.h | 87 |
31 files changed, 9104 insertions, 0 deletions
@@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. @@ -0,0 +1,6 @@ +VDR Plugin 'scraper2vdr' Revision History +----------------------------------------- + +2014-03-02: Version 0.0.1 + +- Initial revision. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..209effe --- /dev/null +++ b/Makefile @@ -0,0 +1,132 @@ +# +# Makefile for a Video Disk Recorder plugin +# +# $Id$ + +# External image lib to use: imagemagick, graphicsmagick +IMAGELIB = imagemagick + +PLUGIN = scraper2vdr + +### The version number of this plugin (taken from the main source file): + +VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).h | awk '{ print $$6 }' | sed -e 's/[";]//g') + +### The directory environment: + +# Use package data if installed...otherwise assume we're under the VDR source directory: +PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell pkg-config --variable=$(1) vdr || pkg-config --variable=$(1) ../../../vdr.pc)) +LIBDIR = $(call PKGCFG,libdir) +LOCDIR = $(call PKGCFG,locdir) +PLGCFG = $(call PKGCFG,plgcfg) +# +TMPDIR ?= /tmp + +### The compiler options: + +export CFLAGS = $(call PKGCFG,cflags) +export CXXFLAGS = $(call PKGCFG,cxxflags) + +### The version number of VDR's plugin API: + +APIVERSION = $(call PKGCFG,apiversion) + +### Allow user defined options to overwrite defaults: + +-include $(PLGCFG) + +LIBS = -lmysqlclient_r -luuid + +### The name of the distribution archive: + +ARCHIVE = $(PLUGIN)-$(VERSION) +PACKAGE = vdr-$(ARCHIVE) + +### The name of the shared object file: + +SOFILE = libvdr-$(PLUGIN).so + +### Includes and Defines (add further entries here): + +INCLUDES += + +DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"' +DEFINES += -DVDR_PLUGIN -DUSEUUID + +ifeq ($(IMAGELIB), imagemagick) + INCLUDES += $(shell pkg-config --cflags Magick++) + LIBS += $(shell pkg-config --libs Magick++) +else ifeq ($(IMAGELIB), graphicsmagick) + INCLUDES += $(shell pkg-config --cflags GraphicsMagick++) + LIBS += $(shell pkg-config --libs GraphicsMagick++) +endif + +### The object files (add further files here): + +OBJS = $(PLUGIN).o config.o setup.o update.o scrapmanager.o tvdbseries.o moviedbmovie.o tools.o lib/db.o lib/tabledef.o lib/common.o lib/config.o + +### The main target: + +all: $(SOFILE) i18n + +### Implicit rules: + +%.o: %.c + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $< + +### Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies +$(DEPFILE): Makefile + @$(MAKEDEP) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@ + +-include $(DEPFILE) + +### Internationalization (I18N): + +PODIR = po +I18Npo = $(wildcard $(PODIR)/*.po) +I18Nmo = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file)))) +I18Nmsgs = $(addprefix $(DESTDIR)$(LOCDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file)))))) +I18Npot = $(PODIR)/$(PLUGIN).pot + +%.mo: %.po + msgfmt -c -o $@ $< + +$(I18Npot): $(wildcard *.c) + xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='<see README>' -o $@ `ls $^` + +%.po: $(I18Npot) + msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $< + @touch $@ + +$(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo + install -D -m644 $< $@ + +.PHONY: i18n +i18n: $(I18Nmo) $(I18Npot) + +install-i18n: $(I18Nmsgs) + +### Targets: + +$(SOFILE): $(OBJS) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) $(LIBS) -o $@ + +install-lib: $(SOFILE) + install -D -m644 $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION) + +install: install-lib install-i18n + +dist: $(I18Npo) clean + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @mkdir $(TMPDIR)/$(ARCHIVE) + @cp -a * $(TMPDIR)/$(ARCHIVE) + @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE) + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @echo Distribution package created as $(PACKAGE).tgz + +clean: + @-rm -f $(PODIR)/*.mo $(PODIR)/*.pot + @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ @@ -0,0 +1,79 @@ +This is a "plugin" for the Video Disk Recorder (VDR). + +Written by: Louis Braun <louis.braun@gmx.de> + +Project's homepage: http://projects.vdr-developer.org/projects/plg-scraper2vdr + +Latest version available at: http://projects.vdr-developer.org/git/vdr-plugin-scraper2vdr.git/ + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +See the file COPYING for more information. + +Description +----------- + +scraper2vdr acts as client and provides scraped metadata for tvshows and +movies from epgd to other plugins via its service interface. The plugin +cares about caching the images locally and also cleans up the images if +not longer needed. + +epgd itself uses the thetvdb.com API for collecting series metadata and +themoviedb.org API for movies. Check the websites of both services for +the terms of use. + +Requirements +------------ + +To run the plugin the following libaries have to be installed: + + - VDR 1.7.x + - libmysql >= 5.07 + - uuid-dev + - imagemagick or graphicksmagick + +Installation and configuration +------------------------------ + +Just install the plugin depending on your used distribution. During VDR +startup the following options can be set: + +-i <IMAGEDIR>, --imagedir=<IMAGEDIR> Set directory where images are stored +-m <MODE>, --mode=<MODE> mode can be client or headless. + +Each running scraper2vdr Plugin reports his recordings to the epgd +database, the epgd then checks these entries and tries to find +appropriate scraping information. epgd performs first a lookup for a +event in the database which belongs to the recording. If this fails, epgd +checks if another client has already reported this recording to the database. +After that, the scrapinfo file in the recording directory (if existing) +is checked. If nothing is successfull, a new scrap process for the name +of the recording is done. If in this case the length of the recording +is less than 70 minutes, a series recording is assumed, otherwise +the scraper searches for a movie. + +In client mode both live epg and recordings metadata is loaded from the +database. In headless mode only recording metadata is loaded. This mode +is useful for headless VDRs so that recordings which are done from this +VDR during no other VDR client with running scraper2vdr Plugin is active +are not missed. The recording information is then written to the database +in time before the related and already reliably scraped event entry is +deleted from the database. + +Service Interface +----------------- + +Other Plugins can and should request information about meta data from +scraper2vdr via a call to the provided service interface. + +First the service "GetEventType" which expects a pointer to a cEvent or +a cRecording object as input variable has to be called. This call provides +the type of the event or recording (tSeries, tMovie, tNone) and the seriesId, +episodeId and movieId. If type is tSeries, movieId is 0 and vice versa. +With that then a second call to GetSeries or GetMovie with the appropriate IDs +provides all stored information for the series or movie in form of a cSeries +or cMovie object. + +For further information just check the self explanatory services.h file. diff --git a/config.c b/config.c new file mode 100644 index 0000000..b3812be --- /dev/null +++ b/config.c @@ -0,0 +1,56 @@ +#include "lib/common.h" +#include "config.h" + +cScraper2VdrConfig::cScraper2VdrConfig() { + mainMenuEntry = 1; + headless = false; + uuid = ""; + imgDirSet = false; + mysqlHost = "localhost"; + mysqlPort = 3306; + mysqlDBName = "epg2vdr"; + mysqlDBUser = "epg2vdr"; + mysqlDBPass = "epg"; + recScrapInfoName = "scrapinfo"; +} + +cScraper2VdrConfig::~cScraper2VdrConfig() { +} + +void cScraper2VdrConfig::SetUuid(cPlugin *plug) { + if (uuid.size() == 0) { + uuid = getUniqueId(); + plug->SetupStore("uuid", uuid.c_str()); + } + tell(0, "epgd uuid: %s", uuid.c_str()); +} + +void cScraper2VdrConfig::SetImageDir(cString dir) { + imageDir = *dir; + imgDirSet = true; +} + +void cScraper2VdrConfig::SetDefaultImageDir(void) { + if (!imgDirSet) { + imageDir = cPlugin::CacheDirectory(PLUGIN_NAME_I18N); + } + tell (0, "using image directory %s", imageDir.c_str()); +} + +void cScraper2VdrConfig::SetMode(string mode) { + if (!mode.compare("headless")) + headless = true; +} + +bool cScraper2VdrConfig::SetupParse(const char *Name, const char *Value) { + if (strcmp(Name, "uuid") == 0) uuid = Value; + else if (strcmp(Name, "mainMenuEntry") == 0) mainMenuEntry = atoi(Value); + else if (strcmp(Name, "mysqlHost") == 0) mysqlHost = Value; + else if (strcmp(Name, "mysqlPort") == 0) mysqlPort = atoi(Value); + else if (strcmp(Name, "mysqlDBName") == 0) mysqlDBName = Value; + else if (strcmp(Name, "mysqlDBUser") == 0) mysqlDBUser = Value; + else if (strcmp(Name, "mysqlDBPass") == 0) mysqlDBPass = Value; + else + return false; + return true; +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..c81a8ef --- /dev/null +++ b/config.h @@ -0,0 +1,31 @@ +#ifndef __SCRAPER2VDR_CONFIG_H +#define __SCRAPER2VDR_CONFIG_H + +#include <string> +#include <vdr/plugin.h> + +using namespace std; + +class cScraper2VdrConfig { + private: + public: + cScraper2VdrConfig(); + ~cScraper2VdrConfig(); + bool SetupParse(const char *Name, const char *Value); + void SetUuid(cPlugin *plug); + void SetImageDir(cString dir); + void SetDefaultImageDir(void); + void SetMode(string mode); + int mainMenuEntry; + bool headless; + string uuid; + bool imgDirSet; + string imageDir; + string mysqlHost; + int mysqlPort; + string mysqlDBName; + string mysqlDBUser; + string mysqlDBPass; + string recScrapInfoName; +}; +#endif //__SCRAPER2VDR_CONFIG_H diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 0000000..f27782b --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,7 @@ + + +all: + g++ -ggdb -DPLGDIR='"."' test.c common.c config.c db.c tabledef.c -lrt -lz -lmysqlclient -o t + +clean: + rm -f *.o *.a *~ core diff --git a/lib/common.c b/lib/common.c new file mode 100644 index 0000000..f3a0436 --- /dev/null +++ b/lib/common.c @@ -0,0 +1,812 @@ +/* + * common.c: + * + * See the README file for copyright information and how to reach the author. + * + */ + +#include <sys/stat.h> + +#ifdef USEUUID +# include <uuid/uuid.h> +#endif + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <zlib.h> + +#ifdef USELIBARCHIVE +# include <archive.h> +# include <archive_entry.h> +#endif + +#ifdef VDR_PLUGIN +# include <vdr/thread.h> +#endif + +#include "common.h" +#include "config.h" + +#ifdef VDR_PLUGIN + cMutex logMutex; +#endif + +//*************************************************************************** +// Debug +//*************************************************************************** + +void tell(int eloquence, const char* format, ...) +{ + if (EPG2VDRConfig.loglevel < eloquence) + return ; + + const int sizeBuffer = 100000; + char t[sizeBuffer+100]; *t = 0; + va_list ap; + +#ifdef VDR_PLUGIN + cMutexLock lock(&logMutex); +#endif + + va_start(ap, format); + +#ifdef VDR_PLUGIN + snprintf(t, sizeBuffer, "scraper2vdr: "); +#endif + + vsnprintf(t+strlen(t), sizeBuffer-strlen(t), format, ap); + + if (EPG2VDRConfig.logstdout) + { + char buf[50+TB]; + time_t now; + time(&now); + strftime(buf, 50, "%y.%m.%d %H:%M:%S", localtime(&now)); + printf("%s %s\n", buf, t); + } + else + syslog(LOG_ERR, "%s", t); + + va_end(ap); +} + +//*************************************************************************** +// Host ID +//*************************************************************************** + +unsigned int getHostId() +{ + static unsigned int id = gethostid() & 0xFFFFFFFF; + return id; +} + +//*************************************************************************** +// String Operations +//*************************************************************************** + +void toUpper(std::string& str) +{ + const char* s = str.c_str(); + int lenSrc = str.length(); + + char* dest = (char*)malloc(lenSrc+TB); *dest = 0; + char* d = dest; + + int csSrc; // size of character + + for (int ps = 0; ps < lenSrc; ps += csSrc) + { + csSrc = max(mblen(&s[ps], lenSrc-ps), 1); + + if (csSrc == 1) + *d++ = toupper(s[ps]); + else if (csSrc == 2 && s[ps] == (char)0xc3 && s[ps+1] >= (char)0xa0) + { + *d++ = s[ps]; + *d++ = s[ps+1] - 32; + } + else + { + for (int i = 0; i < csSrc; i++) + *d++ = s[ps+i]; + } + } + + *d = 0; + + str = dest; + free(dest); +} + +void removeChars(std::string& str, const char* ignore) +{ + const char* s = str.c_str(); + int lenSrc = str.length(); + int lenIgn = strlen(ignore); + + char* dest = (char*)malloc(lenSrc+TB); *dest = 0; + char* d = dest; + + int csSrc; // size of character + int csIgn; // + + for (int ps = 0; ps < lenSrc; ps += csSrc) + { + int skip = no; + + csSrc = max(mblen(&s[ps], lenSrc-ps), 1); + + for (int pi = 0; pi < lenIgn; pi += csIgn) + { + csIgn = max(mblen(&ignore[pi], lenIgn-pi), 1); + + if (csSrc == csIgn && strncmp(&s[ps], &ignore[pi], csSrc) == 0) + { + skip = yes; + break; + } + } + + if (!skip) + { + for (int i = 0; i < csSrc; i++) + *d++ = s[ps+i]; + } + } + + *d = 0; + + str = dest; + free(dest); +} + +void removeCharsExcept(std::string& str, const char* except) +{ + const char* s = str.c_str(); + int lenSrc = str.length(); + int lenIgn = strlen(except); + + char* dest = (char*)malloc(lenSrc+TB); *dest = 0; + char* d = dest; + + int csSrc; // size of character + int csIgn; // + + for (int ps = 0; ps < lenSrc; ps += csSrc) + { + int skip = yes; + + csSrc = max(mblen(&s[ps], lenSrc-ps), 1); + + for (int pi = 0; pi < lenIgn; pi += csIgn) + { + csIgn = max(mblen(&except[pi], lenIgn-pi), 1); + + if (csSrc == csIgn && strncmp(&s[ps], &except[pi], csSrc) == 0) + { + skip = no; + break; + } + } + + if (!skip) + { + for (int i = 0; i < csSrc; i++) + *d++ = s[ps+i]; + } + } + + *d = 0; + + str = dest; + free(dest); +} + +void removeWord(std::string& pattern, std::string word) +{ + size_t pos; + + if ((pos = pattern.find(word)) != std::string::npos) + pattern.swap(pattern.erase(pos, word.length())); +} + +//*************************************************************************** +// String Manipulation +//*************************************************************************** + +void prepareCompressed(std::string& pattern) +{ + // const char* ignore = " (),.;:-_+*!#?=&%$<>§/'`´@~\"[]{}"; + const char* notignore = "ABCDEFGHIJKLMNOPQRSTUVWXYZßÖÄÜöäü0123456789"; + + toUpper(pattern); + removeWord(pattern, " TEIL "); + removeWord(pattern, " FOLGE "); + removeCharsExcept(pattern, notignore); +} + +//*************************************************************************** +// Left Trim +//*************************************************************************** + +char* lTrim(char* buf) +{ + if (buf) + { + char *tp = buf; + + while (*tp && strchr("\n\r\t ",*tp)) + tp++; + + memmove(buf, tp, strlen(tp) +1); + } + + return buf; +} + +//************************************************************************* +// Right Trim +//************************************************************************* + +char* rTrim(char* buf) +{ + if (buf) + { + char *tp = buf + strlen(buf); + + while (tp >= buf && strchr("\n\r\t ",*tp)) + tp--; + + *(tp+1) = 0; + } + + return buf; +} + +//************************************************************************* +// All Trim +//************************************************************************* + +char* allTrim(char* buf) +{ + return lTrim(rTrim(buf)); +} + +//*************************************************************************** +// Number to String +//*************************************************************************** + +std::string num2Str(int num) +{ + char txt[16]; + + snprintf(txt, sizeof(txt), "%d", num); + + return std::string(txt); +} + +//*************************************************************************** +// Long to Pretty Time +//*************************************************************************** + +std::string l2pTime(time_t t) +{ + char txt[30]; + tm* tmp = localtime(&t); + + strftime(txt, sizeof(txt), "%d.%m.%Y %T", tmp); + + return std::string(txt); +} + +//*************************************************************************** +// MS to Duration +//*************************************************************************** + +std::string ms2Dur(uint64_t t) +{ + char txt[30]; + + int s = t / 1000; + int ms = t % 1000; + + snprintf(txt, sizeof(txt), "%d.%03d seconds", s, ms); + + return std::string(txt); +} + +//*************************************************************************** +// Char to Char-String +//*************************************************************************** + +const char* c2s(char c, char* buf) +{ + sprintf(buf, "%c", c); + + return buf; +} + +//*************************************************************************** +// TOOLS +//*************************************************************************** + +int isEmpty(const char* str) +{ + return !str || !*str; +} + +char* sstrcpy(char* dest, const char* src, int max) +{ + if (!dest || !src) + return 0; + + strncpy(dest, src, max); + dest[max-1] = 0; + + return dest; +} + +int isLink(const char* path) +{ + struct stat sb; + + if (lstat(path, &sb) == 0) + return S_ISLNK(sb.st_mode); + + tell(0, "Error: Detecting state for '%s' failed, error was '%m'", path); + + return false; +} + +int fileSize(const char* path) +{ + struct stat sb; + + if (lstat(path, &sb) == 0) + return sb.st_size; + + tell(0, "Error: Detecting state for '%s' failed, error was '%m'", path); + + return false; +} + + +int fileExists(const char* path) +{ + return access(path, F_OK) == 0; +} + +int createLink(const char* link, const char* dest, int force) +{ + if (!fileExists(link) || force) + { + // may be the link exists and point to a wrong or already deleted destination ... + // .. therefore we delete the link at first + + unlink(link); + + if (symlink(dest, link) != 0) + { + tell(0, "Failed to create symlink '%s', error was '%m'", link); + return fail; + } + } + + return success; +} + +//*************************************************************************** +// Remove File +//*************************************************************************** + +int removeFile(const char* filename) +{ + int lnk = isLink(filename); + + if (unlink(filename) != 0) + { + tell(0, "Can't remove file '%s', '%m'", filename); + + return 1; + } + + tell(3, "Removed %s '%s'", lnk ? "link" : "file", filename); + + return 0; +} + +//*************************************************************************** +// Check Dir +//*************************************************************************** + +int chkDir(const char* path) +{ + struct stat fs; + + if (stat(path, &fs) != 0 || !S_ISDIR(fs.st_mode)) + { + tell(0, "Creating directory '%s'", path); + + if (mkdir(path, ACCESSPERMS) == -1) + { + tell(0, "Can't create directory '%m'"); + return fail; + } + } + + return success; +} + +#ifdef USELIBXML + +//*************************************************************************** +// Load XSLT +//*************************************************************************** + +xsltStylesheetPtr loadXSLT(const char* name, const char* path, int utf8) +{ + xsltStylesheetPtr stylesheet; + char* xsltfile; + + asprintf(&xsltfile, "%s/%s-%s.xsl", path, name, utf8 ? "utf-8" : "iso-8859-1"); + + if ((stylesheet = xsltParseStylesheetFile((const xmlChar*)xsltfile)) == 0) + tell(0, "Error: Can't load xsltfile %s", xsltfile); + else + tell(0, "Info: Stylesheet '%s' loaded", xsltfile); + + free(xsltfile); + return stylesheet; +} +#endif + +//*************************************************************************** +// Gnu Unzip +//*************************************************************************** + +int gunzip(MemoryStruct* zippedData, MemoryStruct* unzippedData) +{ + const int growthStep = 1024; + + z_stream stream = {0,0,0,0,0,0,0,0,0,0,0,Z_NULL,Z_NULL,Z_NULL}; + unsigned int resultSize = 0; + int res = 0; + + unzippedData->clear(); + + // determining the size in this way is taken from the sources of the gzip utility. + + memcpy(&unzippedData->size, zippedData->memory + zippedData->size -4, 4); + unzippedData->memory = (char*)malloc(unzippedData->size); + + // zlib initialisation + + stream.avail_in = zippedData->size; + stream.next_in = (Bytef*)zippedData->memory; + stream.avail_out = unzippedData->size; + stream.next_out = (Bytef*)unzippedData->memory; + + // The '+ 32' tells zlib to process zlib&gzlib headers + + res = inflateInit2(&stream, MAX_WBITS + 32); + + if (res != Z_OK) + { + tellZipError(res, " during zlib initialisation", stream.msg); + inflateEnd(&stream); + return fail; + } + + // skip the header + + res = inflate(&stream, Z_BLOCK); + + if (res != Z_OK) + { + tellZipError(res, " while skipping the header", stream.msg); + inflateEnd(&stream); + return fail; + } + + while (res == Z_OK) + { + if (stream.avail_out == 0) + { + unzippedData->size += growthStep; + unzippedData->memory = (char*)realloc(unzippedData->memory, unzippedData->size); + + // Set the stream pointers to the potentially changed buffer! + + stream.avail_out = resultSize - stream.total_out; + stream.next_out = (Bytef*)(unzippedData + stream.total_out); + } + + res = inflate(&stream, Z_SYNC_FLUSH); + resultSize = stream.total_out; + } + + if (res != Z_STREAM_END) + { + tellZipError(res, " during inflating", stream.msg); + inflateEnd(&stream); + return fail; + } + + unzippedData->size = resultSize; + inflateEnd(&stream); + + return success; +} + +//************************************************************************* +// tellZipError +//************************************************************************* + +void tellZipError(int errorCode, const char* op, const char* msg) +{ + if (!op) op = ""; + if (!msg) msg = "None"; + + switch (errorCode) + { + case Z_OK: return; + case Z_STREAM_END: return; + case Z_MEM_ERROR: tell(0, "Error: Not enough memory to unzip file%s!\n", op); return; + case Z_BUF_ERROR: tell(0, "Error: Couldn't unzip data due to output buffer size problem%s!\n", op); return; + case Z_DATA_ERROR: tell(0, "Error: Zipped input data corrupted%s! Details: %s\n", op, msg); return; + case Z_STREAM_ERROR: tell(0, "Error: Invalid stream structure%s. Details: %s\n", op, msg); return; + default: tell(0, "Error: Couldn't unzip data for unknown reason (%6d)%s!\n", errorCode, op); return; + } +} + +//************************************************************************* +// Host Data +//************************************************************************* + +#include <sys/utsname.h> +#include <netdb.h> +#include <ifaddrs.h> + +static struct utsname info; + +const char* getHostName() +{ + // get info from kernel + + if (uname(&info) == -1) + return ""; + + return info.nodename; +} + +const char* getFirstIp() +{ + struct ifaddrs *ifaddr, *ifa; + static char host[NI_MAXHOST] = ""; + + if (getifaddrs(&ifaddr) == -1) + { + tell(0, "getifaddrs() failed"); + return ""; + } + + // walk through linked interface list + + for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) + { + if (!ifa->ifa_addr) + continue; + + // For an AF_INET interfaces + + if (ifa->ifa_addr->sa_family == AF_INET) // || ifa->ifa_addr->sa_family == AF_INET6) + { + int res = getnameinfo(ifa->ifa_addr, + (ifa->ifa_addr->sa_family == AF_INET) ? sizeof(struct sockaddr_in) : + sizeof(struct sockaddr_in6), + host, NI_MAXHOST, 0, 0, NI_NUMERICHOST); + + if (res) + { + tell(0, "getnameinfo() failed: %s", gai_strerror(res)); + return ""; + } + + // skip loopback interface + + if (strcmp(host, "127.0.0.1") == 0) + continue; + + tell(5, "%-8s %-15s %s", ifa->ifa_name, host, + ifa->ifa_addr->sa_family == AF_INET ? " (AF_INET)" : + ifa->ifa_addr->sa_family == AF_INET6 ? " (AF_INET6)" : ""); + } + } + + freeifaddrs(ifaddr); + + return host; +} + +#ifdef USELIBARCHIVE + +//*************************************************************************** +// unzip <file> and get data of first content which name matches <filter> +//*************************************************************************** + +int unzip(const char* file, const char* filter, char*& buffer, int& size, char* entryName) +{ + const int step = 1024*10; + + int bufSize = 0; + int r; + int res; + + struct archive_entry* entry; + struct archive* a = archive_read_new(); + + *entryName = 0; + buffer = 0; + size = 0; + + archive_read_support_filter_all(a); + archive_read_support_format_all(a); + + r = archive_read_open_filename(a, file, 10204); + + if (r != ARCHIVE_OK) + { + tell(0, "Error: Open '%s' failed - %m", file); + return 1; + } + + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) + { + strcpy(entryName, archive_entry_pathname(entry)); + + if (strstr(entryName, filter)) + { + bufSize = step; + buffer = (char*)malloc(bufSize+1); + + while ((res = archive_read_data(a, buffer+size, step)) > 0) + { + size += res; + bufSize += step; + + buffer = (char*)realloc(buffer, bufSize+1); + } + + buffer[size] = 0; + + break; + } + } + + r = archive_read_free(a); + + if (r != ARCHIVE_OK) + { + size = 0; + free(buffer); + return fail; + } + + return size > 0 ? success : fail; +} + +#endif + +//*************************************************************************** +// Class LogDuration +//*************************************************************************** + +#ifdef VDR_PLUGIN + +# include <vdr/plugin.h> + +LogDuration::LogDuration(const char* aMessage, int aLogLevel) +{ + logLevel = aLogLevel; + strcpy(message, aMessage); + + // at last ! + + durationStart = cTimeMs::Now(); +} + +LogDuration::~LogDuration() +{ + tell(logLevel, "duration '%s' was (%dms)", + message, cTimeMs::Now() - durationStart); +} + +void LogDuration::show(const char* label) +{ + tell(logLevel, "elapsed '%s' at '%s' was (%dms)", + message, label, cTimeMs::Now() - durationStart); +} + +#endif + +//*************************************************************************** +// Get Unique ID +//*************************************************************************** + +#ifdef USEUUID +const char* getUniqueId() +{ + static char uuid[sizeUuid+TB] = ""; + + uuid_t id; + uuid_generate(id); + uuid_unparse_upper(id, uuid); + + return uuid; +} +#endif // USEUUID + +//*************************************************************************** +// Create MD5 +//*************************************************************************** + +#ifdef USEMD5 + +int createMd5(const char* buf, md5* md5) +{ + MD5_CTX c; + unsigned char out[MD5_DIGEST_LENGTH]; + + MD5_Init(&c); + MD5_Update(&c, buf, strlen(buf)); + MD5_Final(out, &c); + + for (int n = 0; n < MD5_DIGEST_LENGTH; n++) + sprintf(md5+2*n, "%02x", out[n]); + + md5[sizeMd5] = 0; + + return done; +} + +int createMd5OfFile(const char* path, const char* name, md5* md5) +{ + FILE* f; + char buffer[1000]; + int nread = 0; + MD5_CTX c; + unsigned char out[MD5_DIGEST_LENGTH]; + char* file = 0; + + asprintf(&file, "%s/%s", path, name); + + if (!(f = fopen(file, "r"))) + { + tell(0, "Fatal: Can't access '%s'; %m", file); + free(file); + return fail; + } + + free(file); + + MD5_Init(&c); + + while ((nread = fread(buffer, 1, 1000, f)) > 0) + MD5_Update(&c, buffer, nread); + + fclose(f); + + MD5_Final(out, &c); + + for (int n = 0; n < MD5_DIGEST_LENGTH; n++) + sprintf(md5+2*n, "%02x", out[n]); + + md5[sizeMd5] = 0; + + return success; +} + +#endif // USEMD5 diff --git a/lib/common.h b/lib/common.h new file mode 100644 index 0000000..614dfe6 --- /dev/null +++ b/lib/common.h @@ -0,0 +1,179 @@ +/* + * common.h: EPG2VDR plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + */ + +#ifndef __COMMON_H +#define __COMMON_H + +#include <stdint.h> // uint_64_t +#include <stdlib.h> +#include <string> + +#include <openssl/md5.h> // MD5_* + +#ifdef VDR_PLUGIN +# include <vdr/tools.h> +#endif + +#ifdef USELIBXML +# include <libxslt/transform.h> +# include <libxslt/xsltutils.h> +# include <libexslt/exslt.h> +#endif + +//*************************************************************************** +// +//*************************************************************************** + +#ifndef VDR_PLUGIN + inline long min(long a, long b) { return a < b ? a : b; } + inline long max(long a, long b) { return a > b ? a : b; } +#endif + +enum Misc +{ + success = 0, + done = success, + fail = -1, + na = -1, + ignore = -2, + all = -3, + abrt = -4, + yes = 1, + on = 1, + off = 0, + no = 0, + TB = 1, + + sizeMd5 = 2 * MD5_DIGEST_LENGTH, + sizeUuid = 36, + + tmeSecondsPerMinute = 60, + tmeSecondsPerHour = tmeSecondsPerMinute * 60, + tmeSecondsPerDay = 24 * tmeSecondsPerHour +}; + +//*************************************************************************** +// Tell +//*************************************************************************** + +void tell(int eloquence, const char* format, ...); + +//*************************************************************************** +// MemoryStruct for curl callbacks +//*************************************************************************** + +struct MemoryStruct +{ + MemoryStruct() { memory = 0; clear(); } + ~MemoryStruct() { clear(); } + + // data + + char* memory; + size_t size; + + // tag attribute + + char tag[100]; // the tag to be compared + char name[100]; // content name (filename) + int headerOnly; + + int isEmpty() { return memory == 0; } + + void clear() + { + free(memory); + memory = 0; + size = 0; + *tag = 0; + *name = 0; + headerOnly = no; + } +}; + +//*************************************************************************** +// Tools +//*************************************************************************** + +unsigned int getHostId(); +const char* getHostName(); +const char* getFirstIp(); + +#ifdef USEUUID + const char* getUniqueId(); +#endif + +void removeChars(std::string& str, const char* ignore); +void removeCharsExcept(std::string& str, const char* except); +void removeWord(std::string& pattern, std::string word); +void prepareCompressed(std::string& pattern); + +char* rTrim(char* buf); +char* lTrim(char* buf); +char* allTrim(char* buf); +char* sstrcpy(char* dest, const char* src, int max); +std::string num2Str(int num); +std::string l2pTime(time_t t); +std::string ms2Dur(uint64_t t); +const char* c2s(char c, char* buf); + +int fileExists(const char* path); +int fileSize(const char* path); +int createLink(const char* link, const char* dest, int force); +int isLink(const char* path); +int isEmpty(const char* str); +int removeFile(const char* filename); +int chkDir(const char* path); + +#ifdef USELIBXML + xsltStylesheetPtr loadXSLT(const char* name, const char* path, int utf8); +#endif + +#ifdef USEMD5 + typedef char md5Buf[sizeMd5+TB]; + typedef char md5; + int createMd5(const char* buf, md5* md5); + int createMd5OfFile(const char* path, const char* name, md5* md5); +#endif + +//*************************************************************************** +// Zip +//*************************************************************************** + +int gunzip(MemoryStruct* zippedData, MemoryStruct* unzippedData); +void tellZipError(int errorCode, const char* op, const char* msg); + +#ifdef USELIBARCHIVE +int unzip(const char* file, const char* filter, char*& buffer, + int& size, char* entryName); +#endif + +#ifdef VDR_PLUGIN + +//*************************************************************************** +// Log Duration +//*************************************************************************** + +class LogDuration +{ + public: + + LogDuration(const char* aMessage, int aLogLevel = 2); + ~LogDuration(); + + void show(const char* label = ""); + + protected: + + char message[1000]; + uint64_t durationStart; + int logLevel; +}; +#endif + +//*************************************************************************** +#endif //___COMMON_H diff --git a/lib/config.c b/lib/config.c new file mode 100644 index 0000000..9ca25f1 --- /dev/null +++ b/lib/config.c @@ -0,0 +1,63 @@ +/* + * config.c: + * + * See the README file for copyright information and how to reach the author. + * + */ + +#include <string.h> + +#include "common.h" +#include "config.h" + +cEPG2VDRConfig EPG2VDRConfig; + +cEPG2VDRConfig::cEPG2VDRConfig(void) +{ + mainmenuVisible = yes; + mainmenuFullupdate = 0; + + useproxy = no; + sstrcpy(httpproxy, "127.0.0.1:8000", sizeof(httpproxy)); + sstrcpy(username, "", sizeof(username)); + sstrcpy(password, "", sizeof(password)); + + checkInitial = yes; + updatetime = 6; // hours + days = 8; + upddays = 2; + storeXmlToFs = no; + blacklist = no; + masterMode = 0; + + getepgimages = yes; + maximagesperevent = 1; + epgImageSize = 2; + + seriesEnabled = yes; + sstrcpy(seriesUrl, "eplists.constabel.net", sizeof(seriesUrl)); + seriesPort = 2006; + storeSeriesToFs = no; + +#ifdef VDR_PLUGIN + activeOnEpgd = no; + scheduleBoot = no; +#else + sstrcpy(cachePath, "/var/cache/epgd", sizeof(cachePath)); + sstrcpy(pluginPath, PLGDIR, sizeof(pluginPath)); + sstrcpy(epgView, "eventsview.sql", sizeof(epgView)); + updateThreshold = 200; + maintanance = no; +#endif + + sstrcpy(dbHost, "localhost", sizeof(dbHost)); + dbPort = 3306; + sstrcpy(dbName, "epg2vdr", sizeof(dbName)); + sstrcpy(dbUser, "epg2vdr", sizeof(dbUser)); + sstrcpy(dbPass, "epg", sizeof(dbPass)); + + logstdout = no; + loglevel = 1; + + uuid[0] = 0; +} diff --git a/lib/config.h b/lib/config.h new file mode 100644 index 0000000..e0c729c --- /dev/null +++ b/lib/config.h @@ -0,0 +1,73 @@ +/* + * config.h: + * + * See the README file for copyright information and how to reach the author. + * + * $Id: config.h,v 1.2 2012/10/26 08:44:13 wendel Exp $ + */ + +#ifndef __EPG2VDR_CONFIG_H +#define __EPG2VDR_CONFIG_H + +#include "common.h" + +//*************************************************************************** +// Config +//*************************************************************************** + +struct cEPG2VDRConfig +{ + public: + + cEPG2VDRConfig(void); + + int useproxy; + char httpproxy[256+TB]; + char username[100+TB]; + char password[100+TB]; + + int checkInitial; + int updatetime; + int days; + int upddays; + int storeXmlToFs; + int blacklist; // to enable noepg feature + + int getepgimages; + int maximagesperevent; + int epgImageSize; + + int seriesEnabled; + char seriesUrl[500+TB]; + int seriesPort; + int storeSeriesToFs; + +#ifdef VDR_PLUGIN + int activeOnEpgd; + int scheduleBoot; +#else + char cachePath[256+TB]; + char pluginPath[256+TB]; + char epgView[100+TB]; + int updateThreshold; + int maintanance; +#endif + + char dbHost[100+TB]; + int dbPort; + char dbName[100+TB]; + char dbUser[100+TB]; + char dbPass[100+TB]; + + int logstdout; + int loglevel; + + int mainmenuVisible; + int mainmenuFullupdate; + int masterMode; + char uuid[sizeUuid+TB]; +}; + +extern cEPG2VDRConfig EPG2VDRConfig; + +#endif // __EPG2VDR_CONFIG_H diff --git a/lib/db.c b/lib/db.c new file mode 100644 index 0000000..17e679c --- /dev/null +++ b/lib/db.c @@ -0,0 +1,1086 @@ +/* + * db.c + * + * See the README file for copyright information and how to reach the author. + * + */ + +#include <stdio.h> + +#include <mysql/errmsg.h> + +#include "db.h" + +// #define DEB_HANDLER + +//*************************************************************************** +// DB Statement +//*************************************************************************** + +cDbStatement::cDbStatement(cDbTable* aTable) +{ + table = aTable; + connection = table->getConnection(); + stmtTxt = ""; + stmt = 0; + inCount = 0; + outCount = 0; + inBind = 0; + outBind = 0; + affected = 0; + metaResult = 0; + bindPrefix = 0; +} + +cDbStatement::cDbStatement(cDbConnection* aConnection, const char* stmt) +{ + table = 0; + connection = aConnection; + stmtTxt = stmt; + stmt = 0; + inCount = 0; + outCount = 0; + inBind = 0; + outBind = 0; + affected = 0; + metaResult = 0; + bindPrefix = 0; +} + +//*************************************************************************** +// Execute +//*************************************************************************** + +int cDbStatement::execute(int noResult) +{ + affected = 0; + + if (!connection || !connection->getMySql()) + return fail; + + if (!stmt) + return connection->errorSql(connection, "execute(missing statement)"); + + // tell(0, "execute %d [%s]", stmt, stmtTxt.c_str()); + + if (mysql_stmt_execute(stmt)) + return connection->errorSql(connection, "execute(stmt_execute)", stmt, stmtTxt.c_str()); + + // out binding - if needed + + if (outCount && !noResult) + { + if (mysql_stmt_store_result(stmt)) + return connection->errorSql(connection, "execute(store_result)", stmt, stmtTxt.c_str()); + + // fetch the first result - if any + + if (mysql_stmt_affected_rows(stmt) > 0) + mysql_stmt_fetch(stmt); + } + else if (outCount) + { + mysql_stmt_store_result(stmt); + } + + // result was stored (above) only if output (outCound) is expected, + // therefore we don't need to call freeResult() after insert() or update() + + affected = mysql_stmt_affected_rows(stmt); + + return success; +} + +int cDbStatement::getResultCount() +{ + mysql_stmt_store_result(stmt); + + return mysql_stmt_affected_rows(stmt); +} + +int cDbStatement::find() +{ + if (execute() != success) + return fail; + + return getAffected() > 0 ? yes : no; +} + +int cDbStatement::fetch() +{ + if (!mysql_stmt_fetch(stmt)) + return yes; + + return no; +} + +int cDbStatement::freeResult() +{ + if (metaResult) + mysql_free_result(metaResult); + + if (stmt) + mysql_stmt_free_result(stmt); + + return success; +} + +//*************************************************************************** +// Build Statements - new Interface +//*************************************************************************** + +int cDbStatement::build(const char* format, ...) +{ + if (format) + { + char* tmp; + + va_list more; + va_start(more, format); + vasprintf(&tmp, format, more); + + stmtTxt += tmp; + free(tmp); + } + + return success; +} + +int cDbStatement::bind(int field, int mode, const char* delim) +{ + return bind(table->getRow()->getValue(field), mode, delim); +} + +int cDbStatement::bind(cDbValue* value, int mode, const char* delim) +{ + if (!value || !value->getField()) + return fail; + + if (delim) + stmtTxt += delim; + + if (bindPrefix) + stmtTxt += bindPrefix; + + if (mode & bndIn) + { + if (mode & bndSet) + stmtTxt += value->getName() + string(" ="); + + stmtTxt += " ?"; + appendBinding(value, bndIn); + } + else if (mode & bndOut) + { + stmtTxt += value->getName(); + appendBinding(value, bndOut); + } + + return success; +} + +int cDbStatement::bindCmp(const char* ctable, cDbValue* value, + const char* comp, const char* delim) +{ + if (ctable) + build("%s.", ctable); + + build("%s%s %s ?", delim ? delim : "", value->getName(), comp); + + appendBinding(value, bndIn); + + return success; +} + +int cDbStatement::bindCmp(const char* ctable, int field, cDbValue* value, + const char* comp, const char* delim) +{ + cDbValue* vf = table->getRow()->getValue(field); + cDbValue* vv = value ? value : vf; + + if (ctable) + build("%s.", ctable); + + build("%s%s %s ?", delim ? delim : "", vf->getName(), comp); + + appendBinding(vv, bndIn); + + return success; +} + +//*************************************************************************** +// Clear +//*************************************************************************** + +void cDbStatement::clear() +{ + stmtTxt = ""; + affected = 0; + + if (inCount) + { + free(inBind); + inCount = 0; + inBind = 0; + } + + if (outCount) + { + free(outBind); + outCount = 0; + outBind = 0; + } + + if (stmt) + { + mysql_stmt_free_result(stmt); + mysql_stmt_close(stmt); + stmt = 0; + } +} + +//*************************************************************************** +// Append Binding +//*************************************************************************** + +int cDbStatement::appendBinding(cDbValue* value, BindType bt) +{ + int count = 0; + MYSQL_BIND** bindings = 0; + MYSQL_BIND* newBinding; + + if (bt & bndIn) + { + count = ++inCount; + bindings = &inBind; + } + else if (bt & bndOut) + { + count = ++outCount; + bindings = &outBind; + } + else + return 0; + + if (!bindings) + *bindings = (MYSQL_BIND*)malloc(count * sizeof(MYSQL_BIND)); + else + *bindings = (MYSQL_BIND*)realloc(*bindings, count * sizeof(MYSQL_BIND)); + + newBinding = &((*bindings)[count-1]); + + if (value->getField()->format == ffAscii || value->getField()->format == ffText) + { + newBinding->buffer_type = MYSQL_TYPE_STRING; + newBinding->buffer = value->getStrValueRef(); + newBinding->buffer_length = value->getField()->size; + newBinding->length = value->getStrValueSizeRef(); + + newBinding->is_null = value->getNullRef(); + newBinding->error = 0; // #TODO + } + else if (value->getField()->format == ffMlob) + { + newBinding->buffer_type = MYSQL_TYPE_BLOB; + newBinding->buffer = value->getStrValueRef(); + newBinding->buffer_length = value->getField()->size; + newBinding->length = value->getStrValueSizeRef(); + + newBinding->is_null = value->getNullRef(); + newBinding->error = 0; // #TODO + } + else if (value->getField()->format == ffFloat) + { + newBinding->buffer_type = MYSQL_TYPE_FLOAT; + newBinding->buffer = value->getFloatValueRef(); + + newBinding->length = 0; // #TODO + newBinding->is_null = value->getNullRef(); + newBinding->error = 0; // #TODO + } + else if (value->getField()->format == ffDateTime) + { + newBinding->buffer_type = MYSQL_TYPE_DATETIME; + newBinding->buffer = value->getTimeValueRef(); + + newBinding->length = 0; // #TODO + newBinding->is_null = value->getNullRef(); + newBinding->error = 0; // #TODO + } + else + { + newBinding->buffer_type = MYSQL_TYPE_LONG; + newBinding->buffer = value->getIntValueRef(); + newBinding->is_unsigned = (value->getField()->format == ffUInt); + + newBinding->length = 0; + newBinding->is_null = value->getNullRef(); + newBinding->error = 0; // #TODO + } + + return success; +} + +//*************************************************************************** +// Prepare Statement +//*************************************************************************** + +int cDbStatement::prepare() +{ + if (!stmtTxt.length() || !connection->getMySql()) + return fail; + + stmt = mysql_stmt_init(connection->getMySql()); + + // prepare statement + + if (mysql_stmt_prepare(stmt, stmtTxt.c_str(), stmtTxt.length())) + return connection->errorSql(connection, "prepare(stmt_prepare)", stmt, stmtTxt.c_str()); + + if (outBind) + { + if (mysql_stmt_bind_result(stmt, outBind)) + return connection->errorSql(connection, "execute(bind_result)", stmt); + } + + if (inBind) + { + if (mysql_stmt_bind_param(stmt, inBind)) + return connection->errorSql(connection, "buildPrimarySelect(bind_param)", stmt); + } + + tell(2, "Statement '%s' with (%d) in parameters and (%d) out bindings prepared", + stmtTxt.c_str(), mysql_stmt_param_count(stmt), outCount); + + return success; +} + +//*************************************************************************** +// cDbService +//*************************************************************************** + +const char* cDbService::formats[] = +{ + "INT", + "INT", + "VARCHAR", + "TEXT", + "MEDIUMBLOB", + "FLOAT", + "DATETIME", + + 0 +}; + +const char* cDbService::toString(FieldFormat t) +{ + return formats[t]; +} + +//*************************************************************************** +// Class cDbTable +//*************************************************************************** + +char* cDbTable::confPath = 0; + +char* cDbConnection::encoding = 0; +char* cDbConnection::dbHost = strdup("localhost"); +int cDbConnection::dbPort = 3306; +char* cDbConnection::dbUser = 0; +char* cDbConnection::dbPass = 0; +char* cDbConnection::dbName = 0; + +//*************************************************************************** +// Object +//*************************************************************************** + +cDbTable::cDbTable(cDbConnection* aConnection, FieldDef* f, IndexDef* i) +{ + connection = aConnection; + row = new cDbRow(f); + holdInMemory = no; + + stmtSelect = 0; + stmtInsert = 0; + stmtUpdate = 0; + + indices = i; +} + +cDbTable::~cDbTable() +{ + close(); + + delete row; +} + +//*************************************************************************** +// Open / Close +//*************************************************************************** + +int cDbTable::open() +{ + if (connection->attachConnection() != success) + { + tell(0, "Could not access database '%s:%d' (tried to open %s)", + connection->getHost(), connection->getPort(), TableName()); + + return fail; + } + + return init(); +} + +int cDbTable::close() +{ + if (stmtSelect) { delete stmtSelect; stmtSelect = 0; } + if (stmtInsert) { delete stmtInsert; stmtInsert = 0; } + if (stmtUpdate) { delete stmtUpdate; stmtUpdate = 0; } + + connection->detachConnection(); + + return success; +} + +//*************************************************************************** +// Init +//*************************************************************************** + +int cDbTable::init() +{ + string str; + + if (!isConnected()) return fail; + + // check/create table ... + + if (createTable() != success) + return fail; + + // ------------------------------ + // prepare BASIC statements + // ------------------------------ + + // select by primary key ... + + stmtSelect = new cDbStatement(this); + + stmtSelect->build("select "); + + for (int i = 0, n = 0; row->getField(i)->name; i++) + { + if (row->getField(i)->type & ftCalc) + continue; + + stmtSelect->bind(i, bndOut, n++ ? ", " : ""); + } + + stmtSelect->build(" from %s where ", TableName()); + + for (int i = 0, n = 0; row->getField(i)->name; i++) + { + if (!(row->getField(i)->type & ftPrimary)) + continue; + + stmtSelect->bind(i, bndIn | bndSet, n++ ? " and " : ""); + } + + stmtSelect->build(";"); + + if (stmtSelect->prepare() != success) + return fail; + + // ----------------------------------------- + // insert + + stmtInsert = new cDbStatement(this); + + stmtInsert->build("insert into %s set ", TableName()); + + for (int i = 0, n = 0; row->getField(i)->name; i++) + { + // don't insert autoinc and calculated fields + + if (row->getField(i)->type & ftCalc || row->getField(i)->type & ftAutoinc) + continue; + + stmtInsert->bind(i, bndIn | bndSet, n++ ? ", " : ""); + } + + stmtInsert->build(";"); + + if (stmtInsert->prepare() != success) + return fail; + + // ----------------------------------------- + // update via primary key ... + + stmtUpdate = new cDbStatement(this); + + stmtUpdate->build("update %s set ", TableName()); + + for (int i = 0, n = 0; row->getField(i)->name; i++) + { + // don't update PKey, autoinc and calculated fields + + if (row->getField(i)->type & ftPrimary || + row->getField(i)->type & ftCalc || + row->getField(i)->type & ftAutoinc) + continue; + + if (strcmp(row->getField(i)->name, "inssp") == 0) // don't update the insert stamp + continue; + + stmtUpdate->bind(i, bndIn | bndSet, n++ ? ", " : ""); + } + + stmtUpdate->build(" where "); + + for (int i = 0, n = 0; row->getField(i)->name; i++) + { + if (!(row->getField(i)->type & ftPrimary)) + continue; + + stmtUpdate->bind(i, bndIn | bndSet, n++ ? " and " : ""); + } + + stmtUpdate->build(";"); + + if (stmtUpdate->prepare() != success) + return fail; + + return success; +} + +//*************************************************************************** +// Check Table +//*************************************************************************** + +int cDbTable::exist(const char* name) +{ + if (!name) + name = TableName(); + + MYSQL_RES* result = mysql_list_tables(connection->getMySql(), name); + MYSQL_ROW tabRow = mysql_fetch_row(result); + mysql_free_result(result); + + return tabRow ? yes : no; +} + +//*************************************************************************** +// Create Table +//*************************************************************************** + +int cDbTable::createTable() +{ + string statement; + string aKey; + + if (!isConnected()) + { + if (connection->attachConnection() != success) + { + tell(0, "Could not access database '%s:%d' (tried to create %s)", + connection->getHost(), connection->getPort(), TableName()); + + return fail; + } + } + + // table exists -> nothing to do + + if (exist()) + return done; + + tell(0, "Initialy creating table '%s'", TableName()); + + // build 'create' statement ... + + statement = string("create table ") + TableName() + string("("); + + for (int i = 0; getField(i)->name; i++) + { + int size = getField(i)->size; + char num[10]; + + if (getField(i)->type & ftCalc) + continue; + + if (i) statement += string(", "); + statement += string(getField(i)->name) + " " + string(toString(getField(i)->format)); + + if (getField(i)->format != ffMlob) + { + if (!size) size = getField(i)->format == ffAscii || getField(i)->format == ffText ? 100 : 11; + + if (getField(i)->format != ffFloat) + sprintf(num, "%d", size); + else + sprintf(num, "%d,%d", size/10, size%10); + + statement += "(" + string(num) + ")"; + + if (getField(i)->format == ffUInt) + statement += " unsigned"; + + if (getField(i)->type & ftAutoinc) + statement += " not null auto_increment"; + else if (getField(i)->type & ftDef0) + statement += " default '0'"; + } + } + + aKey = ""; + + for (int i = 0, n = 0; getField(i)->name; i++) + { + if (getField(i)->type & ftPrimary) + { + if (n++) aKey += string(", "); + aKey += string(getField(i)->name) + " DESC"; + } + } + + if (aKey.length()) + { + statement += string(", PRIMARY KEY("); + statement += aKey; + statement += ")"; + } + + aKey = ""; + + for (int i = 0, n = 0; getField(i)->name; i++) + { + if (getField(i)->type & ftAutoinc && !(getField(i)->type & ftPrimary)) + { + if (n++) aKey += string(", "); + aKey += string(getField(i)->name) + " DESC"; + } + } + + if (aKey.length()) + { + statement += string(", KEY("); + statement += aKey; + statement += ")"; + } + + // statement += string(") ENGINE MYISAM;"); + statement += string(") ENGINE InnoDB;"); + + tell(1, "%s", statement.c_str()); + + if (connection->query(statement.c_str())) + return connection->errorSql(getConnection(), "createTable()", + 0, statement.c_str()); + + // create indices + + createIndices(); + + return success; +} + +//*************************************************************************** +// Create Indices +//*************************************************************************** + +int cDbTable::createIndices() +{ + string statement; + + tell(5, "Initialy checking indices for '%s'", TableName()); + + // check/create indexes + + if (!indices) + return done; + + for (int i = 0; getIndex(i)->name; i++) + { + IndexDef* index = getIndex(i); + int fCount; + string idxName; + int expectCount = 0; + + for (; index->fields[expectCount] != na; expectCount++) ; + + if (!expectCount) + continue; + + // check + + idxName = "idx" + string(index->name); + + checkIndex(idxName.c_str(), fCount); + + if (fCount != expectCount) + { + // create index + + statement = "create index " + idxName; + statement += " on " + string(TableName()) + "("; + + int n = 0; + + for (int f = 0; index->fields[f] != na; f++) + { + FieldDef* fld = getField(index->fields[f]); + + if (fld && !(fld->type & ftCalc)) + { + if (n++) statement += string(", "); + statement += fld->name; + } + } + + if (!n) continue; + + statement += ");"; + tell(1, "%s", statement.c_str()); + + if (connection->query(statement.c_str())) + return connection->errorSql(getConnection(), "createIndices()", + 0, statement.c_str()); + } + } + + return success; +} + +//*************************************************************************** +// Check Index +//*************************************************************************** + +int cDbTable::checkIndex(const char* idxName, int& fieldCount) +{ + enum IndexQueryFields + { + idTable, + idNonUnique, + idKeyName, + idSeqInIndex, + idColumnName, + idCollation, + idCardinality, + idSubPart, + idPacked, + idNull, + idIndexType, + idComment, + idIndexComment, + + idCount + }; + + MYSQL_RES* result; + MYSQL_ROW row; + + fieldCount = 0; + + if (connection->query("show index from %s", TableName()) != success) + { + connection->errorSql(getConnection(), "checkIndex()", 0); + + return fail; + } + + if ((result = mysql_store_result(connection->getMySql()))) + { + while ((row = mysql_fetch_row(result))) + { + tell(5, "%s: %-20s %s %s", + row[idTable], row[idKeyName], + row[idSeqInIndex], row[idColumnName]); + + if (strcasecmp(row[idKeyName], idxName) == 0) + fieldCount++; + } + + mysql_free_result(result); + + return success; + } + + connection->errorSql(getConnection(), "checkIndex()"); + + return fail; +} + +//*************************************************************************** +// Copy Values +//*************************************************************************** + +void cDbTable::copyValues(cDbRow* r) +{ + for (int i = 0; i < fieldCount(); i++) + { + if (getField(i)->format == ffAscii || getField(i)->format == ffText) + row->setValue(i, r->getStrValue(i)); + else + row->setValue(i, r->getIntValue(i)); + } +} + +//*************************************************************************** +// SQL Error +//*************************************************************************** + +int cDbConnection::errorSql(cDbConnection* connection, const char* prefix, MYSQL_STMT* stmt, const char* stmtTxt) +{ + if (!connection || !connection->mysql) + { + tell(0, "SQL-Error in '%s'", prefix); + return fail; + } + + int error = mysql_errno(connection->mysql); + char* conErr = 0; + char* stmtErr = 0; + + if (error == CR_SERVER_LOST || + error == CR_SERVER_GONE_ERROR || + error == CR_INVALID_CONN_HANDLE || + error == CR_SERVER_LOST_EXTENDED) + connectDropped = yes; + + if (error) + asprintf(&conErr, "%s (%d) ", mysql_error(connection->mysql), error); + + if (stmt || stmtTxt) + asprintf(&stmtErr, "'%s' [%s]", + stmt ? mysql_stmt_error(stmt) : "", + stmtTxt ? stmtTxt : ""); + + tell(0, "SQL-Error in '%s' - %s%s", prefix, + conErr ? conErr : "", stmtErr ? stmtErr : ""); + + free(conErr); + free(stmtErr); + + if (connectDropped) + tell(0, "Fatal, lost connection to mysql server, aborting pending actions"); + + return fail; +} + +//*************************************************************************** +// Delete Where +//*************************************************************************** + +int cDbTable::deleteWhere(const char* where) +{ + string tmp; + + if (!connection || !connection->getMySql()) + return fail; + + tmp = "delete from " + string(TableName()) + " where " + string(where); + + if (connection->query(tmp.c_str())) + return connection->errorSql(connection, "deleteWhere()", 0, tmp.c_str()); + + return success; +} + +//*************************************************************************** +// Coiunt Where +//*************************************************************************** + +int cDbTable::countWhere(const char* where, int& count, const char* what) +{ + string tmp; + MYSQL_RES* res; + MYSQL_ROW data; + + count = 0; + + if (isEmpty(what)) + what = "count(1)"; + + if (!isEmpty(where)) + tmp = "select " + string(what) + " from " + string(TableName()) + " where " + string(where); + else + tmp = "select " + string(what) + " from " + string(TableName()); + + if (connection->query(tmp.c_str())) + return connection->errorSql(connection, "countWhere()", 0, tmp.c_str()); + + if (res = mysql_store_result(connection->getMySql())) + { + data = mysql_fetch_row(res); + + if (data) + count = atoi(data[0]); + + mysql_free_result(res); + } + + return success; +} + +//*************************************************************************** +// Truncate +//*************************************************************************** + +int cDbTable::truncate() +{ + string tmp; + + tmp = "delete from " + string(TableName()); + + if (connection->query(tmp.c_str())) + return connection->errorSql(connection, "truncate()", 0, tmp.c_str()); + + return success; +} + + +//*************************************************************************** +// Store +//*************************************************************************** + +int cDbTable::store() +{ + int found; + + // insert or just update ... + + if (stmtSelect->execute(/*noResult =*/ yes) != success) + { + connection->errorSql(connection, "store()"); + return no; + } + + found = stmtSelect->getAffected() == 1; + stmtSelect->freeResult(); + + if (found) + return update(); + else + return insert(); +} + +//*************************************************************************** +// Insert +//*************************************************************************** + +int cDbTable::insert() +{ + if (!stmtInsert) + { + tell(0, "Fatal missing insert statement\n"); + return fail; + } + + for (int i = 0; getField(i)->name; i++) + { + if (strcmp(getField(i)->name, "updsp") == 0 || strcmp(getField(i)->name, "inssp") == 0) + setValue(getField(i)->index, time(0)); + } + +#ifdef DEB_HANDLER + + if (strcmp(TableName(), "events") == 0) + tell(1, "inserting vdr event %d for '%s', starttime = %ld, updflg = '%s'", + getIntValue(0), getStrValue(1), getIntValue(15), getStrValue(6)); +#endif + + if (stmtInsert->execute()) + return fail; + + return stmtInsert->getAffected() == 1 ? success : fail; +} + +//*************************************************************************** +// Update +//*************************************************************************** + +int cDbTable::update() +{ + if (!stmtUpdate) + { + tell(0, "Fatal missing update statement\n"); + return fail; + } + + for (int i = 0; getField(i)->name; i++) + { + if (strcmp(getField(i)->name, "updsp") == 0) + { + setValue(getField(i)->index, time(0)); + break; + } + } + +#ifdef DEB_HANDLER + if (strcmp(TableName(), "events") == 0) + tell(1, "updating vdr event %d for '%s', starttime = %ld, updflg = '%s'", + getIntValue(0), getStrValue(1), getIntValue(15), getStrValue(6)); +#endif + + if (stmtUpdate->execute()) + return fail; + + return stmtUpdate->getAffected() == 1 ? success : fail; +} + +//*************************************************************************** +// Find +//*************************************************************************** + +int cDbTable::find() +{ + if (!stmtSelect) + return no; + + if (stmtSelect->execute() != success) + { + connection->errorSql(connection, "find()"); + return no; + } + + return stmtSelect->getAffected() == 1 ? yes : no; +} + +//*************************************************************************** +// Find via Statement +//*************************************************************************** + +int cDbTable::find(cDbStatement* stmt) +{ + if (!stmt) + return no; + + if (stmt->execute() != success) + { + connection->errorSql(connection, "find(stmt)"); + return no; + } + + return stmt->getAffected() > 0 ? yes : no; +} + +//*************************************************************************** +// Fetch +//*************************************************************************** + +int cDbTable::fetch(cDbStatement* stmt) +{ + if (!stmt) + return no; + + return stmt->fetch(); +} + +//*************************************************************************** +// Reset Fetch +//*************************************************************************** + +void cDbTable::reset(cDbStatement* stmt) +{ + if (stmt) + stmt->freeResult(); +} diff --git a/lib/db.h b/lib/db.h new file mode 100644 index 0000000..522226e --- /dev/null +++ b/lib/db.h @@ -0,0 +1,1030 @@ +/* + * db.h + * + * See the README file for copyright information and how to reach the author. + * + */ + +#ifndef __DB_H +#define __DB_H + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> + +#include <sstream> + +#include <mysql/mysql.h> + +#include "common.h" + +class cDbTable; +class cDbConnection; + +using namespace std; + +//*************************************************************************** +// cDbService +//*************************************************************************** + +class cDbService +{ + public: + + enum Misc + { + maxIndexFields = 20 + }; + + enum FieldFormat + { + ffInt, + ffUInt, + ffAscii, // -> VARCHAR + ffText, + ffMlob, // -> MEDIUMBLOB + ffFloat, + ffDateTime, + ffCount + }; + + enum FieldType + { + ftData = 1, + ftPrimary = 2, + ftMeta = 4, + ftCalc = 8, + ftAutoinc = 16, + ftDef0 = 32 + }; + + struct FieldDef + { + const char* name; + FieldFormat format; + int size; + int index; + int type; + }; + + enum BindType + { + bndIn = 0x001, + bndOut = 0x002, + bndSet = 0x004 + }; + + enum ProcType + { + ptProcedure, + ptFunction + }; + + struct IndexDef + { + const char* name; + int fields[maxIndexFields+1]; + int order; // not implemented yet + }; + + static const char* toString(FieldFormat t); + static const char* formats[]; +}; + +typedef cDbService cDBS; + +//*************************************************************************** +// cDbValue +//*************************************************************************** + +class cDbValue : public cDbService +{ + public: + + cDbValue(FieldDef* f = 0) + { + field = 0; + strValue = 0; + ownField = 0; + + if (f) setField(f); + } + + cDbValue(const char* name, FieldFormat format, int size) + { + strValue = 0; + + ownField = new FieldDef; + ownField->name = strdup(name); + ownField->format = format; + ownField->size = size; + ownField->type = ftData; + + field = ownField; + + clear(); + } + + virtual ~cDbValue() + { + free(); + } + + void free() + { + clear(); + ::free(strValue); + strValue = 0; + + if (ownField) + { + ::free((char*)ownField->name); // böser cast ;) + delete ownField; + ownField = 0; + } + + field = 0; + } + + void clear() + { + if (strValue) + *strValue = 0; + + strValueSize = 0; + numValue = 0; + floatValue = 0; + memset(&timeValue, 0, sizeof(timeValue)); + + nullValue = 1; + initialized = no; + } + + virtual void setField(FieldDef* f) + { + free(); + field = f; + + if (field) + strValue = (char*)calloc(field->size+TB, sizeof(char)); + } + + virtual FieldDef* getField() { return field; } + virtual const char* getName() { return field->name; } + + void setValue(const char* value, int size = 0) + { + clear(); + + if (field->format != ffAscii && field->format != ffText && field->format != ffMlob) + { + tell(0, "Setting invalid field format for '%s', expected ASCII or MLOB", field->name); + return; + } + + if (field->format == ffMlob && !size) + { + tell(0, "Missing size for MLOB field '%s'", field->name); + return; + } + + if (value && size) + { + if (size > field->size) + { + tell(0, "Warning, size of %d for '%s' exeeded, got %d bytes!", + field->size, field->name, size); + + size = field->size; + } + + memcpy(strValue, value, size); + strValue[size] = 0; + strValueSize = size; + nullValue = 0; + } + + else if (value) + { + if (strlen(value) > (size_t)field->size) + tell(0, "Warning, size of %d for '%s' exeeded [%s]", + field->size, field->name, value); + + sprintf(strValue, "%.*s", field->size, value); + strValueSize = strlen(strValue); + nullValue = 0; + } + } + + void setCharValue(char value) + { + char tmp[2]; + tmp[0] = value; + tmp[1] = 0; + + setValue(tmp); + } + + void setValue(int value) + { + setValue((long)value); + } + + void setValue(long value) + { + if (field->format == ffInt || field->format == ffUInt) + { + numValue = value; + nullValue = 0; + } + else if (field->format == ffDateTime) + { + struct tm tm; + time_t v = value; + + memset(&tm, 0, sizeof(tm)); + localtime_r(&v, &tm); + + timeValue.year = tm.tm_year + 1900; + timeValue.month = tm.tm_mon + 1; + timeValue.day = tm.tm_mday; + + timeValue.hour = tm.tm_hour; + timeValue.minute = tm.tm_min; + timeValue.second = tm.tm_sec; + + nullValue = 0; + } + else + { + tell(0, "Setting invalid field format for '%s'", field->name); + } + } + + void setValue(double value) + { + if (field->format == ffInt || field->format == ffUInt) + { + numValue = value; + nullValue = 0; + } + else if (field->format == ffFloat) + { + floatValue = value; + nullValue = 0; + } + else + { + tell(0, "Setting invalid field format for '%s'", field->name); + } + } + + int hasValue(long value) + { + if (field->format == ffInt || field->format == ffUInt) + return numValue == value; + + if (field->format == ffDateTime) + return no; // to be implemented! + + tell(0, "Setting invalid field format for '%s'", field->name); + + return no; + } + + int hasValue(double value) + { + if (field->format == ffInt || field->format == ffUInt) + return numValue == value; + + if (field->format == ffFloat) + return floatValue == value; + + tell(0, "Setting invalid field format for '%s'", field->name); + + return no; + } + +// int hasValue(float value) +// { +// if (field->format != ffFloat) +// { +// tell(0, "Checking invalid field format for '%s', expected FLOAT", field->name); +// return no; +// } + +// return floatValue == value; +// } + +// int hasValue(long value) +// { +// if (field->format != ffInt && field->format != ffUInt) +// { +// tell(0, "Checking invalid field format for '%s', expected INT", field->name); +// return no; +// } + +// return numValue == value; +// } + + int hasValue(const char* value) + { + if (!value) + value = ""; + + if (field->format != ffAscii && field->format != ffText) + { + tell(0, "Checking invalid field format for '%s', expected ASCII or MLOB", field->name); + return no; + } + + return strcmp(getStrValue(), value) == 0; + } + + time_t getTimeValue() + { + struct tm tm; + + memset(&tm, 0, sizeof(tm)); + tm.tm_year = timeValue.year - 1900; + tm.tm_mon = timeValue.month - 1; + tm.tm_mday = timeValue.day; + + tm.tm_hour = timeValue.hour; + tm.tm_min = timeValue.minute; + tm.tm_sec = timeValue.second; + + return mktime(&tm); + } + + unsigned long* getStrValueSizeRef() { return &strValueSize; } + unsigned long getStrValueSize() { return strValueSize; } + const char* getStrValue() { return !isNull() && strValue ? strValue : ""; } + long getIntValue() { return !isNull() ? numValue : 0; } + float getFloatValue() { return !isNull() ? floatValue : 0; } + int isNull() { return nullValue; } + + char* getStrValueRef() { return strValue; } + long* getIntValueRef() { return &numValue; } + MYSQL_TIME* getTimeValueRef() { return &timeValue; } + float* getFloatValueRef() { return &floatValue; } + my_bool* getNullRef() { return &nullValue; } + + private: + + FieldDef* ownField; + FieldDef* field; + long numValue; + float floatValue; + MYSQL_TIME timeValue; + char* strValue; + unsigned long strValueSize; + my_bool nullValue; + int initialized; +}; + +//*************************************************************************** +// cDbStatement +//*************************************************************************** + +class cDbStatement : public cDbService +{ + public: + + cDbStatement(cDbTable* aTable); + cDbStatement(cDbConnection* aConnection, const char* stmt = ""); + + virtual ~cDbStatement() { clear(); } + + int execute(int noResult = no); + int find(); + int fetch(); + int freeResult(); + + // interface + + int build(const char* format, ...); + + void setBindPrefix(const char* p) { bindPrefix = p; } + void clrBindPrefix() { bindPrefix = 0; } + int bind(cDbValue* value, int mode, const char* delim = 0); + int bind(int field, int mode, const char* delim = 0); + + int bindCmp(const char* table, cDbValue* value, + const char* comp, const char* delim = 0); + int bindCmp(const char* table, int field, cDbValue* value, + const char* comp, const char* delim = 0); + + // .. + + int prepare(); + int getAffected() { return affected; } + int getResultCount(); + const char* asText() { return stmtTxt.c_str(); } + + private: + + void clear(); + int appendBinding(cDbValue* value, BindType bt); + + string stmtTxt; + MYSQL_STMT* stmt; + int affected; + cDbConnection* connection; + cDbTable* table; + int inCount; + MYSQL_BIND* inBind; // to db + int outCount; + MYSQL_BIND* outBind; // from db (result) + MYSQL_RES* metaResult; + const char* bindPrefix; +}; + +//*************************************************************************** +// Class Database Row +//*************************************************************************** + +class cDbRow : public cDbService +{ + public: + + cDbRow(FieldDef* f) + { + count = 0; + fieldDef = 0; + useFields(f); + + dbValues = new cDbValue[count]; + + for (int f = 0; f < count; f++) + dbValues[f].setField(getField(f)); + } + + virtual ~cDbRow() { delete[] dbValues; } + + void clear() + { + for (int f = 0; f < count; f++) + dbValues[f].clear(); + } + + virtual FieldDef* getField(int f) { return f < 0 ? 0 : fieldDef+f; } + virtual int fieldCount() { return count; } + + void setValue(int f, const char* value, + int size = 0) { dbValues[f].setValue(value, size); } + void setValue(int f, int value) { dbValues[f].setValue(value); } + void setValue(int f, long value) { dbValues[f].setValue(value); } + void setValue(int f, double value) { dbValues[f].setValue(value); } + void setCharValue(int f, char value) { dbValues[f].setCharValue(value); } + + int hasValue(int f, const char* value) const { return dbValues[f].hasValue(value); } + int hasValue(int f, long value) const { return dbValues[f].hasValue(value); } + int hasValue(int f, double value) const { return dbValues[f].hasValue(value); } + + cDbValue* getValue(int f) { return &dbValues[f]; } + + const char* getStrValue(int f) const { return dbValues[f].getStrValue(); } + long getIntValue(int f) const { return dbValues[f].getIntValue(); } + float getFloatValue(int f) const { return dbValues[f].getFloatValue(); } + int isNull(int f) const { return dbValues[f].isNull(); } + + protected: + + virtual void useFields(FieldDef* f) { fieldDef = f; for (count = 0; (fieldDef+count)->name; count++); } + + int count; // field count + FieldDef* fieldDef; + cDbValue* dbValues; +}; + +//*************************************************************************** +// Connection +//*************************************************************************** + +class cDbConnection +{ + public: + + cDbConnection() + { + mysql = 0; + attached = 0; + inTact = no; + connectDropped = yes; + } + + virtual ~cDbConnection() + { + if (mysql) + { + mysql_close(mysql); + mysql_thread_end(); + } + } + + int attachConnection() + { + static int first = yes; + + if (!mysql) + { + connectDropped = yes; + + if (!(mysql = mysql_init(0))) + return errorSql(this, "attachConnection(init)"); + + if (!mysql_real_connect(mysql, dbHost, + dbUser, dbPass, dbName, dbPort, 0, 0)) + { + mysql_close(mysql); + mysql = 0; + tell(0, "Error, connecting to database at '%s' on port (%d) failed", + dbHost, dbPort); + + return fail; + } + + connectDropped = no; + + // init encoding + + if (encoding && *encoding) + { + if (mysql_set_character_set(mysql, encoding)) + errorSql(this, "init(character_set)"); + + if (first) + { + tell(0, "SQL client character now '%s'", mysql_character_set_name(mysql)); + first = no; + } + } + } + + attached++; + + return success; + } + + void detachConnection() + { + attached--; + + if (!attached) + { + mysql_close(mysql); + mysql_thread_end(); + mysql = 0; + } + } + + int isConnected() { return getMySql() > 0; } + + int check() + { + if (!isConnected()) + return fail; + + query("SELECT SYSDATE();"); + queryReset(); + + return isConnected() ? success : fail; + } + + virtual int query(const char* format, ...) + { + int status = 1; + MYSQL* h = getMySql(); + + if (h && format) + { + char* stmt; + + va_list more; + va_start(more, format); + vasprintf(&stmt, format, more); + + if ((status = mysql_query(h, stmt))) + errorSql(this, stmt); + + free(stmt); + } + + return status ? fail : success; + } + + virtual void queryReset() + { + if (getMySql()) + { + MYSQL_RES* result = mysql_use_result(getMySql()); + mysql_free_result(result); + } + } + + virtual int executeSqlFile(const char* file) + { + FILE* f; + int res; + char* buffer; + int size = 1000; + int nread = 0; + + if (!getMySql()) + return fail; + + if (!(f = fopen(file, "r"))) + { + tell(0, "Fatal: Can't access '%s'; %m", file); + return fail; + } + + buffer = (char*)malloc(size+1); + + while (res = fread(buffer+nread, 1, 1000, f)) + { + nread += res; + size += 1000; + buffer = (char*)realloc(buffer, size+1); + } + + fclose(f); + buffer[nread] = 0; + + // execute statement + + tell(2, "Executing '%s'", buffer); + + if (query("%s", buffer)) + { + free(buffer); + return errorSql(this, "executeSqlFile()"); + } + + free(buffer); + + return success; + } + + virtual int startTransaction() + { + inTact = yes; + return query("START TRANSACTION"); + } + + virtual int commit() + { + inTact = no; + return query("COMMIT"); + } + + virtual int rollback() + { + inTact = no; + return query("ROLLBACK"); + } + + virtual int inTransaction() { return inTact; } + + MYSQL* getMySql() + { + if (connectDropped && mysql) + { + mysql_close(mysql); + mysql_thread_end(); + mysql = 0; + attached = 0; + } + + return mysql; + } + + int getAttachedCount() { return attached; } + + // -------------- + // static stuff + + // set/get connecting data + + static void setHost(const char* s) { free(dbHost); dbHost = strdup(s); } + static const char* getHost() { return dbHost; } + static void setName(const char* s) { free(dbName); dbName = strdup(s); } + static const char* getName() { return dbName; } + static void setUser(const char* s) { free(dbUser); dbUser = strdup(s); } + static const char* getUser() { return dbUser; } + static void setPass(const char* s) { free(dbPass); dbPass = strdup(s); } + static const char* getPass() { return dbPass; } + static void setPort(int port) { dbPort = port; } + static int getPort() { return dbPort; } + static void setEncoding(const char* enc) { free(encoding); encoding = strdup(enc); } + static const char* getEncoding() { return encoding; } + + int errorSql(cDbConnection* mysql, const char* prefix, MYSQL_STMT* stmt = 0, const char* stmtTxt = 0); + + static int init() + { + if (mysql_library_init(0, 0, 0)) + { + tell(0, "Error: mysql_library_init failed"); + return fail; // return errorSql(0, "init(library_init)"); + } + + return success; + } + + static int exit() + { + mysql_library_end(); + free(dbHost); + free(dbUser); + free(dbPass); + free(dbName); + free(encoding); + + return done; + } + + MYSQL* mysql; + + private: + + int initialized; + int attached; + int inTact; + int connectDropped; + + static char* encoding; + + // connecting data + + static char* dbHost; + static int dbPort; + static char* dbName; // database name + static char* dbUser; + static char* dbPass; +}; + +//*************************************************************************** +// cDbTable +//*************************************************************************** + +class cDbTable : public cDbService +{ + public: + + cDbTable(cDbConnection* aConnection, FieldDef* f, IndexDef* i = 0); + virtual ~cDbTable(); + + virtual const char* TableName() = 0; + + virtual int open(); + virtual int close(); + + virtual int find(); + virtual void reset() { reset(stmtSelect); } + + virtual int find(cDbStatement* stmt); + virtual int fetch(cDbStatement* stmt); + virtual void reset(cDbStatement* stmt); + + virtual int insert(); + virtual int update(); + virtual int store(); + + virtual int deleteWhere(const char* where); + virtual int countWhere(const char* where, int& count, const char* what = 0); + virtual int truncate(); + + // interface to cDbRow + + void clear() { row->clear(); } + void setValue(int f, const char* value, int size = 0) { row->setValue(f, value, size); } + void setValue(int f, int value) { row->setValue(f, value); } + void setValue(int f, long value) { row->setValue(f, value); } + void setValue(int f, double value) { row->setValue(f, value); } + void setCharValue(int f, char value) { row->setCharValue(f, value); } + + int hasValue(int f, const char* value) { return row->hasValue(f, value); } + int hasValue(int f, long value) { return row->hasValue(f, value); } + int hasValue(int f, double value) { return row->hasValue(f, value); } + + const char* getStrValue(int f) const { return row->getStrValue(f); } + long getIntValue(int f) const { return row->getIntValue(f); } + float getFloatValue(int f) const { return row->getFloatValue(f); } + int isNull(int f) const { return row->isNull(f); } + + FieldDef* getField(int f) { return row->getField(f); } + int fieldCount() { return row->fieldCount(); } + cDbRow* getRow() { return row; } + + cDbConnection* getConnection() { return connection; } + MYSQL* getMySql() { return connection->getMySql(); } + int isConnected() { return connection && connection->getMySql(); } + + virtual IndexDef* getIndex(int i) { return indices+i; } + virtual int exist(const char* name = 0); + virtual int createTable(); + + // static stuff + + static void setConfPath(const char* cpath) { free(confPath); confPath = strdup(cpath); } + + protected: + + virtual int init(); + virtual int createIndices(); + virtual int checkIndex(const char* idxName, int& fieldCount); + + virtual void copyValues(cDbRow* r); + + // data + + cDbRow* row; + int holdInMemory; // hold table additionally in memory (not implemented yet) + + IndexDef* indices; + + // basic statements + + cDbStatement* stmtSelect; + cDbStatement* stmtInsert; + cDbStatement* stmtUpdate; + cDbConnection* connection; + + // statics + + static char* confPath; +}; + +//*************************************************************************** +// cDbView +//*************************************************************************** + +class cDbView : public cDbService +{ + public: + + cDbView(cDbConnection* c, const char* aName) + { + connection = c; + name = strdup(aName); + } + + ~cDbView() { free(name); } + + int exist() + { + if (connection->getMySql()) + { + MYSQL_RES* result = mysql_list_tables(connection->getMySql(), name); + MYSQL_ROW tabRow = mysql_fetch_row(result); + mysql_free_result(result); + + return tabRow ? yes : no; + } + + return no; + } + + int create(const char* path, const char* sqlFile) + { + int status; + char* file = 0; + + asprintf(&file, "%s/%s", path, sqlFile); + + tell(0, "Creating view '%s' using definition in '%s'", + name, file); + + status = connection->executeSqlFile(file); + + free(file); + + return status; + } + + int drop() + { + tell(0, "Drop view '%s'", name); + + return connection->query("drop view %s", name); + } + + protected: + + cDbConnection* connection; + char* name; +}; + +//*************************************************************************** +// cDbProcedure +//*************************************************************************** + +class cDbProcedure : public cDbService +{ + public: + + cDbProcedure(cDbConnection* c, const char* aName, ProcType pt = ptProcedure) + { + connection = c; + type = pt; + name = strdup(aName); + } + + ~cDbProcedure() { free(name); } + + const char* getName() { return name; } + + int call(int ll = 1) + { + if (!connection || !connection->getMySql()) + return fail; + + cDbStatement stmt(connection); + + tell(ll, "Calling '%s'", name); + + stmt.build("call %s", name); + + if (stmt.prepare() != success || stmt.execute() != success) + return fail; + + tell(ll, "'%s' suceeded", name); + + return success; + } + + int created() + { + if (!connection || !connection->getMySql()) + return fail; + + cDbStatement stmt(connection); + + stmt.build("show %s status where name = '%s'", + type == ptProcedure ? "procedure" : "function", name); + + if (stmt.prepare() != success || stmt.execute() != success) + { + tell(0, "%s check of '%s' failed", + type == ptProcedure ? "Procedure" : "Function", name); + return no; + } + else + { + if (stmt.getResultCount() != 1) + return no; + } + + return yes; + } + + int create(const char* path) + { + int status; + char* file = 0; + + asprintf(&file, "%s/%s.sql", path, name); + + tell(1, "Creating %s '%s'", + type == ptProcedure ? "procedure" : "function", name); + + status = connection->executeSqlFile(file); + + free(file); + + return status; + } + + int drop() + { + tell(1, "Drop %s '%s'", type == ptProcedure ? "procedure" : "function", name); + + return connection->query("drop %s %s", type == ptProcedure ? "procedure" : "function", name); + } + + static int existOnFs(const char* path, const char* name) + { + int state; + char* file = 0; + + asprintf(&file, "%s/%s.sql", path, name); + state = fileExists(file); + + free(file); + + return state; + } + + protected: + + cDbConnection* connection; + ProcType type; + char* name; + +}; + +//*************************************************************************** +#endif //__DB_H diff --git a/lib/tabledef.c b/lib/tabledef.c new file mode 100644 index 0000000..d4bb6a9 --- /dev/null +++ b/lib/tabledef.c @@ -0,0 +1,856 @@ +/* + * tabledef.c + * + * See the README file for copyright information and how to reach the author. + * + */ + +#include "tabledef.h" + +//*************************************************************************** +// cEpgdState +//*************************************************************************** + +const char* cEpgdState::states[] = +{ + "init", + "standby", + "stopped", + + "busy (events)", + "busy (match)", + "busy (images)", + "busy (scraping)", + + 0 +}; + +const char* cEpgdState::toName(cEpgdState::State s) +{ + if (!isValid(s)) + return "unknown"; + + return states[s]; +} + +cEpgdState::State cEpgdState::toState(const char* name) +{ + for (int i = 0; i < esCount; i++) + if (strcmp(states[i], name) == 0) + return (State)i; + + return esUnknown; +} + +//*************************************************************************** +// Event Fields +//*************************************************************************** +//*************************************************************************** +// Fields +//*************************************************************************** + +cDbService::FieldDef cTableEvents::fields[] = +{ + // name format size index type + + // primary key + + { "eventid", ffUInt, 0, fiEventId, ftPrimary }, + { "channelid", ffAscii, 50, fiChannelId, ftPrimary }, + + { "masterid", ffUInt, 0, fiMasterId, ftAutoinc }, + { "useid", ffUInt, 0, fiUseId, ftData }, + + // meta + + { "source", ffAscii, 10, fiSource, ftMeta }, + { "fileref", ffAscii, 50, fiFileRef, ftMeta }, + { "inssp", ffInt, 10, fiInsSp, ftMeta }, + { "updsp", ffInt, 10, fiUpdSp, ftMeta }, + { "updflg", ffAscii, 1, fiUpdFlg, ftMeta }, + { "delflg", ffAscii, 1, fiDelFlg, ftMeta }, + + // vdr event data + + { "tableid", ffInt, 2, fiTableId, ftData }, + { "version", ffInt, 3, fiVersion, ftData }, + { "title", ffAscii, 200, fiTitle, ftData }, + { "comptitle", ffAscii, 200, fiCompTitle, ftData }, + { "shorttext", ffAscii, 300, fiShortText, ftData }, + { "compshorttext", ffAscii, 300, fiCompShortText, ftData }, + { "longdescription", ffText, 25000, fiLongDescription, ftData }, + { "starttime", ffInt, 10, fiStartTime, ftData }, + { "duration", ffInt, 5, fiDuration, ftData }, + { "parentalrating", ffInt, 2, fiParentalRating, ftData }, + { "vps", ffInt, 10, fiVps, ftData }, + + { "description", ffText, 50000, fiDescription, ftCalc }, + + // additional external data + + { "shortdescription", ffAscii, 3000, fiShortDescription, ftData }, + { "actor", ffAscii, 3000, fiActor, ftData }, + { "audio", ffAscii, 50, fiAudio, ftData }, + { "category", ffAscii, 50, fiCategory, ftData }, + { "country", ffAscii, 50, fiCountry, ftData }, + { "director", ffAscii, 250, fiDirector, ftData }, + { "flags", ffAscii, 100, fiFlags, ftData }, + { "genre", ffAscii, 100, fiGenre, ftData }, + { "info", ffText, 10000, fiInfo, ftData }, + { "music", ffAscii, 250, fiMusic, ftData }, + { "producer", ffText, 1000, fiProducer, ftData }, + { "screenplay", ffAscii, 500, fiScreenplay, ftData }, + { "shortreview", ffAscii, 500, fiShortreview, ftData }, + { "tipp", ffAscii, 250, fiTipp, ftData }, + { "topic", ffAscii, 500, fiTopic, ftData }, + { "year", ffAscii, 10, fiYear, ftData }, + { "rating", ffAscii, 250, fiRating, ftData }, + { "fsk", ffAscii, 2, fiFsk, ftData }, + { "movieid", ffAscii, 20, fiMovieid, ftData }, + { "moderator", ffAscii, 250, fiModerator, ftData }, + { "other", ffText, 2000, fiOther, ftData }, + { "guest", ffText, 1000, fiGuest, ftData }, + { "camera", ffText, 1000, fiCamera, ftData }, + + { "extepnum", ffInt, 4, fiExtEpNum, ftData }, + { "imagecount", ffInt, 2, fiImageCount, ftData }, + + // episodes (constable) + + { "episode", ffAscii, 250, fiEpisode, ftData }, + { "episodepart", ffAscii, 250, fiEpisodePart, ftData }, + { "episodelang", ffAscii, 3, fiEpisodeLang, ftData }, + + // tv scraper + + { "scrseriesid", ffInt, 11, fiScrSeriesId, ftData }, + { "scrseriesepisode", ffInt, 11, fiScrSeriesEpisode, ftData }, + { "scrmovieid", ffInt, 11, fiScrMovieId, ftData }, + { "scrsp", ffInt, 11, fiScrSp, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableEvents::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableEvents::indices[] = +{ + // index fields + + { "comptitle", { fiCompTitle, na }, 0 }, + { "source", { fiSource, na }, 0 }, + { "FilerefSource", { fiFileRef, fiSource, na }, 0 }, + { "channelid", { fiChannelId, na }, 0 }, + { "useid", { fiUseId, na }, 0 }, + { "useidchannelid", { fiUseId, fiChannelId, na }, 0 }, + { "updflgupdsp", { fiUpdFlg, fiUpdSp, na }, 0 }, + { "idxsourcechannelid", { fiSource, fiChannelId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// Components +//*************************************************************************** + +cDbService::FieldDef cTableComponents::fields[] = +{ + // name format size index type + + { "eventid", ffUInt, 0, fiEventId, ftPrimary }, + { "channelid", ffAscii, 50, fiChannelId, ftPrimary }, + { "stream", ffInt, 3, fiStream, ftPrimary }, + { "type", ffInt, 3, fiType, ftPrimary }, + { "lang", ffAscii, 8, fiLang, ftPrimary }, + { "description", ffAscii, 100, fiDescription, ftPrimary }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + + { 0 } +}; + +//*************************************************************************** +// File References +//*************************************************************************** + +cDbService::FieldDef cTableFileRefs::fields[] = +{ + // name format size index type + + { "name", ffAscii, 100, fiName, ftPrimary }, + { "source", ffAscii, 10, fiSource, ftPrimary }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + + { "extid", ffAscii, 10, fiExternalId, ftData }, + { "fileref", ffAscii, 100, fiFileRef, ftData }, // name + '-' + tag + { "tag", ffAscii, 100, fiTag, ftData }, + + { 0 } +}; + +cDbService::IndexDef cTableFileRefs::indices[] = +{ + // index fields + + { "SourceFileref", { fiSource, fiFileRef, na }, 0 }, + { "Fileref", { fiFileRef, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// Image Ref Fields +//*************************************************************************** + +cDbService::FieldDef cTableImageRefs::fields[] = +{ + // name format size index type + + { "eventid", ffUInt, 0, fiEventId, ftPrimary }, + { "lfn", ffInt, 0, fiLfn, ftPrimary }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + { "source", ffAscii, 10, fiSource, ftMeta }, + + { "fileref", ffAscii, 100, fiFileRef, ftData }, + { "imagename", ffAscii, 100, fiImgName, ftData }, + + { 0 } +}; + +cDbService::IndexDef cTableImageRefs::indices[] = +{ + // index fields + + { "lfn", { fiLfn, na }, 0 }, + { "name", { fiImgName, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// Image Fields +//*************************************************************************** + +cDbService::FieldDef cTableImages::fields[] = +{ + // name format size index type + + { "imagename", ffAscii, 100, fiImgName, ftPrimary }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + + { "image", ffMlob, 200000, fiImage, ftData }, + + { 0 } +}; + +//*************************************************************************** +// Series Episode Fields +//*************************************************************************** + +cDbService::FieldDef cTableEpisodes::fields[] = +{ + // name format size index type + + // primary key + + { "compname", ffAscii, 100, fiCompName, ftPrimary }, // episode name compressed + { "comppartname", ffAscii, 200, fiCompPartName, ftPrimary }, // part name compressed + { "lang", ffAscii, 10, fiLang, ftPrimary }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + { "link", ffInt, 0, fiLink, ftData }, + + // episode data + + { "shortname", ffAscii, 100, fiShortName, ftData }, + { "episodename", ffAscii, 100, fiEpisodeName, ftData }, // episode name + + // part data + + { "partname", ffAscii, 300, fiPartName, ftData }, // part name + { "season", ffInt, 0, fiSeason, ftData }, + { "part", ffInt, 0, fiPart, ftData }, + { "parts", ffInt, 0, fiParts, ftData }, + { "number", ffInt, 0, fiNumber, ftData }, + + { "extracol1", ffAscii, 250, fiExtraCol1, ftData }, + { "extracol2", ffAscii, 250, fiExtraCol2, ftData }, + { "extracol3", ffAscii, 250, fiExtraCol3, ftData }, + + { "comment", ffAscii, 250, fiComment, ftData }, + + { 0 } +}; + +cDbService::IndexDef cTableEpisodes::indices[] = +{ + // index fields + + { "updsp", { fiUpdSp, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// Channel Map Fields +//*************************************************************************** + +cDbService::FieldDef cTableChannelMap::fields[] = +{ + // name format size index type + + { "extid", ffAscii, 10, fiExternalId, ftPrimary }, + { "channelid", ffAscii, 50, fiChannelId, ftPrimary }, + { "source", ffAscii, 20, fiSource, ftPrimary }, + + { "channelname", ffAscii, 100, fiChannelName, ftData }, + + { "vps", ffInt, 0, fiVps, ftData }, + { "merge", ffInt, 0, fiMerge, ftData }, + { "mergesp", ffInt, 0, fiMergeSp, ftData }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + { "updflg", ffAscii, 1, fiUpdFlg, ftMeta }, + + { 0 } +}; + +cDbService::IndexDef cTableChannelMap::indices[] = +{ + // index fields + + { "sourceExtid", { fiSource, fiExternalId, na }, 0 }, + { "source", { fiSource, na }, 0 }, + { "updflg", { fiUpdFlg, na }, 0 }, + { "idxsourcechannelid", { fiSource, fiChannelId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// VDRs Fields +//*************************************************************************** + +cDbService::FieldDef cTableVdrs::fields[] = +{ + // name format size index type + + { "uuid", ffAscii, 40, fiUuid, ftPrimary }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + + { "name", ffAscii, 100, fiName, ftData }, + { "version", ffAscii, 100, fiVersion, ftData }, + { "dbapi", ffUInt, 0, fiDbApi, ftData }, + { "lastupd", ffInt, 0, fiLastUpdate, ftData }, + { "nextupd", ffInt, 0, fiNextUpdate, ftData }, + { "state", ffAscii, 20, fiState, ftData }, + { "master", ffAscii, 1, fiMaster, ftData }, + { "ip", ffAscii, 20, fiIp, ftData }, + + { 0 } +}; + +//*************************************************************************** +// Parameter Fields +//*************************************************************************** + +cDbService::FieldDef cTableParameters::fields[] = +{ + // name format size index type + + { "owner", ffAscii, 40, fiOwner, ftPrimary }, + { "name", ffAscii, 40, fiName, ftPrimary }, + + { "inssp", ffInt, 0, fiInsSp, ftMeta }, + { "updsp", ffInt, 0, fiUpdSp, ftMeta }, + + { "value", ffAscii, 100, fiValue, ftData }, + + { 0 } +}; + +//*************************************************************************** +// Analyse +//*************************************************************************** + +cDbService::FieldDef cTableAnalyse::fields[] = +{ + // name format size index type + + { "channelid", ffAscii, 50, fiChannelId, ftPrimary }, + { "vdr_masterid", ffUInt, 0, fiVdrMasterId, ftData }, + { "vdr_eventid", ffUInt, 0, fiVdrEventId, ftPrimary }, + + { "vdr_starttime", ffInt, 10, fiVdrStartTime, ftData }, + { "vdr_duration", ffInt, 5, fiVdrDuration, ftData }, + { "vdr_title", ffAscii, 200, fiVdrTitle, ftData }, + { "vdr_shorttext", ffAscii, 300, fiVdrShortText, ftData }, + + { "ext_masterid", ffUInt, 0, fiExtMasterId, ftData }, + { "ext_eventid", ffUInt, 0, fiExtEventId, ftData }, + { "ext_starttime", ffInt, 10, fiExtStartTime, ftData }, + { "ext_duration", ffInt, 5, fiExtDuration, ftData }, + { "ext_title", ffAscii, 200, fiExtTitle, ftData }, + { "ext_shorttext", ffAscii, 300, fiExtShortText, ftData }, + { "ext_episode", ffAscii, 1, fiExtEpisode, ftData }, + { "ext_merge", ffInt, 11, fiExtMerge, ftData }, + { "ext_images", ffAscii, 1, fiExiImages, ftData }, + + { "lvmin", ffInt, 3, fiLvMin, ftData }, + { "rank", ffInt, 5, fiRank, ftData }, + + { 0 } +}; + +cDbService::IndexDef cTableAnalyse::indices[] = +{ + // index fields + + { "vdr_masterid", { fiVdrMasterId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// Snapshot +//*************************************************************************** + +cDbService::FieldDef cTableSnapshot::fields[] = +{ + // name format size index type + + { "channelid", ffAscii, 50, fiChannelId, ftData }, + { "source", ffAscii, 10, fiSource, ftData }, + + { "masterid", ffUInt, 0, fiVdrMasterId, ftData }, + { "eventid", ffUInt, 0, fiEventId, ftData }, + { "useid", ffUInt, 0, fiUseId, ftData }, + + { "starttime", ffInt, 10, fiStartTime, ftData }, + { "duration", ffInt, 5, fiDuration, ftData }, + { "title", ffAscii, 200, fiTitle, ftData }, + { "comptitle", ffAscii, 200, fiCompTitle, ftData }, + { "shorttext", ffAscii, 300, fiShortText, ftData }, + { "compshorttext", ffAscii, 300, fiCompShortText, ftData }, + + { "updsp", ffInt, 10, fiUpdsp, ftData }, + + { "episode", ffAscii, 1, fiEpisode, ftData }, + { "merge", ffInt, 0, fiMerge, ftData }, + { "images", ffAscii, 1, fiImages, ftData }, + + { 0 } +}; + +cDbService::IndexDef cTableSnapshot::indices[] = +{ + // index fields + + { "channelid", { fiChannelId, na }, 0 }, + { "starttimeSource", { fiStartTime, fiSource, na }, 0 }, + + { 0 } +}; + + +//*************************************************************************** +// Series Fields +//*************************************************************************** + +cDbService::FieldDef cTableSeries::fields[] = +{ + // name format size index type + + // primary key + { "series_id", ffUInt, 0, fiSeriesId, ftPrimary }, + //Data + { "series_name", ffAscii, 200, fiSeriesName, ftData }, + { "series_last_scraped", ffUInt, 0, fiSeriesLastScraped, ftData }, + { "series_last_updated", ffUInt, 0, fiSeriesLastUpdated, ftData }, + { "series_overview", ffText, 10000, fiSeriesOverview, ftData }, + { "series_firstaired", ffAscii, 50, fiSeriesFirstAired, ftData }, + { "series_network", ffAscii, 100, fiSeriesNetwork, ftData }, + { "series_imdb_id", ffAscii, 20, fiSeriesIMDBId, ftData }, + { "series_genre", ffAscii, 100, fiSeriesGenre, ftData }, + { "series_rating", ffFloat, 31, fiSeriesRating, ftData }, + { "series_status", ffAscii, 50, fiSeriesStatus, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableSeries::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableSeries::indices[] = +{ + // index fields + + { 0 } +}; + + +//*************************************************************************** +// SeriesEpisode Fields +//*************************************************************************** + +cDbService::FieldDef cTableSeriesEpisode::fields[] = +{ + // name format size index type + + // primary key + { "episode_id", ffUInt, 0, fiEpisodeId, ftPrimary }, + //Data + { "episode_number", ffUInt, 0, fiEpisodeNumber, ftData }, + { "season_number", ffUInt, 0, fiSeasonNumber, ftData }, + { "episode_name", ffAscii, 300, fiEpisodeName, ftData }, + { "episode_overview", ffText, 10000, fiEpisodeOverview, ftData }, + { "episode_firstaired", ffAscii, 20, fiEpisodeFirstAired, ftData }, + { "episode_gueststars", ffAscii, 1000, fiEpisodeGuestStars, ftData }, + { "episode_rating", ffFloat, 31, fiEpisodeRating, ftData }, + { "episode_last_updated", ffUInt, 0, fiEpisodeLastUpdated, ftData }, + { "series_id", ffUInt, 0, fiSeriesId, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableSeriesEpisode::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableSeriesEpisode::indices[] = +{ + // index fields + { "series_id", { fiSeriesId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// SeriesMedia Fields +//*************************************************************************** + +cDbService::FieldDef cTableSeriesMedia::fields[] = +{ + // name format size index type + + // primary key + { "series_id", ffUInt, 0, fiSeriesId, ftPrimary }, + { "season_number", ffUInt, 0, fiSeasonNumber, ftPrimary }, + { "episode_id", ffUInt, 0, fiEpisodeId, ftPrimary }, + { "actor_id", ffUInt, 0, fiActorId, ftPrimary }, + { "media_type", ffUInt, 0, fiMediaType, ftPrimary }, + //Data + { "media_url", ffAscii, 100, fiMediaUrl, ftData }, + { "media_width", ffUInt, 0, fiMediaWidth, ftData }, + { "media_height", ffUInt, 0, fiMediaHeight, ftData }, + { "media_rating", ffFloat, 31, fiMediaRating, ftData }, + { "media_content", ffMlob, 1000000, fiMediaContent, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableSeriesMedia::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableSeriesMedia::indices[] = +{ + // index fields + { "series_id", { fiSeriesId, na }, 0 }, + { "season_number", { fiSeasonNumber, na }, 0 }, + { "episode_id", { fiEpisodeId, na }, 0 }, + { "actor_id", { fiActorId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// SeriesActor Fields +//*************************************************************************** + +cDbService::FieldDef cTableSeriesActor::fields[] = +{ + // name format size index type + + // primary key + { "actor_id", ffUInt, 0, fiActorId, ftPrimary }, + //Data + { "actor_name", ffAscii, 100, fiActorName, ftData }, + { "actor_role", ffAscii, 500, fiActorRole, ftData }, + { "actor_sortorder", ffUInt, 0, fiSortOrder, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableSeriesActor::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableSeriesActor::indices[] = +{ + // index fields + + { 0 } +}; + +//*************************************************************************** +// Movie Fields +//*************************************************************************** + +cDbService::FieldDef cTableMovies::fields[] = +{ + // name format size index type + + // primary key + { "movie_id", ffUInt, 0, fiMovieId, ftPrimary }, + //Data + { "movie_title", ffAscii, 300, fiTitle, ftData }, + { "movie_original_title", ffAscii, 300, fiOriginalTitle, ftData }, + { "movie_tagline", ffAscii, 1000, fiTagline, ftData }, + { "movie_overview", ffText, 5000, fiOverview, ftData }, + { "movie_adult", ffUInt, 0, fiIsAdult, ftData }, + { "movie_collection_id", ffUInt, 0, fiCollectionId, ftData }, + { "movie_collection_name", ffAscii, 300, fiCollectionName, ftData }, + { "movie_budget", ffUInt, 0, fiBudget, ftData }, + { "movie_revenue", ffUInt, 0, fiRevenue, ftData }, + { "movie_genres", ffAscii, 500, fiGenres, ftData }, + { "movie_homepage", ffAscii, 300, fiHomepage, ftData }, + { "movie_release_date", ffAscii, 20, fiReleaaseDate, ftData }, + { "movie_runtime", ffUInt, 0, fiRuntime, ftData }, + { "movie_popularity", ffFloat, 31, fiPopularity, ftData }, + { "movie_vote_average", ffFloat, 31, fiVoteAverage, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableMovies::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableMovies::indices[] = +{ + // index fields + { "movie_id", { fiMovieId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// MovieActor Fields +//*************************************************************************** + +cDbService::FieldDef cTableMovieActor::fields[] = +{ + // name format size index type + + // primary key + { "actor_id", ffUInt, 0, fiActorId, ftPrimary }, + //Data + { "actor_name", ffAscii, 300, fiActorName, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableMovieActor::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableMovieActor::indices[] = +{ + // index fields + { "actor_id", { fiActorId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// MovieActors Fields +//*************************************************************************** + +cDbService::FieldDef cTableMovieActors::fields[] = +{ + // name format size index type + + // primary key + { "movie_id", ffUInt, 0, fiMovieId, ftPrimary }, + { "actor_id", ffUInt, 0, fiActorId, ftPrimary }, + //Data + { "actor_role", ffAscii, 300, fiRole, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableMovieActors::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableMovieActors::indices[] = +{ + // index fields + { "movie_id", { fiMovieId, na }, 0 }, + { "actor_id", { fiActorId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// cTableMovieMedia Fields +//*************************************************************************** + +cDbService::FieldDef cTableMovieMedia::fields[] = +{ + // name format size index type + + // primary key + { "movie_id", ffUInt, 0, fiMovieId, ftPrimary }, + { "actor_id", ffUInt, 0, fiActorId, ftPrimary }, + { "media_type", ffUInt, 0, fiMediaType, ftPrimary }, + //Data + { "media_url", ffAscii, 100, fiMediaUrl, ftData }, + { "media_width", ffUInt, 0, fiMediaWidth, ftData }, + { "media_height", ffUInt, 0, fiMediaHeight, ftData }, + { "media_content", ffMlob, 1000000, fiMediaContent, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableMovieMedia::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableMovieMedia::indices[] = +{ + // index fields + { "movie_id", { fiMovieId, na }, 0 }, + { "actor_id", { fiActorId, na }, 0 }, + + { 0 } +}; + +//*************************************************************************** +// cTableRecordings Fields +//*************************************************************************** + +cDbService::FieldDef cTableRecordings::fields[] = +{ + // name format size index type + + // primary key + { "uuid", ffAscii, 40, fiUuid, ftPrimary }, + { "rec_path", ffAscii, 200, fiRecPath, ftPrimary }, + { "rec_start", ffUInt, 0, fiRecStart, ftPrimary }, + + //Data + { "event_id", ffUInt, 0, fiEventId, ftData }, + { "channel_id", ffAscii, 50, fiChannelId, ftData }, + { "scrapinfo_movie_id", ffUInt, 0, fiScrapInfoMovieId, ftData }, + { "scrapinfo_series_id", ffUInt, 0, fiScrapInfoSeriesId, ftData }, + { "scrapinfo_episode_id", ffUInt, 0, fiScrapInfoEpisodeId, ftData }, + { "scrap_new", ffUInt, 0, fiScrapNew, ftData }, + { "rec_title", ffAscii, 200, fiRecTitle, ftData }, + { "rec_subtitle", ffAscii, 500, fiRecSubTitle, ftData }, + { "rec_duration", ffUInt, 0, fiRecDuration, ftData }, + { "movie_id", ffUInt, 0, fiMovieId, ftData }, + { "series_id", ffUInt, 0, fiSeriesId, ftData }, + { "episode_id", ffUInt, 0, fiEpisodeId, ftData }, + + { 0 } +}; + +cDbService::FieldDef* cTableRecordings::toField(const char* name) +{ + for (int i = 0; i < fiCount; i++) + if (strcmp(fields[i].name, name) == 0) + return &fields[i]; + + tell(0, "Request for unexpected field '%s', ignoring", name); + + return 0; +} + +cDbService::IndexDef cTableRecordings::indices[] = +{ + // index fields + { "uuid", { fiUuid, na }, 0 }, + { "rec_path", { fiRecPath, na }, 0 }, + { "rec_start", { fiRecStart, na }, 0 }, + + { 0 } +};
\ No newline at end of file diff --git a/lib/tabledef.h b/lib/tabledef.h new file mode 100644 index 0000000..bd2b043 --- /dev/null +++ b/lib/tabledef.h @@ -0,0 +1,832 @@ +/* + * tabledef.h + * + * See the README file for copyright information and how to reach the author. + * + */ + +#ifndef __TABLEDEF_H +#define __TABLEDEF_H + +#include "db.h" + +//*************************************************************************** +// cEpgdState +//*************************************************************************** + +class cEpgdState +{ + public: + + enum State + { + esUnknown = na, + + esInit, + esStandby, + esStopped, + + esBusy, + esBusyEvents = esBusy, + esBusyMatch, + + esBusyImages, + + esBusyScraping, + + esCount + }; + + static const char* toName(State s); + static State toState(const char* name); + static int isValid(State s) { return s > esUnknown && s < esCount; } + + static const char* states[]; +}; + +typedef cEpgdState Es; + +//*************************************************************************** +// cUpdateState +//*************************************************************************** + +class cUpdateState +{ + public: + + enum State + { + // add to VDRs EPG + + usActive = 'A', + usLink = 'L', + usPassthrough = 'P', + + // remove from VDRs EPG + + usChanged = 'C', + usDelete = 'D', + usRemove = 'R', + + // don't care for VDRs EPG + + usInactive = 'I', + usTarget = 'T' + }; + + // get lists for SQL 'in' statements + + static const char* getDeletable() { return "'A','L','P','R','I'"; } + static const char* getNeeded() { return "'A','L','P','C','D','R'"; } + + // checks fpr c++ code + + static int isNeeded(char c) { return strchr("ALPCDR", c) != 0; } + static int isRemove(char c) { return strchr("CDR", c) != 0; } + +}; + +typedef cUpdateState Us; + +//*************************************************************************** +// class cTableFileRef +//*************************************************************************** + +class cTableFileRefs : public cDbTable +{ + public: + + cTableFileRefs(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "fileref"; } + + enum FieldIndex + { + fiName, + fiSource, + + fiInsSp, + fiUpdSp, + + fiExternalId, + fiFileRef, + fiTag, + + fiCount + }; + + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableImageRef +//*************************************************************************** + +class cTableImageRefs : public cDbTable +{ + public: + + cTableImageRefs(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "imagerefs"; } + + enum FieldIndex + { + fiEventId, + fiLfn, + + fiInsSp, + fiUpdSp, + fiSource, + fiFileRef, + + fiImgName, + + fiCount + }; + + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableImage +//*************************************************************************** + +class cTableImages : public cDbTable +{ + public: + + cTableImages(cDbConnection* aConnection) + : cDbTable(aConnection, fields) { } + + virtual const char* TableName() { return "images"; } + + enum FieldIndex + { + fiImgName, + + fiInsSp, + fiUpdSp, + fiImage, + + fiCount + }; + + static FieldDef fields[]; +}; + +//*************************************************************************** +// class cTableEvent +//*************************************************************************** + +class cTableEvents : public cDbTable +{ + public: + + cTableEvents(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "events"; } + + enum FieldIndex + { + fiEventId, + fiChannelId, + + fiMasterId, + fiUseId, + + fiSource, + fiFileRef, + fiInsSp, + fiUpdSp, + fiUpdFlg, // update flag + fiDelFlg, // deletion flag + + fiTableId, + fiVersion, + fiTitle, + fiCompTitle, // compressed (without whitespace and special characters) + fiShortText, + fiCompShortText, // compressed (without whitespace and special characters) + fiLongDescription, + fiStartTime, + fiDuration, + fiParentalRating, + fiVps, + fiDescription, // view field, not stored! + + fiShortDescription, + fiActor, + fiAudio, + fiCategory, + fiCountry, + fiDirector, + fiFlags, + fiGenre, + fiInfo, + fiMusic, + fiProducer, + fiScreenplay, + fiShortreview, + fiTipp, + fiTopic, + fiYear, + fiRating, + fiFsk, + fiMovieid, + fiModerator, + fiOther, + fiGuest, + fiCamera, + + fiExtEpNum, + fiImageCount, + + fiEpisode, + fiEpisodePart, + fiEpisodeLang, + + fiScrSeriesId, + fiScrSeriesEpisode, + fiScrMovieId, + fiScrSp, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableComponent +//*************************************************************************** + +class cTableComponents : public cDbTable +{ + public: + + cTableComponents(cDbConnection* aConnection) + : cDbTable(aConnection, fields) { } + + virtual const char* TableName() { return "components"; } + + enum FieldIndex + { + fiEventId, + fiChannelId, + fiStream, + fiType, + fiLang, + fiDescription, + + fiInsSp, + fiUpdSp + }; + + static FieldDef fields[]; +}; + +//*************************************************************************** +// class cTableEpisode +//*************************************************************************** + +class cTableEpisodes : public cDbTable +{ + public: + + cTableEpisodes(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "episodes"; } + + + enum FieldIndex + { + // primary key + + fiCompName, // compressed name (without whitespace and special characters) + fiCompPartName, // " " " + fiLang, // "de", "en", ... + + fiInsSp, + fiUpdSp, + fiLink, + + // episode data + + fiShortName, + fiEpisodeName, // episode name (fielname without path and suffix) + + // part data + + fiPartName, // part name + fiSeason, + fiPart, + fiParts, + fiNumber, + + fiExtraCol1, + fiExtraCol2, + fiExtraCol3, + fiComment, + + fiCount + }; + + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableChannelMap +//*************************************************************************** + +class cTableChannelMap : public cDbTable +{ + public: + + cTableChannelMap(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "channelmap"; } + + enum FieldIndex + { + fiExternalId, // + fiChannelId, // + fiSource, + + fiChannelName, + + fiVps, + fiMerge, + fiMergeSp, + + fiInsSp, + fiUpdSp, + fiUpdFlg, + + fiCount + }; + + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableVdr +//*************************************************************************** + +class cTableVdrs : public cDbTable +{ + public: + + cTableVdrs(cDbConnection* aConnection) + : cDbTable(aConnection, fields) { } + + virtual const char* TableName() { return "vdrs"; } + + enum FieldIndex + { + fiUuid, + + fiInsSp, + fiUpdSp, + + fiName, + fiVersion, + fiDbApi, + fiLastUpdate, + fiNextUpdate, + fiState, + fiMaster, + fiIp, + + fiCount + }; + + static FieldDef fields[]; +}; + +//*************************************************************************** +// class cTableParameters +//*************************************************************************** + +class cTableParameters : public cDbTable +{ + public: + + cTableParameters(cDbConnection* aConnection) + : cDbTable(aConnection, fields) { } + + virtual const char* TableName() { return "parameters"; } + + enum FieldIndex + { + fiOwner, + fiName, + + fiInsSp, + fiUpdSp, + + fiValue, + + fiCount + }; + + static FieldDef fields[]; +}; + +//*************************************************************************** +// cTableAnalyse +//*************************************************************************** + +class cTableAnalyse : public cDbTable +{ + public: + + cTableAnalyse(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "analyse"; } + + enum FieldIndex + { + fiChannelId, + fiVdrMasterId, + fiVdrEventId, + + fiVdrStartTime, + fiVdrDuration, + fiVdrTitle, + fiVdrShortText, + + fiExtMasterId, + fiExtEventId, + fiExtStartTime, + fiExtDuration, + fiExtTitle, + fiExtShortText, + fiExtEpisode, + fiExtMerge, + fiExiImages, + + fiLvMin, + fiRank, + + fiCount + }; + + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// cTableSnapshot +//*************************************************************************** + +class cTableSnapshot : public cDbTable +{ + public: + + cTableSnapshot(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "snapshot"; } + + enum FieldIndex + { + fiChannelId, + fiSource, + fiVdrMasterId, + fiEventId, + fiUseId, + fiStartTime, + fiDuration, + fiTitle, + fiCompTitle, + fiShortText, + fiCompShortText, + fiUpdsp, + fiEpisode, + fiMerge, + fiImages, + + fiCount + }; + + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableSeries +//*************************************************************************** + +class cTableSeries : public cDbTable +{ + public: + + cTableSeries(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "series"; } + + enum FieldIndex + { + fiSeriesId, + + fiSeriesName, + fiSeriesLastScraped, + fiSeriesLastUpdated, + fiSeriesOverview, + fiSeriesFirstAired, + fiSeriesNetwork, + fiSeriesIMDBId, + fiSeriesGenre, + fiSeriesRating, + fiSeriesStatus, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableSeriesEpisode +//*************************************************************************** + +class cTableSeriesEpisode : public cDbTable +{ + public: + + cTableSeriesEpisode(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "series_episode"; } + + enum FieldIndex + { + fiEpisodeId, + + fiEpisodeNumber, + fiSeasonNumber, + fiEpisodeName, + fiEpisodeOverview, + fiEpisodeFirstAired, + fiEpisodeGuestStars, + fiEpisodeRating, + fiEpisodeLastUpdated, + fiSeriesId, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableSeriesMedia +//*************************************************************************** + +class cTableSeriesMedia : public cDbTable +{ + public: + + cTableSeriesMedia(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "series_media"; } + + enum FieldIndex + { + fiSeriesId, + fiSeasonNumber, + fiEpisodeId, + fiActorId, + fiMediaType, + + fiMediaUrl, + fiMediaWidth, + fiMediaHeight, + fiMediaRating, + fiMediaContent, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableSeriesActor +//*************************************************************************** + +class cTableSeriesActor : public cDbTable +{ + public: + + cTableSeriesActor(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "series_actor"; } + + enum FieldIndex + { + fiActorId, + + fiActorName, + fiActorRole, + fiSortOrder, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableMovies +//*************************************************************************** + +class cTableMovies : public cDbTable +{ + public: + + cTableMovies(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "movie"; } + + enum FieldIndex + { + fiMovieId, + + fiTitle, + fiOriginalTitle, + fiTagline, + fiOverview, + fiIsAdult, + fiCollectionId, + fiCollectionName, + fiBudget, + fiRevenue, + fiGenres, + fiHomepage, + fiReleaaseDate, + fiRuntime, + fiPopularity, + fiVoteAverage, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableMovieActor +//*************************************************************************** + +class cTableMovieActor : public cDbTable +{ + public: + + cTableMovieActor(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "movie_actor"; } + + enum FieldIndex + { + fiActorId, + + fiActorName, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableMovieActors +//*************************************************************************** + +class cTableMovieActors : public cDbTable +{ + public: + + cTableMovieActors(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "movie_actors"; } + + enum FieldIndex + { + fiMovieId, + fiActorId, + + fiRole, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableMovieMedia +//*************************************************************************** + +class cTableMovieMedia : public cDbTable +{ + public: + + cTableMovieMedia(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "movie_media"; } + + enum FieldIndex + { + fiMovieId, + fiActorId, + fiMediaType, + + fiMediaUrl, + fiMediaWidth, + fiMediaHeight, + fiMediaContent, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +//*************************************************************************** +// class cTableRecordings +//*************************************************************************** + +class cTableRecordings : public cDbTable +{ + public: + + cTableRecordings(cDbConnection* aConnection) + : cDbTable(aConnection, fields, indices) { } + + virtual const char* TableName() { return "recordings"; } + + enum FieldIndex + { + fiUuid, + fiRecPath, + fiRecStart, + + fiEventId, + fiChannelId, + fiScrapInfoMovieId, + fiScrapInfoSeriesId, + fiScrapInfoEpisodeId, + fiScrapNew, + fiRecTitle, + fiRecSubTitle, + fiRecDuration, + fiMovieId, + fiSeriesId, + fiEpisodeId, + + fiCount + }; + + static FieldDef* toField(const char* name); + static FieldDef fields[]; + static IndexDef indices[]; +}; + +#endif //__TABLEDEF_H diff --git a/moviedbmovie.c b/moviedbmovie.c new file mode 100644 index 0000000..c1485c3 --- /dev/null +++ b/moviedbmovie.c @@ -0,0 +1,130 @@ +#define __STL_CONFIG_H
+#include "lib/common.h"
+#include "moviedbmovie.h"
+
+using namespace std;
+
+cMovieDbMovie::cMovieDbMovie(void) {
+ title = "";
+ originalTitle = "";
+ tagline = "";
+ overview = "";
+ adult = false;
+ collectionID = 0;
+ collectionName = "";
+ budget = 0;
+ revenue = 0;
+ genres = "";
+ homepage = "";
+ imdbid = "";
+ releaseDate = "";
+ runtime = 0;
+ popularity = 0.0;
+ voteAverage = 0.0;
+}
+
+cMovieDbMovie::~cMovieDbMovie() {
+ for (map<int, cMovieActor*>::iterator it = actors.begin(); it != actors.end(); it++) {
+ cMovieActor *a = (cMovieActor*)it->second;
+ delete a;
+ }
+ for (map<int, cMovieMedia*>::iterator it = medias.begin(); it != medias.end(); it++) {
+ cMovieMedia *m = (cMovieMedia*)it->second;
+ delete m;
+ }
+}
+
+void cMovieDbMovie::InsertMedia(cMovieMedia *media) {
+ medias.insert(pair<int, cMovieMedia*>(media->mediaType, media));
+}
+
+void cMovieDbMovie::InsertActor(cMovieActor *actor) {
+ cMovieMedia *m = new cMovieMedia();
+ actor->actorThumb = m;
+ actors.insert(pair<int, cMovieActor*>(actor->id, actor));
+}
+
+vector<int> cMovieDbMovie::GetActorIDs(void) {
+ vector<int> IDs;
+ for (map<int, cMovieActor*>::iterator it = actors.begin(); it != actors.end(); it++) {
+ cMovieActor *a = it->second;
+ IDs.push_back(a->id);
+ }
+ return IDs;
+}
+
+void cMovieDbMovie::SetActorThumbSize(int actorId, int imgWidth, int imgHeight) {
+ map<int, cMovieActor*>::iterator hit = actors.find(actorId);
+ if (hit != actors.end()) {
+ cMovieActor *a = hit->second;
+ if (!a->actorThumb)
+ return;
+ cMovieMedia *thumb = a->actorThumb;
+ thumb->width = imgWidth;
+ thumb->height = imgHeight;
+ thumb->mediaType = mmActorThumb;
+ }
+}
+
+void cMovieDbMovie::SetActorPath(int actorId, string path) {
+ map<int, cMovieActor*>::iterator hit = actors.find(actorId);
+ if (hit != actors.end()) {
+ cMovieActor *a = hit->second;
+ if (!a->actorThumb)
+ return;
+ a->actorThumb->path = path;
+ }
+}
+
+bool cMovieDbMovie::GetMedia(mediaMovies mediatype, cTvMedia *p) {
+ map<int, cMovieMedia*>::iterator hit = medias.find(mediatype);
+ if (hit == medias.end())
+ return false;
+ cMovieMedia *pStored = hit->second;
+ p->path = pStored->path;
+ p->width = pStored->width;
+ p->height = pStored->height;
+ return true;
+}
+
+void cMovieDbMovie::GetActors(vector<cActor> *a) {
+ for (map<int, cMovieActor*>::iterator it = actors.begin(); it != actors.end(); it++) {
+ cMovieActor *aStored = it->second;
+ cActor act;
+ act.name = aStored->name;
+ act.role = aStored->role;
+ if (aStored->actorThumb) {
+ act.actorThumb.width = aStored->actorThumb->width;
+ act.actorThumb.height = aStored->actorThumb->height;
+ act.actorThumb.path = aStored->actorThumb->path;
+ }
+ a->push_back(act);
+ }
+}
+
+void cMovieDbMovie::Dump(void) {
+ tell(0, "--------------------------- Movie Info ----------------------------------");
+ tell(0, "title %s, ID: %d", title.c_str(), id);
+ tell(0, "Orig. Title: %s", originalTitle.c_str());
+ tell(0, "Tagline: %s", tagline.c_str());
+ tell(0, "Overview: %s", overview.c_str());
+ tell(0, "Collection: %s", collectionName.c_str());
+ tell(0, "Genre: %s", genres.c_str());
+ tell(0, "Popularity: %f", popularity);
+ tell(0, "--------------------------- Actors ----------------------------------");
+ for (map<int, cMovieActor*>::iterator it = actors.begin(); it != actors.end(); it++) {
+ cMovieActor *a = it->second;
+ tell(0, "Actor %d, Name: %s, Role %s", a->id, a->name.c_str(), a->role.c_str());
+ if (a->actorThumb) {
+ tell(0, "thmbWidth %d, thmbHeight %d", a->actorThumb->width, a->actorThumb->height);
+ tell(0, "Path %s", a->actorThumb->path.c_str());
+ }
+ }
+ tell(0, "--------------------------- Media ----------------------------------");
+ for (map<int, cMovieMedia*>::iterator it = medias.begin(); it != medias.end(); it++) {
+ cMovieMedia *m = it->second;
+ tell(0, "Media %d", m->mediaType);
+ tell(0, "width %d, height %d", m->width, m->height);
+ tell(0, "Path %s", m->path.c_str());
+ }
+}
\ No newline at end of file diff --git a/moviedbmovie.h b/moviedbmovie.h new file mode 100644 index 0000000..d53f7b0 --- /dev/null +++ b/moviedbmovie.h @@ -0,0 +1,98 @@ +#ifndef __TVSCRAPER_MOVIEDBMOVIE_H
+#define __TVSCRAPER_MOVIEDBMOVIE_H
+
+#include <iostream>
+#include <string>
+#include <vector>
+#include <map>
+#include <set>
+#include <utility>
+#include <algorithm>
+#include "services.h"
+
+using namespace std;
+
+enum mediaMovies {
+ mmPoster,
+ mmFanart,
+ mmCollectionPoster,
+ mmCollectionFanart,
+ mmActorThumb,
+ mmPosterThumb,
+};
+
+// --- cMovieMedia -------------------------------------------------------------
+class cMovieMedia {
+public:
+ cMovieMedia(void) {
+ path = "";
+ mediaType = mmPoster;
+ width = 0;
+ height = 0;
+ };
+ ~cMovieMedia(void) {
+ };
+ string path;
+ int mediaType;
+ int width;
+ int height;
+};
+
+// --- cMovieActor -------------------------------------------------------------
+class cMovieActor {
+public:
+ cMovieActor(void) {
+ id = 0;
+ name = "";
+ role = "";
+ actorThumb = NULL;
+ };
+ ~cMovieActor(void) {
+ if (actorThumb)
+ delete actorThumb;
+ };
+ int id;
+ string name;
+ string role;
+ cMovieMedia *actorThumb;
+};
+
+// --- cMovieDbMovie -------------------------------------------------------------
+
+class cMovieDbMovie {
+private:
+ map<int, cMovieActor*> actors;
+ map<int, cMovieMedia*> medias;
+public:
+ cMovieDbMovie(void);
+ virtual ~cMovieDbMovie(void);
+ int id;
+ string title;
+ string originalTitle;
+ string tagline;
+ string overview;
+ bool adult;
+ int collectionID;
+ string collectionName;
+ int budget;
+ int revenue;
+ string genres;
+ string homepage;
+ string imdbid;
+ string releaseDate;
+ int runtime;
+ float popularity;
+ float voteAverage;
+ void InsertActor(cMovieActor *actor);
+ void InsertMedia(cMovieMedia *media);
+ vector<int> GetActorIDs(void);
+ void SetActorThumbSize(int actorId, int imgWidth, int imgHeight);
+ void SetActorPath(int actorId, string path);
+ //Getter for Serivice Calls
+ bool GetMedia(mediaMovies mediatype, cTvMedia *p);
+ void GetActors(vector<cActor> *a);
+ void Dump();
+};
+
+
+#endif //__TVSCRAPER_TVDBSERIES_H
diff --git a/po/de_DE.po b/po/de_DE.po new file mode 100644 index 0000000..ed0652d --- /dev/null +++ b/po/de_DE.po @@ -0,0 +1,65 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: vdr-scraper2vdr 0.0.1\n" +"Report-Msgid-Bugs-To: <see README>\n" +"POT-Creation-Date: 2014-03-30 14:37+0200\n" +"PO-Revision-Date: 2014-03-30 18:30+0100\n" +"Last-Translator: 3PO\n" +"Language-Team: German <vdr@linuxtv.org>\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "Update Scraper Information from Database" +msgstr "Filme und Serien aus Datenbank aktualisieren" + +msgid "Update Scraper Recordings Information from Database" +msgstr "Aufnahmen aus Datenbank aktualisieren" + +msgid "Scan for new recordings in video directory" +msgstr "Videoverzeichnis nach neuen Aufnahmen scannen" + +msgid "Scan for new or updated scrapinfo files" +msgstr "Nach neuen oder geänderten scrapinfo Dateien suchen" + +msgid "Cleanup Recordings in Database" +msgstr "Aufnahmen in Datenbank bereinigen" + +msgid "Updating Scraper EPG Information from Database" +msgstr "Filme und Serien werden aus Datenbank aktualisieren" + +msgid "Updating Scraper Recordings Information from Database" +msgstr "Aufnahmen werden aus Datenbank aktualisieren" + +msgid "Scanning for new recordings in video directory" +msgstr "Scanne Videoverzeichnis nach neuen Aufnahmen" + +msgid "Scanning for new or updated scrapinfo files" +msgstr "Suche nach neuen oder geänderten scrapinfo Dateien" + +msgid "Cleaning up Recordings in Database" +msgstr "Bereinige Aufnahmen in Datenbank" + +msgid "Show Main Menu Entry" +msgstr "Hauptmenüeintrag anzeigen" + +msgid "MySQL Host" +msgstr "MySQL Host" + +msgid "MySQL Port" +msgstr "MySQL Port" + +msgid "MySQL Database Name" +msgstr "MySQL Datenbannk Name" + +msgid "MySQL User" +msgstr "MySQL Benutzer" + +msgid "MySQL Password" +msgstr "MySQL Passwort" + diff --git a/scraper2vdr.c b/scraper2vdr.c new file mode 100644 index 0000000..332c3df --- /dev/null +++ b/scraper2vdr.c @@ -0,0 +1,249 @@ +/* + * scraper2vdr.c: A plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + * $Id$ + */ + +#include "scraper2vdr.h" + +#if defined (APIVERSNUM) && (APIVERSNUM < 10600) +# error VDR API versions < 1.6.0 are not supported ! +#endif + +//*************************************************************************** +// Plugin Main Menu +//*************************************************************************** + +class cScraper2VdrPluginMenu : public cOsdMenu { + public: + cScraper2VdrPluginMenu(const char* title, cUpdate *update); + virtual ~cScraper2VdrPluginMenu() { }; + virtual eOSState ProcessKey(eKeys key); + protected: + cUpdate *update; +}; + +cScraper2VdrPluginMenu::cScraper2VdrPluginMenu(const char* title, cUpdate *update) : cOsdMenu(title) { + this->update = update; + Clear(); + cOsdMenu::Add(new cOsdItem(tr("Update Scraper Information from Database"))); + cOsdMenu::Add(new cOsdItem(tr("Update Scraper Recordings Information from Database"))); + cOsdMenu::Add(new cOsdItem(tr("Scan for new recordings in video directory"))); + cOsdMenu::Add(new cOsdItem(tr("Scan for new or updated scrapinfo files"))); + cOsdMenu::Add(new cOsdItem(tr("Cleanup Recordings in Database"))); + SetHelp(0, 0, 0,0); + Display(); +} + +//*************************************************************************** +// Process Key +//*************************************************************************** + +eOSState cScraper2VdrPluginMenu::ProcessKey(eKeys key) { + eOSState state = cOsdMenu::ProcessKey(key); + + if (state != osUnknown) + return state; + + switch (key) { + case kOk: { + if (Current() == 0) { + Skins.Message(mtInfo, tr("Updating Scraper EPG Information from Database")); + update->ForceUpdate(); + } else if (Current() == 1) { + Skins.Message(mtInfo, tr("Updating Scraper Recordings Information from Database")); + update->ForceRecordingUpdate(); + } else if (Current() == 2) { + Skins.Message(mtInfo, tr("Scanning for new recordings in video directory")); + update->ForceVideoDirUpdate(); + } else if (Current() == 3) { + Skins.Message(mtInfo, tr("Scanning for new or updated scrapinfo files")); + update->ForceScrapInfoUpdate(); + } else if (Current() == 4) { + Skins.Message(mtInfo, tr("Cleaning up Recordings in Database")); + update->TriggerCleanRecordingsDB(); + } + return osEnd; + } + + default: + break; + } + return state; +} + +//*************************************************************************** +// cPluginScraper2vdr +//*************************************************************************** + +cPluginScraper2vdr::cPluginScraper2vdr(void) { + cDbConnection::init(); +} + +cPluginScraper2vdr::~cPluginScraper2vdr() { + delete update; + delete scrapManager; + cDbConnection::exit(); +} + +const char *cPluginScraper2vdr::CommandLineHelp(void) { + return + " -i <IMAGEDIR>, --imagedir=<IMAGEDIR> Set directory where images are stored\n" + " -m <MODE>, --mode=<MODE> mode can be client or headless, see README\n"; +} + +bool cPluginScraper2vdr::ProcessArgs(int argc, char *argv[]) { + static const struct option long_options[] = { + { "imagedir", required_argument, NULL, 'i' }, + { "mode", required_argument, NULL, 'm' }, + { 0, 0, 0, 0 } + }; + int c; + while ((c = getopt_long(argc, argv, "i:m:", long_options, NULL)) != -1) { + switch (c) { + case 'i': + config.SetImageDir(optarg); + break; + case 'm': + config.SetMode(optarg); + break; + default: + return false; + } + } + return true; +} + +bool cPluginScraper2vdr::Initialize(void) { + config.SetUuid(this); + config.SetDefaultImageDir(); + scrapManager = new cScrapManager(); + update = new cUpdate(scrapManager); + return true; +} + +bool cPluginScraper2vdr::Start(void) { + update->Start(); + return true; +} + +void cPluginScraper2vdr::Stop(void) { + update->Stop(); +} + +void cPluginScraper2vdr::Housekeeping(void) { +} + +void cPluginScraper2vdr::MainThreadHook(void) { +} + +cString cPluginScraper2vdr::Active(void) { + return NULL; +} + +time_t cPluginScraper2vdr::WakeupTime(void) { + return 0; +} + +cOsdObject *cPluginScraper2vdr::MainMenuAction(void) { + if (config.mainMenuEntry == 1) + return new cScraper2VdrPluginMenu("Scraper2Vdr", update); + return NULL; +} + +cMenuSetupPage *cPluginScraper2vdr::SetupMenu(void) { + return new cScraper2VdrSetup(update); +} + +bool cPluginScraper2vdr::SetupParse(const char *Name, const char *Value) { + return config.SetupParse(Name, Value); +} + +bool cPluginScraper2vdr::Service(const char *Id, void *Data) { + if (Data == NULL) + return false; + if (strcmp(Id, "GetEventType") == 0) { + ScraperGetEventType* call = (ScraperGetEventType*) Data; + if (!call->event && !call->recording) + return false; + return scrapManager->GetEventType(call); + } + + if (strcmp(Id, "GetSeries") == 0) { + cSeries* call = (cSeries*) Data; + if (call->seriesId == 0) + return false; + return scrapManager->GetSeries(call); + } + + if (strcmp(Id, "GetMovie") == 0) { + cMovie* call = (cMovie*) Data; + if (call->movieId == 0) + return false; + return scrapManager->GetMovie(call); + } + + if (strcmp(Id, "GetPosterBanner") == 0) { + ScraperGetPosterBanner* call = (ScraperGetPosterBanner*) Data; + if (!call->event) + return false; + return scrapManager->GetPosterBanner(call); + } + + if (strcmp(Id, "GetPoster") == 0) { + ScraperGetPoster* call = (ScraperGetPoster*) Data; + if (!call->event && !call->recording) + return false; + return scrapManager->GetPoster(call); + } + + if (strcmp(Id, "GetPosterThumb") == 0) { + ScraperGetPosterThumb* call = (ScraperGetPosterThumb*) Data; + if (!call->event && !call->recording) + return false; + return scrapManager->GetPosterThumb(call); + } + + return false; +} + +const char **cPluginScraper2vdr::SVDRPHelpPages(void) { + static const char *HelpPages[] = { + "UPDT\n" + " Load all series and movies for events from database.", + "UPDR\n" + " Load recordings from database.", + "SCVD\n" + " Trigger scan fornew recordings in video directory.", + "SCSI\n" + " Trigger scan for scrapinfo files in video directory.", + "CRDB\n" + " Trigger cleanup of recordings database.", + 0 + }; + return HelpPages; +} + +cString cPluginScraper2vdr::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) { + if (strcasecmp(Command, "UPDT") == 0) { + update->ForceUpdate(); + return "SCRAPER2VDR full update from database forced."; + } else if (strcasecmp(Command, "UPDR") == 0) { + update->ForceRecordingUpdate(); + return "SCRAPER2VDR scanning of recordings in database triggered."; + } else if (strcasecmp(Command, "SCVD") == 0) { + update->ForceVideoDirUpdate(); + return "SCRAPER2VDR scan for new recordings in video dir triggered."; + } else if (strcasecmp(Command, "SCSI") == 0) { + update->ForceScrapInfoUpdate(); + return "SCRAPER2VDR scan for new or updated scrapinfo files triggered."; + } else if (strcasecmp(Command, "CRDB") == 0) { + update->TriggerCleanRecordingsDB(); + return "SCRAPER2VDR cleanup of recording DB triggered."; + } + return NULL; +} + +VDRPLUGINCREATOR(cPluginScraper2vdr); // Don't touch this! diff --git a/scraper2vdr.h b/scraper2vdr.h new file mode 100644 index 0000000..b955a59 --- /dev/null +++ b/scraper2vdr.h @@ -0,0 +1,57 @@ +#ifndef __SCRAPER2VDR_H
+#define __SCRAPER2VDR_H
+
+#include <getopt.h>
+#include <vdr/plugin.h>
+#include "lib/common.h"
+#include "config.h"
+#include "setup.h"
+#include "scrapmanager.h"
+#include "update.h"
+#include "services.h"
+
+//***************************************************************************
+// Constants
+//***************************************************************************
+static const char *VERSION = "0.0.1";
+static const char *DESCRIPTION = "'scraper2vdr' plugin";
+static const char *MAINMENUENTRY = "Scraper2Vdr";
+
+//***************************************************************************
+// Globals
+//***************************************************************************
+cScraper2VdrConfig config;
+
+//***************************************************************************
+// cPluginScraper2vdr
+//***************************************************************************
+
+class cPluginScraper2vdr : public cPlugin {
+private:
+ cScrapManager *scrapManager;
+ cUpdate *update;
+public:
+ cPluginScraper2vdr(void);
+ virtual ~cPluginScraper2vdr();
+ virtual const char *Version(void) { return VERSION; }
+ virtual const char *Description(void) { return DESCRIPTION; }
+ virtual const char *CommandLineHelp(void);
+ virtual bool ProcessArgs(int argc, char *argv[]);
+ virtual bool Initialize(void);
+ virtual bool Start(void);
+ virtual void Stop(void);
+ virtual void Housekeeping(void);
+ virtual void MainThreadHook(void);
+ virtual cString Active(void);
+ virtual time_t WakeupTime(void);
+ virtual const char *MainMenuEntry(void) { return (config.mainMenuEntry)?MAINMENUENTRY:NULL; }
+ virtual cOsdObject *MainMenuAction(void);
+ virtual cMenuSetupPage *SetupMenu(void);
+ virtual bool SetupParse(const char *Name, const char *Value);
+ virtual bool Service(const char *Id, void *Data = NULL);
+ virtual const char **SVDRPHelpPages(void);
+ virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
+};
+
+//***************************************************************************
+#endif // __SCRAPER2VDR_H
\ No newline at end of file diff --git a/scrapmanager.c b/scrapmanager.c new file mode 100644 index 0000000..b446c17 --- /dev/null +++ b/scrapmanager.c @@ -0,0 +1,503 @@ +#define __STL_CONFIG_H
+#include <vdr/recording.h>
+#include "tools.h"
+#include "scrapmanager.h"
+
+using namespace std;
+
+bool operator<(const sEventsKey& l, const sEventsKey& r) {
+ if (l.eventId != r.eventId)
+ return (l.eventId < r.eventId);
+ int comp = l.channelId.compare(r.channelId);
+ if (comp < 0)
+ return true;
+ return false;
+}
+
+bool operator<(const sRecordingsKey& l, const sRecordingsKey& r) {
+ if (l.recStart != r.recStart)
+ return (l.recStart < r.recStart);
+ int comp = l.recPath.compare(r.recPath);
+ if (comp < 0)
+ return true;
+ return false;
+}
+
+cScrapManager::cScrapManager(void) {
+
+}
+
+cScrapManager::~cScrapManager(void) {
+ for (map<int, cTVDBSeries*>::iterator it = series.begin(); it != series.end(); it++) {
+ cTVDBSeries *s = (cTVDBSeries*)it->second;
+ delete s;
+ }
+ series.clear();
+ for (map<int, cMovieDbMovie*>::iterator it = movies.begin(); it != movies.end(); it++) {
+ cMovieDbMovie *m = (cMovieDbMovie*)it->second;
+ delete m;
+ }
+ movies.clear();
+}
+
+void cScrapManager::InitIterator(bool isRec) {
+ if (!isRec)
+ eventsIterator = events.begin();
+ else
+ recIterator = recordings.begin();
+}
+
+sEventsValue cScrapManager::GetEventInformation(int eventId, string channelId) {
+ sEventsKey k;
+ k.eventId = eventId;
+ k.channelId = channelId;
+ sEventsValue emptyVal;
+ emptyVal.seriesId = 0;
+ emptyVal.episodeId = 0;
+ emptyVal.movieId = 0;
+ emptyVal.isNew = false;
+ map<sEventsKey, sEventsValue>::iterator hit = events.find(k);
+ if (hit != events.end())
+ return hit->second;
+ return emptyVal;
+}
+
+
+void cScrapManager::AddEvent(int eventId, string channelId, int seriesId, int episodeId, int movieId) {
+ sEventsKey k;
+ k.eventId = eventId;
+ k.channelId = channelId;
+ sEventsValue v;
+ v.seriesId = seriesId;
+ v.episodeId = episodeId;
+ v.movieId = movieId;
+ v.isNew = true;
+ events.insert(pair<sEventsKey, sEventsValue>(k, v));
+}
+
+bool cScrapManager::GetNextSeries(bool isRec, int &seriesId, int &episodeId) {
+ bool next = false;
+ if (!isRec) {
+ while (eventsIterator != events.end()) {
+ next = true;
+ sEventsValue ev = eventsIterator->second;
+ if (ev.isNew && (ev.seriesId > 0)) {
+ seriesId = ev.seriesId;
+ episodeId = ev.episodeId;
+ eventsIterator->second.isNew = false;
+ eventsIterator++;
+ break;
+ }
+ eventsIterator++;
+ next = false;
+ }
+ } else {
+ while (recIterator != recordings.end()) {
+ next = true;
+ sEventsValue ev = recIterator->second;
+ if (ev.isNew && (ev.seriesId > 0)) {
+ seriesId = ev.seriesId;
+ episodeId = ev.episodeId;
+ recIterator->second.isNew = false;
+ recIterator++;
+ break;
+ }
+ recIterator++;
+ next = false;
+ }
+
+ }
+ return next;
+}
+
+bool cScrapManager::GetNextMovie(bool isRec, int &movieId) {
+ bool next = false;
+ if (!isRec) {
+ while (eventsIterator != events.end()) {
+ next = true;
+ sEventsValue ev = eventsIterator->second;
+ if (ev.isNew && (ev.movieId > 0)) {
+ movieId = ev.movieId;
+ eventsIterator->second.isNew = false;
+ eventsIterator++;
+ break;
+ }
+ eventsIterator++;
+ next = false;
+ }
+ } else {
+ while (recIterator != recordings.end()) {
+ next = true;
+ sEventsValue ev = recIterator->second;
+ if (ev.isNew && (ev.movieId > 0)) {
+ movieId = ev.movieId;
+ recIterator->second.isNew = false;
+ recIterator++;
+ break;
+ }
+ recIterator++;
+ next = false;
+ }
+ }
+ return next;
+}
+
+cTVDBSeries *cScrapManager::GetSeries(int seriesId) {
+ map<int, cTVDBSeries*>::iterator hit = series.find(seriesId);
+ if (hit == series.end())
+ return NULL;
+ return hit->second;
+}
+
+cMovieDbMovie *cScrapManager::GetMovie(int movieId) {
+ map<int, cMovieDbMovie*>::iterator hit = movies.find(movieId);
+ if (hit == movies.end())
+ return NULL;
+ return hit->second;
+}
+
+cTVDBSeries *cScrapManager::AddSeries(cTableSeries* tSeries) {
+ cTVDBSeries *s = new cTVDBSeries();
+ s->id = tSeries->getIntValue(cTableSeries::fiSeriesId);
+ s->name = tSeries->getStrValue(cTableSeries::fiSeriesName);
+ s->overview = tSeries->getStrValue(cTableSeries::fiSeriesOverview);
+ s->firstAired = tSeries->getStrValue(cTableSeries::fiSeriesFirstAired);
+ s->network = tSeries->getStrValue(cTableSeries::fiSeriesNetwork);
+ string genre = replaceString(tSeries->getStrValue(cTableSeries::fiSeriesGenre), "|", ", ");
+ s->genre = genre;
+ s->rating = tSeries->getFloatValue(cTableSeries::fiSeriesRating);
+ s->status = tSeries->getStrValue(cTableSeries::fiSeriesStatus);
+ series.insert(pair<int, cTVDBSeries*>(tSeries->getIntValue(cTableSeries::fiSeriesId), s));
+ return s;
+}
+
+cMovieDbMovie *cScrapManager::AddMovie(cTableMovies* tMovies) {
+ cMovieDbMovie *m = new cMovieDbMovie();
+ m->id = tMovies->getIntValue(cTableMovies::fiMovieId);
+ m->title = tMovies->getStrValue(cTableMovies::fiTitle);
+ m->originalTitle = tMovies->getStrValue(cTableMovies::fiOriginalTitle);
+ m->tagline = tMovies->getStrValue(cTableMovies::fiTagline);
+ m->overview = tMovies->getStrValue(cTableMovies::fiOverview);
+ m->adult = tMovies->getIntValue(cTableMovies::fiIsAdult);
+ m->collectionName = tMovies->getStrValue(cTableMovies::fiCollectionName);
+ m->budget = tMovies->getIntValue(cTableMovies::fiBudget);
+ m->revenue = tMovies->getIntValue(cTableMovies::fiRevenue);
+ string genre = replaceString(tMovies->getStrValue(cTableMovies::fiGenres), "|", ",");
+ m->genres = genre;
+ m->homepage = tMovies->getStrValue(cTableMovies::fiHomepage);
+ m->releaseDate = tMovies->getStrValue(cTableMovies::fiReleaaseDate);
+ m->runtime = tMovies->getIntValue(cTableMovies::fiRuntime);
+ m->popularity = tMovies->getFloatValue(cTableMovies::fiPopularity);
+ m->voteAverage = tMovies->getFloatValue(cTableMovies::fiVoteAverage);
+ movies.insert(pair<int, cMovieDbMovie*>(tMovies->getIntValue(cTableMovies::fiMovieId), m));
+ return m;
+}
+
+void cScrapManager::AddSeriesEpisode(cTVDBSeries *series, cTableSeriesEpisode* tEpisodes) {
+ cTVDBEpisode *e = new cTVDBEpisode();
+ e->id = tEpisodes->getIntValue(cTableSeriesEpisode::fiEpisodeId);
+ e->name = tEpisodes->getStrValue(cTableSeriesEpisode::fiEpisodeName);
+ e->number = tEpisodes->getIntValue(cTableSeriesEpisode::fiEpisodeNumber);
+ e->season = tEpisodes->getIntValue(cTableSeriesEpisode::fiSeasonNumber);
+ e->overview = tEpisodes->getStrValue(cTableSeriesEpisode::fiEpisodeOverview);
+ e->firstAired = tEpisodes->getStrValue(cTableSeriesEpisode::fiEpisodeFirstAired);
+ string guestStars = replaceString(tEpisodes->getStrValue(cTableSeriesEpisode::fiEpisodeGuestStars), "|", ", ");
+ e->guestStars = guestStars;
+ e->rating = tEpisodes->getFloatValue(cTableSeriesEpisode::fiEpisodeRating);
+ series->InsertEpisode(e);
+}
+
+void cScrapManager::AddSeriesActor(cTVDBSeries *series, cTableSeriesActor* tActors) {
+ cTVDBActor *a = new cTVDBActor();
+ a->id = tActors->getIntValue(cTableSeriesActor::fiActorId);
+ a->name = tActors->getStrValue(cTableSeriesActor::fiActorName);
+ a->role = tActors->getStrValue(cTableSeriesActor::fiActorRole);
+ series->InsertActor(a);
+}
+
+void cScrapManager::AddMovieMedia(cMovieDbMovie *movie, cTableMovieMedia* tMovieMedia, string path) {
+ cMovieMedia *m = new cMovieMedia();
+ m->mediaType = tMovieMedia->getIntValue(cTableMovieMedia::fiMediaType);
+ m->width = tMovieMedia->getIntValue(cTableMovieMedia::fiMediaWidth);
+ m->height = tMovieMedia->getIntValue(cTableMovieMedia::fiMediaHeight);
+ m->path = path;
+ movie->InsertMedia(m);
+}
+
+
+void cScrapManager::AddMovieActor(cMovieDbMovie *movie, cTableMovieActor* tActor, string role) {
+ cMovieActor *a = new cMovieActor();
+ a->id = tActor->getIntValue(cTableMovieActor::fiActorId);
+ a->name = tActor->getStrValue(cTableMovieActor::fiActorName);
+ a->role = role;
+ movie->InsertActor(a);
+}
+
+bool cScrapManager::AddRecording(int recStart, string recPath, int seriesId, int episodeId, int movieId) {
+ sRecordingsKey k;
+ k.recStart = recStart;
+ k.recPath = recPath;
+ //check if recording already exists
+ map<sRecordingsKey, sEventsValue>::iterator hit = recordings.find(k);
+ if (hit != recordings.end()) {
+ sEventsValue v = hit->second;
+ if ((v.seriesId == seriesId) && (v.episodeId == episodeId) && (v.movieId == movieId))
+ return false;
+ else
+ recordings.erase(hit);
+ }
+ sEventsValue v;
+ v.seriesId = seriesId;
+ v.episodeId = episodeId;
+ v.movieId = movieId;
+ v.isNew = true;
+ recordings.insert(pair<sRecordingsKey, sEventsValue>(k, v));
+ return true;
+}
+
+bool cScrapManager::RecordingExists(int recStart, string recPath) {
+ sRecordingsKey k;
+ k.recStart = recStart;
+ k.recPath = recPath;
+ map<sRecordingsKey, sEventsValue>::iterator hit = recordings.find(k);
+ if (hit != recordings.end())
+ return true;
+ return false;
+}
+
+bool cScrapManager::SeriesInUse(int seriesId) {
+ map<int, cTVDBSeries*>::iterator hit = series.find(seriesId);
+ if (hit != series.end())
+ return true;
+ return false;
+}
+
+bool cScrapManager::MovieInUse(int movieId) {
+ map<int, cMovieDbMovie*>::iterator hit = movies.find(movieId);
+ if (hit != movies.end())
+ return true;
+ return false;
+}
+
+void cScrapManager::DumpSeries(int num) {
+ int i=0;
+ tell(0, "Dumping first %d series", num);
+ for (map<int, cTVDBSeries*>::iterator it = series.begin(); it != series.end(); it++) {
+ cTVDBSeries *s = it->second;
+ s->Dump();
+ if (i == num)
+ break;
+ i++;
+ }
+}
+
+void cScrapManager::DumpMovies(int num) {
+ int i=0;
+ tell(0, "Dumping first %d movies", num);
+ for (map<int, cMovieDbMovie*>::iterator it = movies.begin(); it != movies.end(); it++) {
+ cMovieDbMovie *m = it->second;
+ m->Dump();
+ if (i == num)
+ break;
+ i++;
+ }
+}
+
+void cScrapManager::DumpRecordings(int num) {
+ tell(0, "Dumping first %d recordings", num);
+ for (map<sRecordingsKey, sEventsValue>::iterator it = recordings.begin(); it != recordings.end(); it++) {
+ sRecordingsKey key = it->first;
+ sEventsValue val = it->second;
+ tell(0, "recStart %d, recPath %s, seriesId %d, episodeId %d, movieId %d", key.recStart, key.recPath.c_str(), val.seriesId, val.episodeId, val.movieId);
+ }
+}
+
+bool cScrapManager::GetEventType(ScraperGetEventType *call) {
+ sEventsValue v;
+ if (call->event) {
+ sEventsKey k;
+ k.eventId = call->event->EventID();
+ k.channelId = *(call->event->ChannelID().ToString());
+ map<sEventsKey, sEventsValue>::iterator hit = events.find(k);
+ if (hit == events.end())
+ return false;
+ v = hit->second;
+ } else if (call->recording) {
+ sRecordingsKey k;
+ k.recStart = call->recording->Start();
+ k.recPath = call->recording->FileName();
+ map<sRecordingsKey, sEventsValue>::iterator hit = recordings.find(k);
+ if (hit == recordings.end())
+ return false;
+ v = hit->second;
+ }
+ if (v.seriesId > 0) {
+ call->type = tSeries;
+ } else if (v.movieId > 0) {
+ call->type = tMovie;
+ } else {
+ call->type = tNone;
+ return false;
+ }
+ call->seriesId = v.seriesId;
+ call->episodeId = v.episodeId;
+ call->movieId = v.movieId;
+ return true;
+}
+
+bool cScrapManager::GetSeries(cSeries *s) {
+ map<int, cTVDBSeries*>::iterator hit = series.find(s->seriesId);
+ if (hit == series.end())
+ return false;
+ cTVDBSeries *sStored = hit->second;
+ s->name = sStored->name;
+ s->overview = sStored->overview;
+ s->firstAired = sStored->firstAired;
+ s->network = sStored->network;
+ s->genre = sStored->genre;
+ s->rating = sStored->rating;
+ s->status = sStored->status;
+ //Episode
+ if (s->episodeId > 0) {
+ sStored->GetEpisode(s->episodeId, &s->episode);
+ sStored->GetSeasonPoster(s->episodeId, &s->seasonPoster);
+ }
+ //Media
+ sStored->GetPosters(&s->posters);
+ sStored->GetBanners(&s->banners);
+ sStored->GetFanart(&s->fanarts);
+ //Actors
+ sStored->GetActors(&s->actors);
+ return true;
+}
+
+bool cScrapManager::GetMovie(cMovie *m) {
+ map<int, cMovieDbMovie*>::iterator hit = movies.find(m->movieId);
+ if (hit == movies.end())
+ return false;
+ cMovieDbMovie *mStored = hit->second;
+ m->title = mStored->title;
+ m->originalTitle = mStored->originalTitle;
+ m->tagline = mStored->tagline;
+ m->overview = mStored->overview;
+ m->adult = mStored->adult;
+ m->collectionName = mStored->collectionName;
+ m->budget = mStored->budget;
+ m->revenue = mStored->revenue;
+ m->genres = mStored->genres;
+ m->homepage = mStored->homepage;
+ m->releaseDate = mStored->releaseDate;
+ m->runtime = mStored->runtime;
+ m->popularity = mStored->popularity;
+ m->voteAverage = mStored->voteAverage;
+ //Media
+ mStored->GetMedia(mmPoster, &m->poster);
+ mStored->GetMedia(mmFanart, &m->fanart);
+ mStored->GetMedia(mmCollectionPoster, &m->collectionPoster);
+ mStored->GetMedia(mmCollectionFanart, &m->collectionFanart);
+ //Actors
+ mStored->GetActors(&m->actors);
+ return true;
+}
+
+bool cScrapManager::GetPosterBanner(ScraperGetPosterBanner *call) {
+ sEventsKey k;
+ k.eventId = call->event->EventID();
+ k.channelId = *(call->event->ChannelID().ToString());
+ map<sEventsKey, sEventsValue>::iterator hit = events.find(k);
+ if (hit == events.end())
+ return false;
+ sEventsValue v = hit->second;
+ if (v.seriesId > 0) {
+ call->type = tSeries;
+ map<int, cTVDBSeries*>::iterator hitSeries = series.find(v.seriesId);
+ if (hitSeries == series.end())
+ return false;
+ cTVDBSeries *s = hitSeries->second;
+ bool found = s->GetRandomBanner(&call->banner);
+ if (v.episodeId > 0) {
+ s->GetSeasonPoster(v.episodeId, &call->poster);
+ }
+ return found;
+ } else if (v.movieId > 0) {
+ call->type = tMovie;
+ map<int, cMovieDbMovie*>::iterator hitMovies = movies.find(v.movieId);
+ if (hitMovies == movies.end())
+ return false;
+ cMovieDbMovie *m = hitMovies->second;
+ return m->GetMedia(mmPoster, &call->poster);
+ } else {
+ call->type = tNone;
+ }
+ return false;
+}
+
+bool cScrapManager::GetPoster(ScraperGetPoster *call) {
+ sEventsValue v;
+ if (call->event) {
+ sEventsKey k;
+ k.eventId = call->event->EventID();
+ k.channelId = *(call->event->ChannelID().ToString());
+ map<sEventsKey, sEventsValue>::iterator hit = events.find(k);
+ if (hit == events.end())
+ return false;
+ v = hit->second;
+ } else if (call->recording) {
+ sRecordingsKey k;
+ k.recStart = call->recording->Start();
+ k.recPath = call->recording->FileName();
+ map<sRecordingsKey, sEventsValue>::iterator hit = recordings.find(k);
+ if (hit == recordings.end())
+ return false;
+ v = hit->second;
+ }
+ if (v.seriesId > 0) {
+ map<int, cTVDBSeries*>::iterator hitSeries = series.find(v.seriesId);
+ if (hitSeries == series.end())
+ return false;
+ cTVDBSeries *s = hitSeries->second;
+ return s->GetPoster(&call->poster);
+ } else if (v.movieId > 0) {
+ map<int, cMovieDbMovie*>::iterator hitMovies = movies.find(v.movieId);
+ if (hitMovies == movies.end())
+ return false;
+ cMovieDbMovie *m = hitMovies->second;
+ return m->GetMedia(mmPoster, &call->poster);
+ }
+
+}
+
+bool cScrapManager::GetPosterThumb(ScraperGetPosterThumb *call) {
+ sEventsValue v;
+ if (call->event) {
+ sEventsKey k;
+ k.eventId = call->event->EventID();
+ k.channelId = *(call->event->ChannelID().ToString());
+ map<sEventsKey, sEventsValue>::iterator hit = events.find(k);
+ if (hit == events.end())
+ return false;
+ v = hit->second;
+ } else if (call->recording) {
+ sRecordingsKey k;
+ k.recStart = call->recording->Start();
+ k.recPath = call->recording->FileName();
+ map<sRecordingsKey, sEventsValue>::iterator hit = recordings.find(k);
+ if (hit == recordings.end())
+ return false;
+ v = hit->second;
+ }
+ if (v.seriesId > 0) {
+ map<int, cTVDBSeries*>::iterator hitSeries = series.find(v.seriesId);
+ if (hitSeries == series.end())
+ return false;
+ cTVDBSeries *s = hitSeries->second;
+ return s->GetPosterThumb(&call->poster);
+ } else if (v.movieId > 0) {
+ map<int, cMovieDbMovie*>::iterator hitMovies = movies.find(v.movieId);
+ if (hitMovies == movies.end())
+ return false;
+ cMovieDbMovie *m = hitMovies->second;
+ return m->GetMedia(mmPosterThumb, &call->poster);
+ }
+ return false;
+}
diff --git a/scrapmanager.h b/scrapmanager.h new file mode 100644 index 0000000..682bd65 --- /dev/null +++ b/scrapmanager.h @@ -0,0 +1,81 @@ +#ifndef __SCRAPMANAGER_H
+#define __SCRAPMANAGER_H
+
+#include <vector>
+#include <map>
+#include <set>
+#include <utility>
+#include <algorithm>
+
+#include "lib/common.h"
+#include "lib/db.h"
+#include "lib/tabledef.h"
+
+#include "services.h"
+#include "tvdbseries.h"
+#include "moviedbmovie.h"
+
+using namespace std;
+
+struct sEventsKey {
+ int eventId;
+ string channelId;
+};
+
+struct sEventsValue {
+ int seriesId;
+ int episodeId;
+ int movieId;
+ bool isNew;
+};
+
+struct sRecordingsKey {
+ int recStart;
+ string recPath;
+};
+
+class cScrapManager {
+ private:
+ map<sEventsKey, sEventsValue> events;
+ map<sEventsKey, sEventsValue>::iterator eventsIterator;
+ map<sRecordingsKey, sEventsValue> recordings;
+ map<sRecordingsKey, sEventsValue>::iterator recIterator;
+ map<int, cTVDBSeries*> series;
+ map<int, cMovieDbMovie*> movies;
+ public:
+ cScrapManager(void);
+ virtual ~cScrapManager(void);
+ //Series and Movies Handling
+ void AddEvent(int eventId, string channelId, int seriesId, int episodeId, int movieId);
+ void InitIterator(bool isRec);
+ int GetNumSeries(void) { return series.size(); };
+ int GetNumMovies(void) { return movies.size(); };
+ sEventsValue GetEventInformation(int eventId, string channelId);
+ bool GetNextSeries(bool isRec, int &seriesId, int &episodeId);
+ bool GetNextMovie(bool isRec, int &movieId);
+ cTVDBSeries *GetSeries(int seriesId);
+ cMovieDbMovie *GetMovie(int movieId);
+ cTVDBSeries *AddSeries(cTableSeries* tSeries);
+ cMovieDbMovie *AddMovie(cTableMovies* tMovies);
+ void AddSeriesEpisode(cTVDBSeries *series, cTableSeriesEpisode* tEpisodes);
+ void AddSeriesActor(cTVDBSeries *series, cTableSeriesActor* tActors);
+ void AddMovieActor(cMovieDbMovie *movie, cTableMovieActor* tActor, string role);
+ void AddMovieMedia(cMovieDbMovie *movie, cTableMovieMedia* tMovieMedia, string path);
+ //Recording Handling
+ bool AddRecording(int recStart, string recPath, int seriesId, int episodeId, int movieId);
+ bool RecordingExists(int recStart, string recPath);
+ bool SeriesInUse(int seriesId);
+ bool MovieInUse(int movieId);
+ //Debug
+ void DumpSeries(int num);
+ void DumpMovies(int num);
+ void DumpRecordings(int num);
+ //Service Calls
+ bool GetEventType(ScraperGetEventType *call);
+ bool GetSeries(cSeries *series);
+ bool GetMovie(cMovie *movie);
+ bool GetPosterBanner(ScraperGetPosterBanner *call);
+ bool GetPoster(ScraperGetPoster *call);
+ bool GetPosterThumb(ScraperGetPosterThumb *call);
+};
+#endif //__SCRAPMANAGER_H
diff --git a/services.h b/services.h new file mode 100644 index 0000000..703d077 --- /dev/null +++ b/services.h @@ -0,0 +1,194 @@ +#ifndef __SCRAPER2VDRSERVICES_H
+#define __SCRAPER2VDRSERVICES_H
+
+#include <vdr/epg.h>
+#include <vdr/recording.h>
+
+enum tvType {
+ tSeries,
+ tMovie,
+ tNone,
+};
+
+/*********************************************************************
+* Helper Structures
+*********************************************************************/
+class cTvMedia {
+public:
+ cTvMedia(void) {
+ path = "";
+ width = height = 0;
+ };
+ std::string path;
+ int width;
+ int height;
+};
+
+class cEpisode {
+public:
+ cEpisode(void) {
+ number = 0;
+ season = 0;
+ name = "";
+ firstAired = "";
+ guestStars = "";
+ overview = "";
+ rating = 0.0;
+ };
+ int number;
+ int season;
+ std::string name;
+ std::string firstAired;
+ std::string guestStars;
+ std::string overview;
+ float rating;
+ cTvMedia episodeImage;
+};
+
+class cActor {
+public:
+ cActor(void) {
+ name = "";
+ role = "";
+ };
+ std::string name;
+ std::string role;
+ cTvMedia actorThumb;
+};
+
+/*********************************************************************
+* Data Structures for Service Calls
+*********************************************************************/
+
+// Data structure for service "GetEventType"
+class ScraperGetEventType {
+public:
+ ScraperGetEventType(void) {
+ event = NULL;
+ recording = NULL;
+ type = tNone;
+ movieId = 0;
+ seriesId = 0;
+ episodeId = 0;
+ };
+// in
+ const cEvent *event; // check type for this event
+ const cRecording *recording; // or for this recording
+//out
+ tvType type; //typeSeries or typeMovie
+ int movieId;
+ int seriesId;
+ int episodeId;
+};
+
+//Data structure for full series and episode information
+class cMovie {
+public:
+ cMovie(void) {
+ title = "";
+ originalTitle = "";
+ tagline = "";
+ overview = "";
+ adult = false;
+ collectionName = "";
+ budget = 0;
+ revenue = 0;
+ genres = "";
+ homepage = "";
+ releaseDate = "";
+ runtime = 0;
+ popularity = 0.0;
+ voteAverage = 0.0;
+ };
+//IN
+ int movieId; // movieId fetched from ScraperGetEventType
+//OUT
+ std::string title;
+ std::string originalTitle;
+ std::string tagline;
+ std::string overview;
+ bool adult;
+ std::string collectionName;
+ int budget;
+ int revenue;
+ std::string genres;
+ std::string homepage;
+ std::string releaseDate;
+ int runtime;
+ float popularity;
+ float voteAverage;
+ cTvMedia poster;
+ cTvMedia fanart;
+ cTvMedia collectionPoster;
+ cTvMedia collectionFanart;
+ std::vector<cActor> actors;
+};
+
+//Data structure for full series and episode information
+class cSeries {
+public:
+ cSeries(void) {
+ seriesId = 0;
+ episodeId = 0;
+ name = "";
+ overview = "";
+ firstAired = "";
+ network = "";
+ genre = "";
+ rating = 0.0;
+ status = "";
+ };
+//IN
+ int seriesId; // seriesId fetched from ScraperGetEventType
+ int episodeId; // episodeId fetched from ScraperGetEventType
+//OUT
+ std::string name;
+ std::string overview;
+ std::string firstAired;
+ std::string network;
+ std::string genre;
+ float rating;
+ std::string status;
+ cEpisode episode;
+ std::vector<cActor> actors;
+ std::vector<cTvMedia> posters;
+ std::vector<cTvMedia> banners;
+ std::vector<cTvMedia> fanarts;
+ cTvMedia seasonPoster;
+};
+
+// Data structure for service "GetPosterBanner"
+class ScraperGetPosterBanner {
+public:
+ ScraperGetPosterBanner(void) {
+ type = tNone;
+ };
+// in
+ const cEvent *event; // check type for this event
+//out
+ tvType type; //typeSeries or typeMovie
+ cTvMedia poster;
+ cTvMedia banner;
+};
+
+// Data structure for service "GetPoster"
+class ScraperGetPoster {
+public:
+// in
+ const cEvent *event; // check type for this event
+ const cRecording *recording; // or for this recording
+//out
+ cTvMedia poster;
+};
+
+// Data structure for service "GetPosterThumb"
+class ScraperGetPosterThumb {
+public:
+// in
+ const cEvent *event; // check type for this event
+ const cRecording *recording; // or for this recording
+//out
+ cTvMedia poster;
+};
+
+#endif //__SCRAPER2VDRSERVICES_H
\ No newline at end of file @@ -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()); +} @@ -0,0 +1,25 @@ +#ifndef __SCRAPER2VDR_SETUP_H +#define __SCRAPER2VDR_SETUP_H + +#include <vdr/menuitems.h> +#include "update.h" +#include "config.h" + +class cScraper2VdrSetup : public cMenuSetupPage { + public: + cScraper2VdrSetup(cUpdate *update); + virtual ~cScraper2VdrSetup(); + private: + cUpdate *update; + cScraper2VdrConfig tmpConfig; + char host[256]; + char dbname[256]; + char user[256]; + char password[256]; + void Setup(void); + protected: + virtual eOSState ProcessKey(eKeys Key); + virtual void Store(void); + +}; +#endif //__SCRAPER2VDR_SETUP_H
\ No newline at end of file @@ -0,0 +1,170 @@ +#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <dirent.h>
+#include <fstream>
+#include <algorithm>
+#include <functional>
+#include <cctype>
+#include <locale>
+#include <Magick++.h>
+#include <vdr/plugin.h>
+#include "lib/common.h"
+#include "tools.h"
+
+using namespace std;
+using namespace Magick;
+
+bool CreateDirectory(string dir) {
+ mkdir(dir.c_str(), 0775);
+ //check if successfull
+ DIR *pDir;
+ bool exists = false;
+ pDir = opendir(dir.c_str());
+ if (pDir != NULL) {
+ exists = true;
+ closedir(pDir);
+ }
+ return exists;
+}
+
+bool FileExists(string filename, bool isImage) {
+ ifstream ifile(filename.c_str());
+ if (ifile) {
+ //a valid image should be larger then 500 bytes
+ ifile.seekg (0, ifile.end);
+ int length = ifile.tellg();
+ int minimumLength = isImage ? 500 : 1;
+ if (length > minimumLength)
+ return true;
+ }
+ return false;
+}
+
+bool CheckDirExists(const char* dirName) {
+ struct statfs statfsbuf;
+ if (statfs(dirName,&statfsbuf)==-1) return false;
+ if ((statfsbuf.f_type!=0x01021994) && (statfsbuf.f_type!=0x28cd3d45)) return false;
+ if (access(dirName,R_OK|W_OK)==-1) return false;
+ return true;
+
+}
+
+void DeleteFile(string filename) {
+ remove(filename.c_str());
+}
+
+void DeleteDirectory(string dirname) {
+ DIR *dir;
+ struct dirent *entry;
+ if ((dir = opendir (dirname.c_str())) != NULL) {
+ while ((entry = readdir (dir)) != NULL) {
+ string file = entry->d_name;
+ if (!file.compare("."))
+ continue;
+ if (!file.compare(".."))
+ continue;
+ string delFile = dirname + "/" + file;
+ DeleteFile(delFile);
+ }
+ closedir (dir);
+ }
+ rmdir(dirname.c_str());
+}
+
+string TwoFoldersHigher(string folder) {
+ unsigned found = folder.find_last_of("/");
+ if (found != string::npos) {
+ string firstDirRemoved = folder.substr(0,found);
+ unsigned found2 = firstDirRemoved.find_last_of("/");
+ if (found2 != string::npos) {
+ return firstDirRemoved.substr(0,found2);
+ }
+ }
+ return "";
+}
+
+
+// trim from start
+string <rim(string &s) {
+ s.erase(s.begin(), find_if(s.begin(), s.end(), not1(ptr_fun<int, int>(isspace))));
+ return s;
+}
+
+// trim from end
+string &rtrim(string &s) {
+ s.erase(find_if(s.rbegin(), s.rend(), not1(ptr_fun<int, int>(isspace))).base(), s.end());
+ return s;
+}
+
+// trim from both ends
+string &trim(string &s) {
+ return ltrim(rtrim(s));
+}
+
+void toLower(string &s) {
+ transform(s.begin(), s.end(), s.begin(), ::tolower);
+}
+
+bool isNumber(const string& s) {
+ string::const_iterator it = s.begin();
+ while (it != s.end() && isdigit(*it)) ++it;
+ return !s.empty() && it == s.end();
+}
+
+string replaceString(string content, string search, string repl) {
+ size_t pos = 0;
+ while((pos = content.find(search, pos)) != std::string::npos) {
+ if (pos > 3 && pos < content.size() - 2) {
+ content.replace(pos, search.length(), repl);
+ } else {
+ content.replace(pos, search.length(), "");
+ }
+ pos += repl.length();
+ }
+ return content;
+}
+
+
+/****************************************************************************************
+* SPLTSTRING
+****************************************************************************************/
+// split: receives a char delimiter; returns a vector of strings
+// By default ignores repeated delimiters, unless argument rep == 1.
+vector<string>& splitstring::split(char delim, int rep) {
+ if (!flds.empty()) flds.clear(); // empty vector if necessary
+ string work = data();
+ string buf = "";
+ int i = 0;
+ while (i < work.length()) {
+ if (work[i] != delim)
+ buf += work[i];
+ else if (rep == 1) {
+ flds.push_back(buf);
+ buf = "";
+ } else if (buf.length() > 0) {
+ flds.push_back(buf);
+ buf = "";
+ }
+ i++;
+ }
+ if (!buf.empty())
+ flds.push_back(buf);
+ return flds;
+}
+
+void CreateThumbnail(string sourcePath, string destPath, int origWidth, int origHeight, int shrinkFactor) {
+ if (sourcePath.size() < 5 || destPath.size() < 5 || shrinkFactor < 2)
+ return;
+
+ int thumbWidth = origWidth / shrinkFactor;
+ int thumbHeight = origHeight / shrinkFactor;
+
+ InitializeMagick(NULL);
+ Image buffer;
+ try {
+ buffer.read(sourcePath.c_str());
+ buffer.sample(Geometry(thumbWidth, thumbHeight));
+ buffer.write(destPath.c_str());
+ } catch( ... ) {}
+}
\ No newline at end of file @@ -0,0 +1,30 @@ +#include <vector>
+#include <string>
+
+using namespace std;
+
+//Filesystem Functions
+bool CreateDirectory(string dir);
+bool FileExists(string filename, bool isImage = true);
+bool CheckDirExists(const char* dirName);
+void DeleteFile(string filename);
+void DeleteDirectory(string dirname);
+string TwoFoldersHigher(string folder);
+
+//String Functions
+string <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<string> flds;
+public:
+ splitstring(const char *s) : string(s) { };
+ vector<string>& split(char delim, int rep=0);
+};
+
+//Image Functions
+void CreateThumbnail(string sourcePath, string destPath, int origWidth, int origHeight, int shrinkFactor);
\ No newline at end of file diff --git a/tvdbseries.c b/tvdbseries.c new file mode 100644 index 0000000..96bde06 --- /dev/null +++ b/tvdbseries.c @@ -0,0 +1,289 @@ +#define __STL_CONFIG_H
+#include "lib/common.h"
+#include "tvdbseries.h"
+
+using namespace std;
+
+cTVDBSeries::cTVDBSeries(void) {
+ id = 0;
+ name = "";
+ overview = "";
+ firstAired = "";
+ network = "";
+ genre = "";
+ rating = 0.0;
+ status = "";
+ posterThumb = NULL;
+}
+
+cTVDBSeries::~cTVDBSeries() {
+ for (map<int, cTVDBActor*>::iterator it = actors.begin(); it != actors.end(); it++) {
+ cTVDBActor *a = (cTVDBActor*)it->second;
+ delete a;
+ }
+ for (map<int, cTVDBEpisode*>::iterator it = episodes.begin(); it != episodes.end(); it++) {
+ cTVDBEpisode *e = (cTVDBEpisode*)it->second;
+ delete e;
+ }
+ for (vector<cTVDBMedia*>::iterator it = posters.begin(); it != posters.end(); it++) {
+ cTVDBMedia *p = *it;
+ delete p;
+ }
+ for (vector<cTVDBMedia*>::iterator it = banners.begin(); it != banners.end(); it++) {
+ cTVDBMedia *b = *it;
+ delete b;
+ }
+ for (vector<cTVDBMedia*>::iterator it = fanart.begin(); it != fanart.end(); it++) {
+ cTVDBMedia *f = *it;
+ delete f;
+ }
+ for (map<int, cTVDBMedia*>::iterator it = seasonPosters.begin(); it != seasonPosters.end(); it++) {
+ cTVDBMedia *s = (cTVDBMedia*)it->second;
+ delete s;
+ }
+ for (map<int, cTVDBMedia*>::iterator it = seasonPosterThumbs.begin(); it != seasonPosterThumbs.end(); it++) {
+ cTVDBMedia *s = (cTVDBMedia*)it->second;
+ delete s;
+ }
+ if (posterThumb)
+ delete posterThumb;
+}
+
+void cTVDBSeries::InsertEpisode(cTVDBEpisode *episode) {
+ map<int, cTVDBEpisode*>::iterator hit = episodes.find(episode->id);
+ if (hit != episodes.end())
+ delete episode;
+ else
+ episodes.insert(pair<int, cTVDBEpisode*>(episode->id, episode));
+}
+
+void cTVDBSeries::InsertEpisodeImage(int episodeId, int width, int height, string path) {
+ map<int, cTVDBEpisode*>::iterator hit = episodes.find(episodeId);
+ if (hit != episodes.end()) {
+ cTVDBEpisode *e = hit->second;
+ cTVDBMedia *m = new cTVDBMedia();
+ m->width = width;
+ m->height = height;
+ m->path = path;
+ m->mediaType = msEpisodePic;
+ e->episodeImage = m;
+ }
+}
+
+void cTVDBSeries::InsertActor(cTVDBActor *actor) {
+ actors.insert(pair<int, cTVDBActor*>(actor->id, actor));
+}
+
+void cTVDBSeries::InsertActorThumb(int actorId, int imgWidth, int imgHeight, string path) {
+ map<int, cTVDBActor*>::iterator hit = actors.find(actorId);
+ if (hit != actors.end()) {
+ cTVDBActor *a = hit->second;
+ cTVDBMedia *m = new cTVDBMedia();
+ m->width = imgWidth;
+ m->height = imgHeight;
+ m->path = path;
+ m->mediaType = msActorThumb;
+ a->actorThumb = m;
+ }
+}
+
+void cTVDBSeries::InsertMedia(int mediaType, int imgWidth, int imgHeight, string path, int season) {
+ cTVDBMedia *media = new cTVDBMedia();
+ media->width = imgWidth;
+ media->height = imgHeight;
+ media->path = path;
+ media->mediaType = mediaType;
+ switch (mediaType) {
+ case msPoster1:
+ case msPoster2:
+ case msPoster3:
+ posters.push_back(media);
+ break;
+ case msFanart1:
+ case msFanart2:
+ case msFanart3:
+ fanart.push_back(media);
+ break;
+ case msBanner1:
+ case msBanner2:
+ case msBanner3:
+ banners.push_back(media);
+ case msSeasonPoster:
+ seasonPosters.insert(pair<int, cTVDBMedia*>(season, media));
+ break;
+ case msPosterThumb:
+ posterThumb = media;
+ break;
+ case msSeasonPosterThumb:
+ seasonPosterThumbs.insert(pair<int, cTVDBMedia*>(season, media));
+ break;
+ default:
+ break;
+ }
+}
+
+void cTVDBSeries::GetEpisode(int episodeId, cEpisode *e) {
+ map<int, cTVDBEpisode*>::iterator hit = episodes.find(episodeId);
+ if (hit == episodes.end())
+ return;
+ cTVDBEpisode *eStored = hit->second;
+ e->number = eStored->number;
+ e->season = eStored->season;
+ e->name = eStored->name;
+ e->firstAired = eStored->firstAired;
+ e->guestStars = eStored->guestStars;
+ e->overview = eStored->overview;
+ e->rating = eStored->rating;
+ if (eStored->episodeImage) {
+ e->episodeImage.path = eStored->episodeImage->path;
+ e->episodeImage.width = eStored->episodeImage->width;
+ e->episodeImage.height = eStored->episodeImage->height;
+ }
+}
+
+void cTVDBSeries::GetPosters(vector<cTvMedia> *p) {
+ for (vector<cTVDBMedia*>::iterator it = posters.begin(); it != posters.end(); it++) {
+ cTVDBMedia *mStored = *it;
+ cTvMedia m;
+ m.path = mStored->path;
+ m.width = mStored->width;
+ m.height = mStored->height;
+ p->push_back(m);
+ }
+}
+
+bool cTVDBSeries::GetPoster(cTvMedia *p) {
+ if (posters.size() > 0) {
+ p->path = posters[0]->path;
+ p->width = posters[0]->width;
+ p->height = posters[0]->height;
+ return true;
+ }
+ return false;
+}
+
+bool cTVDBSeries::GetPosterThumb(cTvMedia *p) {
+ if (posterThumb) {
+ p->path = posterThumb->path;
+ p->width = posterThumb->width;
+ p->height = posterThumb->height;
+ return true;
+ }
+ return false;
+}
+
+void cTVDBSeries::GetBanners(vector<cTvMedia> *b) {
+ for (vector<cTVDBMedia*>::iterator it = banners.begin(); it != banners.end(); it++) {
+ cTVDBMedia *bStored = *it;
+ cTvMedia m;
+ m.path = bStored->path;
+ m.width = bStored->width;
+ m.height = bStored->height;
+ b->push_back(m);
+ }
+}
+
+bool cTVDBSeries::GetRandomBanner(cTvMedia *b) {
+ int numBanners = banners.size();
+ if (numBanners == 0)
+ return false;
+ srand((unsigned)time(NULL));
+ int banner = rand()%numBanners;
+ cTVDBMedia *bStored = banners[banner];
+ b->path = bStored->path;
+ b->width = bStored->width;
+ b->height = bStored->height;
+ return true;
+}
+
+void cTVDBSeries::GetFanart(vector<cTvMedia> *f) {
+ for (vector<cTVDBMedia*>::iterator it = fanart.begin(); it != fanart.end(); it++) {
+ cTVDBMedia *fStored = *it;
+ cTvMedia m;
+ m.path = fStored->path;
+ m.width = fStored->width;
+ m.height = fStored->height;
+ f->push_back(m);
+ }
+}
+
+void cTVDBSeries::GetSeasonPoster(int episodeId, cTvMedia *sp) {
+ map<int, cTVDBEpisode*>::iterator hit = episodes.find(episodeId);
+ if (hit == episodes.end())
+ return;
+ cTVDBEpisode *e = hit->second;
+ map<int, cTVDBMedia*>::iterator hit2 = seasonPosters.find(e->season);
+ if (hit2 == seasonPosters.end())
+ return;
+ cTVDBMedia *spStored = hit2->second;
+ sp->width = spStored->width;
+ sp->height = spStored->height;
+ sp->path = spStored->path;
+}
+
+void cTVDBSeries::GetActors(vector<cActor> *a) {
+ for (map<int, cTVDBActor*>::iterator it = actors.begin(); it != actors.end(); it++) {
+ cTVDBActor *aStored = it->second;
+ cActor act;
+ act.name = aStored->name;
+ act.role = aStored->role;
+ if (aStored->actorThumb) {
+ act.actorThumb.width = aStored->actorThumb->width;
+ act.actorThumb.height = aStored->actorThumb->height;
+ act.actorThumb.path = aStored->actorThumb->path;
+ }
+ a->push_back(act);
+ }
+}
+
+void cTVDBSeries::Dump(void) {
+ tell(0, "--------------------------- Series Info ----------------------------------");
+ tell(0, "series %s, ID: %d", name.c_str(), id);
+ tell(0, "Overview: %s", overview.c_str());
+ tell(0, "FirstAired: %s", firstAired.c_str());
+ tell(0, "Network: %s", network.c_str());
+ tell(0, "Status: %s", status.c_str());
+ tell(0, "Genre: %s", genre.c_str());
+ tell(0, "Rating: %f", rating);
+ tell(0, "--------------------------- Media ----------------------------------");
+ for (vector<cTVDBMedia*>::iterator it = posters.begin(); it != posters.end(); it++) {
+ cTVDBMedia *m = *it;
+ tell(0, "Poster %d, Path: %s", m->mediaType, m->path.c_str());
+ tell(0, "width %d, height %d", m->width, m->height);
+ }
+ for (vector<cTVDBMedia*>::iterator it = banners.begin(); it != banners.end(); it++) {
+ cTVDBMedia *m = *it;
+ tell(0, "Banner %d, Path: %s", m->mediaType, m->path.c_str());
+ tell(0, "width %d, height %d", m->width, m->height);
+ }
+ for (vector<cTVDBMedia*>::iterator it = fanart.begin(); it != fanart.end(); it++) {
+ cTVDBMedia *m = *it;
+ tell(0, "Fanart %d, Path: %s", m->mediaType, m->path.c_str());
+ tell(0, "width %d, height %d", m->width, m->height);
+ }
+ tell(0, "--------------------------- Episodes ----------------------------------");
+ for (map<int, cTVDBEpisode*>::iterator it = episodes.begin(); it != episodes.end(); it++) {
+ cTVDBEpisode *e = it->second;
+ tell(0, "Episode %d, Name: %s", e->id, e->name.c_str());
+ if (e->episodeImage) {
+ tell(0, "Episode Image: %d x %d, Path: %s", e->episodeImage->width, e->episodeImage->height, e->episodeImage->path.c_str());
+ }
+ }
+ tell(0, "--------------------------- Season Posters ----------------------------------");
+ for (map<int, cTVDBMedia*>::iterator it = seasonPosters.begin(); it != seasonPosters.end(); it++) {
+ int season = it->first;
+ cTVDBMedia *m = it->second;
+ tell(0, "Season %d, %d x %d, Path: %s", season, m->width, m->height, m->path.c_str());
+ }
+ tell(0, "--------------------------- Actors ----------------------------------");
+ for (map<int, cTVDBActor*>::iterator it = actors.begin(); it != actors.end(); it++) {
+ cTVDBActor *a = it->second;
+ tell(0, "Actor %d, Name: %s, Role %s", a->id, a->name.c_str(), a->role.c_str());
+ if (a->actorThumb) {
+ tell(0, "Thumb: %d x %d, Path: %s", a->actorThumb->width, a->actorThumb->height, a->actorThumb->path.c_str());
+ }
+ }
+ if (posterThumb) {
+ tell(0, "posterThumb path %s, width %d, height %d", posterThumb->path.c_str(), posterThumb->width, posterThumb->height);
+ }
+}
diff --git a/tvdbseries.h b/tvdbseries.h new file mode 100644 index 0000000..7d44929 --- /dev/null +++ b/tvdbseries.h @@ -0,0 +1,143 @@ +#ifndef __TVSCRAPER_TVDBSERIES_H
+#define __TVSCRAPER_TVDBSERIES_H
+
+#include <iostream>
+#include <string>
+#include <vector>
+#include <map>
+#include <set>
+#include <utility>
+#include <algorithm>
+#include "services.h"
+
+using namespace std;
+
+enum mediaSeries {
+ msBanner1,
+ msBanner2,
+ msBanner3,
+ msPoster1,
+ msPoster2,
+ msPoster3,
+ msSeasonPoster,
+ msFanart1,
+ msFanart2,
+ msFanart3,
+ msEpisodePic,
+ msActorThumb,
+ msPosterThumb,
+ msSeasonPosterThumb,
+};
+
+// --- cTVDBMedia -------------------------------------------------------------
+class cTVDBMedia {
+public:
+ cTVDBMedia(void) {
+ path = "";
+ mediaType = msBanner1;
+ width = 0;
+ height = 0;
+ };
+ ~cTVDBMedia(void) {
+ };
+ string path;
+ int mediaType;
+ int width;
+ int height;
+};
+
+// --- cTVDBEpisode -------------------------------------------------------------
+class cTVDBEpisode {
+public:
+ cTVDBEpisode(void) {
+ id = 0;
+ number = 0;
+ season = 0;
+ name = "";
+ firstAired = "";
+ guestStars = "";
+ overview = "";
+ rating = 0.0;
+ episodeImage = NULL;
+ };
+ ~cTVDBEpisode(void) {
+ if (episodeImage)
+ delete episodeImage;
+ };
+ int id;
+ int number;
+ int season;
+ string name;
+ string firstAired;
+ string guestStars;
+ string overview;
+ float rating;
+ cTVDBMedia *episodeImage;
+};
+
+// --- cTVDBActor -------------------------------------------------------------
+class cTVDBActor {
+public:
+ cTVDBActor(void) {
+ id = 0;
+ name = "";
+ role = "";
+ thumbWidth = 0;
+ thumbHeight = 0;
+ actorThumb = NULL;
+ };
+ ~cTVDBActor(void) {
+ if (actorThumb)
+ delete actorThumb;
+ };
+ int id;
+ string name;
+ string role;
+ int thumbWidth;
+ int thumbHeight;
+ cTVDBMedia *actorThumb;
+};
+
+// --- cTVDBSeries -------------------------------------------------------------
+
+class cTVDBSeries {
+private:
+ map<int, cTVDBEpisode*> episodes;
+ map<int, cTVDBActor*> actors;
+ vector<cTVDBMedia*> posters;
+ vector<cTVDBMedia*> banners;
+ vector<cTVDBMedia*> fanart;
+ map<int, cTVDBMedia*> seasonPosters;
+ map<int, cTVDBMedia*> seasonPosterThumbs;
+ cTVDBMedia *posterThumb;
+public:
+ cTVDBSeries(void);
+ virtual ~cTVDBSeries(void);
+ int id;
+ string name;
+ string overview;
+ string firstAired;
+ string network;
+ string genre;
+ float rating;
+ string status;
+ void InsertEpisode(cTVDBEpisode *episode);
+ void InsertEpisodeImage(int episodeId, int width, int height, string path);
+ void InsertActor(cTVDBActor *actor);
+ void InsertActorThumb(int actorId, int imgWidth, int imgHeight, string path);
+ void InsertMedia(int mediaType, int imgWidth, int imgHeight, string path, int season = 0);
+ //Getter for Serivice Calls
+ void GetEpisode(int episodeId, cEpisode *e);
+ void GetPosters(vector<cTvMedia> *p);
+ bool GetPoster(cTvMedia *p);
+ bool GetPosterThumb(cTvMedia *p);
+ void GetBanners(vector<cTvMedia> *b);
+ bool GetRandomBanner(cTvMedia *b);
+ void GetFanart(vector<cTvMedia> *f);
+ void GetSeasonPoster(int episodeId, cTvMedia *sp);
+ void GetActors(vector<cActor> *a);
+ void Dump(void);
+};
+
+
+#endif //__TVSCRAPER_TVDBSERIES_H
diff --git a/update.c b/update.c new file mode 100644 index 0000000..8c56055 --- /dev/null +++ b/update.c @@ -0,0 +1,1323 @@ +#define __STL_CONFIG_H +#include <locale.h> + +#include <vdr/videodir.h> +#include <vdr/tools.h> +#include <vdr/plugin.h> + +#include "config.h" +#include "tools.h" +#include "update.h" + +extern cScraper2VdrConfig config; + +cUpdate::cUpdate(cScrapManager *manager) : cThread("update thread started") { + connection = NULL; + vdrDb = NULL; + tEvents = NULL; + tSeries = NULL; + tEpisodes = NULL; + tSeriesMedia = NULL; + tSeriesActors = NULL; + tMovies = NULL; + tMovieActor = NULL; + tMovieActors = NULL; + tMovieMedia = NULL; + tRecordings = NULL; + scrapManager = manager; + imgPathSeries = config.imageDir + "/series"; + imgPathMovies = config.imageDir + "/movies"; + lastScrap = 0; + forceUpdate = false; + forceRecordingUpdate = false; + forceVideoDirUpdate = false; + forceScrapInfoUpdate = false; + forceCleanupRecordingDb = false; + char* lang; + lang = setlocale(LC_CTYPE, 0); + if (lang) { + tell(0, "Set locale to '%s'", lang); + if ((strcasestr(lang, "UTF-8") != 0) || (strcasestr(lang, "UTF8") != 0)){ + tell(0, "detected UTF-8"); + withutf8 = yes; + } + } else { + tell(0, "Reseting locale for LC_CTYPE failed."); + } + cDbConnection::setEncoding(withutf8 ? "utf8": "latin1"); + cDbConnection::setHost(config.mysqlHost.c_str()); + cDbConnection::setPort(config.mysqlPort); + cDbConnection::setName(config.mysqlDBName.c_str()); + cDbConnection::setUser(config.mysqlDBUser.c_str()); + cDbConnection::setPass(config.mysqlDBPass.c_str()); + cDbTable::setConfPath(cPlugin::ConfigDirectory("epg2vdr/")); +} + +cUpdate::~cUpdate() { + if (loopActive) + Stop(); + if (vdrDb) + delete vdrDb; + if (tEvents) + delete tEvents; + if (tSeries) + delete tSeries; + if (tEpisodes) + tEpisodes; + if (tSeriesMedia) + delete tSeriesMedia; + if (tSeriesActors) + delete tSeriesActors; + if (tMovies) + delete tMovies; + if (tMovieActor) + delete tMovieActor; + if (tMovieActors) + delete tMovieActors; + if (tMovieMedia) + delete tMovieMedia; + if (tRecordings) + delete tRecordings; + exitDb(); +} + +int cUpdate::initDb() { + int status = success; + if (!connection) + connection = new cDbConnection(); + if (!connection) + return fail; + vdrDb = new cTableVdrs(connection); + if (vdrDb->open() != success) + return fail; + tEvents = new cTableEvents(connection); + if (tEvents->open() != success) + return fail; + tSeries = new cTableSeries(connection); + if (tSeries->open() != success) + return fail; + tEpisodes = new cTableSeriesEpisode(connection); + if (tEpisodes->open() != success) + return fail; + tSeriesMedia = new cTableSeriesMedia(connection); + if (tSeriesMedia->open() != success) + return fail; + tSeriesActors = new cTableSeriesActor(connection); + if (tSeriesActors->open() != success) + return fail; + tMovies = new cTableMovies(connection); + if (tMovies->open() != success) + return fail; + tMovieActor = new cTableMovieActor(connection); + if (tMovieActor->open() != success) + return fail; + tMovieActors = new cTableMovieActors(connection); + if (tMovieActors->open() != success) + return fail; + tMovieMedia = new cTableMovieMedia(connection); + if (tMovieMedia->open() != success) + return fail; + tRecordings = new cTableRecordings(connection); + if (tRecordings->open() != success) + return fail; + return status; +} + +int cUpdate::exitDb() { + delete connection; + connection = 0; + return done; +} + +void cUpdate::Stop() { + loopActive = false; + waitCondition.Broadcast(); + Cancel(3); +} + +int cUpdate::CheckConnection(int& timeout) { + static int retry = 0; + timeout = retry < 5 ? 10 : 60; + // check connection + if (!dbConnected(yes)) { + // try to connect + tell(0, "Trying to re-connect to database!"); + retry++; + if (initDb() != success) { + tell(0, "Retry #%d failed, retrying in %d seconds!", retry, timeout); + exitDb(); + return fail; + } + retry = 0; + tell(0, "Connection established successfull!"); + } + return success; +} + +bool cUpdate::CheckEpgdBusy(void) { + vdrDb->clear(); + vdrDb->setValue(cTableVdrs::fiUuid, EPGDNAME); + if (vdrDb->find()) { + Es::State epgdState = cEpgdState::toState(vdrDb->getStrValue(cTableVdrs::fiState)); + if (epgdState >= cEpgdState::esBusy) + return true; + } + return false; +} + +int cUpdate::ReadScrapedEvents(void) { + int status = success; + cDbStatement *select = new cDbStatement(tEvents); + select->build("select "); + select->bind(cTableEvents::fiEventId, cDBS::bndOut); + select->bind(cTableEvents::fiChannelId, cDBS::bndOut, ", "); + select->bind(cTableEvents::fiUseId, cDBS::bndOut, ", "); + select->bind(cTableEvents::fiScrSeriesId, cDBS::bndOut, ", "); + select->bind(cTableEvents::fiScrSeriesEpisode, cDBS::bndOut, ", "); + select->bind(cTableEvents::fiScrMovieId, cDBS::bndOut, ", "); + select->bind(cTableEvents::fiScrSp, cDBS::bndOut, ", "); + select->build(" from %s where ", tEvents->TableName()); + select->build(" ((%s is not null and %s > 0) ", + tEvents->getField(cTableEvents::fiScrSeriesId)->name, + tEvents->getField(cTableEvents::fiScrSeriesId)->name); + select->build(" or (%s is not null and %s > 0)) ", + tEvents->getField(cTableEvents::fiScrMovieId)->name, + tEvents->getField(cTableEvents::fiScrMovieId)->name); + if (lastScrap > 0) { + select->build(" and %s > %d", + tEvents->getField(cTableEvents::fiScrSp)->name, + lastScrap); + } + status += select->prepare(); + if (status != success) { + delete select; + return 0; + } + + int eventId = 0; + int seriesId = 0; + int episodeId = 0; + int movieId = 0; + string channelId = ""; + + int numNew = 0; + for (int res = select->find(); res; res = select->fetch()) { + eventId = tEvents->getIntValue(cTableEvents::fiUseId); + channelId = tEvents->getStrValue(cTableEvents::fiChannelId); + seriesId = tEvents->getIntValue(cTableEvents::fiScrSeriesId); + episodeId = tEvents->getIntValue(cTableEvents::fiScrSeriesEpisode); + movieId = tEvents->getIntValue(cTableEvents::fiScrMovieId); + scrapManager->AddEvent(eventId, channelId, seriesId, episodeId, movieId); + lastScrap = max(lastScrap, (int)tEvents->getIntValue(cTableEvents::fiScrSp)); + numNew++; + } + select->freeResult(); + delete select; + return numNew; +} + +//*************************************************************************** +// SERIES +//*************************************************************************** + +int cUpdate::ReadSeries(bool isRec) { + scrapManager->InitIterator(isRec); + int seriesId = 0; + int episodeId = 0; + + if (!CreateDirectory(config.imageDir)) + return 0; + if (!CreateDirectory(imgPathSeries)) + return 0; + + bool isNew = false; + int numNew = 0; + while (scrapManager->GetNextSeries(isRec, seriesId, episodeId)) { + cTVDBSeries *series = scrapManager->GetSeries(seriesId); + if (!series) { + tSeries->clear(); + tSeries->setValue(cTableSeries::fiSeriesId, seriesId); + int res = tSeries->find(); + if (res) { + series = scrapManager->AddSeries(tSeries); + } + isNew = true; + } else { + isNew = false; + } + if (series) { + stringstream sPath; + sPath << imgPathSeries << "/" << seriesId; + string seriesPath = sPath.str(); + if (episodeId) { + ReadEpisode(episodeId, series, seriesPath); + } + if (isNew) { + ReadSeriesActors(series, seriesPath); + LoadSeriesMedia(series, seriesPath); + } + } + numNew++; + } + return numNew; +} + +void cUpdate::ReadEpisode(int episodeId, cTVDBSeries *series, string path) { + int status = success; + tEpisodes->clear(); + tEpisodes->setValue(cTableSeriesEpisode::fiEpisodeId, episodeId); + cDbStatement *selectEpisode = new cDbStatement(tEpisodes); + selectEpisode->build("select "); + selectEpisode->bind(cTableSeriesEpisode::fiEpisodeName, cDBS::bndOut); + selectEpisode->bind(cTableSeriesEpisode::fiEpisodeNumber, cDBS::bndOut, ", "); + selectEpisode->bind(cTableSeriesEpisode::fiSeasonNumber, cDBS::bndOut, ", "); + selectEpisode->bind(cTableSeriesEpisode::fiEpisodeOverview, cDBS::bndOut, ", "); + selectEpisode->bind(cTableSeriesEpisode::fiEpisodeFirstAired, cDBS::bndOut, ", "); + selectEpisode->bind(cTableSeriesEpisode::fiEpisodeGuestStars, cDBS::bndOut, ", "); + selectEpisode->bind(cTableSeriesEpisode::fiEpisodeRating, cDBS::bndOut, ", "); + selectEpisode->build(" from %s where ", tEpisodes->TableName()); + selectEpisode->bind(cTableSeriesEpisode::fiEpisodeId, cDBS::bndIn | cDBS::bndSet); + status += selectEpisode->prepare(); + if (status != success) { + delete selectEpisode; + return; + } + int res = selectEpisode->find(); + if (res) { + scrapManager->AddSeriesEpisode(series, tEpisodes); + LoadEpisodeImage(series, episodeId, path); + int season = tEpisodes->getIntValue(cTableSeriesEpisode::fiSeasonNumber); + if (season > 0) + LoadSeasonPoster(series, season, path); + } + selectEpisode->freeResult(); + delete selectEpisode; + return; +} + +void cUpdate::LoadEpisodeImage(cTVDBSeries *series, int episodeId, string path) { + int status = success; + stringstream iPath; + iPath << path << "/" << "episode_" << episodeId << ".jpg"; + string imgPath = iPath.str(); + bool imgExists = FileExists(imgPath); + if (!CreateDirectory(path)) + return; + tSeriesMedia->clear(); + cDbValue imageSize; + cDBS::FieldDef imageSizeDef = { "media_content", cDBS::ffUInt, 0, 999, cDBS::ftData }; + imageSize.setField(&imageSizeDef); + cDbStatement *selectImg = new cDbStatement(tSeriesMedia); + selectImg->build("select "); + selectImg->bind(cTableSeriesMedia::fiMediaWidth, cDBS::bndOut); + selectImg->bind(cTableSeriesMedia::fiMediaHeight, cDBS::bndOut, ", "); + if (!imgExists) { + selectImg->bind(cTableSeriesMedia::fiMediaContent, cDBS::bndOut, ", "); + selectImg->build(", length("); + selectImg->bind(&imageSize, cDBS::bndOut); + selectImg->build(")"); + } + selectImg->build(" from %s where ", tSeriesMedia->TableName()); + selectImg->bind(cTableSeriesMedia::fiSeriesId, cDBS::bndIn | cDBS::bndSet); + selectImg->bind(cTableSeriesMedia::fiEpisodeId, cDBS::bndIn | cDBS::bndSet, " and "); + status += selectImg->prepare(); + if (status != success) { + delete selectImg; + return; + } + tSeriesMedia->setValue(cTableSeriesMedia::fiSeriesId, series->id); + tSeriesMedia->setValue(cTableSeriesMedia::fiEpisodeId, episodeId); + int res = selectImg->find(); + if (res) { + if (!imgExists) { + int size = imageSize.getIntValue(); + if (FILE* fh = fopen(imgPath.c_str(), "w")) { + fwrite(tSeriesMedia->getStrValue(cTableSeriesMedia::fiMediaContent), 1, size, fh); + fclose(fh); + } + } + int imgWidth = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaWidth); + int imgHeight = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaHeight); + series->InsertEpisodeImage(episodeId, imgWidth, imgHeight, imgPath); + } + selectImg->freeResult(); + delete selectImg; +} + +void cUpdate::LoadSeasonPoster(cTVDBSeries *series, int season, string path) { + int status = success; + stringstream iPath; + iPath << path << "/" << "season_" << season << ".jpg"; + stringstream tPath; + tPath << path << "/" << "season_" << season << "_thumb.jpg"; + string imgPath = iPath.str(); + string thumbPath = tPath.str(); + bool imgExists = FileExists(imgPath); + if (!CreateDirectory(path)) + return; + tSeriesMedia->clear(); + cDbValue imageSize; + cDBS::FieldDef imageSizeDef = { "media_content", cDBS::ffUInt, 0, 999, cDBS::ftData }; + imageSize.setField(&imageSizeDef); + cDbStatement *selectImg = new cDbStatement(tSeriesMedia); + selectImg->build("select "); + selectImg->bind(cTableSeriesMedia::fiMediaWidth, cDBS::bndOut); + selectImg->bind(cTableSeriesMedia::fiMediaHeight, cDBS::bndOut, ", "); + if (!imgExists) { + selectImg->bind(cTableSeriesMedia::fiMediaContent, cDBS::bndOut, ", "); + selectImg->build(", length("); + selectImg->bind(&imageSize, cDBS::bndOut); + selectImg->build(")"); + } + selectImg->build(" from %s where ", tSeriesMedia->TableName()); + selectImg->bind(cTableSeriesMedia::fiSeriesId, cDBS::bndIn | cDBS::bndSet); + selectImg->bind(cTableSeriesMedia::fiSeasonNumber, cDBS::bndIn | cDBS::bndSet, " and "); + selectImg->bind(cTableSeriesMedia::fiMediaType, cDBS::bndIn | cDBS::bndSet, " and "); + status += selectImg->prepare(); + if (status != success) { + delete selectImg; + return; + } + tSeriesMedia->setValue(cTableSeriesMedia::fiSeriesId, series->id); + tSeriesMedia->setValue(cTableSeriesMedia::fiSeasonNumber, season); + tSeriesMedia->setValue(cTableSeriesMedia::fiMediaType, msSeasonPoster); + int res = selectImg->find(); + if (res) { + if (!imgExists) { + int size = imageSize.getIntValue(); + if (FILE* fh = fopen(imgPath.c_str(), "w")) { + fwrite(tSeriesMedia->getStrValue(cTableSeriesMedia::fiMediaContent), 1, size, fh); + fclose(fh); + } + } + int imgWidth = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaWidth); + int imgHeight = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaHeight); + if (!FileExists(thumbPath)) { + CreateThumbnail(imgPath, thumbPath, imgWidth, imgHeight, 2); + } + series->InsertMedia(msSeasonPoster, imgWidth, imgHeight, imgPath, season); + series->InsertMedia(msSeasonPosterThumb, imgWidth/2, imgHeight/2, thumbPath, season); + } + selectImg->freeResult(); + delete selectImg; +} + +void cUpdate::ReadSeriesActors(cTVDBSeries *series, string path) { + int status = success; + tSeriesActors->clear(); + cDbValue series_id; + series_id.setField(tSeriesMedia->getField(cTableSeriesMedia::fiSeriesId)); + series_id.setValue(series->id); + cDbStatement *selectActors = new cDbStatement(tSeriesActors); + selectActors->build("select "); + selectActors->setBindPrefix("series_actor."); + selectActors->bind(cTableSeriesActor::fiActorId, cDBS::bndOut); + selectActors->bind(cTableSeriesActor::fiActorName, cDBS::bndOut, ", "); + selectActors->bind(cTableSeriesActor::fiActorRole, cDBS::bndOut, ", "); + selectActors->clrBindPrefix(); + selectActors->build(" from %s, %s where ", tSeriesActors->TableName(), tSeriesMedia->TableName()); + selectActors->build(" %s.%s = %s.%s ", tSeriesActors->TableName(), + tSeriesActors->getField(cTableSeriesActor::fiActorId)->name, + tSeriesMedia->TableName(), + tSeriesMedia->getField(cTableSeriesMedia::fiActorId)->name); + selectActors->setBindPrefix("series_media."); + selectActors->bind(&series_id, cDBS::bndIn | cDBS::bndSet, " and "); + selectActors->build(" order by %s, %s asc", tSeriesActors->getField(cTableSeriesActor::fiSortOrder)->name, + tSeriesActors->getField(cTableSeriesActor::fiActorRole)->name); + status += selectActors->prepare(); + if (status != success) { + delete selectActors; + return; + } + for (int res = selectActors->find(); res; res = selectActors->fetch()) { + scrapManager->AddSeriesActor(series, tSeriesActors); + LoadSeriesActorThumb(series, tSeriesActors->getIntValue(cTableSeriesActor::fiActorId), path); + } + selectActors->freeResult(); + delete selectActors; +} + +void cUpdate::LoadSeriesActorThumb(cTVDBSeries *series, int actorId, string path) { + int status = success; + stringstream iPath; + iPath << path << "/" << "actor_" << actorId << ".jpg"; + string imgPath = iPath.str(); + bool imgExists = FileExists(imgPath); + if (!CreateDirectory(path)) + return; + cDbValue imageSize; + cDBS::FieldDef imageSizeDef = { "media_content", cDBS::ffUInt, 0, 999, cDBS::ftData }; + imageSize.setField(&imageSizeDef); + tSeriesMedia->clear(); + cDbStatement *selectActorThumbs = new cDbStatement(tSeriesMedia); + selectActorThumbs->build("select "); + selectActorThumbs->bind(cTableSeriesMedia::fiMediaWidth, cDBS::bndOut); + selectActorThumbs->bind(cTableSeriesMedia::fiMediaHeight, cDBS::bndOut, ", "); + if (!imgExists) { + selectActorThumbs->bind(cTableSeriesMedia::fiMediaContent, cDBS::bndOut, ", "); + selectActorThumbs->build(", length("); + selectActorThumbs->bind(&imageSize, cDBS::bndOut); + selectActorThumbs->build(")"); + } + selectActorThumbs->build(" from %s where ", tSeriesMedia->TableName()); + selectActorThumbs->bind(cTableSeriesMedia::fiActorId, cDBS::bndIn | cDBS::bndSet); + status += selectActorThumbs->prepare(); + if (status != success) { + delete selectActorThumbs; + return; + } + tSeriesMedia->setValue(cTableSeriesMedia::fiActorId, actorId); + int res = selectActorThumbs->find(); + if (res) { + if (!imgExists) { + int size = imageSize.getIntValue(); + if (FILE* fh = fopen(imgPath.c_str(), "w")) { + fwrite(tSeriesMedia->getStrValue(cTableSeriesMedia::fiMediaContent), 1, size, fh); + fclose(fh); + } + } + int tmbWidth = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaWidth); + int tmbHeight = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaHeight); + series->InsertActorThumb(actorId, tmbWidth, tmbHeight, imgPath); + } + selectActorThumbs->freeResult(); + delete selectActorThumbs; +} + +void cUpdate::LoadSeriesMedia(cTVDBSeries *series, string path) { + int status = success; + tSeriesMedia->clear(); + cDbStatement *selectImg = new cDbStatement(tSeriesMedia); + selectImg->build("select "); + selectImg->bind(cTableSeriesMedia::fiMediaWidth, cDBS::bndOut); + selectImg->bind(cTableSeriesMedia::fiMediaHeight, cDBS::bndOut, ", "); + selectImg->bind(cTableSeriesMedia::fiMediaType, cDBS::bndOut, ", "); + selectImg->build(" from %s where ", tSeriesMedia->TableName()); + selectImg->bind(cTableSeriesMedia::fiSeriesId, cDBS::bndIn | cDBS::bndSet); + selectImg->build(" and %s in (%d, %d, %d, %d, %d, %d, %d, %d, %d)", + tSeriesMedia->getField(cTableSeriesMedia::fiMediaType)->name, + msPoster1, msPoster2, msPoster3, + msFanart1, msFanart2, msFanart3, + msBanner1, msBanner2, msBanner3); + status += selectImg->prepare(); + if (status != success) { + delete selectImg; + return; + } + tSeriesMedia->setValue(cTableSeriesMedia::fiSeriesId, series->id); + for (int res = selectImg->find(); res; res = selectImg->fetch()) { + int mediaType = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaType); + int mediaWidth = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaWidth); + int mediaHeight = tSeriesMedia->getIntValue(cTableSeriesMedia::fiMediaHeight); + string mediaPath = LoadMediaSeries(series->id, mediaType, path, mediaWidth, mediaHeight); + series->InsertMedia(mediaType, mediaWidth, mediaHeight, mediaPath); + if (mediaType == msPoster1) { + string thumbPath = path + "/poster_thumb.jpg"; + series->InsertMedia(msPosterThumb, mediaWidth/5, mediaHeight/5, thumbPath); + } + } + selectImg->freeResult(); + delete selectImg; +} + +string cUpdate::LoadMediaSeries(int seriesId, int mediaType, string path, int width, int height) { + int status = success; + stringstream iPath; + iPath << path << "/"; + bool createThumb = false; + stringstream tPath; + tPath << path << "/"; + switch (mediaType) { + case msPoster1: + iPath << "poster1.jpg"; + createThumb = true; + tPath << "poster_thumb.jpg"; + break; + case msPoster2: + iPath << "poster2.jpg"; + break; + case msPoster3: + iPath << "poster3.jpg"; + break; + case msFanart1: + iPath << "fanart1.jpg"; + break; + case msFanart2: + iPath << "fanart2.jpg"; + break; + case msFanart3: + iPath << "fanart3.jpg"; + break; + case msBanner1: + iPath << "banner1.jpg"; + break; + case msBanner2: + iPath << "banner2.jpg"; + break; + case msBanner3: + iPath << "banner3.jpg"; + break; + default: + break; + } + string imgPath = iPath.str(); + string thumbPath = tPath.str(); + if (FileExists(imgPath)) { + if (createThumb && !FileExists(thumbPath)) { + CreateThumbnail(imgPath, thumbPath, width, height, 5); + } + return imgPath; + } + if (!CreateDirectory(path)) + return ""; + tSeriesMedia->clear(); + cDbValue imageSize; + cDBS::FieldDef imageSizeDef = { "media_content", cDBS::ffUInt, 0, 999, cDBS::ftData }; + imageSize.setField(&imageSizeDef); + cDbStatement *selectImg = new cDbStatement(tSeriesMedia); + selectImg->build("select "); + selectImg->bind(cTableSeriesMedia::fiMediaContent, cDBS::bndOut); + selectImg->build(", length("); + selectImg->bind(&imageSize, cDBS::bndOut); + selectImg->build(")"); + selectImg->build(" from %s where ", tSeriesMedia->TableName()); + selectImg->bind(cTableSeriesMedia::fiSeriesId, cDBS::bndIn | cDBS::bndSet); + selectImg->bind(cTableSeriesMedia::fiMediaType, cDBS::bndIn | cDBS::bndSet, " and "); + status += selectImg->prepare(); + if (status != success) { + delete selectImg; + return ""; + } + tSeriesMedia->setValue(cTableSeriesMedia::fiSeriesId, seriesId); + tSeriesMedia->setValue(cTableSeriesMedia::fiMediaType, mediaType); + int res = selectImg->find(); + if (res) { + int size = imageSize.getIntValue(); + if (FILE* fh = fopen(imgPath.c_str(), "w")) { + fwrite(tSeriesMedia->getStrValue(cTableSeriesMedia::fiMediaContent), 1, size, fh); + fclose(fh); + } + if (createThumb && !FileExists(thumbPath)) { + CreateThumbnail(imgPath, thumbPath, width, height, 5); + } + } + selectImg->freeResult(); + delete selectImg; + return imgPath; +} + +//*************************************************************************** +// MOVIES +//*************************************************************************** + +int cUpdate::ReadMovies(bool isRec) { + scrapManager->InitIterator(isRec); + int movieId = 0; + int i=0; + + if (!CreateDirectory(config.imageDir)) + return 0; + if (!CreateDirectory(imgPathMovies)) + return 0; + + int numNew = 0; + while (scrapManager->GetNextMovie(isRec, movieId)) { + cMovieDbMovie *movie = scrapManager->GetMovie(movieId); + if (movie) + continue; + tMovies->clear(); + tMovies->setValue(cTableMovies::fiMovieId, movieId); + int res = tMovies->find(); + if (!res) + continue; + movie = scrapManager->AddMovie(tMovies); + stringstream mPath; + mPath << imgPathMovies << "/" << movieId; + string moviePath = mPath.str(); + ReadMovieActors(movie); + LoadMovieActorThumbs(movie); + LoadMovieMedia(movie, moviePath); + numNew++; + } + return numNew; +} + +void cUpdate::ReadMovieActors(cMovieDbMovie *movie) { + int status = success; + cDbValue actorRole; + cDbValue actorMovie; + cDbValue thbWidth; + cDbValue thbHeight; + actorRole.setField(tMovieActors->getField(cTableMovieActors::fiRole)); + actorMovie.setField(tMovieActors->getField(cTableMovieActors::fiMovieId)); + thbWidth.setField(tMovieMedia->getField(cTableMovieMedia::fiMediaWidth)); + thbHeight.setField(tMovieMedia->getField(cTableMovieMedia::fiMediaHeight)); + cDbStatement *selectActors = new cDbStatement(tMovieActor); + selectActors->build("select "); + selectActors->setBindPrefix("act."); + selectActors->bind(cTableMovieActor::fiActorId, cDBS::bndOut); + selectActors->bind(cTableMovieActor::fiActorName, cDBS::bndOut, ", "); + selectActors->setBindPrefix("role."); + selectActors->bind(&actorRole, cDBS::bndOut, ", "); + selectActors->setBindPrefix("thumb."); + selectActors->bind(&thbWidth, cDBS::bndOut, ", "); + selectActors->bind(&thbHeight, cDBS::bndOut, ", "); + selectActors->clrBindPrefix(); + selectActors->build(" from %s act, %s role, %s thumb where ", + tMovieActor->TableName(), tMovieActors->TableName(), tMovieMedia->TableName()); + selectActors->build("act.%s = role.%s ", + tMovieActor->getField(cTableMovieActor::fiActorId)->name, + tMovieActors->getField(cTableMovieActors::fiActorId)->name); + selectActors->build(" and role.%s = thumb.%s ", + tMovieActors->getField(cTableMovieActors::fiActorId)->name, + tMovieMedia->getField(cTableMovieMedia::fiActorId)->name); + selectActors->setBindPrefix("role."); + selectActors->bind(&actorMovie, cDBS::bndIn | cDBS::bndSet, " and "); + status += selectActors->prepare(); + if (status != success) { + delete selectActors; + return; + } + actorMovie.setValue(movie->id); + for (int res = selectActors->find(); res; res = selectActors->fetch()) { + scrapManager->AddMovieActor(movie, tMovieActor, actorRole.getStrValue()); + int tmbWidth = thbWidth.getIntValue(); + int tmbHeight = thbHeight.getIntValue(); + movie->SetActorThumbSize(tMovieActor->getIntValue(cTableMovieActor::fiActorId), tmbWidth, tmbHeight); + } + selectActors->freeResult(); + delete selectActors; +} + +void cUpdate::LoadMovieActorThumbs(cMovieDbMovie *movie) { + int status = success; + cDbValue imageSize; + cDBS::FieldDef imageSizeDef = { "media_content", cDBS::ffUInt, 0, 999, cDBS::ftData }; + imageSize.setField(&imageSizeDef); + tMovieMedia->clear(); + cDbStatement *selectActorThumbs = new cDbStatement(tMovieMedia); + selectActorThumbs->build("select "); + selectActorThumbs->bind(cTableMovieMedia::fiMediaContent, cDBS::bndOut); + selectActorThumbs->build(", length("); + selectActorThumbs->bind(&imageSize, cDBS::bndOut); + selectActorThumbs->build(")"); + selectActorThumbs->build(" from %s where ", tMovieMedia->TableName()); + selectActorThumbs->bind(cTableMovieMedia::fiActorId, cDBS::bndIn | cDBS::bndSet); + status += selectActorThumbs->prepare(); + if (status != success) { + delete selectActorThumbs; + return; + } + string movieActorsPath = imgPathMovies + "/actors"; + if (!CreateDirectory(movieActorsPath)) + return; + + vector<int> IDs = movie->GetActorIDs(); + for (vector<int>::iterator it = IDs.begin(); it != IDs.end(); it++) { + int actorId = (int)*it; + stringstream tName; + tName << "actor_" << actorId << ".jpg"; + string thumbName = tName.str(); + string thumbFullPath = movieActorsPath + "/" + thumbName; + if (!FileExists(thumbFullPath)) { + tMovieMedia->setValue(cTableMovieMedia::fiActorId, actorId); + int res = selectActorThumbs->find(); + if (res) { + int size = imageSize.getIntValue(); + if (FILE* fh = fopen(thumbFullPath.c_str(), "w")) { + fwrite(tMovieMedia->getStrValue(cTableMovieMedia::fiMediaContent), 1, size, fh); + fclose(fh); + } + movie->SetActorPath(actorId, thumbFullPath); + } + } else { + movie->SetActorPath(actorId, thumbFullPath); + } + } + selectActorThumbs->freeResult(); + delete selectActorThumbs; +} + +void cUpdate::LoadMovieMedia(cMovieDbMovie *movie, string moviePath) { + int status = success; + tMovieMedia->clear(); + cDbStatement *selectImg = new cDbStatement(tMovieMedia); + selectImg->build("select "); + selectImg->bind(cTableMovieMedia::fiMediaWidth, cDBS::bndOut); + selectImg->bind(cTableMovieMedia::fiMediaHeight, cDBS::bndOut, ", "); + selectImg->bind(cTableMovieMedia::fiMediaType, cDBS::bndOut, ", "); + selectImg->build(" from %s where ", tMovieMedia->TableName()); + selectImg->bind(cTableMovieMedia::fiMovieId, cDBS::bndIn | cDBS::bndSet); + selectImg->build(" and %s in (%d, %d, %d, %d)", + tMovieMedia->getField(cTableMovieMedia::fiMediaType)->name, + mmPoster, + mmFanart, + mmCollectionPoster, + mmCollectionFanart); + status += selectImg->prepare(); + if (status != success) { + delete selectImg; + return; + } + tMovieMedia->setValue(cTableMovieMedia::fiMovieId, movie->id); + for (int res = selectImg->find(); res; res = selectImg->fetch()) { + int mediaType = tMovieMedia->getIntValue(cTableMovieMedia::fiMediaType); + int mediaWidth = tMovieMedia->getIntValue(cTableMovieMedia::fiMediaWidth); + int mediaHeight = tMovieMedia->getIntValue(cTableMovieMedia::fiMediaHeight); + string imgPath = LoadMediaMovie(movie->id, mediaType, moviePath, mediaWidth, mediaHeight); + if (imgPath.size() > 0) + scrapManager->AddMovieMedia(movie, tMovieMedia, imgPath); + if (mediaType == mmPoster) { + cMovieMedia *m = new cMovieMedia(); + m->mediaType = mmPosterThumb; + m->width = mediaWidth/4; + m->height = mediaHeight/4; + m->path = moviePath + "/poster_thumb.jpg"; + movie->InsertMedia(m); + } + + } + selectImg->freeResult(); + delete selectImg; +} + +string cUpdate::LoadMediaMovie(int movieId, int mediaType, string path, int width, int height) { + int status = success; + stringstream iPath; + iPath << path << "/"; + bool createThumb = false; + stringstream tPath; + tPath << path << "/"; + switch (mediaType) { + case mmPoster: + iPath << "poster.jpg"; + createThumb = true; + tPath << "poster_thumb.jpg"; + break; + case mmFanart: + iPath << "fanart.jpg"; + break; + case mmCollectionPoster: + iPath << "collectionPoster.jpg"; + break; + case mmCollectionFanart: + iPath << "collectionFanart.jpg"; + break; + default: + break; + } + string imgPath = iPath.str(); + string thumbPath = tPath.str(); + if (FileExists(imgPath)) { + if (createThumb && !FileExists(thumbPath)) { + CreateThumbnail(imgPath, thumbPath, width, height, 4); + } + return imgPath; + } + if (!CreateDirectory(path)) + return imgPath; + tMovieMedia->clear(); + cDbValue imageSize; + cDBS::FieldDef imageSizeDef = { "media_content", cDBS::ffUInt, 0, 999, cDBS::ftData }; + imageSize.setField(&imageSizeDef); + cDbStatement *selectImg = new cDbStatement(tMovieMedia); + selectImg->build("select "); + selectImg->bind(cTableMovieMedia::fiMediaContent, cDBS::bndOut); + selectImg->build(", length("); + selectImg->bind(&imageSize, cDBS::bndOut); + selectImg->build(")"); + selectImg->build(" from %s where ", tMovieMedia->TableName()); + selectImg->bind(cTableMovieMedia::fiMovieId, cDBS::bndIn | cDBS::bndSet); + selectImg->bind(cTableMovieMedia::fiMediaType, cDBS::bndIn | cDBS::bndSet, " and "); + status += selectImg->prepare(); + if (status != success) { + delete selectImg; + return ""; + } + tMovieMedia->setValue(cTableMovieMedia::fiMovieId, movieId); + tMovieMedia->setValue(cTableMovieMedia::fiMediaType, mediaType); + int res = selectImg->find(); + if (res) { + int size = imageSize.getIntValue(); + if (FILE* fh = fopen(imgPath.c_str(), "w")) { + fwrite(tMovieMedia->getStrValue(cTableMovieMedia::fiMediaContent), 1, size, fh); + fclose(fh); + } + if (createThumb && !FileExists(thumbPath)) { + CreateThumbnail(imgPath, thumbPath, width, height, 4); + } + } + selectImg->freeResult(); + delete selectImg; + return imgPath; +} + +//*************************************************************************** +// RECORDINGS +//*************************************************************************** +int cUpdate::ReadRecordings(void) { + int status = success; + cDbStatement *select = new cDbStatement(tRecordings); + select->build("select "); + select->bind(cTableRecordings::fiRecPath, cDBS::bndOut); + select->bind(cTableRecordings::fiRecStart, cDBS::bndOut, ", "); + select->bind(cTableRecordings::fiMovieId, cDBS::bndOut, ", "); + select->bind(cTableRecordings::fiSeriesId, cDBS::bndOut, ", "); + select->bind(cTableRecordings::fiEpisodeId, cDBS::bndOut, ", "); + select->build(" from %s where ", tRecordings->TableName()); + select->bind(cTableRecordings::fiUuid, cDBS::bndIn | cDBS::bndSet); + select->build(" and %s = 0", tRecordings->getField(cTableRecordings::fiScrapNew)->name); + + status += select->prepare(); + if (status != success) { + delete select; + return 0; + } + + tRecordings->clear(); + tRecordings->setValue(cTableRecordings::fiUuid, config.uuid.c_str()); + int numRecs = 0; + for (int res = select->find(); res; res = select->fetch()) { + int recStart = tRecordings->getIntValue(cTableRecordings::fiRecStart); + string recPath = tRecordings->getStrValue(cTableRecordings::fiRecPath); + int movieId = tRecordings->getIntValue(cTableRecordings::fiMovieId); + int seriesId = tRecordings->getIntValue(cTableRecordings::fiSeriesId); + int episodeId = tRecordings->getIntValue(cTableRecordings::fiEpisodeId); + bool isNew = scrapManager->AddRecording(recStart, recPath, seriesId, episodeId, movieId); + if (isNew) + numRecs++; + } + select->freeResult(); + delete select; + return numRecs; +} + +int cUpdate::ScanVideoDir(void) { + int newRecs = 0; + for (cRecording *rec = Recordings.First(); rec; rec = Recordings.Next(rec)) { + string recPath = rec->FileName(); + int recStart = rec->Start(); + if (!scrapManager->RecordingExists(recStart, recPath)) { + newRecs++; + int scrapInfoMovieID = 0; + int scrapInfoSeriesID = 0; + int scrapInfoEpisodeID = 0; + ReadScrapInfo(rec->FileName(), scrapInfoMovieID, scrapInfoSeriesID, scrapInfoEpisodeID); + int eventId = 0; + string channelId = ""; + string title = *(rec->BaseName()); + string subTitle = ""; + const cRecordingInfo *recInfo = rec->Info(); + if (recInfo) { + const cEvent *recEvent = recInfo->GetEvent(); + if (recEvent) { + eventId = recEvent->EventID(); + channelId = *(recInfo->ChannelID().ToString()); + subTitle = (recInfo->ShortText())?(recInfo->ShortText()):""; + } + } + tRecordings->clear(); + tRecordings->setValue(cTableRecordings::fiUuid, config.uuid.c_str()); + tRecordings->setValue(cTableRecordings::fiRecPath, recPath.c_str()); + tRecordings->setValue(cTableRecordings::fiRecStart, recStart); + + tRecordings->setValue(cTableRecordings::fiEventId, eventId); + tRecordings->setValue(cTableRecordings::fiChannelId, channelId.c_str()); + tRecordings->setValue(cTableRecordings::fiScrapInfoMovieId, scrapInfoMovieID); + tRecordings->setValue(cTableRecordings::fiScrapInfoSeriesId, scrapInfoSeriesID); + tRecordings->setValue(cTableRecordings::fiScrapInfoEpisodeId, scrapInfoEpisodeID); + tRecordings->setValue(cTableRecordings::fiScrapNew, 1); + tRecordings->setValue(cTableRecordings::fiRecTitle, title.c_str()); + tRecordings->setValue(cTableRecordings::fiRecSubTitle, subTitle.c_str()); + tRecordings->setValue(cTableRecordings::fiRecDuration, rec->LengthInSeconds()/60); + tRecordings->store(); + } + } + return newRecs; +} + +int cUpdate::ScanVideoDirScrapInfo(void) { + int numUpdated = 0; + for (cRecording *rec = Recordings.First(); rec; rec = Recordings.Next(rec)) { + int recStart = rec->Start(); + string recPath = rec->FileName(); + bool recExists = LoadRecording(recStart, recPath); + int scrapInfoMovieID = 0; + int scrapInfoSeriesID = 0; + int scrapInfoEpisodeID = 0; + ReadScrapInfo(rec->FileName(), scrapInfoMovieID, scrapInfoSeriesID, scrapInfoEpisodeID); + if (ScrapInfoChanged(scrapInfoMovieID, scrapInfoSeriesID, scrapInfoEpisodeID)) { + tRecordings->setValue(cTableRecordings::fiScrapNew, 1); + tRecordings->setValue(cTableRecordings::fiScrapInfoMovieId, scrapInfoMovieID); + tRecordings->setValue(cTableRecordings::fiScrapInfoSeriesId, scrapInfoSeriesID); + tRecordings->setValue(cTableRecordings::fiScrapInfoEpisodeId, scrapInfoEpisodeID); + tRecordings->update(); + numUpdated++; + } + } + return numUpdated; +} + +bool cUpdate::LoadRecording(int recStart, string recPath) { + tRecordings->clear(); + tRecordings->setValue(cTableRecordings::fiUuid, config.uuid.c_str()); + tRecordings->setValue(cTableRecordings::fiRecStart, recStart); + tRecordings->setValue(cTableRecordings::fiRecPath, recPath.c_str()); + int found = tRecordings->find(); + if (found == yes) { + return true; + } + return false; +} + +bool cUpdate::ScrapInfoChanged(int scrapInfoMovieID, int scrapInfoSeriesID, int scrapInfoEpisodeID) { + int movieIdCurrent = tRecordings->getIntValue(cTableRecordings::fiScrapInfoMovieId); + int seriesIdCurrent = tRecordings->getIntValue(cTableRecordings::fiScrapInfoSeriesId); + int episodeIdCurrent = tRecordings->getIntValue(cTableRecordings::fiScrapInfoEpisodeId); + if ((movieIdCurrent != scrapInfoMovieID) || + (seriesIdCurrent != scrapInfoSeriesID) || + (episodeIdCurrent != scrapInfoEpisodeID)) + return true; + return false; +} + +void cUpdate::ReadScrapInfo(string recDir, int &scrapInfoMovieID, int &scrapInfoSeriesID, int &scrapInfoEpisodeID) { + stringstream sInfoName; + sInfoName << recDir << "/" << config.recScrapInfoName; + string scrapInfoName = sInfoName.str(); + if (!FileExists(scrapInfoName, false)) { + string twoHigher = TwoFoldersHigher(recDir); + if (twoHigher.size() > 0) { + stringstream sInfoNameAlt; + sInfoNameAlt << twoHigher << "/" << config.recScrapInfoName; + scrapInfoName = sInfoNameAlt.str(); + if (!FileExists(scrapInfoName, false)) { + return; + } + } else + return; + } + vector<string> scrapInfoLines; + FILE *f = fopen(scrapInfoName.c_str(), "r"); + if (!f) + return; + cReadLine ReadLine; + char *line; + while ((line = ReadLine.Read(f)) != NULL) { + scrapInfoLines.push_back(line); + } + fclose(f); + int numLines = scrapInfoLines.size(); + if (numLines < 2) { + tell(0, "invalid scrapinfo file in %s", recDir.c_str()); + return; + } + for (int line=0; line < numLines; line++) { + scrapInfoLines[line] = trim(scrapInfoLines[line]); + toLower(scrapInfoLines[line]); + } + bool isMovie = false; + bool isSeries = false; + if (!scrapInfoLines[0].compare("movie")) + isMovie = true; + else if (!scrapInfoLines[0].compare("series")) + isSeries = true; + else + tell(0, "invalid scrapinfo file in %s", recDir.c_str()); + + int id1 = 0, id2 = 0; + string key = "", value = ""; + + splitstring s(scrapInfoLines[1].c_str()); + vector<string> flds = s.split('='); + if (flds.size() == 2) { + key = trim(flds[0]); + value = trim(flds[1]); + } else { + tell(0, "invalid scrapinfo file in %s", recDir.c_str()); + } + if (!key.compare("id")) { + id1 = atoi(value.c_str()); + if (numLines > 2) { + splitstring s2(scrapInfoLines[2].c_str()); + vector<string> flds2 = s2.split('='); + if (flds2.size() == 2) { + key = trim(flds2[0]); + toLower(key); + value = trim(flds2[1]); + } + if (!key.compare("episode")) { + id2 = atoi(value.c_str()); + } + } + } else + tell(0, "invalid scrapinfo file in %s", recDir.c_str()); + if (isSeries) { + scrapInfoSeriesID = id1; + scrapInfoEpisodeID = id2; + } + if (isMovie) { + scrapInfoMovieID = id1; + } +} + +//*************************************************************************** +// Cleanup +//*************************************************************************** + +int cUpdate::CleanupSeries(void) { + //read existing series in file system + vector<string> storedSeries; + DIR *dir; + struct dirent *entry; + if ((dir = opendir (imgPathSeries.c_str())) != NULL) { + while ((entry = readdir (dir)) != NULL) { + string dirName = entry->d_name; + if (isNumber(dirName)) { + storedSeries.push_back(dirName); + } + } + closedir (dir); + } else return 0; + int deletedSeries = 0; + //aviod complete delete if no series are available + int numSeriesAvailable = scrapManager->GetNumSeries(); + if (numSeriesAvailable == 0) + return 0; + for (vector<string>::iterator seriesDir = storedSeries.begin(); seriesDir != storedSeries.end(); seriesDir++) { + int seriesId = atoi(((string)*seriesDir).c_str()); + if (!scrapManager->SeriesInUse(seriesId)) { + string delDir = imgPathSeries + "/" + ((string)*seriesDir); + DeleteDirectory(delDir); + deletedSeries++; + } + } + return deletedSeries; +} + +int cUpdate::CleanupMovies(void) { + //read existing movies in file system + vector<string> storedMovies; + DIR *dir; + struct dirent *entry; + if ((dir = opendir (imgPathMovies.c_str())) != NULL) { + while ((entry = readdir (dir)) != NULL) { + string dirName = entry->d_name; + if (isNumber(dirName)) { + storedMovies.push_back(dirName); + } + } + closedir (dir); + } else return 0; + int deletedMovies = 0; + //aviod complete delete if no movies are available + int numMoviesAvailable = scrapManager->GetNumMovies(); + if (numMoviesAvailable == 0) + return 0; + for (vector<string>::iterator movieDir = storedMovies.begin(); movieDir != storedMovies.end(); movieDir++) { + int movieId = atoi(((string)*movieDir).c_str()); + if (!scrapManager->MovieInUse(movieId)) { + string delDir = imgPathMovies + "/" + ((string)*movieDir); + DeleteDirectory(delDir); + deletedMovies++; + } + } + return deletedMovies; +} + +int cUpdate::CleanupRecordings(void) { + //delete all not anymore existing recordings in database + int status = success; + cDbStatement *select = new cDbStatement(tRecordings); + select->build("select "); + select->bind(cTableRecordings::fiRecPath, cDBS::bndOut); + select->bind(cTableRecordings::fiRecStart, cDBS::bndOut, ", "); + select->build(" from %s where ", tRecordings->TableName()); + select->bind(cTableRecordings::fiUuid, cDBS::bndIn | cDBS::bndSet); + + status += select->prepare(); + if (status != success) { + delete select; + return 0; + } + + tRecordings->clear(); + tRecordings->setValue(cTableRecordings::fiUuid, config.uuid.c_str()); + int numRecsDeleted = 0; + for (int res = select->find(); res; res = select->fetch()) { + int recStart = tRecordings->getIntValue(cTableRecordings::fiRecStart); + string recPath = tRecordings->getStrValue(cTableRecordings::fiRecPath); + if (!Recordings.GetByName(recPath.c_str())) { + stringstream delWhere; + delWhere << "uuid = '" << config.uuid << "' and rec_path = '" << recPath << "' and rec_start = " << recStart; + tRecordings->deleteWhere(delWhere.str().c_str()); + numRecsDeleted++; + } + } + select->freeResult(); + delete select; + return numRecsDeleted; +} + + +//*************************************************************************** +// Action +//*************************************************************************** + +void cUpdate::Action() { + tell(0, "Update thread started (pid=%d)", getpid()); + mutex.Lock(); + loopActive = yes; + int sleep = 10; + int scanFreq = 60; + int scanNewRecFreq = 60 * 5; + int scanNewRecDBFreq = 60 * 5; + int cleanUpFreq = 60 * 5; + forceUpdate = true; + forceRecordingUpdate = true; + time_t lastScan = time(0); + time_t lastScanNewRec = time(0); + time_t lastScanNewRecDB = time(0); + time_t lastCleanup = time(0); + bool init = true; + while (loopActive && Running()) { + int reconnectTimeout; //set by checkConnection + if (CheckConnection(reconnectTimeout) != success) { + waitCondition.TimedWait(mutex, reconnectTimeout*1000); + continue; + } + //Update Events + if (!config.headless && (forceUpdate || (time(0) - lastScan > scanFreq))) { + if (!init && CheckEpgdBusy()) + continue; + int numNewEvents = ReadScrapedEvents(); + if (numNewEvents > 0) { + tell(0, "Loaded %d new scraped Events from Database", numNewEvents); + } else { + lastScan = time(0); + forceUpdate = false; + init = false; + continue; + } + tell(0, "Loading new Series and Episodes from Database..."); + time_t now = time(0); + int numNewSeries = ReadSeries(false); + int dur = time(0) - now; + tell(0, "Loaded %d new Series and Episodes in %ds from Database", numNewSeries, dur); + //scrapManager->DumpSeries(5); + + tell(0, "Loading new Movies from Database..."); + now = time(0); + int numNewMovies = ReadMovies(false); + dur = time(0) - now; + tell(0, "Loaded %d new Movies in %ds from Database", numNewMovies, dur); + //scrapManager->DumpMovies(5); + lastScan = time(0); + forceUpdate = false; + } + //Update Recordings from Database + if (forceRecordingUpdate || (time(0) - lastScanNewRecDB > scanNewRecDBFreq)) { + if (!init && CheckEpgdBusy()) + continue; + int numNewRecs = ReadRecordings(); + if (numNewRecs > 0) { + int numSeries = ReadSeries(true); + int numMovies = ReadMovies(true); + tell(0, "Loaded %d new Recordings from Database, %d series, %d movies", numNewRecs, numSeries, numMovies); + } + forceRecordingUpdate = false; + } + + //Scan new recordings + if (init || forceVideoDirUpdate || (time(0) - lastScanNewRec > scanNewRecFreq)) { + if (CheckEpgdBusy()) { + waitCondition.TimedWait(mutex, 1000); + continue; + } + static int recState = 0; + if (Recordings.StateChanged(recState)) { + tell(0, "Searching for new recordings because of Recordings State Change..."); + int newRecs = ScanVideoDir(); + tell(0, "found %d new recordings", newRecs); + } + lastScanNewRec = time(0); + forceVideoDirUpdate = false; + } + + init = false; + + //Scan Video dir for scrapinfo files + if (forceScrapInfoUpdate) { + if (CheckEpgdBusy()) { + tell(0, "epgd busy, try again in 1s..."); + waitCondition.TimedWait(mutex, 1000); + continue; + } + tell(0, "Checking for new or updated scrapinfo files in recordings..."); + int numUpdated = ScanVideoDirScrapInfo(); + tell(0, "found %d new or updated scrapinfo files", numUpdated); + forceScrapInfoUpdate = false; + } + + //Cleanup + if (time(0) - lastCleanup > cleanUpFreq) { + if (CheckEpgdBusy()) { + waitCondition.TimedWait(mutex, 1000); + continue; + } + int seriesDeleted = CleanupSeries(); + int moviesDeleted = CleanupMovies(); + if (seriesDeleted > 0 || moviesDeleted > 0) { + tell(0, "Deleted %d outdated series image folders", seriesDeleted); + tell(0, "Deleted %d outdated movie image folders", moviesDeleted); + } + lastCleanup = time(0); + } + + //Cleanup Recording DB + if (forceCleanupRecordingDb) { + if (CheckEpgdBusy()) { + waitCondition.TimedWait(mutex, 1000); + continue; + } + tell(0, "Cleaning up recordings in database..."); + int recsDeleted = CleanupRecordings(); + tell(0, "Deleted %d not anymore existing recordings in database", recsDeleted); + forceCleanupRecordingDb = false; + } + + waitCondition.TimedWait(mutex, sleep*1000); + + } + + loopActive = no; + tell(0, "Update thread ended (pid=%d)", getpid()); +} + +//*************************************************************************** +// External trigggering of Actions +//*************************************************************************** +void cUpdate::ForceUpdate(void) { + tell(0, "full update from database forced"); + forceUpdate = true; +} + +void cUpdate::ForceRecordingUpdate(void) { + tell(0, "scanning of recordings in database triggered"); + forceRecordingUpdate = true; +} + +void cUpdate::ForceVideoDirUpdate(void) { + tell(0, "scanning for new recordings in video directory triggered"); + forceVideoDirUpdate = true; +} + +void cUpdate::ForceScrapInfoUpdate(void) { + tell(0, "scanning of recording scrapinfo files triggered"); + forceScrapInfoUpdate = true; +} + +void cUpdate::TriggerCleanRecordingsDB(void) { + tell(0, "cleanup of recording DB triggered"); + forceCleanupRecordingDb = true; +} diff --git a/update.h b/update.h new file mode 100644 index 0000000..c529fda --- /dev/null +++ b/update.h @@ -0,0 +1,87 @@ +#ifndef __UPDATE_H +#define __UPDATE_H + +#include <mysql/mysql.h> +#include <map> + +#include <vdr/thread.h> +#include "lib/common.h" +#include "lib/db.h" +#include "lib/tabledef.h" +#include "scrapmanager.h" + +#define EPGDNAME "epgd" + +class cUpdate : public cThread { + private: + cScrapManager *scrapManager; + string imgPathSeries; + string imgPathMovies; + bool withutf8; + bool loopActive; + cDbConnection* connection; + cTableVdrs* vdrDb; + cTableEvents* tEvents; + cTableSeries* tSeries; + cTableSeriesEpisode* tEpisodes; + cTableSeriesMedia* tSeriesMedia; + cTableSeriesActor* tSeriesActors; + cTableMovies* tMovies; + cTableMovieActor* tMovieActor; + cTableMovieActors* tMovieActors; + cTableMovieMedia* tMovieMedia; + cTableRecordings* tRecordings; + int lastScrap; + cCondVar waitCondition; + cMutex mutex; + bool forceUpdate; + bool forceRecordingUpdate; + bool forceVideoDirUpdate; + bool forceScrapInfoUpdate; + bool forceCleanupRecordingDb; + int exitDb(); + int dbConnected(int force = no) { return connection && (!force || connection->check() == success); }; + int CheckConnection(int& timeout); + bool CheckEpgdBusy(void); + void Action(void); + int ReadScrapedEvents(void); + //SERIES + int ReadSeries(bool isRec); + void ReadEpisode(int episodeId, cTVDBSeries *series, string path); + void LoadEpisodeImage(cTVDBSeries *series, int episodeId, string path); + void LoadSeasonPoster(cTVDBSeries *series, int season, string path); + void ReadSeriesActors(cTVDBSeries *series, string path); + void LoadSeriesMedia(cTVDBSeries *series, string path); + string LoadMediaSeries(int seriesId, int mediaType, string path, int width, int height); + void LoadSeriesActorThumb(cTVDBSeries *series, int actorId, string path); + //MOVIES + int ReadMovies(bool isRec); + void ReadMovieActors(cMovieDbMovie *movie); + void LoadMovieActorThumbs(cMovieDbMovie *movie); + void LoadMovieMedia(cMovieDbMovie *movie, string moviePath); + string LoadMediaMovie(int movieId, int mediaType, string path, int width, int height); + //RECORDINGS + int ReadRecordings(void); + int ScanVideoDir(void); + int ScanVideoDirScrapInfo(void); + bool LoadRecording(int eventId, string recName); + bool ScrapInfoChanged(int scrapInfoMovieID, int scrapInfoSeriesID, int scrapInfoEpisodeID); + void ReadScrapInfo(string recDir, int &scrapInfoMovieID, int &scrapInfoSeriesID, int &scrapInfoEpisodeID); + //CLEANUP + int CleanupSeries(void); + int CleanupMovies(void); + int CleanupRecordings(void); + public: + cUpdate(cScrapManager *manager); + virtual ~cUpdate(void); + int initDb(); + void Stop(void); + void ForceUpdate(void); + void ForceRecordingUpdate(void); + void ForceVideoDirUpdate(void); + void ForceScrapInfoUpdate(void); + void TriggerCleanRecordingsDB(void); +}; + +//*************************************************************************** +#endif //__UPDATE_H |