diff options
45 files changed, 10387 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,16 @@ +VDR Plugin 'upnp' Revision History +---------------------------------- + +2009-10-23: Version 0.0.1-alpha1 + +- Fixed #185: Database rejected statements with single quotes inside strings + +2009-10-23: Version 0.0.1-alpha0 + +- Initial revision. +- Known limitations: currently only channel lists with less than 30 channels + work. +- LiveTV support +- Limited sort capabilities: Title, Creator, Write status, Publisher, + Description, Long description, Genre + Note: Sort will only work if this properties are present diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fa6d29b --- /dev/null +++ b/Makefile @@ -0,0 +1,141 @@ +# +# Makefile for a Video Disk Recorder plugin +# +# $Id$ + +# The official name of this plugin. +# This name will be used in the '-P...' option of VDR to load the plugin. +# By default the main source file also carries this name. +# IMPORTANT: the presence of this macro is important for the Make.config +# file. So it must be defined, even if it is not used here! +# +PLUGIN = upnp +COMMON = common.h + +### The version number of this plugin (taken from the main source file): + +VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).cpp | awk '{ print $$6 }' | sed -e 's/[";]//g') + +### The C++ compiler and options: + +CXX ?= g++ +CXXFLAGS ?= -fPIC -g -Wall -O2 -Wextra -Woverloaded-virtual -Wno-parentheses -Wl,-R/usr/local/lib + +### The directory environment: + +VDRDIR = ../../.. +LIBDIR = ../../lib +TMPDIR = /tmp + +WEBDIR = $(shell grep '\#define UPNP_DIR_PRESENTATION*' $(COMMON) | awk '{ print $$3 }' | sed -e 's/["/]//g') + +### Allow user defined options to overwrite defaults: + +-include $(VDRDIR)/Make.config + +#DESDIR = /var/lib/vdrdevel/plugins/$(PLUGIN) +DESDIR = $(CONFDIR)/plugins/$(PLUGIN) + +### The version number of VDR's plugin API (taken from VDR's "config.h"): + +APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h) + +### The name of the distribution archive: + +ARCHIVE = $(PLUGIN)-$(VERSION) +PACKAGE = vdr-$(ARCHIVE) + +### Includes and Defines (add further entries here): + +LIBS += -lupnp -lixml -lsqlite3 + +INCLUDES += -I$(VDRDIR)/include -I/usr/include \ + +DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' + +### The object files (add further files here): + +# Root folder +OBJS = $(PLUGIN).o \ + common.o \ + misc/menusetup.o \ + misc/util.o \ + misc/config.o \ + misc/search.o \ + database/database.o \ + database/metadata.o \ + database/object.o \ + database/resources.o \ + server/server.o \ + upnpcomponents/dlna.o \ + upnpcomponents/upnpwebserver.o \ + upnpcomponents/upnpservice.o \ + upnpcomponents/connectionmanager.o \ + upnpcomponents/contentdirectory.o \ + receiver/livereceiver.o \ + receiver/recplayer.o \ + receiver/filehandle.o \ + +### The main target: + +all: libvdr-$(PLUGIN).so i18n + +### Implicit rules: + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $(LIBS) -o $@ $< + +### Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies + +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.cpp) > $@ + +-include $(DEPFILE) + +### Internationalization (I18N): + +PODIR = po +LOCALEDIR = $(VDRDIR)/locale +I18Npo = $(wildcard $(PODIR)/*.po) +I18Nmsgs = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file)))))) +I18Npot = $(PODIR)/$(PLUGIN).pot + +%.mo: %.po + msgfmt -c -o $@ $< + +$(I18Npot): $(wildcard *.cpp) + xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --msgid-bugs-address='<see README>' -o $@ $^ + +%.po: $(I18Npot) + msgmerge -U --no-wrap --no-location --backup=none -q $@ $< + @touch $@ + +$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo + @mkdir -p $(dir $@) + cp $< $@ + +.PHONY: i18n +i18n: $(I18Nmsgs) $(I18Npot) + +### Targets: + +libvdr-$(PLUGIN).so: $(OBJS) + $(CXX) $(CXXFLAGS) $(LIBS) -shared $(OBJS) -o $@ -lc + @cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION) + @-rm -rf $(DESDIR)/$(WEBDIR) + @mkdir -p $(DESDIR)/$(WEBDIR) + @cp --remove-destination -r $(WEBDIR)/* $(DESDIR)/$(WEBDIR) + +dist: clean + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @mkdir $(TMPDIR)/$(ARCHIVE) + @cp -a * $(TMPDIR)/$(ARCHIVE) + @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE) + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @echo Distribution package created as $(PACKAGE).tgz + +clean: + @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ $(PODIR)/*.mo $(PODIR)/*.pot @@ -0,0 +1,74 @@ +This is a "plugin" for the Video Disk Recorder (VDR). + +Written by: Denis Loh <denis.loh@hftl.de> + Andreas Günther <andreas.guenther@hftl.de> + +Project's homepage: http://upnp.vdr-developer.org + +Latest version available at: http://upnp.vdr-developer.org + +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. + +Please note: This plugin is written under the terms of open source +redistribution by + +Hochschule für Telekommunikation Leipzig, +University of Applied Science +Gustav-Freytag-Straße 43-45 +04277 Leipzig +Germany +http://www.hftl.de + +All rights reserved. + +Description: + +UPnP/DLNA Plugin for Video Disk Recorder + +This Plugins extends the VDR with the possibility to act as an UPnP/DLNA Media +Server (DMS). It will serve VDR's contents in the network to any UPnP-AV and +DLNA capable devices. + +The plugin requires a valid network connection with a IPv4 address. It will find +its settings automatically by default. In this case, the first network device in +the network device list with a valid IPv4 address and port 49152 will be used +for connections. You can change this behavior either by setting the command line +options or by editing the setup via the VDRs setup page. The command line +options have a higher priority and substitute the menu settings. + +The command line settings are: + -i <interface> --int=<interface> The server network + interface + e.g: eth0, wlan1 etc. + If given option '-a' must + be absent. + -a <address> --address=<address> The server IPv4 address. + If given option '-i' must + be absent. + -p <port> --port=<port> The server port + Supported ports: + 0 (auto detect) + 49152-65535 (user defined) + +If not options are set, menu options will be used. + +The server has a unique identifier, which is +"uuid:b120ba52-d88d-4500-9b64-888971d83fd3". Other devices in the network can +find and identify the VDR UPnP Server with this ID. However, the server should +be found automatically and being listed under the supported media server +devices. If not, please report this as a bug on the projects homepage or send an +email to the developers of this plugin with the full device description and, if +applicable, the errors thrown by the media player device and the server. + +Dependencies: + +This plugin is tested with and requires the following libraries to work: + +libupnp-1.6.6 +libsqlite-3.6 + +libupnp-1.8.0 is known not to work with this plugin! diff --git a/channelmap.conf.example b/channelmap.conf.example new file mode 100644 index 0000000..6e541d5 --- /dev/null +++ b/channelmap.conf.example @@ -0,0 +1,30 @@ +##################################################### +# # +# VDR UPnP/DLNA Plugin # +# # +# Channel mapping # +# # +##################################################### +# +# Default behavior of the plugin is that it will load +# all channels from VDRs channels.conf and creates +# a Video Broadcast item for every single channel +# +# It can be controlled by combining or excluding +# certain channels in this configuration file +# +# Syntax: +# +# Each rule must be written on a separate line +# +# Combine multiple channels (at least 2) +# <ChannelID>:<ChannelID>[:<ChannelID>] +# +# Example: T-8468-514-516:C-8468-514-516:S-8468-514-516 +# This will combine three channels to one metadata item +# +# Exclude a channel from list +# -<ChannelID> +# +# Example: -T-8468-514-516 +# This will exclude that channel from loading diff --git a/common.cpp b/common.cpp new file mode 100644 index 0000000..7efe9ad --- /dev/null +++ b/common.cpp @@ -0,0 +1,29 @@ +/* + * File: common.cpp + * Author: savop + * + * Created on 17. April 2009, 20:53 + */ + +#include <stdarg.h> +#include "common.h" + +DLNAProfile DLNA_PROFILE_MPEG2_TS_SD_EU = { "MPEG2_TS_SD_EU", "video/mpeg" }; +DLNAProfile DLNA_PROFILE_AVC_TS_HD_EU = { "AVC_TS_HD_EU", "video/vnd.dlna.mpeg-tts" }; +DLNAProfile DLNA_PROFILE_MPEG1_L3 = { "MP3", "audio/mpeg" }; + +DLNAIconProfile DLNA_ICON_JPEG_SM_24 = { "image/jpeg", 48, 48, 24 }; +DLNAIconProfile DLNA_ICON_JPEG_LRG_24 = { "image/jpeg", 120, 120, 24 }; +DLNAIconProfile DLNA_ICON_PNG_SM_24A = { "image/png", 48, 48, 24 }; +DLNAIconProfile DLNA_ICON_PNG_LRG_24A = { "image/png", 120, 120, 24 }; + +#define MESSAGE_SIZE 256 + +void message(const char* File, int Line, const char* Format, ...){ + va_list ap; + char Message[MESSAGE_SIZE]; + snprintf(Message, MESSAGE_SIZE, "(%s:%d) %s\n", File, Line, Format); + va_start(ap, Format); + vprintf(Message, ap); + va_end(ap); +}
\ No newline at end of file diff --git a/common.h b/common.h new file mode 100644 index 0000000..191dd3a --- /dev/null +++ b/common.h @@ -0,0 +1,773 @@ +/* + * File: common.h + * Author: savop + * + * Created on 19. April 2009, 15:22 + */ + +#ifndef _COMMON_H +#define _COMMON_H + +#include "misc/util.h" +#include <libintl.h> +#include <string.h> +#include <vdr/tools.h> +#include <vdr/i18n.h> +#include <vdr/remux.h> + +/**************************************************** + * + * Table of contents + * + * This file includes all (or at least most) constant + * definitions for this plugin. As was growing very + * fast, I decided to insert this table of contents + * for faster navigations. However, you have to scroll + * on your own. + * + * 0. Global constants + * 1. VDR and the VDR subsystem + * 1.1 Versioning + * 1.2 Logging + * 1.3 Plugin constants + * 1.4 Plugin setup + * 2. UPnP + * 2.1 UPnP Namespaces + * 2.2 Directory hierarchy + * 2.3 internal Webserver + * 2.4 Device description + * 2.5 Connection Manager Service (CMS) + * 2.6 Content Directory Service (CDS) + * 2.7 UPnP AV Transport (AVT) + * 2.8 Media classes + * 2.9 Storage Media + * 2.10 Known Errors + * 2.11 Write Status + * 2.12 DIDL Properties + * 3. DLNA + * 3.1 Protocol Info Fields + * 3.2 Protocol Info Flags + * 3.3 Media Profiles + * 3.4 Container types + * 3.5 Device types + * 4. SQLite + * 4.1 Database setup + * + ****************************************************/ + +/**************************************************** + * + * 0. Global constants + * + ****************************************************/ + +#define IN +#define OUT +#define INOUT + +//#define DEBUG + +#define TOSTRING(s) #s + +#define FALSE 0 +#define TRUE 1 + +#define bool_t uint8_t + +/** + * Translation with gettext() + */ +#ifndef _ +#define _(s) tr(s) +#endif + +#define KB(i) i*1024 +#define MB(i) i*KB(1024) + +#define SIZEOF_UUID_STRING 37 // 00000000-0000-0000-0000-000000000000 = 32 + 4 + 1 + +#define strdup0(s) (s!=NULL?strdup(s):NULL) + +#define att(s) strchr(s,'@')!=NULL?strchr(s,'@')+1:NULL +#define prop(s) substr(s, 0, strchr(s,'@')-s) + +void message(const char* File, int Line, const char* Format, ...) __attribute__ ((format (printf, 3, 4))); + +/**************************************************** + * + * 1. VDR and the VDR subsystem + * + ****************************************************/ + +#define VDR_RECORDFILE_PATTERN_PES "%s/%03d.vdr" +#define VDR_RECORDFILE_PATTERN_TS "%s/%05d.ts" +#define VDR_MAX_FILES_PER_RECORDING 65535 +#define VDR_FILENAME_BUFSIZE 2048 + +/**************************************************** + * + * 1.1 Versioning + * + ****************************************************/ + +#define VERSION_INT(maj, min, mic) (maj<<16 | min<<8 | mic) +#define VERSION_DOT(maj, min, mic) maj ##.## min ##.## mic +#define VERSION_STR(maj, min, mic) TOSTRING(maj.min.mic) + +/* If any changes on the version number are commited, please change the version + * string in the main file "upnp.c" as well to avoid errors with the makefile */ +#define PLUGIN_VERSION_MAJOR 0 +#define PLUGIN_VERSION_MINOR 0 +#define PLUGIN_VERSION_MICRO 1 +/* The plugin version as dot-separated string */ +#define PLUGIN_VERSION VERSION_STR(PLUGIN_VERSION_MAJOR, \ + PLUGIN_VERSION_MINOR, \ + PLUGIN_VERSION_MICRO) +/* The plugin version as integer representation */ +#define PLUGIN_VERSION_INT VERSION_INT(PLUGIN_VERSION_MAJOR, \ + PLUGIN_VERSION_MINOR, \ + PLUGIN_VERSION_MICRO) + +/**************************************************** + * + * 1.2 Logging + * + ****************************************************/ + +/** + * Log errors + * + * Errors are critical problems which cannot handled by the server and needs + * the help by the user. + */ +#define ERROR(s...) esyslog("UPnP server error:" s) +/** + * Log warnings + * + * Warnings indicate problems with the server which can be handled + * by the server itself or are not critical to the servers functionality + */ +#define WARNING(s...) isyslog("UPnP server warning: " s) +/** + * Log messages + * + * Messages are additional information about the servers behavior. This will + * be useful for debugging. + */ +#ifdef DEBUG +#define MESSAGE(s...) message(__FILE__, __LINE__, "UPnP server message: " s) +#else +#define MESSAGE(s...) dsyslog("UPnP server message: " s) +#endif + +/**************************************************** + * + * 1.3 Plugin constants + * + ****************************************************/ + +/* The authors of the plugin */ +#define PLUGIN_AUTHORS "Andreas Günther, Denis Loh" +/* The web site of the plugin */ +#define PLUGIN_WEB_PAGE "http://upnp.methodus.de" +/* A small discription of the plugin, which is also used as the device description */ +#define PLUGIN_DESCRIPTION "UPnP/DLNA compliant Media Server functionality for VDR" +/* The short plugin name. This is used as the main menu of VDR */ +#define PLUGIN_SHORT_NAME "DLNA/UPnP" +/* A somewhat longer name, a.k.a device name */ +#define PLUGIN_NAME "VDR DLNA/UPnP Media Server" +/* Where the plugin can be downloaded */ +#define PLUGIN_DOWNLOAD_PAGE PLUGIN_WEB_PAGE + +/**************************************************** + * + * 1.4 Plugin setup + * + ****************************************************/ + +#define SETUP_SERVER_ENABLED "ServerEnabled" +#define SETUP_SERVER_INT "ServerInt" +#define SETUP_SERVER_PORT "ServerPort" +#define SETUP_SERVER_AUTO "ServerAutoDetect" +#define SETUP_SERVER_ADDRESS "ServerAddress" + +/* The server port range where the server interacts with clients */ +#define SERVER_MIN_PORT 49152 +#define SERVER_MAX_PORT 65535 + +#define RECEIVER_LIVEBUFFER_SIZE MB(1) +#define RECEIVER_OUTPUTBUFFER_SIZE MB(1) +#define RECEIVER_RINGBUFFER_MARGIN 10*TS_SIZE + +/**************************************************** + * + * 2. UPnP + * + ****************************************************/ + +/*The maximum size of the device description file + *must NOT exceed 20KB including HTTP headers + */ +#define UPNP_DEVICE_DESCRIPTION_MAX_LEN KB(20) +/* The maximum size of the SOAP requests */ +#define UPNP_SOAP_MAX_LEN KB(20) +/* The max age of announcements in seconds */ +#define UPNP_ANNOUNCE_MAX_AGE 1800 +/* Max resources per object including + * preview images and thumbnails + */ +#define UPNP_MAX_RESOURCES_PER_OBJECT 16 + +enum UPNP_RESOURCE_TYPES { + UPNP_RESOURCE_CHANNEL, + UPNP_RESOURCE_RECORDING, + UPNP_RESOURCE_FILE, + UPNP_RESOURCE_URL +}; + +/**************************************************** + * + * 2.1 Namespaces + * + ****************************************************/ + +#define UPNP_XMLNS_UPNP "urn:schemas-upnp-org:metadata-1-0/upnp/" +#define UPNP_XMLNS_DIDL "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite" +#define UPNP_XMLNS_DLNA_META "urn:schemas-dlna-org:metadata-1-0/" +#define UPNP_XMLNS_UPNP_DEV "urn:schemas-upnp-org:device-1-0" +#define UPNP_XMLNS_DLNA_DEV "urn:schemas-dlna-org:device-1-0" +#define UPNP_XMLNS_DUBLINCORE "http://purl.org/dc/elements/1.1/" + +#define UPNP_XMLNS_PREFIX_UPNP "upnp" +#define UPNP_XMLNS_PREFIX_DC "dc" +#define UPNP_XMLNS_PREFIX_DIDL "" +#define UPNP_XMLNS_PREFIX_DLNA "dlna" + +/**************************************************** + * + * 2.2 Directory hierarchy + * + ****************************************************/ + +#define UPNP_DIR_CONTROL "/control" +#define UPNP_DIR_EVENT "/event" +#define UPNP_DIR_XML "/xml" +#define UPNP_DIR_SHARES "/shares" +#define UPNP_DIR_PRESENTATION "/web" +#define UPNP_DIR_ICONS "/icons" + +/**************************************************** + * + * 2.3 internal webserver + * + ****************************************************/ + +#define UPNP_WEB_PRESENTATION_URL "/index.html" +#define UPNP_WEB_SERVER_ROOT_DIR UPNP_DIR_PRESENTATION + +enum UPNP_WEB_METHODS { + UPNP_WEB_METHOD_BROWSE, + UPNP_WEB_METHOD_SHOW, + UPNP_WEB_METHOD_STREAM, + UPNP_WEB_METHOD_SEARCH, + UPNP_WEB_METHOD_DOWNLOAD +}; + +/**************************************************** + * + * 2.4 Device description + * + ****************************************************/ + +/*The device type of the server*/ +#define UPNP_DEVICE_TYPE "urn:schemas-upnp-org:device:MediaServer:1" +/*Path to device description*/ +#define UPNP_DEVICE_DESCRIPTION_PATH UPNP_WEB_SERVER_ROOT_DIR "/ms_desc.xml" +/*Values to identify device and services*/ +#define UPNP_DEVICE_UDN "uuid:b120ba52-d88d-4500-9b64-888971d83fd3" +/* The friendly device name, human readable */ +#define UPNP_DEVICE_FRIENDLY_NAME PLUGIN_NAME +/* The guys who wrote the crap */ +#define UPNP_DEVICE_MANUFACTURER PLUGIN_AUTHORS +/* The website of the manufacturer, in this case the plugin website */ +#define UPNP_DEVICE_MANUFACTURER_URL PLUGIN_WEB_PAGE +/* There is just the one and only model of the plugin, the plugin itself */ +#define UPNP_DEVICE_MODEL_DESCRIPTION PLUGIN_DESCRIPTION +/* The plugin name... */ +#define UPNP_DEVICE_MODEL_NAME PLUGIN_NAME +/* The plugin version */ +#define UPNP_DEVICE_MODEL_NUMBER PLUGIN_VERSION +/* The website of the plugin, this might be different to the manufactures homepage + * and should redirect to a download mirror where the plugin can be obtained. + */ +#define UPNP_DEVICE_MODEL_URL PLUGIN_DOWNLOAD_PAGE +/* The serial number of the plugin. This is the integer value of the version */ +#define UPNP_DEVICE_SERIAL_NUMBER "VDR_DLNAUPNP_" PLUGIN_VERSION + +#define UPNP_DEVICE_ICON_JPEG_SM UPNP_DIR_ICONS "/upnpIconSm.jpeg" +#define UPNP_DEVICE_ICON_JPEG_LRG UPNP_DIR_ICONS "/upnpIconLrg.jpeg" +#define UPNP_DEVICE_ICON_PNG_SM UPNP_DIR_ICONS "/upnpIconSm.png" +#define UPNP_DEVICE_ICON_PNG_LRG UPNP_DIR_ICONS "/upnpIconLrg.png" + +/**************************************************** + * + * 2.5 DIDL Properties + * + ****************************************************/ + +#define UPNP_OBJECT_ITEM "item" +#define UPNP_OBJECT_CONTAINER "container" + +#define UPNP_PROP_OBJECTID "@id" +#define UPNP_PROP_PARENTID "@parentID" +#define UPNP_PROP_TITLE "dc:title" +#define UPNP_PROP_CREATOR "dc:creator" +#define UPNP_PROP_RESTRICTED "@restricted" +#define UPNP_PROP_WRITESTATUS "upnp:writeStatus" +#define UPNP_PROP_CLASS "upnp:class" +#define UPNP_PROP_CLASSNAME UPNP_PROP_CLASS "@name" +#define UPNP_PROP_SEARCHCLASS "upnp:searchClass" +#define UPNP_PROP_SCLASSDERIVED UPNP_PROP_SEARCHCLASS "@includeDerived" +#define UPNP_PROP_REFERENCEID UPNP_OBJECT_ITEM "@refID" +#define UPNP_PROP_SCLASSNAME UPNP_PROP_SEARCHCLASS "@name" +#define UPNP_PROP_SEARCHABLE UPNP_OBJECT_CONTAINER "@searchable" +#define UPNP_PROP_CHILDCOUNT UPNP_OBJECT_CONTAINER "@childcount" +#define UPNP_PROP_RESOURCE "res" +#define UPNP_PROP_PROTOCOLINFO UPNP_PROP_RESOURCE "@protocolInfo" +#define UPNP_PROP_SIZE UPNP_PROP_RESOURCE "@size" +#define UPNP_PROP_DURATION UPNP_PROP_RESOURCE "@duration" +#define UPNP_PROP_BITRATE UPNP_PROP_RESOURCE "@bitrate" +#define UPNP_PROP_SAMPLEFREQUENCE UPNP_PROP_RESOURCE "@sampleFreq" +#define UPNP_PROP_BITSPERSAMPLE UPNP_PROP_RESOURCE "@bitsPerSample" +#define UPNP_PROP_NOAUDIOCHANNELS UPNP_PROP_RESOURCE "@nrAudioChannels" +#define UPNP_PROP_COLORDEPTH UPNP_PROP_RESOURCE "@colorDepth" +#define UPNP_PROP_RESOLUTION UPNP_PROP_RESOURCE "@resolution" +#define UPNP_PROP_GENRE "upnp:genre" +#define UPNP_PROP_LONGDESCRIPTION "upnp:longDescription" +#define UPNP_PROP_PRODUCER "upnp:producer" +#define UPNP_PROP_RATING "upnp:rating" +#define UPNP_PROP_ACTOR "upnp:actor" +#define UPNP_PROP_DIRECTOR "upnp:director" +#define UPNP_PROP_DESCRIPTION "dc:description" +#define UPNP_PROP_PUBLISHER "dc:publisher" +#define UPNP_PROP_LANGUAGE "dc:language" +#define UPNP_PROP_RELATION "dc:relation" +#define UPNP_PROP_STORAGEMEDIUM "upnp:storageMedium" +#define UPNP_PROP_DVDREGIONCODE "upnp:DVDRegionCode" +#define UPNP_PROP_CHANNELNAME "upnp:channelName" +#define UPNP_PROP_SCHEDULEDSTARTTIME "upnp:scheduledStartTime" +#define UPNP_PROP_SCHEDULEDENDTIME "upnp:scheduledEndTime" +#define UPNP_PROP_ICON "upnp:icon" +#define UPNP_PROP_REGION "upnp:region" +#define UPNP_PROP_CHANNELNR "upnp:channelNr" +#define UPNP_PROP_RIGHTS "dc:rights" +#define UPNP_PROP_RADIOCALLSIGN "upnp:radioCallSign" +#define UPNP_PROP_RADIOSTATIONID "upnp:radioStationID" +#define UPNP_PROP_RADIOBAND "upnp:radioBand" +#define UPNP_PROP_CONTRIBUTOR "dc:contributor" +#define UPNP_PROP_DATE "dc:date" +#define UPNP_PROP_ALBUM "upnp:album" +#define UPNP_PROP_ARTIST "upnp:artist" +#define UPNP_PROP_DLNA_CONTAINERTYPE "dlna:container" + +#define UPNP_DIDL_SKELETON "<DIDL-Lite "\ + "xmlns:dc=\"" UPNP_XMLNS_DUBLINCORE "\" "\ + "xmlns:upnp=\"" UPNP_XMLNS_UPNP "\" "\ + "xmlns:dlna=\"" UPNP_XMLNS_DLNA_META "\" "\ + "xmlns=\"" UPNP_XMLNS_DIDL "\"></DIDL-Lite>" + +/**************************************************** + * + * 2.6 Connection Manager Service (CMS) + * + ****************************************************/ + +/*Path to service description of conection manager service*/ +#define UPNP_CMS_SCPD_URL UPNP_DIR_XML "/cms_scpd.xml" +#define UPNP_CMS_CONTROL_URL UPNP_DIR_CONTROL "/cms_control" +#define UPNP_CMS_EVENT_URL UPNP_DIR_EVENT "/cms_event" +#define UPNP_CMS_SERVICE_ID "urn:upnp-org:serviceId:ConnectionManager" +#define UPNP_CMS_SERVICE_TYPE "urn:schemas-upnp-org:service:ConnectionManager:1" + +/* Compatibility usage only --> See DLNA Profiles */ +#define UPNP_CMS_SUPPORTED_PROTOCOLS "http-get:*:video/mpeg:*," \ + "http-get:*:audio/mpeg:*" + +/**************************************************** + * + * The UPnP CMS actions + * + * This constant definitions represent all actions + * compliant with UPnP ConnectionManager:1 + * + ****************************************************/ + +#define UPNP_CMS_ACTION_GETPROTOCOLINFO "GetProtocolInfo" +#define UPNP_CMS_ACTION_GETCURRENTCONNECTIONIDS "GetCurrentConnectionIDs" +#define UPNP_CMS_ACTION_GETCURRENTCONNECTIONINFO "GetCurrentConnectionInfo" +#define UPNP_CMS_ACTION_PREPAREFORCONNECTION "PrepareForConnection" +#define UPNP_CMS_ACTION_CONNECTIONCOMPLETE "ConnectionComplete" + +/**************************************************** + * + * 2.7 Content Directory Service (CDS) + * + ****************************************************/ + +/*Path to service description of content directory service*/ +#define UPNP_CDS_SCPD_URL UPNP_DIR_XML "/cds_scpd.xml" +#define UPNP_CDS_CONTROL_URL UPNP_DIR_CONTROL "/cds_control" +#define UPNP_CDS_EVENT_URL UPNP_DIR_EVENT "/cds_event" +#define UPNP_CDS_SERVICE_ID "urn:upnp-org:serviceId:ContentDirectory" +#define UPNP_CDS_SERVICE_TYPE "urn:schemas-upnp-org:service:ContentDirectory:1" + +#define UPNP_CDS_SEARCH_CAPABILITIES "" +#define UPNP_CDS_SORT_CAPABILITIES UPNP_PROP_TITLE ","\ + UPNP_PROP_CREATOR ","\ + UPNP_PROP_WRITESTATUS ","\ + UPNP_PROP_DESCRIPTION ","\ + UPNP_PROP_GENRE ","\ + UPNP_PROP_LONGDESCRIPTION ","\ + UPNP_PROP_PUBLISHER + +/**************************************************** + * + * The UPnP CDS actions + * + * This constant definitions represent all actions + * compliant with UPnP ContentDirectory:1 + * + ****************************************************/ + +#define UPNP_CDS_ACTION_SEARCHCAPABILITIES "GetSearchCapabilities" +#define UPNP_CDS_ACTION_SORTCAPABILITIES "GetSortCapabilities" +#define UPNP_CDS_ACTION_SYSTEMUPDATEID "GetSystemUpdateID" +#define UPNP_CDS_ACTION_BROWSE "Browse" +#define UPNP_CDS_ACTION_SEARCH "Search" +#define UPNP_CDS_ACTION_CREATEOBJECT "CreateObject" +#define UPNP_CDS_ACTION_DESTROYOBJECT "DestroyObject" +#define UPNP_CDS_ACTION_UPDATEOBJECT "UpdateObject" +#define UPNP_CDS_ACTION_IMPORTRESOURCE "ImportResource" +#define UPNP_CDS_ACTION_EXPORTRESOURCE "ExportResource" +#define UPNP_CDS_ACTION_STOPTRANSFERRES "StopTransferResource" +#define UPNP_CDS_ACTION_TRANSFERPROGRESS "GetTransferProgress" +#define UPNP_CDS_ACTION_DELETERESOURCE "DeleteResource" +#define UPNP_CDS_ACTION_CREATEREFERENCE "CreateReference" + +/**************************************************** + * + * 2.8 UPnP AV Transport (AVT) + * + ****************************************************/ + +#define UPNP_AVT_SCPD_URL UPNP_DIR_XML "/avt_scpd.xml" +#define UPNP_AVT_CONTROL_URL UPNP_DIR_CONTROL "/avt_control" +#define UPNP_AVT_EVENT_URL UPNP_DIR_EVENT "/avt_event" +#define UPNP_AVT_SERVICE_ID "urn:upnp-org:serviceID:AVTransport" +#define UPNP_AVT_SERVICE_TYPE "urn:schemas-upnp-org:service:AVTransport:1" + +/**************************************************** + * + * The UPnP AVT actions + * + * This constant definitions represent all actions + * compliant with UPnP AVTransport:1 + * + ****************************************************/ + +/**************************************************** + * + * 2.9 Media classes + * + ****************************************************/ + +#define UPNP_CLASS_OBJECT "object" +#define UPNP_CLASS_ITEM UPNP_CLASS_OBJECT "." "item" +#define UPNP_CLASS_CONTAINER UPNP_CLASS_OBJECT "." "container" +#define UPNP_CLASS_IMAGE UPNP_CLASS_ITEM "." "imageItem" +#define UPNP_CLASS_AUDIO UPNP_CLASS_ITEM "." "audioItem" +#define UPNP_CLASS_VIDEO UPNP_CLASS_ITEM "." "videoItem" +#define UPNP_CLASS_PLAYLIST UPNP_CLASS_ITEM "." "playlistItem" +#define UPNP_CLASS_TEXT UPNP_CLASS_ITEM "." "textItem" +#define UPNP_CLASS_PHOTO UPNP_CLASS_IMAGE "." "photo" +#define UPNP_CLASS_MUSICTRACK UPNP_CLASS_AUDIO "." "musikTrack" +#define UPNP_CLASS_AUDIOBC UPNP_CLASS_AUDIO "." "audioBroadcast" +#define UPNP_CLASS_AUDIOBOOK UPNP_CLASS_AUDIO "." "audioBook" +#define UPNP_CLASS_MOVIE UPNP_CLASS_VIDEO "." "movie" +#define UPNP_CLASS_VIDEOBC UPNP_CLASS_VIDEO "." "videoBroadcast" +#define UPNP_CLASS_MUSICVIDCLIP UPNP_CLASS_VIDEO "." "musicVideoClip" +#define UPNP_CLASS_PERSON UPNP_CLASS_CONTAINER "." "person" +#define UPNP_CLASS_PLAYLISTCONT UPNP_CLASS_CONTAINER "." "playlistContainer" +#define UPNP_CLASS_ALBUM UPNP_CLASS_CONTAINER "." "album" +#define UPNP_CLASS_GENRE UPNP_CLASS_CONTAINER "." "genre" +#define UPNP_CLASS_STORAGESYS UPNP_CLASS_CONTAINER "." "storageSystem" +#define UPNP_CLASS_STORAGEVOL UPNP_CLASS_CONTAINER "." "storageVolume" +#define UPNP_CLASS_STORAGEFOLD UPNP_CLASS_CONTAINER "." "storageFolder" +#define UPNP_CLASS_MUSICARTIST UPNP_CLASS_PERSON "." "musicArtist" +#define UPNP_CLASS_MUSICALBUM UPNP_CLASS_ALBUM "." "musicAlbum" +#define UPNP_CLASS_PHOTOALBUM UPNP_CLASS_ALBUM "." "photoAlbum" +#define UPNP_CLASS_MUSICGENRE UPNP_CLASS_GENRE "." "musicGenre" +#define UPNP_CLASS_MOVIEGENRE UPNP_CLASS_GENRE "." "movieGenre" + +/**************************************************** + * + * 2.10 Storage media + * + ****************************************************/ + +enum UPnPStorageMedia { + SM_UNKNOWN, + SM_DV, + SM_MINI_DV, + SM_VHS, + SM_W_VHS, + SM_S_VHS, + SM_D_VHS, + SM_VHSC, + SM_VIDEO8, + SM_HI8, + SM_CD_ROM, + SM_CD_DA, + SM_CD_R, + SM_CD_RW, + SM_VIDEO_CD, + SM_SACD, + SM_MD_AUDIO, + SM_MD_PICTURE, + SM_DVD_ROM, + SM_DVD_VIDEO, + SM_DVD_R_MINUS, + SM_DVD_RW_PLUS, + SM_DVD_RW_MINUS, + SM_DVD_RAM, + SM_DVD_AUDIO, + SM_DAT, + SM_LD, + SM_HDD, + SM_MICRO_MV, + SM_NETWORK, + SM_NONE +}; + +/**************************************************** + * + * 2.11 Known Errors + * + ****************************************************/ + +/* Errors 401-404, 501 are already defined in + * Intel SDK, however 403 MUST NOT USED. + */ + +/****** 600 Common Action Errors ******/ + +#define UPNP_SOAP_E_ARGUMENT_INVALID 600 +#define UPNP_SOAP_E_ARGUMENT_OUT_OF_RANGE 601 +#define UPNP_SOAP_E_ACTION_NOT_IMPLEMENTED 602 +#define UPNP_SOAP_E_OUT_OF_MEMORY 603 +#define UPNP_SOAP_E_HUMAN_INTERVENTION 604 +#define UPNP_SOAP_E_STRING_TO_LONG 605 +#define UPNP_SOAP_E_NOT_AUTHORIZED 606 +#define UPNP_SOAP_E_SIGNATURE_FAILURE 607 +#define UPNP_SOAP_E_SIGNATURE_MISSING 608 +#define UPNP_SOAP_E_NOT_ENCRYPTED 609 +#define UPNP_SOAP_E_INVALID_SEQUENCE 610 +#define UPNP_SOAP_E_INVALID_CONTROL_URL 611 +#define UPNP_SOAP_E_NO_SUCH_SESSION 612 + +/****** 700 Action specific Errors ******/ + +#define UPNP_CDS_E_NO_SUCH_OBJECT 701 +#define UPNP_CDS_E_INVALID_CURRENT_TAG 702 +#define UPNP_CDS_E_INVALID_NEW_TAG 703 +#define UPNP_CDS_E_REQUIRED_TAG 704 +#define UPNP_CDS_E_READ_ONLY_TAG 705 +#define UPNP_CDS_E_PARAMETER_MISMATCH 706 +#define UPNP_CDS_E_INVALID_SEARCH_CRITERIA 708 +#define UPNP_CDS_E_INVALID_SORT_CRITERIA 709 +#define UPNP_CDS_E_NO_SUCH_CONTAINER 710 +#define UPNP_CDS_E_RESTRICTED_OBJECT 711 +#define UPNP_CDS_E_BAD_METADATA 712 +#define UPNP_CDS_E_RESTRICTED_PARENT 713 +#define UPNP_CDS_E_NO_SUCH_SOURCE_RESOURCE 714 +#define UPNP_CDS_E_RESOURCE_ACCESS_DENIED 715 +#define UPNP_CDS_E_TRANSFER_BUSY 716 +#define UPNP_CDS_E_NO_SUCH_FILE_TRANSFER 717 +#define UPNP_CDS_E_NO_SUCH_DESTINATION_RESOURCE 718 +#define UPNP_CDS_E_DEST_RESOURCE_ACCESS_DENIED 719 +#define UPNP_CDS_E_CANT_PROCESS_REQUEST 720 + +#define UPNP_CMS_E_INCOMPATIBLE_PROTOCOL_INFO 701 +#define UPNP_CMS_E_INCOMPATIBLE_DIRECTIONS 702 +#define UPNP_CMS_E_INSUFFICIENT_RESOURCES 703 +#define UPNP_CMS_E_LOCAL_RESTRICTIONS 704 +#define UPNP_CMS_E_ACCESS_DENIED 705 +#define UPNP_CMS_E_INVALID_CONNECTION_REFERENCE 706 +#define UPNP_CMS_E_NOT_IN_NETWORK 707 + +/**************************************************** + * + * 2.12 Write Status + * + ****************************************************/ + +enum UPnPWriteStatus { + WS_UNKNOWN=0, + WS_WRITABLE, + WS_PROTECTED, + WS_NOT_WRITABLE, + WS_MIXED +}; + +/**************************************************** + * + * 3. DLNA + * + ****************************************************/ + +#define DLNA_PROTOCOL_VERSION_MAJOR 1 +#define DLNA_PROTOCOL_VERSION_MINOR 5 +#define DLNA_PROTOCOL_VERSION_MICRO 0 + +#define DLNA_PROTOCOL_VERSION_INT VERSION_INT(DLNA_PROTOCOL_VERSION_MAJOR, \ + DLNA_PROTOCOL_VERSION_MINOR, \ + DLNA_PROTOCOL_VERSION_MICRO) + +#define DLNA_PROTOCOL_VERSION_STR VERSION_STR(DLNA_PROTOCOL_VERSION_MAJOR, \ + DLNA_PROTOCOL_VERSION_MINOR, \ + DLNA_PROTOCOL_VERSION_MICRO) + +/**************************************************** + * + * 3.1 Protocol info fields + * + ****************************************************/ + +/** + * ATTENTION + * + * The following operation field assumes that s0 is NOT changing. Only changes to sN are permitted. + * If s0 and/or sN changes these fields must be set to false. Use DLNA_FLAG_*_BASED_SEEK flags instead. + */ +#define DLNA_OPERATION_NONE 00 // No seek operations supported +#define DLNA_OPERATION_TIME_SEEK_RANGE 10 // is the server supporting time based seeks? +#define DLNA_OPERATION_RANGE 01 // or byte based seeks? + +#define DLNA_CONVERSION_TRANSCODED 1 // the content was converted from one media format to another +#define DLNA_CONVERSION_NONE 0 // the content is available without conversion + +#define DLNA_SUPPORTED_PLAYSPEEDS "2,4,8,-2,-4,-8"; // 1 is required, but omited in the PS parameter + +#define DLNA_TRANSFER_PROTOCOL_HTTP 1 // use http tranfer +#define DLNA_TRANSFER_PROTOCOL_RTP 2 // use rtp tranfer + +/**************************************************** + * + * 3.2 Protocol info flags + * + ****************************************************/ + +#define DLNA_FLAG_SENDER_PACED 1 << 31 // is the server setting the pace (i.e. RTP)? +#define DLNA_FLAG_TIME_BASED_SEEK 1 << 30 // is the server supporting time based seeks? +#define DLNA_FLAG_BYTE_BASED_SEEK 1 << 29 // or byte based seeking? +#define DLNA_FLAG_PLAY_CONTAINER 1 << 28 // is it possible to play all contents of a container? +#define DLNA_FLAG_S0_INCREASE 1 << 27 // is the beginning changing (time shift)? +#define DLNA_FLAG_SN_INCREASE 1 << 26 // is the end changing (live-TV)? +#define DLNA_FLAG_RTSP_PAUSE 1 << 25 // is pausing rtp streams permitted? +#define DLNA_FLAG_STREAMING_TRANSFER 1 << 24 // is the transfer a stream (Audio/AV)? +#define DLNA_FLAG_INTERACTIVE_TRANSFER 1 << 23 // is the transfer interactiv (printings)? +#define DLNA_FLAG_BACKGROUND_TRANSFER 1 << 22 // is the tranfer done in background (downloaded)? +#define DLNA_FLAG_CONNECTION_STALLING 1 << 21 // can the connection be paused on HTTP streams? +#define DLNA_FLAG_VERSION_1_5 1 << 20 // does the server complies with DLNA V1.5 +#define DLNA_FLAG_CLEARTEXT_CONTENT 1 << 16 // (Link Protection) currently not used +#define DLNA_FLAG_CLEARTEXT_BYTE_FULL_SEEK 1 << 15 // (Link Protection) currently not used +#define DLNA_FLAG_CLEARTEXT_LIMITED_SEEK 1 << 14 // (Link Protection) currently not used + +#define DLNA_FLAGS_PLUGIN_SUPPORT DLNA_FLAG_BYTE_BASED_SEEK | \ + DLNA_FLAG_SN_INCREASE | \ + DLNA_FLAG_STREAMING_TRANSFER | \ + DLNA_FLAG_BACKGROUND_TRANSFER | \ + DLNA_FLAG_CONNECTION_STALLING | \ + DLNA_FLAG_VERSION_1_5 + +/**************************************************** + * + * 3.3 Media profiles + * + ****************************************************/ + +/** + * The combination of DLNA profile ID and the corresponding mime type + * + * This complies with the DLNA media format guidelines. Though this is very + * similar to the profile structure of libdlna, it comes without the additional + * label field as it seams to be not needed. + */ +struct DLNAProfile { + const char* ID; + const char* mime; +}; + +struct DLNAIconProfile { + const char* mime; + unsigned short width; + unsigned short height; + unsigned char bitDepth; +}; + +/* Images */ +/* Audio */ +extern DLNAProfile DLNA_PROFILE_MPEG1_L3; // MP3 +/* Video */ +extern DLNAProfile DLNA_PROFILE_MPEG2_TS_SD_EU; // This is the profile for DVB-TV +extern DLNAProfile DLNA_PROFILE_AVC_TS_HD_EU; // This is the profile for DVB-TV + +/* Icons */ +extern DLNAIconProfile DLNA_ICON_JPEG_SM_24; +extern DLNAIconProfile DLNA_ICON_JPEG_LRG_24; +extern DLNAIconProfile DLNA_ICON_PNG_SM_24A; +extern DLNAIconProfile DLNA_ICON_PNG_LRG_24A; + +/**************************************************** + * + * 3.4 Container types + * + ****************************************************/ + +enum DLNAContainerTypes { + TUNER_1_0 +}; + +#define DLNA_CONTAINER_TUNER "Tuner_1_0" // The DLNA container type for a tuner + +/**************************************************** + * + * 3.5 Device types + * + ****************************************************/ + +#define DLNA_DEVICE_DMS_1_0 "DMS-1.00" +#define DLNA_DEVICE_DMS_1_5 "DMS-1.50" + +/**************************************************** + * + * 4. SQLite + * + ****************************************************/ +/**************************************************** + * + * 4.1 Database setup + * + ****************************************************/ + +#define SQLITE_DB_FILE "metadata.db" + +/**************************************************** + * + * Please see database.h for further definitions, + * SQL statements and triggers + * + ****************************************************/ + +#endif /* _COMMON_H */ + diff --git a/database/database.cpp b/database/database.cpp new file mode 100644 index 0000000..22c41cd --- /dev/null +++ b/database/database.cpp @@ -0,0 +1,274 @@ +/* + * File: database.h + * Author: savop + * + * Created on 3. September 2009, 22:20 + */ + +#include <string.h> +#include <stdlib.h> +#include <sqlite3.h> +#include "database.h" +#include "../common.h" +#include "object.h" +#include "../upnp.h" + +cSQLiteDatabase* cSQLiteDatabase::mInstance = NULL; + +cSQLiteDatabase::cSQLiteDatabase(){ + this->mActiveTransaction = false; + this->mDatabase = NULL; + this->mLastRow = NULL; + this->mRows = NULL; +} + +cSQLiteDatabase::~cSQLiteDatabase(){ + sqlite3_close(this->mDatabase); +} + +cSQLiteDatabase* cSQLiteDatabase::getInstance(){ + if(cSQLiteDatabase::mInstance == NULL){ + cSQLiteDatabase::mInstance = new cSQLiteDatabase; + DatabaseLocker.Wait(); + cSQLiteDatabase::mInstance->initialize(); + } + + if(cSQLiteDatabase::mInstance != NULL) + return cSQLiteDatabase::mInstance; + else + return NULL; +} + +int cSQLiteDatabase::execStatement(const char* Statement){ + char* Error; + if(!this->mDatabase){ + ERROR("Database not open. Cannot continue"); + return -1; + } + this->mRows = new cRows; +#ifdef SQLITE_PRINT_STATEMENTS + MESSAGE("SQLite: %s", Statement); +#endif + if(sqlite3_exec(this->mDatabase, Statement, cSQLiteDatabase::getResultRow, (cSQLiteDatabase*)this, &Error)!=SQLITE_OK){ + ERROR("Database error: %s", Error); + ERROR("Statement was: %s", Statement); + sqlite3_free(Error); + return -1; + } + + sqlite3_free(Error); + return 0; +} + +int cSQLiteDatabase::getResultRow(void* DB, int NumCols, char** Values, char** ColNames){ + cRow* Row = new cRow; + Row->ColCount = NumCols; + Row->Columns = new char*[NumCols]; + Row->Values = new char*[NumCols]; + for(int i=0; i < NumCols; i++){ + Row->Columns[i] = strdup0(ColNames[i]); + Row->Values[i] = strdup0(Values[i]); + } + cSQLiteDatabase* Database = (cSQLiteDatabase*)DB; + Database->mRows->Add(Row); + return 0; +} + +cRows::cRows(){ + this->mLastRow = NULL; +} + +cRows::~cRows(){ + this->mLastRow = NULL; +} + +bool cRows::fetchRow(cRow** Row){ + if(this->mLastRow==NULL){ + this->mLastRow = this->First(); + } + else { + this->mLastRow = this->Next(this->mLastRow); + } + if(this->mLastRow != NULL){ + *Row = this->mLastRow; + return true; + } + else { + *Row = NULL; + return false; + } + return false; +} + +cRow::cRow(){ + this->currentCol = 0; + this->ColCount = 0; + this->Columns = NULL; + this->Values = NULL; +} + +cRow::~cRow(){ + for(int i=0;i<this->ColCount;i++){ + delete Columns[i]; + delete Values[i]; + } + this->Columns = NULL; + this->Values = NULL; +} + +bool cRow::fetchColumn(cString* Column, cString* Value){ + char *Col, *Val; + bool ret = this->fetchColumn(&Col, &Val); + if(ret){ + *Column = cString(Col,true); + *Value = cString(Val,true); + } + return ret; +} + +bool cRow::fetchColumn(char** Column, char** Value){ + if(currentCol>=this->ColCount){ + return false; + } + #ifdef SQLITE_PRINT_FETCHES + MESSAGE("Fetching column %s='%s' (%d/%d)", this->Columns[currentCol], this->Values[currentCol], currentCol+1, this->ColCount); + #endif + *Column = strdup0(this->Columns[currentCol]); + *Value = strcasecmp(this->Values[currentCol],"NULL")?strdup0(this->Values[currentCol]):NULL; + currentCol++; + return true; +} + +int cSQLiteDatabase::initialize(){ + int ret; + cString File = cString::sprintf("%s/%s", cPluginUpnp::getConfigDirectory(), SQLITE_DB_FILE); + if((ret = sqlite3_open(File, &this->mDatabase))){ + ERROR("Unable to open database file %s (Error code: %d)!", *File, ret); + sqlite3_close(this->mDatabase); + return -1; + } + MESSAGE("Database file %s opened.", *File); + if(this->initializeTables()){ + ERROR("Error while creating tables"); + return -1; + } + else if(this->initializeTriggers()){ + ERROR("Error while setting triggers"); + return -1; + } + return 0; +} + +void cSQLiteDatabase::startTransaction(){ + if(this->mActiveTransaction){ + if(this->mAutoCommit){ + this->commitTransaction(); + } + else { + this->rollbackTransaction(); + } + } + this->execStatement("BEGIN TRANSACTION"); + this->mActiveTransaction = true; +} + +void cSQLiteDatabase::commitTransaction(){ + this->execStatement("COMMIT TRANSACTION"); + this->mActiveTransaction = false; +} + +void cSQLiteDatabase::rollbackTransaction(){ + this->execStatement("ROLLBACK TRANSACTION"); + this->mActiveTransaction = false; +} + +int cSQLiteDatabase::initializeTables(){ + int ret = 0; + this->startTransaction(); + if(this->execStatement(SQLITE_CREATE_TABLE_SYSTEM)==-1) ret = -1; + if(this->execStatement(SQLITE_CREATE_TABLE_PRIMARY_KEYS)==-1) ret = -1; + if(this->execStatement(SQLITE_CREATE_TABLE_ALBUMS)==-1) ret = -1; + if(this->execStatement(SQLITE_CREATE_TABLE_AUDIOBROADCASTS)==-1) ret = -1; + if(this->execStatement(SQLITE_CREATE_TABLE_AUDIOITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_CREATE_TABLE_CONTAINER)==-1) ret = -1; + if(this->execStatement(SQLITE_CREATE_TABLE_IMAGEITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_CREATE_TABLE_ITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_CREATE_TABLE_MOVIES)==-1) ret = -1; + if(this->execStatement(SQLITE_CREATE_TABLE_OBJECTS)==-1) ret = -1; + if(this->execStatement(SQLITE_CREATE_TABLE_PHOTOS)==-1) ret = -1; + if(this->execStatement(SQLITE_CREATE_TABLE_PLAYLISTS)==-1) ret = -1; + if(this->execStatement(SQLITE_CREATE_TABLE_RESOURCES)==-1) ret = -1; + if(this->execStatement(SQLITE_CREATE_TABLE_SEARCHCLASS)==-1) ret = -1; + if(this->execStatement(SQLITE_CREATE_TABLE_VIDEOBROADCASTS)==-1) ret = -1; + if(this->execStatement(SQLITE_CREATE_TABLE_VIDEOITEMS)==-1) ret = -1; + if(ret){ + this->rollbackTransaction(); + } + else { + this->commitTransaction(); + } + return ret; +} + +int cSQLiteDatabase::initializeTriggers(){ + int ret = 0; + this->startTransaction(); + if(this->execStatement(SQLITE_TRIGGER_UPDATE_SYSTEM)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_UPDATE_OBJECTID)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_D_AUDIOITEMS_AUDIOBROADCASTS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_D_CONTAINERS_ALBUMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_D_CONTAINERS_PLAYLISTS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_D_CONTAINERS_SEARCHCLASSES)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_D_IMAGEITEMS_PHOTOS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_D_ITEMS_AUDIOITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_D_ITEMS_IMAGEITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_D_ITEMS_ITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_D_ITEMS_VIDEOITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_D_OBJECTS_OBJECTS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_D_OBJECT_CONTAINERS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_D_OBJECT_ITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_D_OBJECT_RESOURCES)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_D_VIDEOITEMS_MOVIES)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_D_VIDEOITEMS_VIDEOBROADCASTS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_I_AUDIOITEMS_AUDIOBROADCASTS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_I_CONTAINERS_ALBUMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_I_CONTAINERS_PLAYLISTS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_I_CONTAINERS_SEARCHCLASSES)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_I_IMAGEITEMS_PHOTOS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_I_ITEMS_AUDIOITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_I_ITEMS_IMAGEITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_I_ITEMS_ITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_I_ITEMS_VIDEOITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_I_OBJECTS_OBJECTS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_I_OBJECT_CONTAINERS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_I_OBJECT_ITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_I_OBJECT_RESOURCES)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_I_VIDEOITEMS_MOVIES)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_I_VIDEOITEMS_VIDEOBROADCASTS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_U_AUDIOITEMS_AUDIOBROADCASTS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_U_CONTAINERS_ALBUMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_U_CONTAINERS_PLAYLISTS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_U_CONTAINERS_SEARCHCLASSES)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_U_IMAGEITEMS_PHOTOS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_U_ITEMS_AUDIOITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_U_ITEMS_IMAGEITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_U_ITEMS_ITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_U_ITEMS_VIDEOITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_U_OBJECTS_OBJECTS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_U_OBJECT_CONTAINERS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_U_OBJECT_ITEMS)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_U_OBJECT_RESOURCES)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_U_VIDEOITEMS_MOVIES)==-1) ret = -1; + if(this->execStatement(SQLITE_TRIGGER_U_VIDEOITEMS_VIDEOBROADCASTS)==-1) ret = -1; + if(ret){ + this->rollbackTransaction(); + } + else { + this->commitTransaction(); + } + return ret; +} + +long cSQLiteDatabase::getLastInsertRowID() const { + return (long)sqlite3_last_insert_rowid(this->mDatabase); +}
\ No newline at end of file diff --git a/database/database.h b/database/database.h new file mode 100644 index 0000000..fe91f2b --- /dev/null +++ b/database/database.h @@ -0,0 +1,834 @@ +/* + * File: database.h + * Author: savop + * + * Created on 3. September 2009, 22:20 + */ + +#ifndef _DATABASE_H +#define _DATABASE_H + +#include <sqlite3.h> +#include <vdr/tools.h> +#include "../common.h" + +//#define SQLITE_PRINT_STATEMENTS +//#define SQLITE_PRINT_FETCHES +#define SQLITE_CASCADE_DELETES + +#define PK_OBJECTS TOSTRING(1) +#define PK_RESOURCES TOSTRING(2) +#define PK_SEARCHCLASSES TOSTRING(3) + +#define SQLITE_FIRST_CUSTOMID TOSTRING(0) + +#define SQLITE_COLUMN_NAME_LENGTH 64 + +#define SQLITE_TABLE_RESOURCES "Resources" +#define SQLITE_TABLE_OBJECTS "Objects" +#define SQLITE_TABLE_ITEMS "Items" +#define SQLITE_TABLE_CONTAINERS "Containers" +#define SQLITE_TABLE_VIDEOITEMS "VideoItems" +#define SQLITE_TABLE_AUDIOITEMS "AudioItems" +#define SQLITE_TABLE_IMAGEITEMS "ImageItems" +#define SQLITE_TABLE_VIDEOBROADCASTS "VideoBroadcasts" +#define SQLITE_TABLE_AUDIOBROADCASTS "AudioBroadcasts" +#define SQLITE_TABLE_MOVIES "Movies" +#define SQLITE_TABLE_PHOTOS "Photos" +#define SQLITE_TABLE_ALBUMS "Albums" +#define SQLITE_TABLE_PLAYLISTS "Playlists" +#define SQLITE_TABLE_SEARCHCLASS "SearchClass" +#define SQLITE_TABLE_PRIMARY_KEYS "PrimaryKeys" +#define SQLITE_TABLE_SYSTEM "System" + +#define SQLITE_TYPE_TEXT "TEXT" +#define SQLITE_TYPE_INTEGER "INTEGER" +#define SQLITE_TYPE_BOOL SQLITE_TYPE_INTEGER +#define SQLITE_TYPE_DATE SQLITE_TYPE_TEXT +#define SQLITE_TYPE_ULONG SQLITE_TYPE_INTEGER +#define SQLITE_TYPE_LONG SQLITE_TYPE_INTEGER +#define SQLITE_TYPE_UINTEGER SQLITE_TYPE_INTEGER + +#define SQLITE_TRANSACTION_BEGIN "BEGIN IMMEDIATE TRANSACTION " +#define SQLITE_TRANSACTION_END "COMMIT TRANSACTION" +#define SQLITE_TRANSACTION_TYPE "ROLLBACK" + +#define SQLITE_CONFLICT_CLAUSE "ON CONFLICT " SQLITE_TRANSACTION_TYPE +#define SQLITE_PRIMARY_KEY SQLITE_TYPE_INTEGER " PRIMARY KEY" +#define SQLITE_NOT_NULL "NOT NULL" +#define SQLITE_UNIQUE "UNIQUE" + +#define SQLITE_COL_OBJECTID "ObjectID" +#define SQLITE_COL_PARENTID "ParentID" +#define SQLITE_COL_TITLE "Title" +#define SQLITE_COL_CREATOR "Creator" +#define SQLITE_COL_CLASS "Class" +#define SQLITE_COL_RESTRICTED "Restricted" +#define SQLITE_COL_WRITESTATUS "WriteStatus" +#define SQLITE_COL_REFERENCEID "RefID" +#define SQLITE_COL_CLASSDERIVED "IncludeDerived" +#define SQLITE_COL_SEARCHABLE "Searchable" +#define SQLITE_COL_CONTAINER_UID "UpdateID" +#define SQLITE_COL_RESOURCEID "ResourceID" +#define SQLITE_COL_PROTOCOLINFO "ProtocolInfo" +#define SQLITE_COL_CONTENTTYPE "ContentType" +#define SQLITE_COL_RESOURCETYPE "ResourceType" +#define SQLITE_COL_RESOURCE "Resource" +#define SQLITE_COL_SIZE "Size" +#define SQLITE_COL_DURATION "Duration" +#define SQLITE_COL_BITRATE "Bitrate" +#define SQLITE_COL_SAMPLEFREQUENCE "SampleFreq" +#define SQLITE_COL_BITSPERSAMPLE "BitsPerSample" +#define SQLITE_COL_NOAUDIOCHANNELS "NoAudioChannels" +#define SQLITE_COL_COLORDEPTH "ColorDepth" +#define SQLITE_COL_RESOLUTION "Resolution" +#define SQLITE_COL_GENRE "Genre" +#define SQLITE_COL_LONGDESCRIPTION "LongDescription" +#define SQLITE_COL_PRODUCER "Producer" +#define SQLITE_COL_RATING "Rating" +#define SQLITE_COL_ACTOR "Actor" +#define SQLITE_COL_DIRECTOR "Director" +#define SQLITE_COL_DESCRIPTION "Description" +#define SQLITE_COL_PUBLISHER "Publisher" +#define SQLITE_COL_LANGUAGE "Language" +#define SQLITE_COL_RELATION "Relation" +#define SQLITE_COL_STORAGEMEDIUM "StorageMedium" +#define SQLITE_COL_DVDREGIONCODE "DVDRegionCode" +#define SQLITE_COL_CHANNELNAME "Channelname" +#define SQLITE_COL_SCHEDULEDSTARTTIME "ScheduledStartTime" +#define SQLITE_COL_SCHEDULEDENDTIME "ScheduledEndTime" +#define SQLITE_COL_ICON "Icon" +#define SQLITE_COL_REGION "Region" +#define SQLITE_COL_CHANNELNR "ChannelNr" +#define SQLITE_COL_RIGHTS "Rights" +#define SQLITE_COL_RADIOCALLSIGN "CallSign" +#define SQLITE_COL_RADIOSTATIONID "StationID" +#define SQLITE_COL_RADIOBAND "Band" +#define SQLITE_COL_CONTRIBUTOR "Contributor" +#define SQLITE_COL_DATE "Date" +#define SQLITE_COL_ALBUM "Album" +#define SQLITE_COL_ARTIST "Artist" +#define SQLITE_COL_DLNA_CONTAINERTYPE "DLNAContainer" +#define SQLITE_COL_CHILDCOUNT "ChildCount" + +#define SQLITE_UPNP_OBJECTID SQLITE_COL_OBJECTID " " SQLITE_TYPE_INTEGER " " SQLITE_NOT_NULL " " SQLITE_CONFLICT_CLAUSE " "\ + SQLITE_UNIQUE " " SQLITE_CONFLICT_CLAUSE + +#define SQLITE_INSERT_TRIGGER(TableA,TableB,Class) "CREATE TRIGGER IF NOT EXISTS "\ + TableA "_I_" TableB " "\ + "BEFORE INSERT ON "\ + TableB " "\ + "FOR EACH ROW BEGIN "\ + "SELECT CASE "\ + "WHEN ("\ + "((SELECT " SQLITE_COL_OBJECTID " FROM " TableA " "\ + "WHERE " SQLITE_COL_OBJECTID "=NEW." SQLITE_COL_OBJECTID " "\ + ") IS NULL) "\ + "OR "\ + "((SELECT " SQLITE_COL_OBJECTID " FROM " SQLITE_TABLE_OBJECTS " "\ + "WHERE " SQLITE_COL_OBJECTID "=NEW." SQLITE_COL_OBJECTID " "\ + "AND " SQLITE_COL_CLASS " LIKE '" Class "%') IS NULL) "\ + ") THEN "\ + "RAISE(" SQLITE_TRANSACTION_TYPE ", "\ + "'INSERT on table " TableB " failed due constraint violation "\ + "on foreign key " SQLITE_COL_OBJECTID "'"\ + ") "\ + "END; END;" + +#define SQLITE_UPDATE_TRIGGER(TableA,TableB,Class) "CREATE TRIGGER IF NOT EXISTS "\ + TableA "_U_" TableB " "\ + "BEFORE UPDATE ON "\ + TableB " "\ + "FOR EACH ROW BEGIN "\ + "SELECT CASE "\ + "WHEN ("\ + "((SELECT " SQLITE_COL_OBJECTID " FROM " TableA " "\ + "WHERE " SQLITE_COL_OBJECTID "=NEW." SQLITE_COL_OBJECTID " "\ + "AND " SQLITE_COL_CLASS " LIKE '" Class "%') IS NULL)"\ + ") THEN "\ + "RAISE(" SQLITE_TRANSACTION_TYPE ", "\ + "'UPDATE on table " TableB " failed due constraint violation "\ + "on foreign key " SQLITE_COL_OBJECTID "'"\ + ") "\ + "END; END;" + +#define SQLITE_INSERT_REFERENCE_TRIGGER(Table,Column) "CREATE TRIGGER IF NOT EXISTS "\ + Table "_I_" Table " "\ + "BEFORE INSERT ON " \ + Table " " \ + "FOR EACH ROW BEGIN "\ + "SELECT CASE "\ + "WHEN ( "\ + "((SELECT " SQLITE_COL_OBJECTID " FROM " Table " "\ + "WHERE " SQLITE_COL_OBJECTID " = NEW." Column ") IS NULL) "\ + "AND "\ + "(NEW." Column "!=-1)"\ + ")THEN "\ + "RAISE(" SQLITE_TRANSACTION_TYPE ", 'INSERT on table " Table " "\ + "violates foreign key \"" Column "\"') "\ + "END; END;" + +#define SQLITE_UPDATE_REFERENCE_TRIGGER(Table,Column) "CREATE TRIGGER IF NOT EXISTS "\ + Table "_U_" Table " "\ + "BEFORE INSERT ON " \ + Table " " \ + "FOR EACH ROW BEGIN "\ + "SELECT CASE "\ + "WHEN ( "\ + "((SELECT " SQLITE_COL_OBJECTID " FROM " Table " "\ + "WHERE " SQLITE_COL_OBJECTID " = NEW." Column ") IS NULL) "\ + "AND "\ + "(NEW." Column "!=-1)"\ + ")THEN "\ + "RAISE(" SQLITE_TRANSACTION_TYPE ", 'UPDATE on table " Table " "\ + "violates foreign key \"" Column "\"') "\ + "END; END;" + +#define SQLITE_DELETE_REFERENCE_TRIGGER(Table,Column) "CREATE TRIGGER IF NOT EXISTS "\ + Table "_D_" Table " " \ + "BEFORE DELETE ON " \ + Table " " \ + "FOR EACH ROW BEGIN "\ + "SELECT CASE "\ + "WHEN ("\ + "(SELECT " Column " FROM " Table " "\ + "WHERE " Column " = OLD." SQLITE_COL_OBJECTID ") IS NOT NULL"\ + ")THEN "\ + "RAISE(" SQLITE_TRANSACTION_TYPE ", 'DELETE on table " Table " "\ + "violates foreign key \"" Column "\"') "\ + "END; END;" + +#ifdef SQLITE_CASCADE_DELETES +#define SQLITE_DELETE_TRIGGER(TableA,TableB) "CREATE TRIGGER IF NOT EXISTS "\ + TableA "_D_" TableB " "\ + "BEFORE DELETE ON "\ + TableA " "\ + "FOR EACH ROW BEGIN "\ + "DELETE FROM " TableB " "\ + "WHERE " SQLITE_COL_OBJECTID "=OLD." SQLITE_COL_OBJECTID "; "\ + "END;" + +#define SQLITE_DELETE_PARENT_TRIGGER "CREATE TRIGGER IF NOT EXISTS "\ + SQLITE_TABLE_OBJECTS "_D_" SQLITE_TABLE_OBJECTS " " \ + "BEFORE DELETE ON " \ + SQLITE_TABLE_OBJECTS " " \ + "FOR EACH ROW BEGIN "\ + "DELETE FROM " SQLITE_TABLE_OBJECTS " "\ + "WHERE " SQLITE_COL_PARENTID "=OLD." SQLITE_COL_OBJECTID "; "\ + "END;" +#else +#define SQLITE_DELETE_TRIGGER(TableA,TableB) "CREATE TRIGGER IF NOT EXISTS "\ + TableA "_D_" TableB " "\ + "BEFORE DELETE ON "\ + TableA " "\ + "FOR EACH ROW BEGIN "\ + "SELECT CASE "\ + "WHEN ("\ + "(SELECT " SQLITE_COL_OBJECTID " FROM " TableB " "\ + "WHERE " SQLITE_COL_OBJECTID "=OLD." SQLITE_COL_OBJECTID ") IS NOT NULL"\ + ") THEN "\ + "RAISE(" SQLITE_TRANSACTION_TYPE ", "\ + "'DELETE on table " TableA " failed due constraint violation "\ + "on foreign key " SQLITE_COL_OBJECTID "'"\ + ") "\ + "END; END;" + +#define SQLITE_DELETE_PARENT_TRIGGER SQLITE_DELETE_REFERENCE_TRIGGER(SQLITE_TABLE_OBJECTS, SQLITE_COL_PARENTID) +#endif + + /**********************************************\ + * * + * Primary keys * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_PRIMARY_KEYS "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_PRIMARY_KEYS \ + "("\ + "KeyID " SQLITE_PRIMARY_KEY " " SQLITE_NOT_NULL ","\ + "Key " SQLITE_TYPE_INTEGER " " SQLITE_NOT_NULL\ + ");"\ + "INSERT OR IGNORE INTO "\ + SQLITE_TABLE_PRIMARY_KEYS \ + "(KeyID, Key) VALUES ("\ + PK_OBJECTS "," SQLITE_FIRST_CUSTOMID\ + ");"\ + "INSERT OR IGNORE INTO "\ + SQLITE_TABLE_PRIMARY_KEYS \ + "(KeyID, Key) VALUES ("\ + PK_RESOURCES ",0"\ + ");"\ + "INSERT OR IGNORE INTO "\ + SQLITE_TABLE_PRIMARY_KEYS \ + "(KeyID, Key) VALUES ("\ + PK_SEARCHCLASSES ",0"\ + ");" + +#define SQLITE_TRIGGER_UPDATE_OBJECTID "CREATE TRIGGER IF NOT EXISTS "\ + SQLITE_TABLE_OBJECTS "_PK_UPDATE "\ + "AFTER INSERT ON "\ + SQLITE_TABLE_OBJECTS " "\ + "BEGIN "\ + "UPDATE " SQLITE_TABLE_PRIMARY_KEYS " SET Key=Key+1 WHERE KeyID=" PK_OBJECTS "; "\ + "END;" + + /**********************************************\ + * * + * System settings * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_SYSTEM "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_SYSTEM " "\ + "("\ + "Key " SQLITE_TYPE_TEXT " " SQLITE_NOT_NULL " " SQLITE_UNIQUE ","\ + "Value " SQLITE_TYPE_TEXT " "\ + ");" + +#define SQLITE_TRIGGER_UPDATE_SYSTEM "CREATE TRIGGER IF NOT EXISTS "\ + SQLITE_TABLE_SYSTEM "_VALUE_UPDATE "\ + "BEFORE UPDATE "\ + "ON " SQLITE_TABLE_SYSTEM " "\ + "WHEN ((SELECT Key FROM " SQLITE_TABLE_SYSTEM " WHERE Key=NEW.Key) IS NULL) "\ + "BEGIN INSERT INTO " SQLITE_TABLE_SYSTEM " (Key) VALUES (NEW.Key); END;" + + /**********************************************\ + * * + * Objects * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_OBJECTS "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_OBJECTS \ + "(" \ + SQLITE_COL_OBJECTID " " SQLITE_PRIMARY_KEY " " SQLITE_NOT_NULL " " SQLITE_CONFLICT_CLAUSE "," \ + SQLITE_COL_PARENTID " " SQLITE_TYPE_INTEGER " " SQLITE_NOT_NULL " " SQLITE_CONFLICT_CLAUSE "," \ + SQLITE_COL_TITLE " " SQLITE_TYPE_TEXT " " SQLITE_NOT_NULL "," \ + SQLITE_COL_CREATOR " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_CLASS " " SQLITE_TYPE_TEXT " " SQLITE_NOT_NULL "," \ + SQLITE_COL_RESTRICTED " " SQLITE_TYPE_BOOL " " SQLITE_NOT_NULL "," \ + SQLITE_COL_WRITESTATUS " " SQLITE_TYPE_INTEGER \ + ");" + +// Trigger for foreign key ParentID + +#define SQLITE_TRIGGER_D_OBJECTS_OBJECTS SQLITE_DELETE_PARENT_TRIGGER + +#define SQLITE_TRIGGER_I_OBJECTS_OBJECTS SQLITE_INSERT_REFERENCE_TRIGGER(SQLITE_TABLE_OBJECTS, SQLITE_COL_PARENTID)\ + "CREATE TRIGGER IF NOT EXISTS "\ + SQLITE_TABLE_OBJECTS "_PI_" SQLITE_TABLE_OBJECTS " "\ + "BEFORE INSERT ON "\ + SQLITE_TABLE_OBJECTS " " \ + "FOR EACH ROW BEGIN "\ + "SELECT CASE "\ + "WHEN ("\ + "((SELECT " SQLITE_COL_PARENTID " FROM " SQLITE_TABLE_OBJECTS " "\ + "WHERE " SQLITE_COL_PARENTID "=-1) IS NOT NULL) "\ + "AND "\ + "(NEW." SQLITE_COL_PARENTID "=-1) "\ + ") THEN "\ + "RAISE(" SQLITE_TRANSACTION_TYPE ","\ + "'INSERT on table " SQLITE_TABLE_OBJECTS " violates constraint. "\ + SQLITE_COL_PARENTID " must uniquely be -1') "\ + "END; END;" + +#define SQLITE_TRIGGER_U_OBJECTS_OBJECTS SQLITE_UPDATE_REFERENCE_TRIGGER(SQLITE_TABLE_OBJECTS, SQLITE_COL_PARENTID)\ + "CREATE TRIGGER IF NOT EXISTS "\ + SQLITE_TABLE_OBJECTS "_PU_" SQLITE_TABLE_OBJECTS " "\ + "BEFORE UPDATE ON "\ + SQLITE_TABLE_OBJECTS " " \ + "FOR EACH ROW BEGIN "\ + "SELECT CASE "\ + "WHEN ("\ + "((SELECT " SQLITE_COL_PARENTID " FROM " SQLITE_TABLE_OBJECTS " "\ + "WHERE " SQLITE_COL_PARENTID "=-1 "\ + "AND " SQLITE_COL_OBJECTID "!=NEW." SQLITE_COL_OBJECTID " ) IS NOT NULL) "\ + "AND "\ + "(NEW." SQLITE_COL_PARENTID "=-1) "\ + ") THEN "\ + "RAISE(" SQLITE_TRANSACTION_TYPE ","\ + "'UPDATE on table " SQLITE_TABLE_OBJECTS " violates constraint. "\ + SQLITE_COL_PARENTID " must uniquely be -1') "\ + "END; END;" + + /**********************************************\ + * * + * Items * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_ITEMS "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_ITEMS \ + "(" \ + SQLITE_UPNP_OBJECTID "," \ + SQLITE_COL_REFERENCEID " " SQLITE_TYPE_INTEGER " DEFAULT -1" \ + ");" + +// Trigger for foreign key ObjectID + +#define SQLITE_TRIGGER_D_OBJECT_ITEMS SQLITE_DELETE_TRIGGER(SQLITE_TABLE_OBJECTS,\ + SQLITE_TABLE_ITEMS) + +#define SQLITE_TRIGGER_I_OBJECT_ITEMS SQLITE_INSERT_TRIGGER(SQLITE_TABLE_OBJECTS,\ + SQLITE_TABLE_ITEMS,\ + UPNP_CLASS_ITEM) + +#define SQLITE_TRIGGER_U_OBJECT_ITEMS SQLITE_UPDATE_TRIGGER(SQLITE_TABLE_OBJECTS,\ + SQLITE_TABLE_ITEMS,\ + UPNP_CLASS_ITEM) + +// Trigger for Reference items + +#define SQLITE_TRIGGER_I_ITEMS_ITEMS SQLITE_INSERT_REFERENCE_TRIGGER(SQLITE_TABLE_ITEMS, SQLITE_COL_REFERENCEID) + +#define SQLITE_TRIGGER_U_ITEMS_ITEMS SQLITE_UPDATE_REFERENCE_TRIGGER(SQLITE_TABLE_ITEMS, SQLITE_COL_REFERENCEID) + +#define SQLITE_TRIGGER_D_ITEMS_ITEMS SQLITE_DELETE_REFERENCE_TRIGGER(SQLITE_TABLE_ITEMS, SQLITE_COL_REFERENCEID) + + /**********************************************\ + * * + * Containers * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_CONTAINER "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_CONTAINERS \ + "(" \ + SQLITE_UPNP_OBJECTID "," \ + SQLITE_COL_SEARCHABLE " " SQLITE_TYPE_INTEGER ","\ + SQLITE_COL_CONTAINER_UID " " SQLITE_TYPE_INTEGER " " SQLITE_NOT_NULL ","\ + SQLITE_COL_DLNA_CONTAINERTYPE " " SQLITE_TYPE_TEXT \ + ");" + +#define SQLITE_TRIGGER_D_OBJECT_CONTAINERS SQLITE_DELETE_TRIGGER(SQLITE_TABLE_OBJECTS,\ + SQLITE_TABLE_CONTAINERS) + +#define SQLITE_TRIGGER_I_OBJECT_CONTAINERS SQLITE_INSERT_TRIGGER(SQLITE_TABLE_OBJECTS,\ + SQLITE_TABLE_CONTAINERS,\ + UPNP_CLASS_CONTAINER) + +#define SQLITE_TRIGGER_U_OBJECT_CONTAINERS SQLITE_UPDATE_TRIGGER(SQLITE_TABLE_OBJECTS,\ + SQLITE_TABLE_CONTAINERS,\ + UPNP_CLASS_CONTAINER) + + /**********************************************\ + * * + * Video items * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_VIDEOITEMS "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_VIDEOITEMS \ + "(" \ + SQLITE_UPNP_OBJECTID "," \ + SQLITE_COL_GENRE " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_LONGDESCRIPTION " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_PRODUCER " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_RATING " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_ACTOR " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_DIRECTOR " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_DESCRIPTION " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_PUBLISHER " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_LANGUAGE " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_RELATION " " SQLITE_TYPE_TEXT \ + ");" + +#define SQLITE_TRIGGER_D_ITEMS_VIDEOITEMS SQLITE_DELETE_TRIGGER(SQLITE_TABLE_ITEMS, SQLITE_TABLE_VIDEOITEMS) + +#define SQLITE_TRIGGER_U_ITEMS_VIDEOITEMS SQLITE_UPDATE_TRIGGER(SQLITE_TABLE_ITEMS, \ + SQLITE_TABLE_VIDEOITEMS, \ + UPNP_CLASS_VIDEO) + +#define SQLITE_TRIGGER_I_ITEMS_VIDEOITEMS SQLITE_INSERT_TRIGGER(SQLITE_TABLE_ITEMS, \ + SQLITE_TABLE_VIDEOITEMS, \ + UPNP_CLASS_VIDEO) + + /**********************************************\ + * * + * Audio items * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_AUDIOITEMS "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_AUDIOITEMS \ + "(" \ + SQLITE_UPNP_OBJECTID "," \ + SQLITE_COL_GENRE " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_LONGDESCRIPTION " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_DESCRIPTION " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_PUBLISHER " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_RELATION " " SQLITE_TYPE_TEXT \ + ");" + +#define SQLITE_TRIGGER_D_ITEMS_AUDIOITEMS SQLITE_DELETE_TRIGGER(SQLITE_TABLE_ITEMS, SQLITE_TABLE_AUDIOITEMS) + +#define SQLITE_TRIGGER_U_ITEMS_AUDIOITEMS SQLITE_UPDATE_TRIGGER(SQLITE_TABLE_ITEMS, \ + SQLITE_TABLE_AUDIOITEMS, \ + UPNP_CLASS_AUDIO) + +#define SQLITE_TRIGGER_I_ITEMS_AUDIOITEMS SQLITE_INSERT_TRIGGER(SQLITE_TABLE_ITEMS, \ + SQLITE_TABLE_AUDIOITEMS, \ + UPNP_CLASS_AUDIO) + + /**********************************************\ + * * + * Image items * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_IMAGEITEMS "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_IMAGEITEMS \ + "("\ + SQLITE_UPNP_OBJECTID "," \ + SQLITE_COL_LONGDESCRIPTION " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_DESCRIPTION " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_PUBLISHER " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_STORAGEMEDIUM " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_RATING " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_DATE " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_RIGHTS " " SQLITE_TYPE_TEXT\ + ");" + +#define SQLITE_TRIGGER_D_ITEMS_IMAGEITEMS SQLITE_DELETE_TRIGGER(SQLITE_TABLE_ITEMS, SQLITE_TABLE_IMAGEITEMS) + +#define SQLITE_TRIGGER_U_ITEMS_IMAGEITEMS SQLITE_UPDATE_TRIGGER(SQLITE_TABLE_ITEMS, \ + SQLITE_TABLE_IMAGEITEMS, \ + UPNP_CLASS_IMAGE) + +#define SQLITE_TRIGGER_I_ITEMS_IMAGEITEMS SQLITE_INSERT_TRIGGER(SQLITE_TABLE_ITEMS, \ + SQLITE_TABLE_IMAGEITEMS, \ + UPNP_CLASS_IMAGE) + + /**********************************************\ + * * + * Video broadcasts * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_VIDEOBROADCASTS "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_VIDEOBROADCASTS \ + "("\ + SQLITE_UPNP_OBJECTID "," \ + SQLITE_COL_ICON " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_REGION " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_CHANNELNR " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_CHANNELNAME " " SQLITE_TYPE_TEXT " " SQLITE_UNIQUE \ + ");" + +#define SQLITE_TRIGGER_D_VIDEOITEMS_VIDEOBROADCASTS SQLITE_DELETE_TRIGGER(SQLITE_TABLE_VIDEOITEMS, SQLITE_TABLE_VIDEOBROADCASTS) + +#define SQLITE_TRIGGER_U_VIDEOITEMS_VIDEOBROADCASTS SQLITE_UPDATE_TRIGGER(SQLITE_TABLE_VIDEOITEMS,\ + SQLITE_TABLE_VIDEOBROADCASTS,\ + UPNP_CLASS_VIDEOBC) + +#define SQLITE_TRIGGER_I_VIDEOITEMS_VIDEOBROADCASTS SQLITE_INSERT_TRIGGER(SQLITE_TABLE_VIDEOITEMS,\ + SQLITE_TABLE_VIDEOBROADCASTS,\ + UPNP_CLASS_VIDEOBC) + + /**********************************************\ + * * + * Audio broadcasts * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_AUDIOBROADCASTS "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_AUDIOBROADCASTS \ + "("\ + SQLITE_UPNP_OBJECTID "," \ + SQLITE_COL_REGION " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_RADIOCALLSIGN " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_RADIOSTATIONID " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_RADIOBAND " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_CHANNELNR " " SQLITE_TYPE_INTEGER \ + ");" + +#define SQLITE_TRIGGER_D_AUDIOITEMS_AUDIOBROADCASTS SQLITE_DELETE_TRIGGER(SQLITE_TABLE_AUDIOITEMS, SQLITE_TABLE_AUDIOBROADCASTS) + +#define SQLITE_TRIGGER_I_AUDIOITEMS_AUDIOBROADCASTS SQLITE_INSERT_TRIGGER(SQLITE_TABLE_AUDIOITEMS,\ + SQLITE_TABLE_AUDIOBROADCASTS,\ + UPNP_CLASS_AUDIOBC) + +#define SQLITE_TRIGGER_U_AUDIOITEMS_AUDIOBROADCASTS SQLITE_UPDATE_TRIGGER(SQLITE_TABLE_AUDIOITEMS,\ + SQLITE_TABLE_AUDIOBROADCASTS,\ + UPNP_CLASS_AUDIOBC) + + /**********************************************\ + * * + * Movies * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_MOVIES "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_MOVIES \ + "("\ + SQLITE_UPNP_OBJECTID "," \ + SQLITE_COL_STORAGEMEDIUM " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_DVDREGIONCODE " " SQLITE_TYPE_INTEGER "," \ + SQLITE_COL_CHANNELNAME " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_SCHEDULEDSTARTTIME " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_SCHEDULEDENDTIME " " SQLITE_TYPE_TEXT\ + ");" + +#define SQLITE_TRIGGER_D_VIDEOITEMS_MOVIES SQLITE_DELETE_TRIGGER(SQLITE_TABLE_VIDEOITEMS, SQLITE_TABLE_MOVIES) + + +#define SQLITE_TRIGGER_I_VIDEOITEMS_MOVIES SQLITE_INSERT_TRIGGER(SQLITE_TABLE_VIDEOITEMS,\ + SQLITE_TABLE_MOVIES,\ + UPNP_CLASS_MOVIE) + +#define SQLITE_TRIGGER_U_VIDEOITEMS_MOVIES SQLITE_UPDATE_TRIGGER(SQLITE_TABLE_VIDEOITEMS,\ + SQLITE_TABLE_MOVIES,\ + UPNP_CLASS_MOVIE) + + /**********************************************\ + * * + * Photos * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_PHOTOS "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_PHOTOS \ + "("\ + SQLITE_UPNP_OBJECTID "," \ + SQLITE_COL_ALBUM " " SQLITE_TYPE_TEXT\ + ");" + +#define SQLITE_TRIGGER_D_IMAGEITEMS_PHOTOS SQLITE_DELETE_TRIGGER(SQLITE_TABLE_IMAGEITEMS, SQLITE_TABLE_PHOTOS) + +#define SQLITE_TRIGGER_I_IMAGEITEMS_PHOTOS SQLITE_INSERT_TRIGGER(SQLITE_TABLE_IMAGEITEMS,\ + SQLITE_TABLE_PHOTOS,\ + UPNP_CLASS_PHOTO) + +#define SQLITE_TRIGGER_U_IMAGEITEMS_PHOTOS SQLITE_UPDATE_TRIGGER(SQLITE_TABLE_IMAGEITEMS,\ + SQLITE_TABLE_PHOTOS,\ + UPNP_CLASS_PHOTO) + + /**********************************************\ + * * + * Albums * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_ALBUMS "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_ALBUMS \ + "("\ + SQLITE_UPNP_OBJECTID "," \ + SQLITE_COL_STORAGEMEDIUM " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_LONGDESCRIPTION " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_DESCRIPTION " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_PUBLISHER " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_CONTRIBUTOR " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_DATE " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_RELATION " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_RIGHTS " " SQLITE_TYPE_TEXT \ + ");" + +#define SQLITE_TRIGGER_D_CONTAINERS_ALBUMS SQLITE_DELETE_TRIGGER(SQLITE_TABLE_CONTAINERS, SQLITE_TABLE_ALBUMS) + +#define SQLITE_TRIGGER_U_CONTAINERS_ALBUMS SQLITE_UPDATE_TRIGGER(SQLITE_TABLE_CONTAINERS,\ + SQLITE_TABLE_ALBUMS,\ + UPNP_CLASS_ALBUM) + +#define SQLITE_TRIGGER_I_CONTAINERS_ALBUMS SQLITE_INSERT_TRIGGER(SQLITE_TABLE_CONTAINERS,\ + SQLITE_TABLE_ALBUMS,\ + UPNP_CLASS_ALBUM) + + /**********************************************\ + * * + * Playlists * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_PLAYLISTS "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_PLAYLISTS \ + "(" \ + SQLITE_UPNP_OBJECTID "," \ + SQLITE_COL_ARTIST " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_GENRE " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_LONGDESCRIPTION " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_DESCRIPTION " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_PRODUCER " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_STORAGEMEDIUM " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_CONTRIBUTOR " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_DATE " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_LANGUAGE " " SQLITE_TYPE_TEXT ","\ + SQLITE_COL_RIGHTS " " SQLITE_TYPE_TEXT\ + ");" + +#define SQLITE_TRIGGER_D_CONTAINERS_PLAYLISTS SQLITE_DELETE_TRIGGER(SQLITE_TABLE_CONTAINERS, SQLITE_TABLE_PLAYLISTS) + +#define SQLITE_TRIGGER_I_CONTAINERS_PLAYLISTS SQLITE_INSERT_TRIGGER(SQLITE_TABLE_CONTAINERS,\ + SQLITE_TABLE_PLAYLISTS,\ + UPNP_CLASS_PLAYLISTCONT) + +#define SQLITE_TRIGGER_U_CONTAINERS_PLAYLISTS SQLITE_UPDATE_TRIGGER(SQLITE_TABLE_CONTAINERS,\ + SQLITE_TABLE_PLAYLISTS,\ + UPNP_CLASS_PLAYLISTCONT) + + /**********************************************\ + * * + * Search classes * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_SEARCHCLASS "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_SEARCHCLASS \ + "(" \ + SQLITE_COL_OBJECTID " " SQLITE_TYPE_INTEGER " " SQLITE_NOT_NULL "," \ + SQLITE_COL_CLASS " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_CLASSDERIVED " " SQLITE_TYPE_BOOL \ + ");" + +#define SQLITE_TRIGGER_D_CONTAINERS_SEARCHCLASSES "CREATE TRIGGER IF NOT EXISTS " \ + SQLITE_TABLE_CONTAINERS "_D_" SQLITE_TABLE_SEARCHCLASS " " \ + "BEFORE DELETE ON " \ + SQLITE_TABLE_CONTAINERS " " \ + "FOR EACH ROW BEGIN "\ + "DELETE FROM " SQLITE_TABLE_SEARCHCLASS " "\ + "WHERE " SQLITE_COL_OBJECTID "= OLD." SQLITE_COL_OBJECTID "; " \ + "END;" + +#define SQLITE_TRIGGER_U_CONTAINERS_SEARCHCLASSES "CREATE TRIGGER IF NOT EXISTS " \ + SQLITE_TABLE_CONTAINERS "_U_" SQLITE_TABLE_SEARCHCLASS " " \ + "BEFORE UPDATE ON " \ + SQLITE_TABLE_SEARCHCLASS " " \ + "FOR EACH ROW BEGIN "\ + "SELECT CASE "\ + "WHEN ("\ + "(SELECT " SQLITE_COL_OBJECTID " FROM " SQLITE_TABLE_CONTAINERS " "\ + "WHERE " SQLITE_COL_OBJECTID "=NEW." SQLITE_COL_OBJECTID ") IS NULL "\ + ") THEN "\ + "RAISE (" SQLITE_TRANSACTION_TYPE ", 'UPDATE on table " SQLITE_TABLE_SEARCHCLASS " "\ + "violates foreign key constraint \"" SQLITE_COL_OBJECTID "\"') " \ + "END; END;" + +#define SQLITE_TRIGGER_I_CONTAINERS_SEARCHCLASSES "CREATE TRIGGER IF NOT EXISTS " \ + SQLITE_TABLE_CONTAINERS "_I_" SQLITE_TABLE_SEARCHCLASS " " \ + "BEFORE INSERT ON " \ + SQLITE_TABLE_SEARCHCLASS " " \ + "FOR EACH ROW BEGIN "\ + "SELECT CASE "\ + "WHEN ("\ + "(SELECT " SQLITE_COL_OBJECTID " FROM " SQLITE_TABLE_CONTAINERS " "\ + "WHERE " SQLITE_COL_OBJECTID "=NEW." SQLITE_COL_OBJECTID ") IS NULL "\ + ") THEN "\ + "RAISE (" SQLITE_TRANSACTION_TYPE ", 'INSERT on table " SQLITE_TABLE_SEARCHCLASS " "\ + "violates foreign key constraint \"" SQLITE_COL_OBJECTID "\"') " \ + "END; END;" + + /**********************************************\ + * * + * Resources * + * * + \**********************************************/ + +#define SQLITE_CREATE_TABLE_RESOURCES "CREATE TABLE IF NOT EXISTS "\ + SQLITE_TABLE_RESOURCES \ + "(" \ + SQLITE_COL_RESOURCEID " " SQLITE_PRIMARY_KEY " " SQLITE_NOT_NULL "," \ + SQLITE_COL_OBJECTID " " SQLITE_TYPE_INTEGER " " SQLITE_NOT_NULL "," \ + SQLITE_COL_PROTOCOLINFO " " SQLITE_TYPE_TEXT " " SQLITE_NOT_NULL "," \ + SQLITE_COL_CONTENTTYPE " " SQLITE_TYPE_TEXT " " SQLITE_NOT_NULL "," \ + SQLITE_COL_RESOURCETYPE " " SQLITE_TYPE_INTEGER " " SQLITE_NOT_NULL "," \ + SQLITE_COL_RESOURCE " " SQLITE_TYPE_TEXT " " SQLITE_NOT_NULL "," \ + SQLITE_COL_SIZE " " SQLITE_TYPE_ULONG "," \ + SQLITE_COL_DURATION " " SQLITE_TYPE_TEXT "," \ + SQLITE_COL_BITRATE " " SQLITE_TYPE_UINTEGER "," \ + SQLITE_COL_SAMPLEFREQUENCE " " SQLITE_TYPE_UINTEGER "," \ + SQLITE_COL_BITSPERSAMPLE " " SQLITE_TYPE_UINTEGER "," \ + SQLITE_COL_NOAUDIOCHANNELS " " SQLITE_TYPE_UINTEGER "," \ + SQLITE_COL_COLORDEPTH " " SQLITE_TYPE_UINTEGER "," \ + SQLITE_COL_RESOLUTION " " SQLITE_TYPE_TEXT \ + ");" + +#define SQLITE_TRIGGER_D_OBJECT_RESOURCES "CREATE TRIGGER IF NOT EXISTS " \ + SQLITE_TABLE_OBJECTS "_D_" SQLITE_TABLE_RESOURCES " " \ + "BEFORE DELETE ON " \ + SQLITE_TABLE_OBJECTS " " \ + "FOR EACH ROW BEGIN "\ + "DELETE FROM " SQLITE_TABLE_RESOURCES " "\ + "WHERE " SQLITE_COL_OBJECTID "= OLD." SQLITE_COL_OBJECTID "; " \ + "END;" + +#define SQLITE_TRIGGER_I_OBJECT_RESOURCES "CREATE TRIGGER IF NOT EXISTS " \ + SQLITE_TABLE_OBJECTS "_I_" SQLITE_TABLE_RESOURCES " " \ + "BEFORE INSERT ON " \ + SQLITE_TABLE_RESOURCES " " \ + "FOR EACH ROW BEGIN "\ + "SELECT CASE "\ + "WHEN ("\ + "(SELECT " SQLITE_COL_OBJECTID " FROM " SQLITE_TABLE_OBJECTS " "\ + "WHERE " SQLITE_COL_OBJECTID "=NEW." SQLITE_COL_OBJECTID ") IS NULL"\ + ") THEN "\ + "RAISE (" SQLITE_TRANSACTION_TYPE ", 'INSERT on table " SQLITE_TABLE_RESOURCES " "\ + "violates foreign key constraint \"" SQLITE_COL_OBJECTID "\"') " \ + "END; END;" + +#define SQLITE_TRIGGER_U_OBJECT_RESOURCES "CREATE TRIGGER IF NOT EXISTS " \ + SQLITE_TABLE_OBJECTS "_U_" SQLITE_TABLE_RESOURCES " " \ + "BEFORE UPDATE ON " \ + SQLITE_TABLE_RESOURCES " " \ + "FOR EACH ROW BEGIN "\ + "SELECT CASE "\ + "WHEN ("\ + "(SELECT " SQLITE_COL_OBJECTID " FROM " SQLITE_TABLE_OBJECTS " "\ + "WHERE " SQLITE_COL_OBJECTID "=NEW." SQLITE_COL_OBJECTID ") IS NULL"\ + ") THEN "\ + "RAISE (" SQLITE_TRANSACTION_TYPE ", 'INSERT on table " SQLITE_TABLE_RESOURCES " "\ + "violates foreign key constraint \"" SQLITE_COL_OBJECTID "\"') " \ + "END; END;" + +class cSQLiteDatabase; + +class cRow : public cListObject { + friend class cSQLiteDatabase; +private: + int currentCol; + int ColCount; + char** Columns; + char** Values; + cRow(); +public: + virtual ~cRow(); + int Count(){ return this->ColCount; } + bool fetchColumn(cString* Column, cString* Value); + bool fetchColumn(char** Column, char** Value); +}; + +class cRows : public cList<cRow> { + friend class cSQLiteDatabase; +private: + cRow* mLastRow; + cRows(); +public: + virtual ~cRows(); + bool fetchRow(cRow** Row); +}; + +class cSQLiteDatabase { +private: + bool mAutoCommit; + bool mActiveTransaction; + cRow* mLastRow; + cRows* mRows; + sqlite3* mDatabase; + static cSQLiteDatabase* mInstance; + cSQLiteDatabase(); + int initialize(); + int initializeTables(); + int initializeTriggers(); + static int getResultRow(void* DB, int NumCols, char** Values, char** ColNames); +public: + virtual ~cSQLiteDatabase(); + static cSQLiteDatabase* getInstance(); + int getResultCount() const { return this->mRows->Count(); } + long getLastInsertRowID() const; + cRows* getResultRows() const { return this->mRows; } + int execStatement(const char* Statement); + void startTransaction(); + void commitTransaction(); + void rollbackTransaction(); + void setAutoCommit(bool Commit=true){ this->mAutoCommit = Commit; } +}; + +#endif /* _DATABASE_H */
\ No newline at end of file diff --git a/database/metadata.cpp b/database/metadata.cpp new file mode 100644 index 0000000..ac19118 --- /dev/null +++ b/database/metadata.cpp @@ -0,0 +1,389 @@ +/* + * File: metadata.cpp + * Author: savop + * + * Created on 28. Mai 2009, 16:50 + */ + +#include <upnp/ixml.h> +#include <time.h> +#include <vdr/tools.h> +#include "object.h" +#include "resources.h" +#include "metadata.h" +#include "../common.h" +#include "../misc/search.h" +#include <vdr/channels.h> +#include <vdr/epg.h> +#include <upnp/upnp.h> + +#define KEY_SYSTEM_UPDATE_ID "SystemUpdateID" + + /**********************************************\ + * * + * Media database * + * * + \**********************************************/ + +cMediaDatabase::cMediaDatabase(){ + this->mSystemUpdateID = 0; + this->mLastInsertObjectID = 0; + this->mDatabase = cSQLiteDatabase::getInstance(); + this->mObjects = new cHash<cUPnPClassObject>; + this->mFactory = cUPnPObjectFactory::getInstance(); + this->mFactory->registerMediator(UPNP_CLASS_ITEM, new cUPnPItemMediator(this)); + this->mFactory->registerMediator(UPNP_CLASS_CONTAINER, new cUPnPContainerMediator(this)); + this->mFactory->registerMediator(UPNP_CLASS_VIDEO, new cUPnPVideoItemMediator(this)); + this->mFactory->registerMediator(UPNP_CLASS_VIDEOBC, new cUPnPVideoBroadcastMediator(this)); +} + +cMediaDatabase::~cMediaDatabase(){ + delete this->mDatabase; +} + +bool cMediaDatabase::init(){ + MESSAGE("Initializing..."); + if(this->prepareDatabase()){ + ERROR("Initializing of database failed."); + return false; + } + if(this->loadChannels()){ + ERROR("Loading channels failed"); + return false; + } + return true; +} + +void cMediaDatabase::updateSystemID(){ + cString Statement = cString::sprintf("INSERT OR REPLACE INTO %s (Key,Value) VALUES ('%s','%d')", + SQLITE_TABLE_SYSTEM, + KEY_SYSTEM_UPDATE_ID, + this->getSystemUpdateID()+1 + ); + this->mDatabase->execStatement(Statement); +} + +const char* cMediaDatabase::getContainerUpdateIDs(){ + return ""; +} + +unsigned int cMediaDatabase::getSystemUpdateID(){ + cString Statement = cString::sprintf("SELECT Value FROM %s WHERE Key='%s'", + SQLITE_TABLE_SYSTEM, + KEY_SYSTEM_UPDATE_ID + ); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return 0; + } + cRows* Rows = this->mDatabase->getResultRows(); + cRow* Row; + cString Column, Value; + if(!Rows->fetchRow(&Row)){ + ERROR("No rows found"); + return 0; + } + while(Row->fetchColumn(&Column, &Value)){ + if(!strcasecmp(Column, "Value")){ + this->mSystemUpdateID = (unsigned int)atoi(Value); + } + } + return this->mSystemUpdateID; +} + +cUPnPObjectID cMediaDatabase::getNextObjectID(){ + cString Statement, Column, Value; + const char* Format = "SELECT Key FROM %s WHERE KeyID=%s"; + Statement = cString::sprintf(Format, SQLITE_TABLE_PRIMARY_KEYS, PK_OBJECTS); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return 0; + } + cRows* Rows = this->mDatabase->getResultRows(); + cRow* Row; + if(!Rows->fetchRow(&Row)){ + ERROR("No rows found"); + return 0; + } + while(Row->fetchColumn(&Column, &Value)){ + if(!strcasecmp(Column, "Key")){ + this->mLastInsertObjectID = atoi(Value); + return this->mLastInsertObjectID; + } + } + delete Rows; + return 0; +} + +cUPnPClassObject* cMediaDatabase::getObjectByID(cUPnPObjectID ID){ + MESSAGE("Try to find Object with ID '%s'", *ID); + cUPnPClassObject* Object; + if((Object = this->mObjects->Get((unsigned int)ID))){ + MESSAGE("Found cached object with ID '%s'", *ID); + } + else if((Object = this->mFactory->getObject(ID))){ + //this->cacheObject(Object); + MESSAGE("Found object with ID '%s' in database", *ID); + } + else { + ERROR("No object with such ID '%s'", *ID); + return NULL; + } + return Object; +} + +void cMediaDatabase::cacheObject(cUPnPClassObject* Object){ + if(this->mObjects->Get((unsigned int)Object->getID())==NULL){ + MESSAGE("Added %s to cache.", *Object->getID()); + this->mObjects->Add(Object, (unsigned int)Object->getID()); + } +} + +int cMediaDatabase::prepareDatabase(){ + if(this->getObjectByID(0)==NULL){ + MESSAGE("Creating database structure"); + cUPnPClassContainer* Root = (cUPnPClassContainer*)this->mFactory->createObject(UPNP_CLASS_CONTAINER, _(PLUGIN_SHORT_NAME)); + Root->setID(0); + if(this->mFactory->saveObject(Root)) return -1; + + cClass VideoClass = { UPNP_CLASS_VIDEO, true }; + cClass AudioClass = { UPNP_CLASS_AUDIO, true }; + cClass VideoBCClass = { UPNP_CLASS_VIDEOBC, true }; + cClass AudioBCClass = { UPNP_CLASS_AUDIOBC, true }; + + cUPnPClassContainer* Video = (cUPnPClassContainer*)this->mFactory->createObject(UPNP_CLASS_CONTAINER, _("Video")); + Video->setID(1); + Root->addObject(Video); + Video->addSearchClass(VideoClass); + Video->setSearchable(true); + if(this->mFactory->saveObject(Video)) return -1; + + cUPnPClassContainer* Audio = (cUPnPClassContainer*)this->mFactory->createObject(UPNP_CLASS_CONTAINER, _("Audio")); + Audio->setID(2); + Root->addObject(Audio); + Audio->addSearchClass(AudioClass); + Audio->setSearchable(true); + if(this->mFactory->saveObject(Audio)) return -1; + + cUPnPClassContainer* TV = (cUPnPClassContainer*)this->mFactory->createObject(UPNP_CLASS_CONTAINER, _("TV")); + TV->setID(3); + TV->setContainerType(DLNA_CONTAINER_TUNER); + TV->setSearchable(true); + TV->addSearchClass(VideoBCClass); + Video->addObject(TV); + if(this->mFactory->saveObject(TV)) return -1; + + cUPnPClassContainer* Records = (cUPnPClassContainer*)this->mFactory->createObject(UPNP_CLASS_CONTAINER, _("Records")); + Records->setID(4); + Video->addObject(Records); + Records->addSearchClass(VideoClass); + Records->setSearchable(true); + if(this->mFactory->saveObject(Records)) return -1; + + cUPnPClassContainer* Radio = (cUPnPClassContainer*)this->mFactory->createObject(UPNP_CLASS_CONTAINER, _("Radio")); + Radio->setID(5); + Audio->addObject(Radio); + Radio->addSearchClass(AudioBCClass); + Radio->setSearchable(true); + if(this->mFactory->saveObject(Radio)) return -1; + } + return 0; +} + +int cMediaDatabase::loadChannels(){ + MESSAGE("Loading channels from Database"); + cUPnPClassContainer* TV = (cUPnPClassContainer*)this->getObjectByID(3); + if(TV){ + // Iterating channels + cList<cUPnPClassObject>* List = TV->getObjectList(); + for(cChannel* Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)){ + bool inList = false; + for(cUPnPClassVideoBroadcast* Child = (cUPnPClassVideoBroadcast*)List->First(); + Child; + Child = (cUPnPClassVideoBroadcast*)List->Next(Child)){ + if(!strcasecmp(Child->getChannelName(),Channel->Name())){ inList = true; break; } + } + if(!inList){ + if(!Channel->GroupSep()){ + tChannelID ChannelID = Channel->GetChannelID(); + MESSAGE("Adding channel '%s' ID:%s", Channel->Name(), *ChannelID.ToString()); + cUPnPClassVideoBroadcast* ChannelItem; + ChannelItem = (cUPnPClassVideoBroadcast*)this->mFactory->createObject(UPNP_CLASS_VIDEOBC, Channel->Name()); + ChannelItem->setChannelName(Channel->Name()); + ChannelItem->setChannelNr(Channel->Number()); + // Set primary language of the stream + if(Channel->Alang(0)){ + ChannelItem->setLanguage(Channel->Alang(0)); + } + cUPnPResources::getInstance()->createFromChannel(ChannelItem, Channel); + TV->addObject(ChannelItem); + if(this->mFactory->saveObject(ChannelItem)) return -1; + } + else { + MESSAGE("Skipping group '%s'", Channel->Name()); + // Skip channel groups + // Channel groups may be supported theoretically. However, DLNA states that a tuner needs + // a consecutive list of channels. A simple work-around may be a virtual tuner for each group. + } + } + else { + MESSAGE("Skipping %s, already in database", Channel->Name()); + } + } + } + return 0; +} + +void cMediaDatabase::Action(){ + time_t LastEPGUpdate = 0; + while(this->Running()){ + + if(cSchedules::Modified() >= LastEPGUpdate){ + MESSAGE("Schedule changed. Updating..."); + updateChannelEPG(); + LastEPGUpdate = cSchedules::Modified(); + } + + cCondWait::SleepMs(60 * 1000); // sleep a minute + } +} + +void cMediaDatabase::updateChannelEPG(){ + cUPnPClassContainer* TV = (cUPnPClassContainer*)this->getObjectByID(3); + if(TV){ + // Iterating channels + MESSAGE("Getting schedule..."); + cSchedulesLock SchedulesLock; + const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); + + cList<cUPnPClassObject>* List = TV->getObjectList(); + MESSAGE("TV folder has %d items", List->Count()); + for(cUPnPClassVideoBroadcast* ChannelItem = (cUPnPClassVideoBroadcast*)List->First(); + ChannelItem; + ChannelItem = (cUPnPClassVideoBroadcast*)List->Next(ChannelItem) + ){ + MESSAGE("Find channel by number %d", ChannelItem->getChannelNr()); + cChannel* Channel = Channels.GetByNumber(ChannelItem->getChannelNr()); + MESSAGE("Found channel with ID %s", *Channel->GetChannelID().ToString()); + + const cSchedule* Schedule = Schedules->GetSchedule(Channel); + const cEvent* Event = Schedule?Schedule->GetPresentEvent():NULL; + if(Event){ + + time_t LastEPGChange = Event->StartTime(); + time_t LastObjectChange = ChannelItem->modified(); + + MESSAGE("Last event start: %s", ctime(&LastEPGChange)); + MESSAGE("Last object modification: %s", ctime(&LastObjectChange)); + if(LastEPGChange >= LastObjectChange){ + MESSAGE("Updating details"); + + if(Event){ + ChannelItem->setTitle(Event->Title()?Event->Title():Channel->Name()); + ChannelItem->setLongDescription(Event->Description()); + ChannelItem->setDescription(Event->ShortText()); + } + else { + ChannelItem->setTitle(Channel->Name()); + ChannelItem->setLongDescription(NULL); + ChannelItem->setDescription(NULL); + } + + this->mFactory->saveObject(ChannelItem); + } + else { + MESSAGE("Channel did not change"); + } + } + else { + MESSAGE("No EPG data"); + ChannelItem->setTitle(Channel->Name()); + ChannelItem->setLongDescription(NULL); + ChannelItem->setDescription(NULL); + } + } + } +} + +int cMediaDatabase::browse(cUPnPResultSet** Results, const char* ID, bool BrowseMetadata, const char* Filter, unsigned int Offset, unsigned int Count, const char* SortCriteria){ + *Results = new cUPnPResultSet; + (*Results)->mNumberReturned = 0; + (*Results)->mTotalMatches = 0; + (*Results)->mResult = NULL; + + MESSAGE("===== Browsing ====="); + MESSAGE("ID: %s", ID); + MESSAGE("Browse %s", BrowseMetadata?"metadata":"children"); + MESSAGE("Filter: %s", Filter); + MESSAGE("Offset: %d", Offset); + MESSAGE("Count: %d", Count); + MESSAGE("Sort: %s", SortCriteria); + + cUPnPObjectID ObjectID = atoi(ID); + + cStringList* FilterList = cFilterCriteria::parse(Filter); + cList<cSortCrit>* SortCriterias = cSortCriteria::parse(SortCriteria); + + if(!SortCriterias){ + return UPNP_CDS_E_INVALID_SORT_CRITERIA; + } + + cUPnPClassObject* Object = this->getObjectByID(ObjectID); + if(Object){ + IXML_Document* DIDLDoc = NULL; + if(ixmlParseBufferEx(UPNP_DIDL_SKELETON, &DIDLDoc)==IXML_SUCCESS){ + + IXML_Node* Root = ixmlNode_getFirstChild((IXML_Node*) DIDLDoc); + switch(BrowseMetadata){ + case true: + ixmlNode_appendChild(Root, Object->createDIDLFragment(DIDLDoc, FilterList)); + delete FilterList; + (*Results)->mNumberReturned = 1; + (*Results)->mTotalMatches = 1; + (*Results)->mResult = ixmlDocumenttoString(DIDLDoc); + ixmlDocument_free(DIDLDoc); + return UPNP_E_SUCCESS; + case false: + if(Object->isContainer()){ + cUPnPClassContainer* Container = Object->getContainer(); + (*Results)->mTotalMatches = Container->getChildCount(); + cUPnPObjects* Children = Container->getObjectList(); + + if(SortCriterias){ + for(cSortCrit* SortBy = SortCriterias->First(); SortBy ; SortBy = SortCriterias->Next(SortBy)){ + MESSAGE("Sorting by %s %s", SortBy->Property, SortBy->SortDescending?"ascending":"descending"); + Children->SortBy(SortBy->Property, SortBy->SortDescending); + } + } + + cUPnPClassObject* Child = Children->First(); + if(Count==0) Count = Container->getChildCount(); + while(Offset-- && (Child = Children->Next(Child))){} + for(; Count && Child ; Child = Children->Next(Child), Count--){ + MESSAGE("Appending %s to didl", Child->getTitle()); + ixmlNode_appendChild(Root, Child->createDIDLFragment(DIDLDoc, FilterList)); + (*Results)->mNumberReturned++; + } + delete FilterList; + delete SortCriterias; + } + else { + (*Results)->mNumberReturned = 0; + (*Results)->mTotalMatches = 0; + } + (*Results)->mResult = ixmlDocumenttoString(DIDLDoc); + ixmlDocument_free(DIDLDoc); + return UPNP_E_SUCCESS; + } + } + else { + ERROR("Unable to parse DIDL skeleton"); + return UPNP_CDS_E_CANT_PROCESS_REQUEST; + } + } + else { + ERROR("No such object: %s", ID); + return UPNP_CDS_E_NO_SUCH_OBJECT; // No such object; + } + return UPNP_SOAP_E_ACTION_FAILED; +}
\ No newline at end of file diff --git a/database/metadata.h b/database/metadata.h new file mode 100644 index 0000000..32d663e --- /dev/null +++ b/database/metadata.h @@ -0,0 +1,60 @@ +/* + * File: metadata.h + * Author: savop + * + * Created on 28. Mai 2009, 21:14 + */ + +#ifndef _METADATA_H +#define _METADATA_H + +#include <vdr/tools.h> +#include <vdr/channels.h> +#include <vdr/recording.h> +#include "../common.h" +#include "database.h" +#include "object.h" +#include "resources.h" + +struct cUPnPResultSet { + int mNumberReturned; + int mTotalMatches; + const char* mResult; +}; + +struct cSearchCriteria { + const char* Property; + bool Descending; +}; + +class cMediaDatabase : public cThread { + friend class cUPnPServer; + friend class cUPnPObjectMediator; +private: + unsigned int mSystemUpdateID; + cUPnPObjectFactory* mFactory; + cHash<cUPnPClassObject>* mObjects; + cSQLiteDatabase* mDatabase; + cUPnPObjectID mLastInsertObjectID; + cUPnPObjectID getNextObjectID(); + void cacheObject(cUPnPClassObject* Object); + int prepareDatabase(); + int loadChannels(); + int loadRecordings(); + void updateChannelEPG(); + void updateRecordings(); + bool init(); + void updateSystemID(); + virtual void Action(); +public: + unsigned int getSystemUpdateID(); + const char* getContainerUpdateIDs(); + cMediaDatabase(); + virtual ~cMediaDatabase(); + cUPnPClassObject* getObjectByID(cUPnPObjectID ID); + int browse(OUT cUPnPResultSet** Results, IN const char* ID, IN bool BrowseMetadata, IN const char* Filter = "*", IN unsigned int Offset = 0, IN unsigned int Count = 0, IN const char* SortCriteria = ""); + int search(OUT cUPnPResultSet** Results, IN const char* ID, IN const char* Search, IN const char* Filter = "*", IN unsigned int Offset = 0, IN unsigned int Count = 0, IN const char* SortCriteria = ""); +}; + +#endif /* _METADATA_H */ + diff --git a/database/object.cpp b/database/object.cpp new file mode 100644 index 0000000..748078d --- /dev/null +++ b/database/object.cpp @@ -0,0 +1,1702 @@ +/* + * File: object.cpp + * Author: savop + * + * Created on 11. September 2009, 20:39 + */ + +#include <string.h> +#include <stdio.h> +#include <upnp/upnptools.h> +#include <vdr/recording.h> +#include <vector> +#include "database.h" +#include <vdr/tools.h> +#include <upnp/ixml.h> +#include "metadata.h" +#include "object.h" +#include "../common.h" +#include "resources.h" + +cUPnPResource::cUPnPResource(){ + this->mBitrate = 0; + this->mBitsPerSample = 0; + this->mColorDepth = 0; + this->mDuration = NULL; + this->mImportURI = NULL; + this->mNrAudioChannels = 0; + this->mObjectID = 0; + this->mProtocolInfo = NULL; + this->mResolution = NULL; + this->mResource = NULL; + this->mResourceID = 0; + this->mSampleFrequency = 0; + this->mSize = 0; + this->mContentType = NULL; +} + +off64_t cUPnPResource::getFileSize() const { + return (this->mSize) ? this->mSize : -1; +} + +time_t cUPnPResource::getLastModification() const { + time_t Time; + const cRecording* Recording; + const cEvent* Event; + switch(this->mResourceType){ + case UPNP_RESOURCE_CHANNEL: + case UPNP_RESOURCE_URL: + Time = time(NULL); + break; + case UPNP_RESOURCE_RECORDING: + Recording = Recordings.GetByName(this->mResource); + Event = (Recording)?Recording->Info()->GetEvent():NULL; + Time = (Event)?Event->EndTime():time(NULL); + break; + case UPNP_RESOURCE_FILE: + //break; + default: + ERROR("Invalid resource type. This resource might be broken"); + Time = -1; + } + return Time; +} + +static int CompareUPnPObjects(const void *a, const void *b){ + const cUPnPClassObject *la = *(const cUPnPClassObject **)a; + const cUPnPClassObject *lb = *(const cUPnPClassObject **)b; + return la->Compare(*lb); +} + +cUPnPObjects::cUPnPObjects(){} + +cUPnPObjects::~cUPnPObjects(){} + +void cUPnPObjects::SortBy(const char* Property, bool Descending){ + int n = Count(); + cUPnPClassObject *a[n]; + cUPnPClassObject *object = (cUPnPClassObject *)objects; + int i = 0; + while (object && i < n) { + object->setSortCriteria(Property, Descending); + a[i++] = object; + object = (cUPnPClassObject *)object->Next(); + } + qsort(a, n, sizeof(cUPnPClassObject *), CompareUPnPObjects); + objects = lastObject = NULL; + for (i = 0; i < n; i++) { + a[i]->Unlink(); + count--; + Add(a[i]); + } +} + + /**********************************************\ + * * + * UPnP Objects * + * * + \**********************************************/ + + /**********************************************\ + * * + * Object * + * * + \**********************************************/ + +cUPnPClassObject::cUPnPClassObject(){ + this->mID = -1; + this->mResources = new cList<cUPnPResource>; + this->mResourcesID = new cHash<cUPnPResource>; + this->mParent = NULL; + this->mClass = NULL; + this->mCreator = NULL; + this->mTitle = NULL; + this->mWriteStatus = WS_UNKNOWN; + this->mRestricted = true; + this->mDIDLFragment = NULL; + this->mSortCriteria = NULL; + this->mLastModified = NULL; +} + +cUPnPClassObject::~cUPnPClassObject(){ + if(this->mParent) this->mParent->getContainer()->removeObject(this); + this->mResources->Clear(); + this->mResourcesID->Clear(); + delete this->mResources; + delete this->mResourcesID; + free(this->mDIDLFragment); +} + +int cUPnPClassObject::Compare(const cListObject& ListObject) const { + char* Value1 = NULL; char* Value2 = NULL; int ret = 0; + cUPnPClassObject* Object = (cUPnPClassObject*)&ListObject; + if(Object->getProperty(this->mSortCriteria, &Value1) && + this->getProperty(this->mSortCriteria, &Value2)){ + ret = strcmp(Value1, Value2); + if(this->mSortDescending) ret *= -1; + } + return ret; +} + +void cUPnPClassObject::setSortCriteria(const char* Property, bool Descending){ + this->mSortCriteria = Property; + this->mSortDescending = Descending; +} + +void cUPnPClassObject::clearSortCriteria(){ + this->mSortCriteria = NULL; + this->mSortDescending = false; +} + +int cUPnPClassObject::setID(cUPnPObjectID ID){ + MESSAGE("Set ID from %s to %s", *this->getID(),*ID); + if((int)ID < 0){ + ERROR("Invalid object ID '%s'",*ID); + return -1; + } + this->mID = ID; + return 0; +} + +int cUPnPClassObject::setParent(cUPnPClassContainer* Parent){ + if(Parent==NULL){ + MESSAGE("Object '%s' elected as root object", *this->getID()); + } + // unregister from old parent + if(this->mParent && Parent != this->mParent){ + this->mParent->getContainer()->removeObject(this); + } + this->mParent = Parent; + return 0; +} + +int cUPnPClassObject::setClass(const char* Class){ + if( !strcasecmp(Class, UPNP_CLASS_ALBUM) || + !strcasecmp(Class, UPNP_CLASS_AUDIO) || + !strcasecmp(Class, UPNP_CLASS_AUDIOBC) || + !strcasecmp(Class, UPNP_CLASS_AUDIOBOOK) || + !strcasecmp(Class, UPNP_CLASS_CONTAINER) || + !strcasecmp(Class, UPNP_CLASS_GENRE) || + !strcasecmp(Class, UPNP_CLASS_IMAGE) || + !strcasecmp(Class, UPNP_CLASS_ITEM) || + !strcasecmp(Class, UPNP_CLASS_MOVIE) || + !strcasecmp(Class, UPNP_CLASS_MOVIEGENRE) || + !strcasecmp(Class, UPNP_CLASS_MUSICALBUM) || + !strcasecmp(Class, UPNP_CLASS_MUSICARTIST) || + !strcasecmp(Class, UPNP_CLASS_MUSICGENRE) || + !strcasecmp(Class, UPNP_CLASS_MUSICTRACK) || + !strcasecmp(Class, UPNP_CLASS_MUSICVIDCLIP) || + !strcasecmp(Class, UPNP_CLASS_OBJECT) || + !strcasecmp(Class, UPNP_CLASS_PERSON) || + !strcasecmp(Class, UPNP_CLASS_PHOTO) || + !strcasecmp(Class, UPNP_CLASS_PHOTOALBUM) || + !strcasecmp(Class, UPNP_CLASS_PLAYLIST) || + !strcasecmp(Class, UPNP_CLASS_PLAYLISTCONT) || + !strcasecmp(Class, UPNP_CLASS_STORAGEFOLD) || + !strcasecmp(Class, UPNP_CLASS_STORAGESYS) || + !strcasecmp(Class, UPNP_CLASS_STORAGEVOL) || + !strcasecmp(Class, UPNP_CLASS_TEXT) || + !strcasecmp(Class, UPNP_CLASS_VIDEO) || + !strcasecmp(Class, UPNP_CLASS_VIDEOBC) + ){ + this->mClass = strdup0(Class); + return 0; + } + else { + ERROR("Invalid or unsupported class '%s'", Class); + return -1; + } +} + +int cUPnPClassObject::setTitle(const char* Title){ + if(Title==NULL){ + ERROR("Title is empty but required"); + return -1; + } + this->mTitle = strdup0(Title); + return 0; +} + +int cUPnPClassObject::setCreator(const char* Creator){ + this->mCreator = strdup0(Creator); + return 0; +} + +int cUPnPClassObject::setRestricted(bool Restricted){ + this->mRestricted = Restricted; + return 0; +} + +int cUPnPClassObject::setWriteStatus(int WriteStatus){ + if( WriteStatus == WS_MIXED || + WriteStatus == WS_NOT_WRITABLE || + WriteStatus == WS_PROTECTED || + WriteStatus == WS_UNKNOWN || + WriteStatus == WS_WRITABLE){ + this->mWriteStatus = WriteStatus; + return 0; + } + else { + ERROR("Invalid write status '%d'", WriteStatus); + return -1; + } +} + +bool cUPnPClassObject::getProperty(const char* Property, char** Value) const { + cString Val; + if(!strcasecmp(Property, SQLITE_COL_OBJECTID) || !strcasecmp(Property, UPNP_PROP_OBJECTID)){ + Val = *this->getID(); + } + else if(!strcasecmp(Property, SQLITE_COL_PARENTID) || !strcasecmp(Property, UPNP_PROP_PARENTID)){ + Val = *this->getParentID(); + } + else if(!strcasecmp(Property, SQLITE_COL_CLASS) || !strcasecmp(Property, UPNP_PROP_CLASS)){ + Val = this->getClass(); + } + else if(!strcasecmp(Property, SQLITE_COL_TITLE) || !strcasecmp(Property, UPNP_PROP_TITLE)){ + Val = this->getTitle(); + } + else if(!strcasecmp(Property, SQLITE_COL_CREATOR) || !strcasecmp(Property, UPNP_PROP_CREATOR)){ + Val = this->getCreator(); + } + else if(!strcasecmp(Property, SQLITE_COL_RESTRICTED) || !strcasecmp(Property, UPNP_PROP_RESTRICTED)){ + Val = this->isRestricted()?"1":"0"; + } + else if(!strcasecmp(Property, SQLITE_COL_WRITESTATUS) || !strcasecmp(Property, UPNP_PROP_WRITESTATUS)){ + Val = cString::sprintf("%d",this->getWriteStatus()); + } + else { + ERROR("Invalid property '%s'", Property); + return false; + } + *Value = strdup0(*Val); + return true; +} + +cStringList* cUPnPClassObject::getPropertyList(){ + cStringList* Properties = new cStringList; + Properties->Append(strdup(UPNP_PROP_CREATOR)); + Properties->Append(strdup(UPNP_PROP_WRITESTATUS)); + return Properties; +} + +bool cUPnPClassObject::setProperty(const char* Property, const char* Value){ + int ret; + if(!strcasecmp(Property, SQLITE_COL_OBJECTID) || !strcasecmp(Property, UPNP_PROP_OBJECTID)){ + ERROR("Not allowed to set object ID by hand"); + return false; + } + else if(!strcasecmp(Property, SQLITE_COL_PARENTID) || !strcasecmp(Property, UPNP_PROP_PARENTID)){ + ERROR("Not allowed to set parent ID by hand"); + return false; + } + else if(!strcasecmp(Property, SQLITE_COL_CLASS) || !strcasecmp(Property, UPNP_PROP_CLASS)){ + ERROR("Not allowed to set class by hand"); + return false; + } + else if(!strcasecmp(Property, SQLITE_COL_TITLE) || !strcasecmp(Property, UPNP_PROP_TITLE)){ + ret = this->setTitle(Value); + } + else if(!strcasecmp(Property, SQLITE_COL_CREATOR) || !strcasecmp(Property, UPNP_PROP_CREATOR)){ + ret = this->setCreator(Value); + } + else if(!strcasecmp(Property, SQLITE_COL_RESTRICTED) || !strcasecmp(Property, UPNP_PROP_RESTRICTED)){ + ret = this->setRestricted(atoi(Value)==1?true:false); + } + else if(!strcasecmp(Property, SQLITE_COL_WRITESTATUS) || !strcasecmp(Property, UPNP_PROP_WRITESTATUS)){ + ret= this->setWriteStatus(atoi(Value)); + } + else { + ERROR("Invalid property '%s'", Property); + return false; + } + return ret<0?false:true; +} + +int cUPnPClassObject::addResource(cUPnPResource* Resource){ + MESSAGE("Adding resource #%d", Resource->getID()); + if(!Resource){ + ERROR("No resource"); + return -1; + } + this->mResources->Add(Resource); + this->mResourcesID->Add(Resource, Resource->getID()); + return 0; +} + +int cUPnPClassObject::removeResource(cUPnPResource* Resource){ + if(!Resource){ + ERROR("No resource"); + return -1; + } + this->mResourcesID->Del(Resource, Resource->getID()); + this->mResources->Del(Resource); + return 0; +} + + /**********************************************\ + * * + * Item * + * * + \**********************************************/ + +cUPnPClassItem::cUPnPClassItem(){ + this->setClass(UPNP_CLASS_ITEM); + this->mReference = NULL; +} + +int cUPnPClassItem::setReference(cUPnPClassItem* Reference){ + this->mReference = Reference; + return 0; +} + +cStringList* cUPnPClassItem::getPropertyList(){ + cStringList* Properties = cUPnPClassObject::getPropertyList(); + Properties->Append(strdup(UPNP_PROP_REFERENCEID)); + return Properties; +} + +bool cUPnPClassItem::getProperty(const char* Property, char** Value) const { + + if(!strcasecmp(Property, SQLITE_COL_REFERENCEID) || !strcasecmp(Property, UPNP_PROP_REFERENCEID)){ + *Value = strdup0(*this->getReferenceID()); + } + else return cUPnPClassObject::getProperty(Property, Value); + return true; +} + +bool cUPnPClassItem::setProperty(const char* Property, const char* Value){ + return cUPnPClassObject::setProperty(Property, Value); +} + +IXML_Node* cUPnPClassItem::createDIDLFragment(IXML_Document* Document, cStringList* Filter){ + this->mDIDLFragment = Document; + + MESSAGE("==(%s)= %s =====", *this->getID(), this->getTitle()); + MESSAGE("ParentID: %s", *this->getParentID()); + MESSAGE("Restricted: %s", this->isRestricted()?"1":"0"); + MESSAGE("Class: %s", this->getClass()); + + IXML_Node* Didl = ixmlNode_getFirstChild((IXML_Node*) this->mDIDLFragment); + + IXML_Element* eItem = ixmlDocument_createElement(this->mDIDLFragment, "item"); + ixmlElement_setAttribute(eItem, att(UPNP_PROP_OBJECTID), *this->getID()); + ixmlElement_setAttribute(eItem, att(UPNP_PROP_PARENTID), *this->getParentID()); + ixmlElement_setAttribute(eItem, att(UPNP_PROP_RESTRICTED), this->isRestricted()?"1":"0"); + + ixmlNode_appendChild(Didl, (IXML_Node*) eItem); + + IXML_Element* eTitle = ixmlDocument_createElement(this->mDIDLFragment, UPNP_PROP_TITLE); + IXML_Node* Title = ixmlDocument_createTextNode(this->mDIDLFragment, this->getTitle()); + + IXML_Element* eClass = ixmlDocument_createElement(this->mDIDLFragment, UPNP_PROP_CLASS); + IXML_Node* Class = ixmlDocument_createTextNode(this->mDIDLFragment, this->getClass()); + + ixmlNode_appendChild((IXML_Node*) eTitle, Title); + ixmlNode_appendChild((IXML_Node*) eClass, Class); + ixmlNode_appendChild((IXML_Node*) eItem, (IXML_Node*) eTitle); + ixmlNode_appendChild((IXML_Node*) eItem, (IXML_Node*) eClass); + +// if(Filter==NULL || Filter->Find(UPNP_PROP_CREATOR)) ixmlAddProperty(this->mDIDLFragment, eItem, UPNP_PROP_CREATOR, this->getCreator()); +// if(Filter==NULL || Filter->Find(UPNP_PROP_WRITESTATUS)) ixmlAddProperty(this->mDIDLFragment, eItem, UPNP_PROP_WRITESTATUS, itoa(this->getWriteStatus())); +// if(Filter==NULL || Filter->Find(UPNP_PROP_REFERENCEID)) ixmlAddProperty(this->mDIDLFragment, eItem, UPNP_PROP_REFERENCEID, *this->getReferenceID()); + + for(cUPnPResource* Resource = this->getResources()->First(); Resource; Resource = this->getResources()->Next(Resource)){ + MESSAGE("Resource: %s", Resource->getResource()); + MESSAGE("Protocolinfo: %s", Resource->getProtocolInfo()); + + cString URLBase = cString::sprintf("http://%s:%d", UpnpGetServerIpAddress(), UpnpGetServerPort()); + cString ResourceURL = cString::sprintf("%s%s/get?resId=%d", *URLBase, UPNP_DIR_SHARES, Resource->getID()); + + MESSAGE("Resource-URI: %s", *ResourceURL); + + IXML_Element* eRes = ixmlDocument_createElement(this->mDIDLFragment, UPNP_PROP_RESOURCE); + IXML_Node* Res = ixmlDocument_createTextNode(this->mDIDLFragment, *ResourceURL); + ixmlNode_appendChild((IXML_Node*) eRes, Res); + + if(Resource->getBitrate()) ixmlElement_setAttribute(eRes, att(UPNP_PROP_BITRATE), itoa(Resource->getBitrate())); + if(Resource->getBitsPerSample()) ixmlElement_setAttribute(eRes, att(UPNP_PROP_BITSPERSAMPLE), itoa(Resource->getBitsPerSample())); + if(Resource->getColorDepth()) ixmlElement_setAttribute(eRes, att(UPNP_PROP_COLORDEPTH), itoa(Resource->getColorDepth())); + if(Resource->getDuration()) ixmlElement_setAttribute(eRes, att(UPNP_PROP_DURATION), Resource->getDuration()); + if(Resource->getProtocolInfo()) ixmlElement_setAttribute(eRes, att(UPNP_PROP_PROTOCOLINFO), Resource->getProtocolInfo()); + + ixmlNode_appendChild((IXML_Node*) eItem, (IXML_Node*) eRes); + } + + return (IXML_Node*)eItem; +} + + /**********************************************\ + * * + * Container * + * * + \**********************************************/ + +cUPnPClassContainer::cUPnPClassContainer(){ + this->setClass(UPNP_CLASS_CONTAINER); + this->mChildren = new cUPnPObjects; + this->mChildrenID = new cHash<cUPnPClassObject>; + this->mContainerType = NULL; + this->mUpdateID = 0; + this->mSearchable = false; +} + +cUPnPClassContainer::~cUPnPClassContainer(){ + delete this->mChildren; + delete this->mChildrenID; +} + +IXML_Node* cUPnPClassContainer::createDIDLFragment(IXML_Document* Document, cStringList* Filter){ + this->mDIDLFragment = Document; + + MESSAGE("===(%s)= %s =====", *this->getID(), this->getTitle()); + MESSAGE("ParentID: %s", *this->getParentID()); + MESSAGE("Restricted: %s", this->isRestricted()?"1":"0"); + MESSAGE("Class: %s", this->getClass()); + + IXML_Node* Didl = ixmlNode_getFirstChild((IXML_Node*) this->mDIDLFragment); + IXML_Element* eItem = ixmlDocument_createElement(this->mDIDLFragment, "container"); + ixmlElement_setAttribute(eItem, att(UPNP_PROP_OBJECTID), *this->getID()); + ixmlElement_setAttribute(eItem, att(UPNP_PROP_PARENTID), *this->getParentID()); + ixmlElement_setAttribute(eItem, att(UPNP_PROP_RESTRICTED), this->isRestricted()?"1":"0"); + ixmlNode_appendChild(Didl, (IXML_Node*) eItem); + + IXML_Element* eTitle = ixmlDocument_createElement(this->mDIDLFragment, UPNP_PROP_TITLE); + IXML_Node* Title = ixmlDocument_createTextNode(this->mDIDLFragment, this->getTitle()); + + IXML_Element* eClass = ixmlDocument_createElement(this->mDIDLFragment, UPNP_PROP_CLASS); + IXML_Node* Class = ixmlDocument_createTextNode(this->mDIDLFragment, this->getClass()); + + ixmlNode_appendChild((IXML_Node*) eTitle, Title); + ixmlNode_appendChild((IXML_Node*) eClass, Class); + ixmlNode_appendChild((IXML_Node*) eItem, (IXML_Node*) eTitle); + ixmlNode_appendChild((IXML_Node*) eItem, (IXML_Node*) eClass); + + return (IXML_Node*)eItem; +} + +int cUPnPClassContainer::setUpdateID(unsigned int UID){ + this->mUpdateID = UID; + return 0; +} + +cStringList* cUPnPClassContainer::getPropertyList(){ + cStringList* Properties = cUPnPClassObject::getPropertyList(); + Properties->Append(strdup(UPNP_PROP_DLNA_CONTAINERTYPE)); + Properties->Append(strdup(UPNP_PROP_SEARCHABLE)); + return Properties; +} + +bool cUPnPClassContainer::setProperty(const char* Property, const char* Value){ + int ret; + if(!strcasecmp(Property, SQLITE_COL_DLNA_CONTAINERTYPE) || !strcasecmp(Property, UPNP_PROP_DLNA_CONTAINERTYPE)){ + ret = this->setContainerType(Value); + } + else if(!strcasecmp(Property, SQLITE_COL_SEARCHABLE) || !strcasecmp(Property, UPNP_PROP_SEARCHABLE)){ + ret = this->setSearchable(Value); + } + else if(!strcasecmp(Property, SQLITE_COL_CONTAINER_UID)){ + ret = this->setUpdateID((unsigned int)atoi(Value)); + } + else return cUPnPClassObject::setProperty(Property, Value); + return ret<0?false:true; +} + +bool cUPnPClassContainer::getProperty(const char* Property, char** Value) const { + cString Val; + if(!strcasecmp(Property, SQLITE_COL_DLNA_CONTAINERTYPE) || !strcasecmp(Property, UPNP_PROP_DLNA_CONTAINERTYPE)){ + Val = this->getContainerType(); + } + else if(!strcasecmp(Property, SQLITE_COL_SEARCHABLE) || !strcasecmp(Property, UPNP_PROP_SEARCHABLE)){ + Val = this->isSearchable()?"1":"0"; + } + else if(!strcasecmp(Property, SQLITE_COL_CONTAINER_UID)){ + Val = cString::sprintf("%d", this->getUpdateID()); + } + else return cUPnPClassObject::getProperty(Property, Value); + *Value = strdup0(*Val); + return true; +} + +void cUPnPClassContainer::addObject(cUPnPClassObject* Object){ + MESSAGE("Adding object (ID:%s) to container (ID:%s)", *Object->getID(), *this->getID()); + this->mChildren->Add(Object); + this->mChildrenID->Add(Object, (unsigned int)Object->getID()); + Object->setParent(this); +} + +void cUPnPClassContainer::removeObject(cUPnPClassObject* Object){ + MESSAGE("Removing object (ID:%s) from container (ID:%s)", *Object->getID(), *this->getID()); + this->mChildrenID->Del(Object, (unsigned int)Object->getID()); + this->mChildren->Del(Object, false); + Object->mParent = NULL; +} + +cUPnPClassObject* cUPnPClassContainer::getObject(cUPnPObjectID ID) const { + MESSAGE("Getting object (ID:%s)", *ID); + if((int)ID < 0){ + ERROR("Invalid object ID"); + return NULL; + } + return this->mChildrenID->Get((unsigned int)ID); +} + +int cUPnPClassContainer::setContainerType(const char* Type){ + if(Type==NULL){ + this->mContainerType = Type; + } + else if(!strcasecmp(Type, DLNA_CONTAINER_TUNER)){ + this->mContainerType = Type; + } + else { + ERROR("Invalid container type '%s'",Type); + return -1; + } + return 0; +} + +int cUPnPClassContainer::addSearchClass(cClass SearchClass){ + this->mSearchClasses.push_back(SearchClass); + return 0; +} + +int cUPnPClassContainer::delSearchClass(cClass SearchClass){ + tClassVector::iterator it = this->mSearchClasses.begin(); + cClass Class; + for(unsigned int i=0; i<this->mSearchClasses.size(); i++){ + Class = this->mSearchClasses[i]; + if(Class == SearchClass){ + this->mSearchClasses.erase(it+i); + return 0; + } + } + return -1; +} + +int cUPnPClassContainer::addCreateClass(cClass CreateClass){ + this->mCreateClasses.push_back(CreateClass); + return 0; +} + +int cUPnPClassContainer::delCreateClass(cClass CreateClass){ + tClassVector::iterator it = this->mCreateClasses.begin(); + cClass Class; + for(unsigned int i=0; i<this->mCreateClasses.size(); i++){ + Class = this->mCreateClasses[i]; + if(Class == CreateClass){ + this->mCreateClasses.erase(it+i); + return 0; + } + } + return -1; +} + +int cUPnPClassContainer::setSearchClasses(std::vector<cClass> SearchClasses){ + this->mSearchClasses = SearchClasses; + return 0; +} + +int cUPnPClassContainer::setCreateClasses(std::vector<cClass> CreateClasses){ + this->mCreateClasses = CreateClasses; + return 0; +} + +int cUPnPClassContainer::setSearchable(bool Searchable){ + this->mSearchable = Searchable; + return 0; +} + +bool cUPnPClassContainer::isUpdated(){ + static unsigned int lastUpdateID = this->getUpdateID(); + if(lastUpdateID != this->getUpdateID()){ + lastUpdateID = this->getUpdateID(); + return true; + } + else return false; +} + + /**********************************************\ + * * + * Video item * + * * + \**********************************************/ + +cUPnPClassVideoItem::cUPnPClassVideoItem(){ + this->setClass(UPNP_CLASS_VIDEO); + this->mGenre = NULL; + this->mLongDescription = NULL; + this->mProducers = NULL; + this->mRating = NULL; + this->mActors = NULL; + this->mDirectors = NULL; + this->mDescription = NULL; + this->mPublishers = NULL; + this->mLanguage = NULL; + this->mRelations = NULL; +} + +cUPnPClassVideoItem::~cUPnPClassVideoItem(){ +} + +//cString cUPnPClassVideoItem::createDIDLFragment(cStringList* Filter){ +// return NULL; +//} + +cStringList* cUPnPClassVideoItem::getPropertyList(){ + cStringList* Properties = cUPnPClassItem::getPropertyList(); + Properties->Append(strdup(UPNP_PROP_LONGDESCRIPTION)); + Properties->Append(strdup(UPNP_PROP_PRODUCER)); + Properties->Append(strdup(UPNP_PROP_GENRE)); + Properties->Append(strdup(UPNP_PROP_RATING)); + Properties->Append(strdup(UPNP_PROP_ACTOR)); + Properties->Append(strdup(UPNP_PROP_DIRECTOR)); + Properties->Append(strdup(UPNP_PROP_DESCRIPTION)); + Properties->Append(strdup(UPNP_PROP_PUBLISHER)); + Properties->Append(strdup(UPNP_PROP_LANGUAGE)); + Properties->Append(strdup(UPNP_PROP_RELATION)); + return Properties; +} + +bool cUPnPClassVideoItem::getProperty(const char* Property, char** Value) const { + cString Val; + if(!strcasecmp(Property,SQLITE_COL_GENRE) || !strcasecmp(Property,UPNP_PROP_GENRE)){ + Val = this->getGenre(); + } + else if(!strcasecmp(Property,SQLITE_COL_LONGDESCRIPTION) || !strcasecmp(Property,UPNP_PROP_LONGDESCRIPTION)){ + Val = this->getLongDescription(); + } + else if(!strcasecmp(Property,SQLITE_COL_PRODUCER) || !strcasecmp(Property,UPNP_PROP_PRODUCER)){ + Val = this->getProducers(); + } + else if(!strcasecmp(Property,SQLITE_COL_RATING) || !strcasecmp(Property,UPNP_PROP_RATING)){ + Val = this->getRating(); + } + else if(!strcasecmp(Property,SQLITE_COL_ACTOR) || !strcasecmp(Property,UPNP_PROP_ACTOR)){ + Val = this->getActors(); + } + else if(!strcasecmp(Property,SQLITE_COL_DIRECTOR) || !strcasecmp(Property,UPNP_PROP_DIRECTOR)){ + Val = this->getDirectors(); + } + else if(!strcasecmp(Property,SQLITE_COL_DESCRIPTION) || !strcasecmp(Property,UPNP_PROP_DESCRIPTION)){ + Val = this->getDescription(); + } + else if(!strcasecmp(Property,SQLITE_COL_PUBLISHER) || !strcasecmp(Property,UPNP_PROP_PUBLISHER)){ + Val = this->getPublishers(); + } + else if(!strcasecmp(Property,SQLITE_COL_LANGUAGE) || !strcasecmp(Property,UPNP_PROP_LANGUAGE)){ + Val = this->getLanguage(); + } + else if(!strcasecmp(Property,SQLITE_COL_RELATION) || !strcasecmp(Property,UPNP_PROP_RELATION)){ + Val = this->getRelations(); + } + else return cUPnPClassItem::getProperty(Property, Value); + *Value = strdup0(*Val); + return true; +} + +bool cUPnPClassVideoItem::setProperty(const char* Property, const char* Value){ + bool ret; + if(!strcasecmp(Property,SQLITE_COL_GENRE) || !strcasecmp(Property,UPNP_PROP_GENRE)){ + ret = this->setGenre(Value); + } + else if(!strcasecmp(Property,SQLITE_COL_LONGDESCRIPTION) || !strcasecmp(Property,UPNP_PROP_LONGDESCRIPTION)){ + ret = this->setLongDescription(Value); + } + else if(!strcasecmp(Property,SQLITE_COL_PRODUCER) || !strcasecmp(Property,UPNP_PROP_PRODUCER)){ + ret = this->setProducers(Value); + } + else if(!strcasecmp(Property,SQLITE_COL_RATING) || !strcasecmp(Property,UPNP_PROP_RATING)){ + ret = this->setRating(Value); + } + else if(!strcasecmp(Property,SQLITE_COL_ACTOR) || !strcasecmp(Property,UPNP_PROP_ACTOR)){ + ret = this->setActors(Value); + } + else if(!strcasecmp(Property,SQLITE_COL_DIRECTOR) || !strcasecmp(Property,UPNP_PROP_DIRECTOR)){ + ret = this->setDirectors(Value); + } + else if(!strcasecmp(Property,SQLITE_COL_DESCRIPTION) || !strcasecmp(Property,UPNP_PROP_DESCRIPTION)){ + ret = this->setDescription(Value); + } + else if(!strcasecmp(Property,SQLITE_COL_PUBLISHER) || !strcasecmp(Property,UPNP_PROP_PUBLISHER)){ + ret = this->setPublishers(Value); + } + else if(!strcasecmp(Property,SQLITE_COL_LANGUAGE) || !strcasecmp(Property,UPNP_PROP_LANGUAGE)){ + ret = this->setLanguage(Value); + } + else if(!strcasecmp(Property,SQLITE_COL_RELATION) || !strcasecmp(Property,UPNP_PROP_RELATION)){ + ret = this->setRelations(Value); + } + else return cUPnPClassItem::setProperty(Property, Value); + return ret<0?false:true; +} + +int cUPnPClassVideoItem::setActors(const char* Actors){ + this->mActors = Actors; + return 0; +} + +int cUPnPClassVideoItem::setGenre(const char* Genre){ + this->mGenre = Genre; + return 0; +} + +int cUPnPClassVideoItem::setDescription(const char* Description){ + this->mDescription = Description; + return 0; +} + +int cUPnPClassVideoItem::setLongDescription(const char* LongDescription){ + this->mLongDescription = LongDescription; + return 0; +} + +int cUPnPClassVideoItem::setProducers(const char* Producers){ + this->mProducers = Producers; + return 0; +} + +int cUPnPClassVideoItem::setRating(const char* Rating){ + this->mRating = Rating; + return 0; +} + +int cUPnPClassVideoItem::setDirectors(const char* Directors){ + this->mDirectors = Directors; + return 0; +} + +int cUPnPClassVideoItem::setPublishers(const char* Publishers){ + this->mPublishers = Publishers; + return 0; +} + +int cUPnPClassVideoItem::setLanguage(const char* Language){ + this->mLanguage = Language; + return 0; +} + +int cUPnPClassVideoItem::setRelations(const char* Relations){ + this->mRelations = Relations; + return 0; +} + + /**********************************************\ + * * + * Video Broadcast item * + * * + \**********************************************/ + +cUPnPClassVideoBroadcast::cUPnPClassVideoBroadcast(){ + this->setClass(UPNP_CLASS_VIDEOBC); + this->mIcon = NULL; + this->mRegion = NULL; + this->mChannelNr = 0; +} + +cUPnPClassVideoBroadcast::~cUPnPClassVideoBroadcast(){ +} + +//cString cUPnPClassVideoBroadcast::createDIDLFragment(cStringList* Filter){ +// return NULL; +//} + +cStringList* cUPnPClassVideoBroadcast::getPropertyList(){ + cStringList* Properties = cUPnPClassVideoItem::getPropertyList(); + Properties->Append(strdup(UPNP_PROP_CHANNELNAME)); + Properties->Append(strdup(UPNP_PROP_CHANNELNR)); + Properties->Append(strdup(UPNP_PROP_ICON)); + Properties->Append(strdup(UPNP_PROP_REGION)); + return Properties; +} + +bool cUPnPClassVideoBroadcast::setProperty(const char* Property, const char* Value){ + bool ret; + if(!strcasecmp(Property, SQLITE_COL_CHANNELNAME) || !strcasecmp(Property, UPNP_PROP_CHANNELNAME)){ + ret = this->setChannelName(Value); + } + else if(!strcasecmp(Property, SQLITE_COL_CHANNELNR) || !strcasecmp(Property, UPNP_PROP_CHANNELNR)){ + ret = this->setChannelNr(atoi(Value)); + } + else if(!strcasecmp(Property, SQLITE_COL_ICON) || !strcasecmp(Property, UPNP_PROP_ICON)){ + ret = this->setIcon(Value); + } + else if(!strcasecmp(Property, SQLITE_COL_REGION) || !strcasecmp(Property, UPNP_PROP_REGION)){ + ret = this->setRegion(Value); + } + else return cUPnPClassVideoItem::setProperty(Property, Value); + return ret<0?false:true; +} + +bool cUPnPClassVideoBroadcast::getProperty(const char* Property, char** Value) const { + cString Val; + if(!strcasecmp(Property, SQLITE_COL_CHANNELNAME) || !strcasecmp(Property, UPNP_PROP_CHANNELNAME)){ + Val = this->getChannelName(); + } + else if(!strcasecmp(Property, SQLITE_COL_CHANNELNR) || !strcasecmp(Property, UPNP_PROP_CHANNELNR)){ + Val = itoa(this->getChannelNr()); + } + else if(!strcasecmp(Property, SQLITE_COL_ICON) || !strcasecmp(Property, UPNP_PROP_ICON)){ + Val = this->getIcon(); + } + else if(!strcasecmp(Property, SQLITE_COL_REGION) || !strcasecmp(Property, UPNP_PROP_REGION)){ + Val = this->getRegion(); + } + else return cUPnPClassVideoItem::getProperty(Property, Value); + *Value = strdup0(*Val); + return true; +} + +int cUPnPClassVideoBroadcast::setChannelName(const char* ChannelName){ + this->mChannelName = ChannelName; + return 0; +} + +int cUPnPClassVideoBroadcast::setChannelNr(int ChannelNr){ + this->mChannelNr = ChannelNr; + return 0; +} + +int cUPnPClassVideoBroadcast::setIcon(const char* IconURI){ + this->mIcon = IconURI; + return 0; +} + +int cUPnPClassVideoBroadcast::setRegion(const char* Region){ + this->mRegion = Region; + return 0; +} + + /**********************************************\ + * * + * Mediator factory * + * * + \**********************************************/ + +cUPnPObjectFactory* cUPnPObjectFactory::mInstance = NULL; + +cUPnPObjectFactory* cUPnPObjectFactory::getInstance(){ + if(!cUPnPObjectFactory::mInstance) + cUPnPObjectFactory::mInstance = new cUPnPObjectFactory(); + + if(cUPnPObjectFactory::mInstance) return cUPnPObjectFactory::mInstance; + else return NULL; +} + +cUPnPObjectFactory::cUPnPObjectFactory(){ + this->mDatabase = cSQLiteDatabase::getInstance(); +} + +void cUPnPObjectFactory::registerMediator(const char* UPnPClass, cMediatorInterface* Mediator){ + if(UPnPClass == NULL){ + ERROR("Class is undefined"); + return; + } + if(Mediator == NULL){ + ERROR("Mediator is undefined"); + return; + } + MESSAGE("Registering mediator for class '%s'", UPnPClass); + this->mMediators[UPnPClass] = Mediator; + MESSAGE("Now %d mediators registered", this->mMediators.size()); + return; +} + +void cUPnPObjectFactory::unregisterMediator(const char* UPnPClass, bool freeMediator){ + if(UPnPClass == NULL){ + ERROR("Class is undefined"); + return; + } + tMediatorMap::iterator MediatorIterator = this->mMediators.find(UPnPClass); + if(MediatorIterator==this->mMediators.end()){ + ERROR("No such mediator found for class '%s'", UPnPClass); + return; + } + MESSAGE("Unregistering mediator for class '%s'", UPnPClass); + this->mMediators.erase(MediatorIterator); + if(freeMediator) delete MediatorIterator->second; + MESSAGE("Now %d mediators registered", this->mMediators.size()); + return; +} + +cMediatorInterface* cUPnPObjectFactory::findMediatorByID(cUPnPObjectID ID){ + cString Format = "SELECT %s FROM %s WHERE %s=%s"; + cString Statement = NULL, Column = NULL, Value = NULL, Class = NULL; + cRows* Rows; cRow* Row; + Statement = cString::sprintf(Format, SQLITE_COL_CLASS, SQLITE_TABLE_OBJECTS, SQLITE_COL_OBJECTID, *ID); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return NULL; + } + Rows = this->mDatabase->getResultRows(); + if(!Rows->fetchRow(&Row)){ + ERROR("No such object with ID '%s'",*ID); + return NULL; + } + while(Row->fetchColumn(&Column, &Value)){ + if(!strcasecmp(Column, SQLITE_COL_CLASS)){ + Class = strdup0(*Value); + } + } + return this->findMediatorByClass(Class); +} + +cMediatorInterface* cUPnPObjectFactory::findMediatorByClass(const char* Class){ + if(!Class){ ERROR("No class specified"); return NULL; } + MESSAGE("Searching for mediator '%s' in %d mediators", Class, this->mMediators.size()); + tMediatorMap::iterator MediatorIterator = this->mMediators.find(Class); + if(MediatorIterator==this->mMediators.end()){ + ERROR("No matching mediator for class '%s'",Class); + return NULL; + } + else { + return MediatorIterator->second; + } +} + +cUPnPClassObject* cUPnPObjectFactory::getObject(cUPnPObjectID ID){ + cMediatorInterface* Mediator = this->findMediatorByID(ID); + if(Mediator) return Mediator->getObject(ID); + else { + return NULL; + } +} + +cUPnPClassObject* cUPnPObjectFactory::createObject(const char* UPnPClass, const char* Title, bool Restricted){ + cMediatorInterface* Mediator = this->findMediatorByClass(UPnPClass); + return Mediator->createObject(Title, Restricted); +} + +int cUPnPObjectFactory::deleteObject(cUPnPClassObject* Object){ + cMediatorInterface* Mediator = this->findMediatorByClass(Object->getClass()); + return Mediator->deleteObject(Object); +} + +int cUPnPObjectFactory::clearObject(cUPnPClassObject* Object){ + cMediatorInterface* Mediator = this->findMediatorByClass(Object->getClass()); + return Mediator->clearObject(Object); +} + +int cUPnPObjectFactory::saveObject(cUPnPClassObject* Object){ + cMediatorInterface* Mediator = this->findMediatorByClass(Object->getClass()); + return Mediator->saveObject(Object); +} + + /**********************************************\ + * * + * Mediators * + * * + \**********************************************/ + + /**********************************************\ + * * + * Object mediator * + * * + \**********************************************/ + +cUPnPObjectMediator::cUPnPObjectMediator(cMediaDatabase* MediaDatabase) : + mMediaDatabase(MediaDatabase){ + this->mDatabase = cSQLiteDatabase::getInstance(); +} + +cUPnPObjectMediator::~cUPnPObjectMediator(){ + delete this->mDatabase; + delete this->mMediaDatabase; +} + +int cUPnPObjectMediator::saveObject(cUPnPClassObject* Object){ + bool succesful = true; + + this->mDatabase->startTransaction(); + if(Object->getID() == -1) succesful = false; + else if(this->objectToDatabase(Object)) succesful = false; + else succesful = true; + + if(succesful){ + this->mDatabase->commitTransaction(); + Object->setModified(); + this->mMediaDatabase->cacheObject(Object); + this->mMediaDatabase->updateSystemID(); + return 0; + } + else { + this->mDatabase->rollbackTransaction(); + return -1; + } + return -1; +} + +int cUPnPObjectMediator::deleteObject(cUPnPClassObject* Object){ + cString Statement = NULL; + cString Format = "DELETE FROM %s WHERE %s=%s"; + Statement = cString::sprintf(Format, SQLITE_TABLE_OBJECTS, SQLITE_COL_OBJECTID, *Object->getID()); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + #ifdef SQLITE_CASCADE_DELETES + this->clearObject(Object); + #endif + delete Object; Object = NULL; + return 0; +} + +int cUPnPObjectMediator::clearObject(cUPnPClassObject* Object){ + cUPnPClassContainer* Container = Object->getContainer(); + if(Container){ + cList<cUPnPClassObject>* List = Container->getObjectList(); + for(cUPnPClassObject* Child = List->First(); Child; Child = List->Next(Child)){ + if(this->deleteObject(Child)) return -1; + } + } + return 0; +} + +int cUPnPObjectMediator::initializeObject(cUPnPClassObject* Object, const char* Class, const char* Title, bool Restricted){ + cUPnPObjectID ObjectID = this->mMediaDatabase->getNextObjectID(); + if(Object->setID(ObjectID)){ + ERROR("Error while setting ID"); + return -1; + } + cUPnPClassObject* Root = this->mMediaDatabase->getObjectByID(0); + if(Root){ + Root->getContainer()->addObject(Object); + } + else { + Object->setParent(NULL); + } + if(Object->setClass(Class)){ + ERROR("Error while setting class"); + return -1; + } + if(Object->setTitle(Title)){ + ERROR("Error while setting title"); + return -1; + } + if(Object->setRestricted(Restricted)){ + ERROR("Error while setting restriction"); + return -1; + } + char* escapedTitle; + escapeSQLite(Object->getTitle(), &escapedTitle); + cString Statement = cString::sprintf("INSERT INTO %s (%s, %s, %s, %s, %s) VALUES (%s, %s, '%s', '%s', %d)", + SQLITE_TABLE_OBJECTS, + SQLITE_COL_OBJECTID, + SQLITE_COL_PARENTID, + SQLITE_COL_CLASS, + SQLITE_COL_TITLE, + SQLITE_COL_RESTRICTED, + *Object->getID(), + *Object->getParentID(), + Object->getClass(), + escapedTitle, + Object->isRestricted()?1:0 + ); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + free(escapedTitle); + return 0; +} + +cUPnPClassObject* cUPnPObjectMediator::getObject(cUPnPObjectID){ WARNING("Getting instance of class 'Object' forbidden"); return NULL; } + +cUPnPClassObject* cUPnPObjectMediator::createObject(const char*, bool){ WARNING("Getting instance of class 'Object' forbidden"); return NULL; } + +int cUPnPObjectMediator::objectToDatabase(cUPnPClassObject* Object){ + cString Format = "UPDATE %s SET %s WHERE %s='%s'"; + //cString Format = "INSERT OR REPLACE INTO %s (%s) VALUES (%s);"; + cString Statement=NULL; + cString Set=NULL; + //cString Columns=NULL, Values=NULL; + char *Value=NULL; + cString Properties[] = { + SQLITE_COL_OBJECTID, + SQLITE_COL_PARENTID, + SQLITE_COL_CLASS, + SQLITE_COL_TITLE, + SQLITE_COL_RESTRICTED, + SQLITE_COL_CREATOR, + SQLITE_COL_WRITESTATUS, + NULL + }; + for(cString* Property = Properties; *(*Property)!=NULL; Property++){ + //Columns = cString::sprintf("%s%s%s", *Columns?*Columns:"", *Columns?",":"", *(*Property)); + if(!Object->getProperty(*Property, &Value)){ + ERROR("No such property '%s' in object with ID '%s'",*(*Property),*Object->getID()); + return -1; + } + char *escapedValue; + escapeSQLite(Value, &escapedValue); + //Values = cString::sprintf("%s%s'%s'", *Values?*Values:"", *Values?",":"", Value?Value:"NULL"); + Set = cString::sprintf("%s%s%s='%s'", *Set?*Set:"", *Set?",":"", *(*Property), escapedValue?escapedValue:"NULL"); + + } + Statement = cString::sprintf(Format, SQLITE_TABLE_OBJECTS, *Set, SQLITE_COL_OBJECTID, *Object->getID()); + //Statement = cString::sprintf(Format, SQLITE_TABLE_OBJECTS, *Columns, *Values); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + return 0; +} + +int cUPnPObjectMediator::databaseToObject(cUPnPClassObject* Object, cUPnPObjectID ID){ + cString Format = "SELECT * FROM %s WHERE %s=%s"; + cString Statement = NULL, Column = NULL, Value = NULL; + cRows* Rows; cRow* Row; + Statement = cString::sprintf(Format, SQLITE_TABLE_OBJECTS, SQLITE_COL_OBJECTID, *ID); +// MESSAGE("Fehler hier"); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + Rows = this->mDatabase->getResultRows(); + if(!Rows->fetchRow(&Row)){ + ERROR("No such object with ID '%s'",*ID); + return -1; + } + while(Row->fetchColumn(&Column, &Value)){ + if(!strcasecmp(Column, SQLITE_COL_OBJECTID)){ + if(Object->setID(atoi(Value))){ + ERROR("Error while setting object ID"); + return -1; + } + this->mMediaDatabase->cacheObject(Object); + } + else if(!strcasecmp(Column, SQLITE_COL_PARENTID)){ + cUPnPObjectID RefID = atoi(Value); + cUPnPClassContainer* ParentObject; + if(RefID == -1){ + ParentObject = NULL; + } + else { + ParentObject = (cUPnPClassContainer*)this->mMediaDatabase->getObjectByID(RefID); + if(!ParentObject){ + ERROR("No such parent with ID '%s' found.",*RefID); + return -1; + } + } + Object->setParent(ParentObject); + } + else if(!strcasecmp(Column, SQLITE_COL_CLASS)){ + if(Object->setClass(Value)){ + ERROR("Error while setting class"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_TITLE)){ + if(Object->setTitle(Value)){ + ERROR("Error while setting title"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_RESTRICTED)){ + if(Object->setRestricted(atoi(Value)==1?true:false)){ + ERROR("Error while setting restriction"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_CREATOR)){ + if(Object->setCreator(Value)){ + ERROR("Error while setting creator"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_WRITESTATUS)){ + if(Object->setWriteStatus(atoi(Value))){ + ERROR("Error while setting write status"); + return -1; + } + } + } + cUPnPResources::getInstance()->getResourcesOfObject(Object); + return 0; +} + + /**********************************************\ + * * + * Item mediator * + * * + \**********************************************/ + +cUPnPItemMediator::cUPnPItemMediator(cMediaDatabase* MediaDatabase) : + cUPnPObjectMediator(MediaDatabase){} + +int cUPnPItemMediator::objectToDatabase(cUPnPClassObject* Object){ + if(cUPnPObjectMediator::objectToDatabase(Object)) return -1; + cString Format = "INSERT OR REPLACE INTO %s (%s) VALUES (%s);"; + cString Statement=NULL, Columns=NULL, Values=NULL; + char *Value=NULL; + cString Properties[] = { + SQLITE_COL_OBJECTID, + SQLITE_COL_REFERENCEID, + NULL + }; + for(cString* Property = Properties; *(*Property); Property++){ + Columns = cString::sprintf("%s%s%s", *Columns?*Columns:"", *Columns?",":"", *(*Property)); + if(!Object->getProperty(*Property, &Value)){ + ERROR("No such property '%s' in object with ID '%s'",*(*Property),*Object->getID()); + return -1; + } + char *escapedValue; + escapeSQLite(Value, &escapedValue); + Values = cString::sprintf("%s%s'%s'", *Values?*Values:"", *Values?",":"", escapedValue?escapedValue:"NULL"); + + } + Statement = cString::sprintf(Format, SQLITE_TABLE_ITEMS, *Columns, *Values); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + return 0; +} + +int cUPnPItemMediator::databaseToObject(cUPnPClassObject* Object, cUPnPObjectID ID){ + if(cUPnPObjectMediator::databaseToObject(Object,ID)){ + ERROR("Error while loading object"); + return -1; + } + cUPnPClassItem* Item = (cUPnPClassItem*) Object; + cString Format = "SELECT * FROM %s WHERE %s=%s"; + cString Statement = NULL, Column = NULL, Value = NULL; + cRows* Rows; cRow* Row; + Statement = cString::sprintf(Format, SQLITE_TABLE_ITEMS, SQLITE_COL_OBJECTID, *ID); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + Rows = this->mDatabase->getResultRows(); + if(!Rows->fetchRow(&Row)){ + MESSAGE("No item properties found"); + return 0; + } + while(Row->fetchColumn(&Column, &Value)){ + if(!strcasecmp(Column, SQLITE_COL_REFERENCEID)){ + cUPnPObjectID RefID = atoi(Value); + cUPnPClassItem* RefObject; + if(RefID == -1){ + RefObject = NULL; + } + else { + RefObject = (cUPnPClassItem*)this->mMediaDatabase->getObjectByID(RefID); + if(!RefObject){ + ERROR("No such reference item with ID '%s' found.",*RefID); + return -1; + } + } + Item->setReference(RefObject); + } + } + return 0; +} + +cUPnPClassItem* cUPnPItemMediator::getObject(cUPnPObjectID ID){ + MESSAGE("Getting Item with ID '%s'",*ID); + cUPnPClassItem* Object = new cUPnPClassItem; + if(this->databaseToObject(Object, ID)) return NULL; + return Object; +} + +cUPnPClassItem* cUPnPItemMediator::createObject(const char* Title, bool Restricted){ + MESSAGE("Creating Item '%s'",Title); + cUPnPClassItem* Object = new cUPnPClassItem; + if(this->initializeObject(Object, UPNP_CLASS_ITEM, Title, Restricted)) return NULL; + return Object; +} + + /**********************************************\ + * * + * Container mediator * + * * + \**********************************************/ + +cUPnPContainerMediator::cUPnPContainerMediator(cMediaDatabase* MediaDatabase) : + cUPnPObjectMediator(MediaDatabase){} + +int cUPnPContainerMediator::objectToDatabase(cUPnPClassObject* Object){ + if(cUPnPObjectMediator::objectToDatabase(Object)) return -1; + cUPnPClassContainer* Container = (cUPnPClassContainer*)Object; + cString Format = "INSERT OR REPLACE INTO %s (%s) VALUES (%s);"; + cString Statement=NULL, Columns=NULL, Values=NULL; + char *Value=NULL; + cString Properties[] = { + SQLITE_COL_OBJECTID, + SQLITE_COL_DLNA_CONTAINERTYPE, + SQLITE_COL_SEARCHABLE, + SQLITE_COL_CONTAINER_UID, + NULL + }; + for(cString* Property = Properties; *(*Property); Property++){ + Columns = cString::sprintf("%s%s%s", *Columns?*Columns:"", *Columns?",":"", *(*Property)); + if(!Container->getProperty(*Property, &Value)){ + ERROR("No such property '%s' in object with ID '%s'",*(*Property),*Container->getID()); + return -1; + } + char *escapedValue; + escapeSQLite(Value, &escapedValue); + Values = cString::sprintf("%s%s'%s'", *Values?*Values:"", *Values?",":"", escapedValue?escapedValue:"NULL"); + } + Statement = cString::sprintf(Format, SQLITE_TABLE_CONTAINERS, *Columns, *Values); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + for(unsigned int i=0; i<Container->getSearchClasses()->size(); i++){ + cClass Class = Container->getSearchClasses()->at(i); + Columns = cString::sprintf("%s,%s,%s", SQLITE_COL_OBJECTID, SQLITE_COL_CLASS, SQLITE_COL_CLASSDERIVED); + Values = cString::sprintf("'%s','%s','%s'", *Container->getID(), *Class.ID, Class.includeDerived?"1":"0"); + Statement = cString::sprintf(Format, SQLITE_TABLE_SEARCHCLASS, *Columns, *Values); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + } + // Create classes not necessary at the moment + return 0; +} + +int cUPnPContainerMediator::databaseToObject(cUPnPClassObject* Object, cUPnPObjectID ID){ + if(cUPnPObjectMediator::databaseToObject(Object,ID)){ + ERROR("Error while loading object"); + return -1; + } + cUPnPClassContainer* Container = (cUPnPClassContainer*)Object; + cString Format = "SELECT * FROM %s WHERE %s=%s"; + cString Statement = NULL, Column = NULL, Value = NULL; + cRows* Rows; cRow* Row; + Statement = cString::sprintf(Format, SQLITE_TABLE_CONTAINERS, SQLITE_COL_OBJECTID, *ID); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + Rows = this->mDatabase->getResultRows(); + if(!Rows->fetchRow(&Row)){ + MESSAGE("No item properties found"); + return 0; + } + while(Row->fetchColumn(&Column, &Value)){ + if(!strcasecmp(Column, SQLITE_COL_DLNA_CONTAINERTYPE)){ + if(Container->setContainerType(Value)){ + ERROR("Error while setting container type"); + return -1; + } + } + if(!strcasecmp(Column, SQLITE_COL_CONTAINER_UID)){ + if(Container->setUpdateID((unsigned int)atoi(Value))){ + ERROR("Error while setting update ID"); + return -1; + } + } + if(!strcasecmp(Column, SQLITE_COL_SEARCHABLE)){ + if(Container->setSearchable(atoi(Value)==1?true:false)){ + ERROR("Error while setting searchable"); + return -1; + } + } + } + Statement = cString::sprintf("SELECT %s FROM %s WHERE %s=%s", SQLITE_COL_OBJECTID, + SQLITE_TABLE_OBJECTS, + SQLITE_COL_PARENTID, + *ID); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + Rows = this->mDatabase->getResultRows(); + while(Rows->fetchRow(&Row)){ + while(Row->fetchColumn(&Column, &Value)){ + if(!strcasecmp(Column, SQLITE_COL_OBJECTID)){ + Container->addObject(this->mMediaDatabase->getObjectByID(atoi(Value))); + } + } + } + Statement = cString::sprintf(Format, SQLITE_TABLE_SEARCHCLASS, SQLITE_COL_OBJECTID, *ID); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + std::vector<cClass> SearchClasses; + Rows = this->mDatabase->getResultRows(); + while(Rows->fetchRow(&Row)){ + cClass Class; + while(Row->fetchColumn(&Column, &Value)){ + if(!strcasecmp(Column, SQLITE_COL_CLASS)){ + Class.ID = strdup0(*Value); + } + else if(!strcasecmp(Column, SQLITE_COL_CLASSDERIVED)){ + Class.includeDerived = atoi(Value)==1?"true":"false"; + } + } + SearchClasses.push_back(Class); + } + if(Container->setSearchClasses(SearchClasses)){ + ERROR("Error while setting search classes"); + return -1; + } + return 0; +} + +cUPnPClassContainer* cUPnPContainerMediator::createObject(const char* Title, bool Restricted){ + MESSAGE("Creating Container '%s'",Title); + cUPnPClassContainer* Object = new cUPnPClassContainer; + if(this->initializeObject(Object, UPNP_CLASS_CONTAINER, Title, Restricted)) return NULL; + return Object; +} + +cUPnPClassContainer* cUPnPContainerMediator::getObject(cUPnPObjectID ID){ + MESSAGE("Getting Container with ID '%s'",*ID); + cUPnPClassContainer* Object = new cUPnPClassContainer; + if(this->databaseToObject(Object, ID)) return NULL; + return Object; +} + + /**********************************************\ + * * + * Video item mediator * + * * + \**********************************************/ + +cUPnPVideoItemMediator::cUPnPVideoItemMediator(cMediaDatabase* MediaDatabase) : + cUPnPItemMediator(MediaDatabase){} + +cUPnPClassVideoItem* cUPnPVideoItemMediator::createObject(const char* Title, bool Restricted){ + MESSAGE("Creating Video item '%s'",Title); + cUPnPClassVideoItem* Object = new cUPnPClassVideoItem; + if(this->initializeObject(Object, UPNP_CLASS_VIDEO, Title, Restricted)) return NULL; + return Object; +} + +cUPnPClassVideoItem* cUPnPVideoItemMediator::getObject(cUPnPObjectID ID){ + MESSAGE("Getting Video item with ID '%s'",*ID); + cUPnPClassVideoItem* Object = new cUPnPClassVideoItem; + if(this->databaseToObject(Object, ID)) return NULL; + return Object; +} + +int cUPnPVideoItemMediator::objectToDatabase(cUPnPClassObject* Object){ + if(cUPnPItemMediator::objectToDatabase(Object)) return -1; + cUPnPClassVideoItem* VideoItem = (cUPnPClassVideoItem*)Object; + cString Format = "INSERT OR REPLACE INTO %s (%s) VALUES (%s);"; + cString Statement=NULL, Columns=NULL, Values=NULL; + char *Value=NULL; + cString Properties[] = { + SQLITE_COL_OBJECTID, + SQLITE_COL_GENRE, + SQLITE_COL_LONGDESCRIPTION, + SQLITE_COL_PRODUCER, + SQLITE_COL_RATING, + SQLITE_COL_ACTOR, + SQLITE_COL_DIRECTOR, + SQLITE_COL_DESCRIPTION, + SQLITE_COL_PUBLISHER, + SQLITE_COL_LANGUAGE, + SQLITE_COL_RELATION, + NULL + }; + for(cString* Property = Properties; *(*Property); Property++){ + Columns = cString::sprintf("%s%s%s", *Columns?*Columns:"", *Columns?",":"", *(*Property)); + if(!VideoItem->getProperty(*Property, &Value)){ + ERROR("No such property '%s' in object with ID '%s'",*(*Property),* VideoItem->getID()); + return -1; + } + char *escapedValue; + escapeSQLite(Value, &escapedValue); + Values = cString::sprintf("%s%s'%s'", *Values?*Values:"", *Values?",":"", escapedValue?escapedValue:"NULL"); + + } + Statement = cString::sprintf(Format, SQLITE_TABLE_VIDEOITEMS, *Columns, *Values); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + return 0; +} + +int cUPnPVideoItemMediator::databaseToObject(cUPnPClassObject* Object, cUPnPObjectID ID){ + if(cUPnPItemMediator::databaseToObject(Object,ID)){ + ERROR("Error while loading object"); + return -1; + } + cUPnPClassVideoItem* VideoItem = (cUPnPClassVideoItem*)Object; + cString Format = "SELECT * FROM %s WHERE %s=%s"; + cString Statement = NULL, Column = NULL, Value = NULL; + cRows* Rows; cRow* Row; + Statement = cString::sprintf(Format, SQLITE_TABLE_VIDEOITEMS, SQLITE_COL_OBJECTID, *ID); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + Rows = this->mDatabase->getResultRows(); + if(!Rows->fetchRow(&Row)){ + MESSAGE("No item properties found"); + return 0; + } + while(Row->fetchColumn(&Column, &Value)){ + if(!strcasecmp(Column, SQLITE_COL_GENRE)){ + if(VideoItem->setGenre(Value)){ + ERROR("Error while setting genre"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_LONGDESCRIPTION)){ + if(VideoItem->setLongDescription(Value)){ + ERROR("Error while setting long description"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_PRODUCER)){ + if(VideoItem->setProducers(Value)){ + ERROR("Error while setting producers"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_RATING)){ + if(VideoItem->setRating(Value)){ + ERROR("Error while setting rating"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_ACTOR)){ + if(VideoItem->setActors(Value)){ + ERROR("Error while setting actors"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_DIRECTOR)){ + if(VideoItem->setDirectors(Value)){ + ERROR("Error while setting directors"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_DESCRIPTION)){ + if(VideoItem->setDescription(Value)){ + ERROR("Error while setting description"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_PUBLISHER)){ + if(VideoItem->setPublishers(Value)){ + ERROR("Error while setting publishers"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_LANGUAGE)){ + if(VideoItem->setLanguage(Value)){ + ERROR("Error while setting language"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_RELATION)){ + if(VideoItem->setRelations(Value)){ + ERROR("Error while setting relations"); + return -1; + } + } + } + return 0; +} + + /**********************************************\ + * * + * Video broadcast item mediator * + * * + \**********************************************/ + +cUPnPVideoBroadcastMediator::cUPnPVideoBroadcastMediator(cMediaDatabase* MediaDatabase) : + cUPnPVideoItemMediator(MediaDatabase){} + +cUPnPClassVideoBroadcast* cUPnPVideoBroadcastMediator::createObject(const char* Title, bool Restricted){ + MESSAGE("Creating Video broadcast '%s'",Title); + cUPnPClassVideoBroadcast* Object = new cUPnPClassVideoBroadcast; + if(this->initializeObject(Object, UPNP_CLASS_VIDEOBC, Title, Restricted)) return NULL; + return Object; +} + +cUPnPClassVideoBroadcast* cUPnPVideoBroadcastMediator::getObject(cUPnPObjectID ID){ + MESSAGE("Getting Video broadcast with ID '%s'",*ID); + cUPnPClassVideoBroadcast* Object = new cUPnPClassVideoBroadcast; + if(this->databaseToObject(Object, ID)) return NULL; + return Object; +} + +int cUPnPVideoBroadcastMediator::objectToDatabase(cUPnPClassObject* Object){ + if(cUPnPVideoItemMediator::objectToDatabase(Object)) return -1; + cUPnPClassVideoBroadcast* VideoBroadcast = (cUPnPClassVideoBroadcast*)Object; + cString Format = "INSERT OR REPLACE INTO %s (%s) VALUES (%s);"; + cString Statement=NULL, Columns=NULL, Values=NULL; + char *Value=NULL; + cString Properties[] = { + SQLITE_COL_OBJECTID, + SQLITE_COL_ICON, + SQLITE_COL_REGION, + SQLITE_COL_CHANNELNAME, + SQLITE_COL_CHANNELNR, + NULL + }; + for(cString* Property = Properties; *(*Property); Property++){ + Columns = cString::sprintf("%s%s%s", *Columns?*Columns:"", *Columns?",":"", *(*Property)); + if(!VideoBroadcast->getProperty(*Property, &Value)){ + ERROR("No such property '%s' in object with ID '%s'",*(*Property),* VideoBroadcast->getID()); + return -1; + } + char *escapedValue; + escapeSQLite(Value, &escapedValue); + Values = cString::sprintf("%s%s'%s'", *Values?*Values:"", *Values?",":"", escapedValue?escapedValue:"NULL"); + + } + Statement = cString::sprintf(Format, SQLITE_TABLE_VIDEOBROADCASTS, *Columns, *Values); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + return 0; +} + +int cUPnPVideoBroadcastMediator::databaseToObject(cUPnPClassObject* Object, cUPnPObjectID ID){ + if(cUPnPVideoItemMediator::databaseToObject(Object,ID)){ + ERROR("Error while loading object"); + return -1; + } + cUPnPClassVideoBroadcast* VideoBroadcast = (cUPnPClassVideoBroadcast*)Object; + cString Format = "SELECT * FROM %s WHERE %s=%s"; + cString Statement = NULL, Column = NULL, Value = NULL; + cRows* Rows; cRow* Row; + Statement = cString::sprintf(Format, SQLITE_TABLE_VIDEOBROADCASTS, SQLITE_COL_OBJECTID, *ID); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + Rows = this->mDatabase->getResultRows(); + if(!Rows->fetchRow(&Row)){ + MESSAGE("No item properties found"); + return 0; + } + while(Row->fetchColumn(&Column, &Value)){ + if(!strcasecmp(Column, SQLITE_COL_ICON)){ + if(VideoBroadcast->setIcon(Value)){ + ERROR("Error while setting icon"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_REGION)){ + if(VideoBroadcast->setRegion(Value)){ + ERROR("Error while setting region"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_CHANNELNR)){ + if(VideoBroadcast->setChannelNr(atoi(Value))){ + ERROR("Error while setting channel number"); + return -1; + } + } + else if(!strcasecmp(Column, SQLITE_COL_CHANNELNAME)){ + if(VideoBroadcast->setChannelName(Value)){ + ERROR("Error while setting channel name"); + return -1; + } + } + } + return 0; +}
\ No newline at end of file diff --git a/database/object.h b/database/object.h new file mode 100644 index 0000000..9b62c54 --- /dev/null +++ b/database/object.h @@ -0,0 +1,397 @@ +/* + * File: object.h + * Author: savop + * + * Created on 11. September 2009, 20:39 + */ + +#ifndef _OBJECT_H +#define _OBJECT_H + +#include "database.h" +#include "../common.h" +#include "../misc/util.h" +#include <string.h> +#include <vdr/tools.h> +#include <map> +#include <vector> +#include <upnp/ixml.h> + +struct cUPnPObjectID { + int _ID; + cUPnPObjectID():_ID(-1){} + cUPnPObjectID(long ID){ _ID = (int)ID; } + cUPnPObjectID(int ID){ _ID = ID; } + cUPnPObjectID &operator=(long ID){ _ID = ID; return *this; } + cUPnPObjectID &operator=(int ID){ _ID = ID; return *this; } + cUPnPObjectID &operator=(const cUPnPObjectID& ID){ if(this != &ID){ _ID = ID._ID; } return *this; } + cUPnPObjectID &operator++(){ _ID++; return *this; } + cUPnPObjectID operator++(int){ cUPnPObjectID old = *this; _ID++; return old; } + cUPnPObjectID operator--(int){ cUPnPObjectID old = *this; _ID--; return old; } + cUPnPObjectID &operator--(){ _ID--; return *this; } + bool operator!=(long ID){ return _ID != ID; } + bool operator==(long ID){ return _ID == ID; } + bool operator!=(int ID){ return _ID != ID; } + bool operator==(int ID){ return _ID == ID; } + bool operator!=(const cUPnPObjectID& ID){ return *this == ID; } + bool operator==(const cUPnPObjectID& ID){ return *this == ID; } + operator unsigned int(){ return (unsigned int)_ID; } + operator int(){ return _ID; } + operator long(){ return (long)_ID; } + const char* operator*(){ char* buf; return asprintf(&buf,"%d",_ID)?buf:NULL; } +}; + +struct cClass { + cString ID; + bool includeDerived; + bool operator==(const cClass &cmp){ return (!strcasecmp(cmp.ID,ID) && includeDerived==cmp.includeDerived); } + bool operator!=(const cClass &cmp){ return !(*this==cmp); } +}; + +class cUPnPResource : public cListObject { + friend class cUPnPResourceMediator; + friend class cUPnPResources; +private: + unsigned int mResourceID; + cUPnPObjectID mObjectID; + int mResourceType; + cString mResource; + cString mDuration; + cString mResolution; + cString mProtocolInfo; + cString mContentType; + cString mImportURI; + unsigned long mSize; + unsigned int mBitrate; + unsigned int mSampleFrequency; + unsigned int mBitsPerSample; + unsigned int mNrAudioChannels; + unsigned int mColorDepth; + cUPnPResource(); +public: + unsigned int getID() const { return this->mResourceID; } + const char* getResource() const { return this->mResource; } + const char* getDuration() const { return this->mDuration; } + const char* getResolution() const { return this->mResolution; } + const char* getProtocolInfo() const { return this->mProtocolInfo; } + const char* getContentType() const { return this->mContentType; } + const char* getImportURI() const { return this->mImportURI; } + int getResourceType() const { return this->mResourceType; } + unsigned long getSize() const { return this->mSize; } + off64_t getFileSize() const; + time_t getLastModification() const; + unsigned int getBitrate() const { return this->mBitrate; } + unsigned int getSampleFrequency() const { return this->mSampleFrequency; } + unsigned int getBitsPerSample() const { return this->mBitsPerSample; } + unsigned int getNrAudioChannels() const { return this->mNrAudioChannels; } + unsigned int getColorDepth() const { return this->mColorDepth; } +}; + +class cUPnPClassObject; +class cUPnPObjectMediator; +class cUPnPContainerMediator; +class cUPnPClassContainer; + +class cUPnPObjects : public cList<cUPnPClassObject> { +public: + cUPnPObjects(); + virtual ~cUPnPObjects(); + void SortBy(const char* Property, bool Descending = false); +}; + +class cUPnPClassObject : public cListObject { + friend class cMediaDatabase; + friend class cUPnPObjectMediator; + friend class cUPnPClassContainer; +private: + bool mDeleted; // is this Objected marked as deleted +protected: + time_t mLastModified; + cUPnPObjectID mID; // The object ID + cUPnPClassObject* mParent; + cString mClass; // Class (Who am I?) + cString mTitle; // Object title + cString mCreator; // Creator of this object + bool mRestricted; // Ability of changing metadata? + int mWriteStatus; // Ability of writing resources? + cList<cUPnPResource>* mResources; // The resources of this object + cHash<cUPnPResource>* mResourcesID; + IXML_Document* mDIDLFragment; + cString mSortCriteria; + bool mSortDescending; + cUPnPClassObject(); + int setID(cUPnPObjectID ID); + int setParent(cUPnPClassContainer* Parent); + int setClass(const char* Class); + void setModified(void){ this->mLastModified = time(NULL); } +public: + time_t modified() const { return this->mLastModified; } + virtual ~cUPnPClassObject(); + virtual int Compare(const cListObject& ListObject) const; + virtual cStringList* getPropertyList(); + virtual bool getProperty(const char* Property, char** Value) const ; + virtual bool setProperty(const char* Property, const char* Value); + virtual cUPnPClassContainer* getContainer(){ return NULL; } + virtual IXML_Node* createDIDLFragment(IXML_Document* Document, cStringList* Filter) = 0; + bool isContainer(){ return this->getContainer()==NULL?false:true; } + void setSortCriteria(const char* Property, bool Descending = false); + void clearSortCriteria(); + /******* Setter *******/ + int setTitle(const char* Title); + int setCreator(const char* Creator); + int setRestricted(bool Restricted); + int setWriteStatus(int Status); + int setResources(cList<cUPnPResource>* Resources); + int addResource(cUPnPResource* Resource); + int removeResource(cUPnPResource* Resource); + /******* Getter *******/ + cUPnPObjectID getID() const { return this->mID; } + cUPnPObjectID getParentID() const { return this->mParent?this->mParent->getID():cUPnPObjectID(-1); } + cUPnPClassContainer* getParent() const { return (cUPnPClassContainer*)this->mParent; } + const char* getTitle() const { return this->mTitle; } + const char* getClass() const { return this->mClass; } + const char* getCreator() const { return this->mCreator; } + bool isRestricted() const { return this->mRestricted; } + int getWriteStatus() const { return this->mWriteStatus; } + cUPnPResource* getResource(unsigned int ResourceID) const { return this->mResourcesID->Get(ResourceID); } + cList<cUPnPResource>* getResources() const { return this->mResources; } +}; + +class cUPnPClassItem : public cUPnPClassObject { + friend class cMediaDatabase; + friend class cUPnPObjectMediator; + friend class cUPnPItemMediator; +protected: +// cUPnPObjectID mReferenceID; + cUPnPClassItem* mReference; + cUPnPClassItem(); +public: + virtual ~cUPnPClassItem(){}; + virtual cStringList* getPropertyList(); + virtual IXML_Node* createDIDLFragment(IXML_Document* Document, cStringList* Filter); + virtual bool setProperty(const char* Property, const char* Value); + virtual bool getProperty(const char* Property, char** Value) const; + /******** Setter ********/ + int setReference(cUPnPClassItem* Reference); + /******** Getter ********/ + cUPnPClassItem* getReference() const { return this->mReference; } + cUPnPObjectID getReferenceID() const { return this->mReference?this->mReference->getID():cUPnPObjectID(-1); } +}; + +typedef std::vector<cClass> tClassVector; + +class cUPnPClassContainer : public cUPnPClassObject { + friend class cMediaDatabase; + friend class cUPnPObjectMediator; + friend class cUPnPContainerMediator; +protected: + cString mContainerType; + tClassVector mSearchClasses; + tClassVector mCreateClasses; + bool mSearchable; + unsigned int mUpdateID; + cUPnPObjects* mChildren; + cHash<cUPnPClassObject>* mChildrenID; + void update(); + int setUpdateID(unsigned int UID); + cUPnPClassContainer(); +public: + virtual ~cUPnPClassContainer(); + virtual cStringList* getPropertyList(); + virtual IXML_Node* createDIDLFragment(IXML_Document* Document, cStringList* Filter); + virtual bool setProperty(const char* Property, const char* Value); + virtual bool getProperty(const char* Property, char** Value) const; + virtual cUPnPClassContainer* getContainer(){ return this; } + void addObject(cUPnPClassObject* Object); + void removeObject(cUPnPClassObject* Object); + cUPnPClassObject* getObject(cUPnPObjectID ID) const; + cUPnPObjects* getObjectList() const { return this->mChildren; } + int addSearchClass(cClass SearchClass); + int delSearchClass(cClass SearchClass); + int addCreateClass(cClass CreateClass); + int delCreateClass(cClass CreateClass); + /******** Setter ********/ + int setContainerType(const char* Type); + int setSearchClasses(std::vector<cClass> SearchClasses); + int setCreateClasses(std::vector<cClass> CreateClasses); + int setSearchable(bool Searchable); + /******** Getter ********/ + const char* getContainerType() const { return this->mContainerType; } + const std::vector<cClass>* getSearchClasses() const { return &(this->mSearchClasses); } + const std::vector<cClass>* getCreateClasses() const { return &(this->mCreateClasses); } + bool isSearchable() const { return this->mSearchable; } + unsigned int getChildCount() const { return this->mChildren->Count(); } + unsigned int getUpdateID() const { return this->mUpdateID; } + bool isUpdated(); +}; + +class cUPnPClassVideoItem : public cUPnPClassItem { + friend class cMediaDatabase; + friend class cUPnPObjectMediator; + friend class cUPnPVideoItemMediator; +protected: + cString mGenre; // Genre + cString mDescription; // Description + cString mLongDescription; // a longer description + cString mPublishers; // CSV of Publishers + cString mLanguage; // RFC 1766 Language code + cString mRelations; // Relation to other contents + cString mProducers; // CSV of Producers + cString mRating; // Rating (for parential control) + cString mActors; // CSV of Actors + cString mDirectors; // CSV of Directors + cUPnPClassVideoItem(); +public: + virtual ~cUPnPClassVideoItem(); + //virtual cString createDIDLFragment(cStringList* Filter); + virtual cStringList* getPropertyList(); + virtual bool setProperty(const char* Property, const char* Value); + virtual bool getProperty(const char* Property, char** Value) const; + /******** Setter ********/ + int setLongDescription(const char* LongDescription); + int setDescription(const char* Description); + int setPublishers(const char* Publishers); + int setGenre(const char* Genre); + int setLanguage(const char* Language); + int setRelations(const char* Relations); + int setDirectors(const char* Directors); + int setActors(const char* Actors); + int setProducers(const char* Producers); + int setRating(const char* Rating); + /******** Getter ********/ + const char* getGenre() const { return this->mGenre; } + const char* getLongDescription() const { return this->mLongDescription; } + const char* getDescription() const { return this->mDescription; } + const char* getPublishers() const { return this->mPublishers; } + const char* getLanguage() const { return this->mLanguage; } + const char* getRelations() const { return this->mRelations; } + const char* getActors() const { return this->mActors; } + const char* getProducers() const { return this->mProducers; } + const char* getDirectors() const { return this->mDirectors; } + const char* getRating() const { return this->mRating; } +}; + +class cUPnPClassVideoBroadcast : public cUPnPClassVideoItem { + friend class cMediaDatabase; + friend class cUPnPObjectMediator; + friend class cUPnPVideoBroadcastMediator; +protected: + cString mIcon; + cString mRegion; + int mChannelNr; + cString mChannelName; + cUPnPClassVideoBroadcast(); +public: + virtual ~cUPnPClassVideoBroadcast(); + //virtual cString createDIDLFragment(cStringList* Filter); + virtual cStringList* getPropertyList(); + virtual bool setProperty(const char* Property, const char* Value); + virtual bool getProperty(const char* Property, char** Value) const; + /******** Setter ********/ + int setIcon(const char* IconURI); + int setRegion(const char* Region); + int setChannelNr(int ChannelNr); + int setChannelName(const char* ChannelName); + /******** Getter ********/ + const char* getIcon() const { return this->mIcon; } + const char* getRegion() const { return this->mRegion; } + int getChannelNr() const { return this->mChannelNr; } + const char* getChannelName() const { return this->mChannelName; } +}; + +class cMediatorInterface { +public: + virtual ~cMediatorInterface(){}; + virtual cUPnPClassObject* createObject(const char* Title, bool Restricted) = 0; + virtual cUPnPClassObject* getObject(cUPnPObjectID ID) = 0; + virtual int saveObject(cUPnPClassObject* Object) = 0; + virtual int deleteObject(cUPnPClassObject* Object) = 0; + virtual int clearObject(cUPnPClassObject* Object) = 0; +}; + +typedef std::map<const char*, cMediatorInterface*, strCmp> tMediatorMap; + +class cUPnPObjectFactory { +private: + static cUPnPObjectFactory* mInstance; + cSQLiteDatabase* mDatabase; + tMediatorMap mMediators; + cMediatorInterface* findMediatorByID(cUPnPObjectID ID); + cMediatorInterface* findMediatorByClass(const char* Class); + cUPnPObjectFactory(); +public: + static cUPnPObjectFactory* getInstance(); + void registerMediator(const char* UPnPClass, cMediatorInterface* Mediator); + void unregisterMediator(const char* UPnPClass, bool freeMediator=true); + cUPnPClassObject* createObject(const char* UPnPClass, const char* Title, bool Restricted=true); + cUPnPClassObject* getObject(cUPnPObjectID ID); + int saveObject(cUPnPClassObject* Object); + int deleteObject(cUPnPClassObject* Object); + int clearObject(cUPnPClassObject* Object); +}; + +class cMediaDatabase; + +class cUPnPObjectMediator : public cMediatorInterface { +protected: + cSQLiteDatabase* mDatabase; + cMediaDatabase* mMediaDatabase; + cUPnPObjectMediator(cMediaDatabase* MediaDatabase); + virtual int initializeObject(cUPnPClassObject* Object, const char* Class, const char* Title, bool Restricted); + virtual int objectToDatabase(cUPnPClassObject* Object); + virtual int databaseToObject(cUPnPClassObject* Object, cUPnPObjectID ID); +public: + virtual ~cUPnPObjectMediator(); + virtual cUPnPClassObject* createObject(const char* Title, bool Restricted); + virtual cUPnPClassObject* getObject(cUPnPObjectID); + virtual int saveObject(cUPnPClassObject* Object); + virtual int deleteObject(cUPnPClassObject* Object); + virtual int clearObject(cUPnPClassObject* Object); +}; + +class cUPnPItemMediator : public cUPnPObjectMediator { +protected: + virtual int objectToDatabase(cUPnPClassObject* Object); + virtual int databaseToObject(cUPnPClassObject* Object, cUPnPObjectID ID); +public: + cUPnPItemMediator(cMediaDatabase* MediaDatabase); + virtual ~cUPnPItemMediator(){}; + virtual cUPnPClassItem* createObject(const char* Title, bool Restricted); + virtual cUPnPClassItem* getObject(cUPnPObjectID ID); +}; + +class cUPnPVideoItemMediator : public cUPnPItemMediator { +protected: + virtual int objectToDatabase(cUPnPClassObject* Object); + virtual int databaseToObject(cUPnPClassObject* Object, cUPnPObjectID ID); +public: + cUPnPVideoItemMediator(cMediaDatabase* MediaDatabase); + virtual ~cUPnPVideoItemMediator(){}; + virtual cUPnPClassVideoItem* createObject(const char* Title, bool Restricted); + virtual cUPnPClassVideoItem* getObject(cUPnPObjectID ID); +}; + +class cUPnPVideoBroadcastMediator : public cUPnPVideoItemMediator { +protected: + virtual int objectToDatabase(cUPnPClassObject* Object); + virtual int databaseToObject(cUPnPClassObject* Object, cUPnPObjectID ID); +public: + cUPnPVideoBroadcastMediator(cMediaDatabase* MediaDatabase); + virtual ~cUPnPVideoBroadcastMediator(){}; + virtual cUPnPClassVideoBroadcast* createObject(const char* Title, bool Restricted); + virtual cUPnPClassVideoBroadcast* getObject(cUPnPObjectID ID); +}; + +class cUPnPContainerMediator : public cUPnPObjectMediator { +protected: + virtual int objectToDatabase(cUPnPClassObject* Object); + virtual int databaseToObject(cUPnPClassObject* Object, cUPnPObjectID ID); +public: + cUPnPContainerMediator(cMediaDatabase* MediaDatabase); + virtual ~cUPnPContainerMediator(){}; + virtual cUPnPClassContainer* createObject(const char* Title, bool Restricted); + virtual cUPnPClassContainer* getObject(cUPnPObjectID ID); +}; + +#endif /* _OBJECT_H */ + diff --git a/database/resources.cpp b/database/resources.cpp new file mode 100644 index 0000000..f681a54 --- /dev/null +++ b/database/resources.cpp @@ -0,0 +1,282 @@ +/* + * File: resources.cpp + * Author: savop + * + * Created on 30. September 2009, 15:17 + */ + +#include <string.h> +#include <vdr/channels.h> +#include "../upnpcomponents/dlna.h" +#include <vdr/tools.h> +#include "resources.h" + +cUPnPResources* cUPnPResources::mInstance = NULL; + +cUPnPResources::cUPnPResources(){ + this->mResources = new cHash<cUPnPResource>; + this->mMediator = new cUPnPResourceMediator; + this->mDatabase = cSQLiteDatabase::getInstance(); +} + +cUPnPResources::~cUPnPResources(){ + delete this->mResources; + delete this->mMediator; +} + +cUPnPResources* cUPnPResources::getInstance(){ + if(!cUPnPResources::mInstance) + cUPnPResources::mInstance = new cUPnPResources(); + if(cUPnPResources::mInstance) return cUPnPResources::mInstance; + else return NULL; +} + +int cUPnPResources::loadResources(){ + cString Statement = cString::sprintf("SELECT %s FROM %s", + SQLITE_COL_RESOURCEID, + SQLITE_TABLE_RESOURCES + ); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + cRows* Rows = this->mDatabase->getResultRows(); cRow* Row; + cString Column = NULL, Value = NULL; + while(Rows->fetchRow(&Row)){ + while(Row->fetchColumn(&Column, &Value)){ + if(!strcasecmp(Column, SQLITE_COL_RESOURCEID)){ + unsigned int ResourceID = (unsigned int)atoi(Value); + this->getResource(ResourceID); + } + } + } + return 0; +} + +int cUPnPResources::getResourcesOfObject(cUPnPClassObject* Object){ + cString Statement = cString::sprintf("SELECT %s FROM %s WHERE %s='%s'", + SQLITE_COL_RESOURCEID, + SQLITE_TABLE_RESOURCES, + SQLITE_COL_OBJECTID, + *Object->getID() + ); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + cRows* Rows = this->mDatabase->getResultRows(); cRow* Row; + cString Column = NULL, Value = NULL; + while(Rows->fetchRow(&Row)){ + while(Row->fetchColumn(&Column, &Value)){ + if(!strcasecmp(Column, SQLITE_COL_RESOURCEID)){ + unsigned int ResourceID = (unsigned int)atoi(Value); + Object->addResource(this->getResource(ResourceID)); + } + } + } + return 0; +} + +cUPnPResource* cUPnPResources::getResource(unsigned int ResourceID){ + cUPnPResource* Resource; + if((Resource = this->mResources->Get(ResourceID))){ + MESSAGE("Found cached resource"); + return Resource; + } + else if((Resource = this->mMediator->getResource(ResourceID))){ + MESSAGE("Found resource in database"); + this->mResources->Add(Resource, ResourceID); + return Resource; + } + else { + ERROR("No such resource with ID '%d'", ResourceID); + return NULL; + } +} + +int cUPnPResources::createFromChannel(cUPnPClassVideoBroadcast* Object, cChannel* Channel){ + if(!Object || !Channel){ + ERROR("Invalid input arguments"); + return -1; + } + + DLNAProfile* Profile = cDlna::getInstance()->getProfileOfChannel(Channel); + const char* ProtocolInfo = cDlna::getInstance()->getProtocolInfo(Profile); + + MESSAGE("Protocol info: %s", ProtocolInfo); + + // Adapted from streamdev + int index = 0; + for(int i=0; Channel->Apid(i)!=0; i++, index++){ + MESSAGE("Analog channel %d", i); + cString ResourceFile = cString::sprintf("%s:%d", *Channel->GetChannelID().ToString(), index); + cUPnPResource* Resource = this->mMediator->newResource(Object, UPNP_RESOURCE_CHANNEL,ResourceFile, Profile->mime, ProtocolInfo); + Resource->mBitrate = 0; + Resource->mBitsPerSample = 0; + Resource->mColorDepth = 0; + Resource->mDuration = NULL; + Resource->mImportURI = NULL; + Resource->mResolution = NULL; + Resource->mSampleFrequency = 0; + Resource->mSize = 0; + Resource->mNrAudioChannels = 0; + Object->addResource(Resource); + this->mMediator->saveResource(Resource); + this->mResources->Add(Resource, Resource->getID()); + } + for(int i=0; Channel->Dpid(i)!=0; i++, index++){ + MESSAGE("Digital channel %d", i); + cString ResourceFile = cString::sprintf("%s:%d", *Channel->GetChannelID().ToString(), index); + cUPnPResource* Resource = this->mMediator->newResource(Object, UPNP_RESOURCE_CHANNEL,ResourceFile, Profile->mime, ProtocolInfo); + Resource->mBitrate = 0; + Resource->mBitsPerSample = 0; + Resource->mColorDepth = 0; + Resource->mDuration = NULL; + Resource->mImportURI = NULL; + Resource->mResolution = NULL; + Resource->mSampleFrequency = 0; + Resource->mSize = 0; + Object->addResource(Resource); + this->mMediator->saveResource(Resource); + this->mResources->Add(Resource, Resource->getID()); + } + + return 0; +} + +cUPnPResourceMediator::cUPnPResourceMediator(){ + this->mDatabase = cSQLiteDatabase::getInstance(); +} + +cUPnPResourceMediator::~cUPnPResourceMediator(){} + +cUPnPResource* cUPnPResourceMediator::getResource(unsigned int ResourceID){ + cUPnPResource* Resource = new cUPnPResource; + Resource->mResourceID = ResourceID; + cString Statement = cString::sprintf("SELECT * FROM %s WHERE %s=%d", + SQLITE_TABLE_RESOURCES, + SQLITE_COL_RESOURCEID, + ResourceID + ); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return NULL; + } + cRows* Rows = this->mDatabase->getResultRows(); cRow* Row; + if(!Rows->fetchRow(&Row)){ + ERROR("No such resource found"); + return NULL; + } + cString Column = NULL, Value = NULL; + while(Row->fetchColumn(&Column, &Value)){ + if(!strcasecmp(SQLITE_COL_OBJECTID, Column)){ + Resource->mObjectID = atoi(Value); + } + else if(!strcasecmp(SQLITE_COL_PROTOCOLINFO, Column)){ + Resource->mProtocolInfo = Value; + } + else if(!strcasecmp(SQLITE_COL_RESOURCE, Column)){ + Resource->mResource = Value; + } + else if(!strcasecmp(SQLITE_COL_SIZE, Column)){ + Resource->mSize = atol(Value); + } + else if(!strcasecmp(SQLITE_COL_DURATION, Column)){ + Resource->mDuration = Value; + } + else if(!strcasecmp(SQLITE_COL_BITRATE, Column)){ + Resource->mBitrate = atoi(Value); + } + else if(!strcasecmp(SQLITE_COL_SAMPLEFREQUENCE, Column)){ + Resource->mSampleFrequency = atoi(Value); + } + else if(!strcasecmp(SQLITE_COL_BITSPERSAMPLE, Column)){ + Resource->mBitsPerSample = atoi(Value); + } + else if(!strcasecmp(SQLITE_COL_NOAUDIOCHANNELS, Column)){ + Resource->mNrAudioChannels = atoi(Value); + } + else if(!strcasecmp(SQLITE_COL_COLORDEPTH, Column)){ + Resource->mColorDepth = atoi(Value); + } + else if(!strcasecmp(SQLITE_COL_RESOLUTION, Column)){ + Resource->mResolution = Value; + } + else if(!strcasecmp(SQLITE_COL_CONTENTTYPE, Column)){ + Resource->mContentType = Value; + } + else if(!strcasecmp(SQLITE_COL_RESOURCETYPE, Column)){ + Resource->mResourceType = atoi(Value); + } + } + return Resource; +} + +int cUPnPResourceMediator::saveResource(cUPnPResource* Resource){ + cString Format = "UPDATE %s SET %s WHERE %s=%d"; + cString Sets = cString::sprintf("%s='%s'," + "%s='%s'," + "%s='%s'," + "%s=%ld," + "%s='%s'," + "%s=%d," + "%s=%d," + "%s=%d," + "%s=%d," + "%s=%d," + "%s='%s'," + "%s='%s'," + "%s=%d", + SQLITE_COL_OBJECTID, *Resource->mObjectID?*Resource->mObjectID:"NULL", + SQLITE_COL_PROTOCOLINFO, *Resource->mProtocolInfo?*Resource->mProtocolInfo:"NULL", + SQLITE_COL_RESOURCE, *Resource->mResource?*Resource->mResource:"NULL", + SQLITE_COL_SIZE, Resource->mSize, + SQLITE_COL_DURATION, *Resource->mDuration?*Resource->mDuration:"NULL", + SQLITE_COL_BITRATE, Resource->mBitrate, + SQLITE_COL_SAMPLEFREQUENCE, Resource->mSampleFrequency, + SQLITE_COL_BITSPERSAMPLE, Resource->mBitsPerSample, + SQLITE_COL_NOAUDIOCHANNELS, Resource->mNrAudioChannels, + SQLITE_COL_COLORDEPTH, Resource->mColorDepth, + SQLITE_COL_RESOLUTION, *Resource->mResolution?*Resource->mResolution:"NULL", + SQLITE_COL_CONTENTTYPE, *Resource->mContentType, + SQLITE_COL_RESOURCETYPE, Resource->mResourceType + ); + + cString Statement = cString::sprintf(Format, SQLITE_TABLE_RESOURCES, *Sets, SQLITE_COL_RESOURCEID, Resource->mResourceID); + + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return -1; + } + + return 0; +} + +cUPnPResource* cUPnPResourceMediator::newResource(cUPnPClassObject* Object, int ResourceType, cString ResourceFile, cString ContentType, cString ProtocolInfo){ + cUPnPResource* Resource = new cUPnPResource; + cString Statement = cString::sprintf("INSERT INTO %s (%s,%s,%s,%s,%s) VALUES ('%s','%s','%s','%s','%d')", + SQLITE_TABLE_RESOURCES, + SQLITE_COL_OBJECTID, + SQLITE_COL_RESOURCE, + SQLITE_COL_PROTOCOLINFO, + SQLITE_COL_CONTENTTYPE, + SQLITE_COL_RESOURCETYPE, + *Object->getID(), + *ResourceFile, + *ProtocolInfo, + *ContentType, + ResourceType + ); + if(this->mDatabase->execStatement(Statement)){ + ERROR("Error while executing statement"); + return NULL; + } + Resource->mResourceID = (unsigned int)this->mDatabase->getLastInsertRowID(); + Resource->mObjectID = Object->getID(); + Resource->mResource = ResourceFile; + Resource->mProtocolInfo = ProtocolInfo; + Resource->mContentType = ContentType; + Resource->mResourceType = ResourceType; + + return Resource; +}
\ No newline at end of file diff --git a/database/resources.h b/database/resources.h new file mode 100644 index 0000000..46ec0d5 --- /dev/null +++ b/database/resources.h @@ -0,0 +1,51 @@ +/* + * File: resources.h + * Author: savop + * + * Created on 30. September 2009, 15:17 + */ + +#ifndef _RESOURCES_H +#define _RESOURCES_H + +#include "database.h" +#include "object.h" +#include <vdr/channels.h> +#include <vdr/recording.h> + +class cUPnPResourceMediator; +class cMediaDatabase; + +class cUPnPResources { +private: + cHash<cUPnPResource>* mResources; + static cUPnPResources* mInstance; + cUPnPResourceMediator* mMediator; + cSQLiteDatabase* mDatabase; + cUPnPResources(); +public: + int getResourcesOfObject(cUPnPClassObject* Object); + int loadResources(); + cUPnPResource* getResource(unsigned int ResourceID); + virtual ~cUPnPResources(); + static cUPnPResources* getInstance(); + int createFromChannel(cUPnPClassVideoBroadcast* Object, cChannel* Channel); + int createFromRecording(cUPnPClassVideoItem* Object, cRecording* Recording); + int createFromFile(cUPnPClassItem* Object, cString File); +}; + +class cUPnPResourceMediator { + friend class cUPnPResources; +private: + cSQLiteDatabase* mDatabase; + cUPnPResourceMediator(); + unsigned int getNextResourceID(); +public: + virtual ~cUPnPResourceMediator(); + cUPnPResource* getResource(unsigned int ResourceID); + int saveResource(cUPnPResource* Resource); + cUPnPResource* newResource(cUPnPClassObject* Object, int ResourceType, cString ResourceFile, cString ContentType, cString ProtocolInfo); +}; + +#endif /* _RESOURCES_H */ + diff --git a/misc/config.cpp b/misc/config.cpp new file mode 100644 index 0000000..59bf4a6 --- /dev/null +++ b/misc/config.cpp @@ -0,0 +1,109 @@ +/* + * File: config.cpp + * Author: savop + * + * Created on 15. August 2009, 13:03 + */ + +#include <stdio.h> +#include <vdr/tools.h> +#include <getopt.h> +#include "config.h" +#include "../common.h" + +cUPnPConfig::cUPnPConfig(){ + this->mParsedArgs = NULL; + this->mInterface = NULL; + this->mAddress = NULL; + this->mAutoSetup = 0; + this->mEnable = 0; + this->mPort = 0; +} + +cUPnPConfig::~cUPnPConfig(){} + +cUPnPConfig* cUPnPConfig::get(){ + if(cUPnPConfig::mInstance == NULL) + cUPnPConfig::mInstance = new cUPnPConfig(); + + if(cUPnPConfig::mInstance) + return cUPnPConfig::mInstance; + else return NULL; +} + +cUPnPConfig* cUPnPConfig::mInstance = NULL; + +bool cUPnPConfig::processArgs(int argc, char* argv[]){ + // Implement command line argument processing here if applicable. + static struct option long_options[] = { + {"int", required_argument, NULL, 'i'}, + {"address", required_argument, NULL, 'a'}, + {"port", required_argument, NULL, 'p'}, + {"autodetect", no_argument, NULL, 'd'} + }; + + int c = 0; + + // Check if anything went wrong by setting 'success' to false + // As there are multiple tests you may get a faulty behavior + // if the current value is not considered in latter tests. + // Asume that: Success = true; + // success && false = false; --> new value of success is false + // success && true = true; --> still true. + // So, in the case of true and only true, success contains the + // desired value. + bool success = true; + bool ifaceExcistent = false; + bool addExcistent = false; + + while((c = getopt_long(argc, argv, "i:a:p:v",long_options, NULL)) != -1){ + switch(c){ + case 'i': + if(addExcistent) { ERROR("Address given but must be absent!"); return false; } + success = this->parseSetup(SETUP_SERVER_INT, optarg) && success; + success = this->parseSetup(SETUP_SERVER_ADDRESS, NULL) && success; + success = this->parseSetup(SETUP_SERVER_AUTO, "0") && success; + ifaceExcistent = true; + break; + case 'a': + if(ifaceExcistent) { ERROR("Interface given but must be absent!"); return false; } + success = this->parseSetup(SETUP_SERVER_ADDRESS, optarg) && success; + success = this->parseSetup(SETUP_SERVER_INT, NULL) && success; + success = this->parseSetup(SETUP_SERVER_AUTO, "0") && success; + addExcistent = true; + break; + case 'p': + success = this->parseSetup(SETUP_SERVER_PORT, optarg) && success; + success = this->parseSetup(SETUP_SERVER_AUTO, "0") && success; + break; + case 'd': + success = this->parseSetup(SETUP_SERVER_AUTO, optarg) && success; + default: + return false; + } + } + + return success; +} + +bool cUPnPConfig::parseSetup(const char *Name, const char *Value) +{ + const char* ptr; + if(*this->mParsedArgs && (ptr = strstr(this->mParsedArgs,Name))!=NULL){ + MESSAGE("Skipping %s=%s, was overridden in command line.",Name, Value); + return true; + } + + MESSAGE("VARIABLE %s has value %s", Name, Value); + // Parse your own setup parameters and store their values. + if (!strcasecmp(Name, SETUP_SERVER_ENABLED)) this->mEnable = atoi(Value); + else if (!strcasecmp(Name, SETUP_SERVER_AUTO)) this->mAutoSetup = atoi(Value); + else if (!strcasecmp(Name, SETUP_SERVER_INT)) this->mInterface = strdup0(Value); // (Value) ? strn0cpy(this->mInterface, Value, strlen(this->mInterface)) : NULL; + else if (!strcasecmp(Name, SETUP_SERVER_ADDRESS)) this->mAddress = strdup0(Value); //(Value) ? strn0cpy(this->mAddress, Value, strlen(this->mAddress)) : NULL; + else if (!strcasecmp(Name, SETUP_SERVER_PORT)) this->mPort = atoi(Value); + else return false; + + this->mParsedArgs = cString::sprintf("%s%s",*this->mParsedArgs,Name); + + return true; +}
\ No newline at end of file diff --git a/misc/config.h b/misc/config.h new file mode 100644 index 0000000..018213e --- /dev/null +++ b/misc/config.h @@ -0,0 +1,33 @@ +/* + * File: config.h + * Author: savop + * + * Created on 15. August 2009, 13:03 + */ + +#ifndef _CONFIG_H +#define _CONFIG_H + +#include <vdr/tools.h> +#include "../common.h" + +class cUPnPConfig { +private: + static cUPnPConfig* mInstance; + cString mParsedArgs; + cUPnPConfig(); +public: + char* mInterface; + char* mAddress; + int mPort; + int mEnable; + int mAutoSetup; +public: + virtual ~cUPnPConfig(); + static cUPnPConfig* get(); + bool parseSetup(const char* Name, const char* Value); + bool processArgs(int argc, char* argv[]); +}; + +#endif /* _CONFIG_H */ + diff --git a/misc/menusetup.cpp b/misc/menusetup.cpp new file mode 100644 index 0000000..9e8386f --- /dev/null +++ b/misc/menusetup.cpp @@ -0,0 +1,205 @@ +/* + * File: menusetup.cpp + * Author: savop + * + * Created on 19. April 2009, 16:50 + */ + +#include "config.h" +#include <vdr/osdbase.h> +#include "menusetup.h" +#include "../common.h" +#include "util.h" +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <vdr/menuitems.h> + + +cMenuSetupUPnP::cMenuSetupUPnP(){ + // Get server acitve state + MESSAGE("Creating menu"); + this->mCtrlBind = NULL; + this->mCtrlAutoMode = NULL; + this->mCtrlEnabled = NULL; + this->mCtrlPort = NULL; + this->mEnable = 0; + this->mDetectPort = 0; + this->mAutoSetup = 0; + this->mPort = 0; + this->mAddress = NULL; + this->mInterfaceIndex = 0; + this->Load(); + this->Update(); +} + +//cMenuSetupUPnP::~cMenuSetupUPnP() { +// delete this->mCtrlAutoMode; +// delete this->mCtrlEnabled; +// delete this->mCtrlPort; +// free(this->mAddress); +//} + +void cMenuSetupUPnP::Load(void){ + cUPnPConfig* Config = cUPnPConfig::get(); + this->mEnable = Config->mEnable; + this->mAutoSetup = Config->mAutoSetup; + this->mInterfaceIndex = this->getInterfaceIndex(Config->mInterface); + this->mAddress = strdup(Config->mAddress?Config->mAddress:"0.0.0.0"); + this->mPort = Config->mPort; + + if(Config->mPort==0) this->mDetectPort = 1; +} + +const char* const* cMenuSetupUPnP::getInterfaceList(int* count){ + char** Ifaces = getNetworkInterfaces(count); + char** IfaceList = new char*[++(*count)]; + IfaceList[0] = strdup(_("User defined")); + for(int i=0; i < *count-1; i++){ + IfaceList[i+1] = strdup(Ifaces[i]); + } + delete [] Ifaces; + return IfaceList; +} + +int cMenuSetupUPnP::getInterfaceIndex(const char* Interface){ + MESSAGE("Getting Index of %s", Interface); + if(!Interface) return 0; + int count; + int Index = 0; + const char* const* Ifaces = this->getInterfaceList(&count); + + for(int i=1; i < count; i++){ + if(!strcmp(Interface, Ifaces[i])){ + Index = i; + break; + } + } + delete [] Ifaces; + return Index; +} + +const char* cMenuSetupUPnP::getInterface(int Index){ + int count; + const char* const* Ifaces = this->getInterfaceList(&count); + + if(count < Index || Index < 1) return NULL; + const char* Interface = strdup0(Ifaces[Index]); + delete [] Ifaces; + return Interface; +} + +void cMenuSetupUPnP::Update(void){ + int Current = this->Current(); + this->Clear(); + // Add OSD menu item for enabling UPnP Server + this->Add(mCtrlEnabled = new cMenuEditBoolItem(_("Enable UPnP Server"),&this->mEnable,_("disabled"),_("enabled"))); + if(this->mEnable){ + cMenuEditIntItem* editPortItem = NULL; + this->Add(mCtrlAutoMode = new cMenuEditBoolItem(_("Auto detect settings"),&this->mAutoSetup,_("no"),_("yes"))); + // Add OSD menu item for IP address + int Count; + const char* const* Interfaces = this->getInterfaceList(&Count); + this->Add(mCtrlBind = new cMenuEditStraItem(_("Bind to network interface"), &this->mInterfaceIndex, Count, Interfaces)); + + cMenuEditIpItem* editIpItem; + if(this->mInterfaceIndex){ + const sockaddr_in* addr = getIPFromInterface(this->getInterface(this->mInterfaceIndex)); + char* IP = strdup(inet_ntoa(addr->sin_addr)); + editIpItem = new cMenuEditIpItem(_("Current IP address"),IP); + editIpItem->SetSelectable(false); + free(IP); + } + else { + editIpItem = new cMenuEditIpItem(_("Set IP address"),this->mAddress); + } + this->Add(editIpItem); + this->Add(mCtrlPort = new cMenuEditBoolItem(_("Select port"), &this->mDetectPort, _("auto"), _("user definied"))); + if(this->mDetectPort){ + this->Add(editPortItem = new cMenuEditIntItem(_("User specified port"), + &this->mPort, + SERVER_MIN_PORT, + SERVER_MAX_PORT + )); + } + + if(this->mAutoSetup){ + if(mCtrlPort) mCtrlPort->SetSelectable(false); + if(mCtrlBind) mCtrlBind->SetSelectable(false); + if(editPortItem) editPortItem->SetSelectable(false); + if(editIpItem) editIpItem->SetSelectable(false); + } + else { + if(mCtrlPort) mCtrlPort->SetSelectable(true); + if(mCtrlBind) mCtrlBind->SetSelectable(true); + if(editPortItem) editPortItem->SetSelectable(true); + if(editIpItem && !this->mInterfaceIndex) editIpItem->SetSelectable(true); + } + } + this->SetCurrent(this->Get(Current)); + this->Display(); +} + +eOSState cMenuSetupUPnP::ProcessKey(eKeys Key){ + + cOsdItem *Item = this->Get(this->Current()); + + eOSState State = cMenuSetupPage::ProcessKey(Key); + + Key = NORMALKEY(Key); + + if(Key != kRight && Key != kLeft){ + return State; + } + + if(Item == this->mCtrlEnabled){ + if(this->mEnable){ + this->Update(); + } + else if (!this->mEnable) { + this->Update(); + } + } + else if (Item == this->mCtrlPort){ + if(this->mDetectPort){ + this->Update(); + } + else if(!this->mDetectPort) { + this->Update(); + } + } + else if (Item == this->mCtrlAutoMode){ + if(this->mAutoSetup){ + this->Update(); + } + else if(!this->mAutoSetup) { + this->Update(); + } + } + else if(Item == this->mCtrlBind){ +// if(!this->mInterfaceIndex){ +// this->Update(); +// } +// else if(!this->mInterfaceIndex){ +// this->Update(); +// } + this->Update(); + } + return State; +} + +void cMenuSetupUPnP::Store(void){ + cUPnPConfig* Config = cUPnPConfig::get(); + Config->mAddress = strdup0(this->mAddress); + Config->mAutoSetup = this->mAutoSetup; + Config->mEnable = this->mEnable; + Config->mInterface = strdup0(this->getInterface(this->mInterfaceIndex)); + Config->mPort = (this->mDetectPort) ? 0 : this->mPort; + + this->SetupStore(SETUP_SERVER_AUTO, this->mAutoSetup); + this->SetupStore(SETUP_SERVER_ENABLED, this->mEnable); + this->SetupStore(SETUP_SERVER_INT, this->getInterface(this->mInterfaceIndex)); + this->SetupStore(SETUP_SERVER_ADDRESS, this->mAddress); + this->SetupStore(SETUP_SERVER_PORT, this->mPort); + +} diff --git a/misc/menusetup.h b/misc/menusetup.h new file mode 100644 index 0000000..ae40e97 --- /dev/null +++ b/misc/menusetup.h @@ -0,0 +1,78 @@ +/* + * File: menusetup.h + * Author: savop + * + * Created on 19. April 2009, 16:50 + */ + +#ifndef _CMENUSETUPUPNP_H +#define _CMENUSETUPUPNP_H + +#include <vdr/plugin.h> +#include "../server/server.h" +#include "config.h" + +/** + * The VDR setup page + * + * This class shows and manages the settings within the VDR setup OSD + * + * @author Denis Loh + * @version 0.0.1 + */ +class cMenuSetupUPnP : public cMenuSetupPage { +public: + cMenuSetupUPnP(); +// virtual ~cMenuSetupUPnP(); + virtual eOSState ProcessKey(eKeys Key); +protected: + virtual void Store(void); + void Update(void); + void Load(void); +private: + const char* const* getInterfaceList(int *count); + int getInterfaceIndex(const char* Interface); + const char* getInterface(int Index); + cOsdItem *mCtrlBind; + cOsdItem *mCtrlEnabled; + cOsdItem *mCtrlPort; + cOsdItem *mCtrlAutoMode; + cUPnPServer* mUpnpServer; + /** + * Is the server enabled or not + * + * The server can be switched on or off. If it is turned off, the server + * will close open transmissions and ports + * + */ + int mEnable; + int mAutoSetup; + /** + * The port to listen to (Default: 0 autodetect) + * + * The port the server is bound to. The default setting is 0. + * So, the server will determine automatically a free random port between + * 49152 and 65535. If a server should use a specific port it can be set + * to one out of that range. + * + */ + int mPort; + int mDetectPort; + /** + * The Interface the server is bound to + * + * If multiple interfaces exist the server can be bound to a specific + * one + * + */ + int mInterfaceIndex; + /** + * The socket address of the server + * + * The IP address and the port of the server + */ + char *mAddress; +}; + +#endif /* _CMENUSETUPUPNP_H */ + diff --git a/misc/search.cpp b/misc/search.cpp new file mode 100644 index 0000000..deee8b0 --- /dev/null +++ b/misc/search.cpp @@ -0,0 +1,795 @@ +/* + * File: search.cpp + * Author: savop + * + * Created on 27. August 2009, 21:21 + */ + +// uncomment this to enable debuging of the grammar +//#define BOOST_SPIRIT_DEBUG + +#include <string> +#include "search.h" +#include "../common.h" +#include <boost/spirit.hpp> +#include <boost/function.hpp> +#include <boost/bind.hpp> + +using namespace std; +using namespace boost; +using namespace boost::spirit; + +// This is the standard callback function which will be overloaded +// with all the specific callbacks +typedef function2<void, const char*, const char*> expCallback; +typedef function1<void, const char*> propCallback; +typedef function1<void, const char*> opCallback; +typedef function1<void, const char> charCallback; +typedef function1<void, int> intCallback; + +// The defined ColumnNames +struct cProperties : symbols<const char*> { +//struct cProperties : symbols<> { + cProperties(){ + add +// (UPNP_PROP_OBJECTID, UPNP_PROP_OBJECTID) +// (UPNP_PROP_PARENTID, UPNP_PROP_PARENTID) +// (UPNP_PROP_RESTRICTED, UPNP_PROP_RESTRICTED) +// (UPNP_PROP_CLASS, UPNP_PROP_CLASS) +// (UPNP_PROP_CLASSNAME, UPNP_PROP_CLASSNAME) +// (UPNP_PROP_WRITESTATUS, UPNP_PROP_WRITESTATUS) +// (UPNP_PROP_CHILDCOUNT, UPNP_PROP_CHILDCOUNT) +// (UPNP_PROP_REFERENCEID, UPNP_PROP_REFERENCEID) +// (UPNP_PROP_TITLE, UPNP_PROP_TITLE) +// (UPNP_PROP_CREATOR, UPNP_PROP_CREATOR) +// ("dc:description", "Description") +// ("dc:date", "Date") +// ("res", "Resource") +// ("res@bitrate", "Bitrate") +// ("res@duration", "Duration") +// ("res@size", "Size") +// ("res@sampleFrequency", "SampleFrequency") +// ("res@resolution", "Resolution") +// ("res@protocolInfo", "ProtocolInfo") +// ; + (UPNP_PROP_OBJECTID,UPNP_PROP_OBJECTID) + (UPNP_PROP_PARENTID,UPNP_PROP_PARENTID) + (UPNP_PROP_TITLE,UPNP_PROP_TITLE) + (UPNP_PROP_CREATOR,UPNP_PROP_CREATOR) + (UPNP_PROP_RESTRICTED,UPNP_PROP_RESTRICTED) + (UPNP_PROP_WRITESTATUS,UPNP_PROP_WRITESTATUS) + (UPNP_PROP_CLASS,UPNP_PROP_CLASS) + (UPNP_PROP_CLASSNAME,UPNP_PROP_CLASSNAME) + (UPNP_PROP_SEARCHCLASS,UPNP_PROP_SEARCHCLASS) + (UPNP_PROP_SCLASSDERIVED,UPNP_PROP_SCLASSDERIVED) + (UPNP_PROP_REFERENCEID,UPNP_PROP_REFERENCEID) + (UPNP_PROP_SCLASSNAME,UPNP_PROP_SCLASSNAME) + (UPNP_PROP_SEARCHABLE,UPNP_PROP_SEARCHABLE) + (UPNP_PROP_CHILDCOUNT,UPNP_PROP_CHILDCOUNT) + (UPNP_PROP_RESOURCE,UPNP_PROP_RESOURCE) + (UPNP_PROP_PROTOCOLINFO,UPNP_PROP_PROTOCOLINFO) + (UPNP_PROP_SIZE,UPNP_PROP_SIZE) + (UPNP_PROP_DURATION,UPNP_PROP_DURATION) + (UPNP_PROP_BITRATE,UPNP_PROP_BITRATE) + (UPNP_PROP_SAMPLEFREQUENCE,UPNP_PROP_SAMPLEFREQUENCE) + (UPNP_PROP_BITSPERSAMPLE,UPNP_PROP_BITSPERSAMPLE) + (UPNP_PROP_NOAUDIOCHANNELS,UPNP_PROP_NOAUDIOCHANNELS) + (UPNP_PROP_COLORDEPTH,UPNP_PROP_COLORDEPTH) + (UPNP_PROP_RESOLUTION,UPNP_PROP_RESOLUTION) + (UPNP_PROP_GENRE,UPNP_PROP_GENRE) + (UPNP_PROP_LONGDESCRIPTION,UPNP_PROP_LONGDESCRIPTION) + (UPNP_PROP_PRODUCER,UPNP_PROP_PRODUCER) + (UPNP_PROP_RATING,UPNP_PROP_RATING) + (UPNP_PROP_ACTOR,UPNP_PROP_ACTOR) + (UPNP_PROP_DIRECTOR,UPNP_PROP_DIRECTOR) + (UPNP_PROP_DESCRIPTION,UPNP_PROP_DESCRIPTION) + (UPNP_PROP_PUBLISHER,UPNP_PROP_PUBLISHER) + (UPNP_PROP_LANGUAGE,UPNP_PROP_LANGUAGE) + (UPNP_PROP_RELATION,UPNP_PROP_RELATION) + (UPNP_PROP_STORAGEMEDIUM,UPNP_PROP_STORAGEMEDIUM) + (UPNP_PROP_DVDREGIONCODE,UPNP_PROP_DVDREGIONCODE) + (UPNP_PROP_CHANNELNAME,UPNP_PROP_CHANNELNAME) + (UPNP_PROP_SCHEDULEDSTARTTIME,UPNP_PROP_SCHEDULEDSTARTTIME) + (UPNP_PROP_SCHEDULEDENDTIME,UPNP_PROP_SCHEDULEDENDTIME) + (UPNP_PROP_ICON,UPNP_PROP_ICON) + (UPNP_PROP_REGION,UPNP_PROP_REGION) + (UPNP_PROP_CHANNELNR,UPNP_PROP_CHANNELNR) + (UPNP_PROP_RIGHTS,UPNP_PROP_RIGHTS) + (UPNP_PROP_RADIOCALLSIGN,UPNP_PROP_RADIOCALLSIGN) + (UPNP_PROP_RADIOSTATIONID,UPNP_PROP_RADIOSTATIONID) + (UPNP_PROP_RADIOBAND,UPNP_PROP_RADIOBAND) + (UPNP_PROP_CONTRIBUTOR,UPNP_PROP_CONTRIBUTOR) + (UPNP_PROP_DATE,UPNP_PROP_DATE) + (UPNP_PROP_ALBUM,UPNP_PROP_ALBUM) + (UPNP_PROP_ARTIST,UPNP_PROP_ARTIST) + (UPNP_PROP_DLNA_CONTAINERTYPE,UPNP_PROP_DLNA_CONTAINERTYPE) + ; + } +} Properties; + +struct cOperators : symbols<const char*> { + cOperators(){ + add + ("=", "==") + ("!=", "!=") + ("<", "<") + (">", ">") + ("<=", "<=") + (">=", ">=") + ("contains", "LIKE") + ("doesNotContain", "NOT LIKE") + ("derivedfrom", "derivedFrom") + ; + } +} Operators; + +struct cConcatOperators : symbols<const char*> { + cConcatOperators(){ + add + ("and", "AND") + ("or", "OR") + ; + } +} ConcatOperators; + +struct cExistanceOperator : symbols<const char*> { + cExistanceOperator(){ + add + ("true", "NOT NULL") + ("false", "NULL") + ; + } +} Existance; + +// THE GRAMMAR! +// This is the grammar including the functors which calls the member functions +// of search. The callback definitions at the end of the constructor are +// essential. DO NOT MODIFY if you don't know how! +struct cSearchGrammar : public boost::spirit::grammar<cSearchGrammar> { + // The callbacks members + charCallback &endBrackedExp; + expCallback &pushSimpleExp; + opCallback &pushOperator; + expCallback &pushQuotedValue; + opCallback &pushExistance; + propCallback &pushProperty; + opCallback &pushConcatOp; + charCallback &startBrackedExp; + + // Constructor with the callback functions + cSearchGrammar( + charCallback &endBrackedExp, + expCallback &pushSimpleExp, + opCallback &pushOperator, + expCallback &pushQuotedValue, + opCallback &pushExistance, + propCallback &pushProperty, + opCallback &pushConcatOp, + charCallback &startBrackedExp): + endBrackedExp(endBrackedExp), + pushSimpleExp(pushSimpleExp), + pushOperator(pushOperator), + pushQuotedValue(pushQuotedValue), + pushExistance(pushExistance), + pushProperty(pushProperty), + pushConcatOp(pushConcatOp), + startBrackedExp(startBrackedExp){} + + template <typename scanner> + struct definition { + boost::spirit::rule<scanner> searchCrit, searchExp, logOp, \ + relExp, binOp, relOp, stringOp, \ + existsOp, boolVal, quotedVal, \ + wChar, property, brackedExp, exp; + const boost::spirit::rule<scanner> &start(){ + return searchCrit; + } + definition(const cSearchGrammar &self){ + /*************************************************************************\ + * * + * The grammar of a UPnP search expression * + * * + * searchCrit ::= searchExp | asterisk * + * * + * searchExp ::= relExp | * + * searchExp wChar+ logOp wChar+ searchExp | * + * '(' wChar* searchExp wChar* ')' * + * * + * logOp ::= 'and' | 'or' * + * * + * relExp ::= property wChar+ binOp wChar+ quotedVal | * + * property wChar* existsOp wChar+ boolVal * + * * + * binOp ::= relOp | stringOp * + * * + * relOp ::= '=' | '!=' | '<' | '<=' | '>' | '>=' * + * * + * stringOp ::= 'contains' | 'doesNotContain' | 'derivedfrom' * + * * + * existsOp ::= 'exists' * + * * + * boolVal ::= 'true' | 'false' * + * * + * quotedVal ::= dQuote escapedQuote dQuote * + * * + * wChar ::= space | hTab | lineFeed | * + * vTab | formFeed | return * + * * + * property ::= See ContentDirectory Section 2.4 * + * * + * escapedQuote ::= See ContentDirectory Section 2.3.1 * + * * + \*************************************************************************/ + searchCrit = searchExp | "*"; + + searchExp = exp >> *(+wChar >> logOp >> +wChar >> exp); + ; + + exp = relExp + | brackedExp + ; + + brackedExp = confix_p( + ch_p('(')[self.startBrackedExp], + *wChar >> searchExp >> *wChar, + ch_p(')')[self.endBrackedExp] + ) + ; + + logOp = ConcatOperators[self.pushConcatOp] + ; + + relExp = (property >> +wChar >> binOp >> +wChar >> quotedVal) [self.pushSimpleExp] + | (property >> +wChar >> existsOp >> +wChar >> boolVal) [self.pushSimpleExp] + ; + + binOp = Operators[self.pushOperator] + ; + + existsOp = str_p("exists") + ; + + boolVal = Existance[self.pushExistance] + ; + + quotedVal = confix_p('"', (*c_escape_ch_p)[self.pushQuotedValue], '"'); + + wChar = space_p; + + property = Properties[self.pushProperty] + ; + + // Debug mode + #ifdef BOOST_SPIRIT_DEBUG + BOOST_SPIRIT_DEBUG_NODE(searchCrit); + BOOST_SPIRIT_DEBUG_NODE(searchExp); + BOOST_SPIRIT_DEBUG_NODE(logOp); + BOOST_SPIRIT_DEBUG_NODE(relExp); + BOOST_SPIRIT_DEBUG_NODE(binOp); + BOOST_SPIRIT_DEBUG_NODE(relOp); + BOOST_SPIRIT_DEBUG_NODE(stringOp); + BOOST_SPIRIT_DEBUG_NODE(existsOp); + BOOST_SPIRIT_DEBUG_NODE(boolVal); + BOOST_SPIRIT_DEBUG_NODE(quotedVal); + BOOST_SPIRIT_DEBUG_NODE(wChar); + BOOST_SPIRIT_DEBUG_NODE(property); + #endif + } + }; +}; + +struct cSortGrammar : public boost::spirit::grammar<cSortGrammar> { + // The callback members + propCallback &pushProperty; + charCallback &pushDirection; + + cSortGrammar( + propCallback &pushProperty, + charCallback &pushDirection): + pushProperty(pushProperty), + pushDirection(pushDirection){} + + template <typename scanner> + struct definition { + boost::spirit::rule<scanner> sortCrit, sortExp, property, direction; + + const boost::spirit::rule<scanner> &start(){ + return sortCrit; + } + definition(const cSortGrammar &self){ + sortCrit = sortExp + ; + + sortExp = direction >> property >> *(ch_p(',') >> sortExp) + ; + + direction = sign_p[self.pushDirection] + ; + + property = Properties[self.pushProperty] + ; + } + }; +}; + +struct cFilterGrammar : public boost::spirit::grammar<cFilterGrammar> { + // The callback members + propCallback &pushProperty; + charCallback &pushAsterisk; + + cFilterGrammar( + propCallback &pushProperty, + charCallback &pushAsterisk): + pushProperty(pushProperty), + pushAsterisk(pushAsterisk){} + + template <typename scanner> + struct definition { + boost::spirit::rule<scanner> filterCrit, filterExp, property; + + const boost::spirit::rule<scanner> &start(){ + return filterCrit; + } + definition(const cFilterGrammar &self){ + filterCrit = filterExp + | ch_p('*')[self.pushAsterisk] + ; + + filterExp = property >> *(ch_p(',') >> filterExp) + ; + + property = Properties[self.pushProperty] + ; + } + }; +}; + + /**********************************************\ + * * + * The actors * + * * + \**********************************************/ + +void cSearch::pushEndBrackedExp(const char){ + MESSAGE("Pushing closing bracket"); + if(asprintf(&this->SQLWhereStmt, "%s)", this->SQLWhereStmt)==-1){ + ERROR("Unable to allocate SQL Statement"); + return; + } +} + +void cSearch::pushExpression(const char*, const char*){ + + const char* Property = this->CurrentProperty; + const char* Operator = this->CurrentOperator; + const char* Value = this->CurrentValue; + + if(Property && Operator && Value){ + char* Statement; + long int IntegerValue; + + if(sscanf(Value, "%ld", &IntegerValue)!=EOF && sscanf(Value, "%*4d-%*2d-%*2d")==EOF){ + MESSAGE("Popping '%s %s %ld'",Property, Operator, IntegerValue); + if(asprintf(&Statement, "%s %s %ld", Property, Operator, IntegerValue)==-1){ + ERROR("Failed to allocated memory for statement."); + return; + } + } + else if(!strcasecmp(Operator, "IS")){ + MESSAGE("Popping '%s %s %s'", Property, Operator, Value); + if(asprintf(&Statement, "%s %s %s", Property, Operator, Value)==-1){ + ERROR("Failed to allocated memory for statement."); + return; + } + } + else { + MESSAGE("Popping '%s %s \"%s\"'",Property, Operator, Value); + if(asprintf(&Statement, "%s %s '%s'", Property, Operator, Value)==-1){ + ERROR("Failed to allocated memory for statement."); + return; + } + } + + if(asprintf(&this->SQLWhereStmt, "%s %s", this->SQLWhereStmt, Statement)==-1){ + ERROR("Unable to allocate SQL Statement"); + return; + } + + } + return; +} + +void cSearch::pushProperty(const char* Property){ + this->CurrentProperty = strdup(Property); + MESSAGE("Property %s added",Property); +} + +void cSearch::pushOperator(const char* Operator){ + this->CurrentOperator = strdup(Operator); + MESSAGE("Operator %s added",Operator); +} + +void cSearch::pushValue(const char* Start, const char* End){ + const char* Value = string(Start,End).c_str(); + if(!Value || !strcmp(Value,"")) return; + + this->CurrentValue = strdup(Value); + MESSAGE("Value %s added", Value); +} + +void cSearch::pushExistance(const char* Exists){ + this->CurrentValue = strdup(Exists); + this->CurrentOperator = strdup("IS"); + MESSAGE("Existance expression called. '%s'", Exists); +} + +void cSearch::pushConcatOp(const char* Operator){ + if(asprintf(&this->SQLWhereStmt, "%s %s ", this->SQLWhereStmt, Operator)==-1){ + ERROR("Unable to allocate SQL Statement"); + return; + } + + MESSAGE("Concatenation expression called. '%s'", Operator); +} + +void cSearch::pushStartBrackedExp(const char){ + MESSAGE("Pushing opening bracket"); + if(asprintf(&this->SQLWhereStmt, "%s(", this->SQLWhereStmt)==-1){ + ERROR("Unable to allocate SQL Statement"); + return; + } +} + + /**********************************************\ + * * + * The rest * + * * + \**********************************************/ + +cSearch* cSearch::mInstance = NULL; + +const char* cSearch::parse(const char* Search){ + if(!cSearch::mInstance) cSearch::mInstance = new cSearch(); + + if(cSearch::mInstance && cSearch::mInstance->parseCriteria(Search)){ + return cSearch::mInstance->SQLWhereStmt; + } + return NULL; +} + +bool cSearch::parseCriteria(const char* Search){ + + charCallback endBrackedExpCB(bind(&cSearch::pushEndBrackedExp, this, _1)); + expCallback pushSimpleExpCB(bind(&cSearch::pushExpression, this, _1, _2)); + opCallback pushOperatorCB(bind(&cSearch::pushOperator, this, _1)); + expCallback pushQuotedValueCB(bind(&cSearch::pushValue, this, _1, _2)); + opCallback pushExistanceCB(bind(&cSearch::pushExistance, this, _1)); + propCallback pushPropertyCB(bind(&cSearch::pushProperty, this, _1)); + opCallback pushConcatOpCB(bind(&cSearch::pushConcatOp, this, _1)); + charCallback startBrackedExpCB(bind(&cSearch::pushStartBrackedExp, this, _1)); + + // Craft the grammar + cSearchGrammar Grammar(endBrackedExpCB, + pushSimpleExpCB, + pushOperatorCB, + pushQuotedValueCB, + pushExistanceCB, + pushPropertyCB, + pushConcatOpCB, + startBrackedExpCB); + + MESSAGE("Starting search parsing"); + + if(boost::spirit::parse(Search, Grammar).full){ + MESSAGE("Parsing successful"); + } + else { + ERROR("Parsing failed"); + return false; + } + return true; +} + +cSearch::cSearch(){ + this->CurrentOperator = NULL; + this->CurrentProperty = NULL; + this->CurrentValue = NULL; + this->SQLWhereStmt = strdup(""); +} + +cSearch::~cSearch(){ + delete this->CurrentOperator; + delete this->CurrentProperty; + delete this->CurrentValue; +} + + /**********************************************\ + * * + * The filter * + * * + \**********************************************/ + +cFilterCriteria::cFilterCriteria(){ + this->mFilterList = NULL; +} + +cFilterCriteria::~cFilterCriteria(){} + +cStringList* cFilterCriteria::parse(const char* Filter){ + cFilterCriteria* FilterParser = new cFilterCriteria; + cStringList* List = NULL; + + if(FilterParser && FilterParser->parseFilter(Filter)){ + List = FilterParser->getFilterList(); + } + + delete FilterParser; + + return List; +} + +bool cFilterCriteria::parseFilter(const char* Filter){ + this->mFilterList = new cStringList; + + if(Filter && !strcasecmp(Filter, "")){ + MESSAGE("Empty filter"); + return true; + } + + charCallback pushAsteriskCB(bind(&cFilterCriteria::pushAsterisk,this,_1)); + propCallback pushPropertyCB(bind(&cFilterCriteria::pushProperty,this,_1)); + + cFilterGrammar Grammar(pushPropertyCB, pushAsteriskCB); + + if(boost::spirit::parse(Filter, Grammar).full){ + MESSAGE("Parse filter successful"); + } + else { + ERROR("Parsing filter failed"); + return false; + } + return true; +} + + /**********************************************\ + * * + * The actors * + * * + \**********************************************/ + +void cFilterCriteria::pushProperty(const char* Property){ + MESSAGE("Pushing property"); + this->mFilterList->Append(strdup(Property)); +} + +void cFilterCriteria::pushAsterisk(const char){ + MESSAGE("Pushing asterisk (*)"); + if(this->mFilterList) delete this->mFilterList; + this->mFilterList = NULL; + return; +} + + /**********************************************\ + * * + * The sorter * + * * + \**********************************************/ + +cSortCriteria::cSortCriteria(){ + this->mCriteriaList = new cList<cSortCrit>; + this->mCurrentCrit = NULL; +} + +cSortCriteria::~cSortCriteria(){} + +cList<cSortCrit>* cSortCriteria::parse(const char* Sort){ + cSortCriteria* SortParser = new cSortCriteria; + cList<cSortCrit>* List = NULL; + if(SortParser && SortParser->parseSort(Sort)){ + List = SortParser->getSortList(); + } + + delete SortParser; + + return List; +} + +bool cSortCriteria::parseSort(const char* Sort){ + if(!Sort || !strcasecmp(Sort, "")){ + MESSAGE("Empty Sort"); + return true; + } + + charCallback pushDirectionCB(bind(&cSortCriteria::pushDirection,this,_1)); + propCallback pushPropertyCB(bind(&cSortCriteria::pushProperty,this,_1)); + + cSortGrammar Grammar(pushPropertyCB, pushDirectionCB); + + if(boost::spirit::parse(Sort, Grammar).full){ + MESSAGE("Parse Sort successful"); + } + else { + ERROR("Parsing Sort failed"); + return false; + } + return true; +} + + /**********************************************\ + * * + * The actors * + * * + \**********************************************/ + +void cSortCriteria::pushProperty(const char* Property){ + MESSAGE("Pushing property '%s'", Property); + this->mCurrentCrit->Property = strdup(Property); + this->mCriteriaList->Add(this->mCurrentCrit); + return; +} + +void cSortCriteria::pushDirection(const char Direction){ + MESSAGE("Pushing direction '%c'", Direction); + this->mCurrentCrit = new cSortCrit; + this->mCurrentCrit->SortDescending = (Direction=='-')?true:false; + return; +} + + /**********************************************\ + * * + * The pathparser * + * * + \**********************************************/ + +struct cWebserverSections : symbols<> { + cWebserverSections(){ + add + (UPNP_DIR_SHARES) + ; + } +} WebserverSections; + +struct cWebserverMethods : symbols<int> { + cWebserverMethods(){ + add + ("browse", UPNP_WEB_METHOD_BROWSE) + ("download", UPNP_WEB_METHOD_DOWNLOAD) + ("search", UPNP_WEB_METHOD_SEARCH) + ("show", UPNP_WEB_METHOD_SHOW) + ("get", UPNP_WEB_METHOD_STREAM) + ; + } +} WebserverMethods; + +struct cPathParserGrammar : public boost::spirit::grammar<cPathParserGrammar> { + + intCallback &pushSection; + intCallback &pushMethod; + expCallback &pushPropertyKey; + expCallback &pushPropertyValue; + + cPathParserGrammar(intCallback &pushSection, + intCallback &pushMethod, + expCallback &pushPropertyKey, + expCallback &pushPropertyValue): + pushSection(pushSection), + pushMethod(pushMethod), + pushPropertyKey(pushPropertyKey), + pushPropertyValue(pushPropertyValue){} + + template <typename scanner> + struct definition { + boost::spirit::rule<scanner> pathExp, section, method, methodProperties, + property, key, value, uncriticalChar; + + const boost::spirit::rule<scanner> &start(){ + return pathExp; + } + definition(const cPathParserGrammar &self){ + pathExp = section >> ch_p('/') >> method >> ch_p('?') >> methodProperties + ; + + section = WebserverSections[self.pushSection] + ; + + method = WebserverMethods[self.pushMethod] + ; + + methodProperties = property >> *(ch_p('&') >> methodProperties) + ; + + property = key >> ch_p('=') >> value + ; + + key = (+alnum_p)[self.pushPropertyKey] + ; + + value = (*uncriticalChar)[self.pushPropertyValue] + ; + + uncriticalChar = chset_p("-_.%~0-9A-Za-z") + ; + } + }; +}; + +cPathParser::cPathParser(){ + this->mSection = 0; + this->mMethod = 0; +} + +cPathParser::~cPathParser(){} + +bool cPathParser::parse(const char* Path, int* Section, int* Method, propertyMap* Properties){ + cPathParser* Parser = new cPathParser(); + bool ret = (Parser && Parser->parsePath(Path, Section, Method, Properties)) ? true : false; + + delete Parser; + + return ret; +} + +bool cPathParser::parsePath(const char* Path, int* Section, int* Method, propertyMap* Properties){ + if(!Path){ + return false; + } + + intCallback pushSectionCB(bind(&cPathParser::pushSection,this,_1)); + intCallback pushMethodCB(bind(&cPathParser::pushMethod,this,_1)); + expCallback pushPropertyKeyCB(bind(&cPathParser::pushPropertyKey, this, _1, _2)); + expCallback pushPropertyValueCB(bind(&cPathParser::pushPropertyValue, this, _1, _2)); + + cPathParserGrammar Grammar(pushSectionCB, pushMethodCB, pushPropertyKeyCB, pushPropertyValueCB); + + if(boost::spirit::parse(Path, Grammar).full){ + MESSAGE("Parse path successful"); + *Section = this->mSection; + *Method = this->mMethod; + *Properties = this->mProperties; + return true; + } + else { + ERROR("Parsing path failed"); + return false; + } + + return true; +} + + /**********************************************\ + * * + * The actors * + * * + \**********************************************/ + +void cPathParser::pushPropertyKey(const char* Start, const char* End){ + char* Key = strndup(Start, End-Start); + + MESSAGE("Pushing key '%s'", Key); + + this->mKey = Key; + + free(Key); +} + +void cPathParser::pushPropertyValue(const char* Start, const char* End){ + char* Value = strndup(Start, End-Start); + + MESSAGE("Pushing value '%s'", Value); + // TODO: urlDecode Value + + if(*this->mKey){ + char* Key = strdup(this->mKey); + this->mProperties[Key] = Value; + } +} + +void cPathParser::pushMethod(int Method){ + MESSAGE("Pushing method '%d'", Method); + this->mMethod = Method; +} + +void cPathParser::pushSection(int Section){ + MESSAGE("Pushing section '%d'", Section); + this->mSection = Section; +}
\ No newline at end of file diff --git a/misc/search.h b/misc/search.h new file mode 100644 index 0000000..df2442e --- /dev/null +++ b/misc/search.h @@ -0,0 +1,90 @@ +/* + * File: search.h + * Author: savop + * + * Created on 27. August 2009, 21:21 + */ + +#ifndef _SEARCH_H +#define _SEARCH_H + +#include <map> +#include <vdr/tools.h> +#include "util.h" + +struct cSortCrit : public cListObject { + const char* Property; + bool SortDescending; +}; + +typedef std::map<const char*, const char*, strCmp> propertyMap; + +class cPathParser { +private: + cString mKey; + propertyMap mProperties; + int mSection; + int mMethod; + bool parsePath(const char* Path, int* Section, int* Method, propertyMap* Properties); + void pushPropertyKey(const char* Start, const char* End); + void pushPropertyValue(const char* Start, const char* End); + void pushMethod(int Method); + void pushSection(int Section); + cPathParser(); +public: + virtual ~cPathParser(); + static bool parse(const char* Path, int* Section, int* Method, propertyMap* Properties); +}; + +class cSortCriteria { +private: + cSortCrit* mCurrentCrit; + cList<cSortCrit>* mCriteriaList; + bool parseSort(const char* Sort); + void pushProperty(const char* Property); + void pushDirection(const char Direction); + cList<cSortCrit>* getSortList() const { return this->mCriteriaList; } + cSortCriteria(); +public: + virtual ~cSortCriteria(); + static cList<cSortCrit>* parse(const char* Sort); +}; + +class cFilterCriteria { +private: + cStringList* mFilterList; + cFilterCriteria(); + bool parseFilter(const char* Filter); + void pushProperty(const char* Property); + void pushAsterisk(const char Asterisk); + cStringList* getFilterList() const { return this->mFilterList; } +public: + virtual ~cFilterCriteria(); + static cStringList* parse(const char* Filter); +}; + +class cSearch { +private: + char* SQLWhereStmt; + const char* CurrentProperty; + const char* CurrentOperator; + const char* CurrentValue; + static cSearch* mInstance; + cSearch(); + bool parseCriteria(const char* Search); + void pushExistance (const char* Exists); + void pushProperty (const char* Property); + void pushOperator (const char* Operator); + void pushConcatOp (const char* Operator); + void pushStartBrackedExp(const char); + void pushEndBrackedExp(const char); + void pushValue (const char* Start, const char* End); + void pushExpression(const char* Start, const char* End); +public: + virtual ~cSearch(); + static const char* parse(const char* Search); +}; + + +#endif /* _SEARCH_H */ + diff --git a/misc/util.cpp b/misc/util.cpp new file mode 100644 index 0000000..dc75706 --- /dev/null +++ b/misc/util.cpp @@ -0,0 +1,488 @@ +/* + * File: util.cpp + * Author: savop, andreas + * + * Created on 21. Mai 2009, 21:25 + * + * Extracted from streamdev-server plugin common.c + * $Id: common.c,v 1.6 2008/03/31 10:34:26 schmirl Exp $ + */ +#include "util.h" +#include "../common.h" +#include <string.h> +#include <string> +#include <sys/ioctl.h> +#include <net/if.h> +#include <upnp/ixml.h> +#include <arpa/inet.h> +#include <iosfwd> + +char* substr(const char* str, unsigned int offset, unsigned int length){ + if(offset > strlen(str)) return NULL; + if(length > strlen(str+offset)) length = strlen(str+offset); + char* substring = (char*)malloc(sizeof(substring)*length+1); + strncpy(substring, str+offset, length); + substring[length] = '\0'; + return substring; +} + +const char* getMACFromInterface(const char* Interface) { + int fd; + struct ifreq ifr; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + + ifr.ifr_addr.sa_family = AF_INET; + strncpy(ifr.ifr_name, Interface, IFNAMSIZ-1); + + ioctl(fd, SIOCGIFHWADDR, &ifr); + + close(fd); + + char *ret = new char[18]; + + sprintf(ret, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", + (unsigned char)ifr.ifr_hwaddr.sa_data[0], + (unsigned char)ifr.ifr_hwaddr.sa_data[1], + (unsigned char)ifr.ifr_hwaddr.sa_data[2], + (unsigned char)ifr.ifr_hwaddr.sa_data[3], + (unsigned char)ifr.ifr_hwaddr.sa_data[4], + (unsigned char)ifr.ifr_hwaddr.sa_data[5]); + + return ret; +} + +char** getNetworkInterfaces(int *count){ + int fd; + struct ifconf ifc; + struct ifreq ifr[10]; + int nifaces, i; + char** ifaces; + *count = 0; + + memset(&ifc,0,sizeof(ifc)); + ifc.ifc_buf = (char*) (ifr); + ifc.ifc_len = sizeof(ifr); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + int ret = ioctl(fd, SIOCGIFCONF, &ifc); + close(fd); + if(ret==0){ + nifaces = ifc.ifc_len/sizeof(struct ifreq); + ifaces = new char* [nifaces+1]; + for(i = 0; i < nifaces; i++){ + ifaces[i] = new char[IFNAMSIZ]; + ifaces[i] = strdup(ifr[i].ifr_name); + } + ifaces[i] = NULL; + *count = nifaces; + return ifaces; + } + else { + return NULL; + } +} + +const sockaddr_in* getIPFromInterface(const char* Interface){ + if(Interface==NULL) return NULL; + int fd; + struct ifreq ifr; + fd = socket(AF_INET, SOCK_DGRAM, 0); + /* I want to get an IPv4 IP address */ + ifr.ifr_addr.sa_family = AF_INET; + /* I want IP address attached to "eth0" */ + strncpy(ifr.ifr_name, Interface, IFNAMSIZ-1); + int ret = ioctl(fd, SIOCGIFADDR, &ifr); + close(fd); + const sockaddr_in* IpAddress = new sockaddr_in; + if(ret==0){ + IpAddress = (sockaddr_in *)&ifr.ifr_addr; + return IpAddress; + } + else { + delete IpAddress; + return NULL; + } +} + +cMenuEditIpItem::cMenuEditIpItem(const char *Name, char *Value):cMenuEditItem(Name) { + value = Value; + curNum = -1; + pos = -1; + step = false; + Set(); +} + +cMenuEditIpItem::~cMenuEditIpItem() { +} + +void cMenuEditIpItem::Set(void) { + char buf[1000]; + if (pos >= 0) { + in_addr_t addr = inet_addr(value); + if ((int)addr == -1) + addr = 0; + int p = 0; + for (int i = 0; i < 4; ++i) { + p += snprintf(buf + p, sizeof(buf) - p, pos == i ? "[%d]" : "%d", + pos == i ? curNum : (addr >> (i * 8)) & 0xff); + if (i < 3) + buf[p++] = '.'; + } + SetValue(buf); + } else + SetValue(value); +} + +eOSState cMenuEditIpItem::ProcessKey(eKeys Key) { + in_addr addr; + addr.s_addr = inet_addr(value); + if ((int)addr.s_addr == -1) + addr.s_addr = 0; + + switch (Key) { + case kUp: + if (pos >= 0) { + if (curNum < 255) ++curNum; + } else + return cMenuEditItem::ProcessKey(Key); + break; + + case kDown: + if (pos >= 0) { + if (curNum > 0) --curNum; + } else + return cMenuEditItem::ProcessKey(Key); + break; + + case kOk: + if (pos >= 0) { + addr.s_addr = inet_addr(value); + if ((int)addr.s_addr == -1) + addr.s_addr = 0; + addr.s_addr &= ~(0xff << (pos * 8)); + addr.s_addr |= curNum << (pos * 8); + strcpy(value, inet_ntoa(addr)); + } else + return cMenuEditItem::ProcessKey(Key); + curNum = -1; + pos = -1; + break; + + case kRight: + if (pos >= 0) { + addr.s_addr = inet_addr(value); + if ((int)addr.s_addr == -1) + addr.s_addr = 0; + addr.s_addr &= ~(0xff << (pos * 8)); + addr.s_addr |= curNum << (pos * 8); + strcpy(value, inet_ntoa(addr)); + } + + if (pos == -1 || pos == 3) + pos = 0; + else + ++pos; + + curNum = (addr.s_addr >> (pos * 8)) & 0xff; + step = true; + break; + + case kLeft: + if (pos >= 0) { + addr.s_addr = inet_addr(value); + if ((int)addr.s_addr == -1) + addr.s_addr = 0; + addr.s_addr &= ~(0xff << (pos * 8)); + addr.s_addr |= curNum << (pos * 8); + strcpy(value, inet_ntoa(addr)); + } + + if (pos <= 0) + pos = 3; + else + --pos; + + curNum = (addr.s_addr >> (pos * 8)) & 0xff; + step = true; + break; + + case k0 ... k9: /* Netbeans reports error with this line (.. is okay but wrong) */ + if (pos == -1) + pos = 0; + + if (curNum == -1 || step) { + curNum = Key - k0; + step = false; + } else + curNum = curNum * 10 + (Key - k0); + + if ((curNum * 10 > 255) || (curNum == 0)) { + in_addr addr; + addr.s_addr = inet_addr(value); + if ((int)addr.s_addr == -1) + addr.s_addr = 0; + addr.s_addr &= ~(0xff << (pos * 8)); + addr.s_addr |= curNum << (pos * 8); + strcpy(value, inet_ntoa(addr)); + if (++pos == 4) + pos = 0; + curNum = (addr.s_addr >> (pos * 8)) & 0xff; + step = true; + } + break; + + default: + return cMenuEditItem::ProcessKey(Key); + } + + Set(); + return osContinue; +} + +const char* escapeSQLite(const char* Data, char** Buf){ + if(!Data){ + *Buf = NULL; + } + else { + std::string NewData = ""; + int Char = 0; + for(unsigned int i = 0; i < strlen(Data); i++){ + Char = Data[i]; + switch(Char){ + case L'\'': NewData += "''"; break; + default: NewData += Data[i]; break; + } + } + *Buf = strdup(NewData.c_str()); + } + return (*Buf); +} + +const char* escapeXMLCharacters(const char* Data, char** Buf){ + if(Data==NULL){ + ERROR("Escape XML: No data to escape"); + return NULL; + } + std::string NewData = ""; + int Char = 0; + for(unsigned int i = 0; i < strlen(Data); i++){ + Char = Data[i]; + switch(Char){ + case L'€': NewData += "€"; break; + case L'"': NewData += """; break; + case L'&': NewData += "&"; break; + case L'<': NewData += "<"; break; + case L'>': NewData += ">"; break; + case L'¡': NewData += "¡"; break; + case L'¢': NewData += "¢"; break; + case L'£': NewData += "£"; break; + case L'¤': NewData += "¤"; break; + case L'¥': NewData += "¥"; break; + case L'¦': NewData += "¦"; break; + case L'§': NewData += "§"; break; + case L'¨': NewData += "¨"; break; + case L'©': NewData += "©"; break; + case L'ª': NewData += "ª"; break; + case L'¬': NewData += "¬"; break; + case L'': NewData += "­"; break; + case L'®': NewData += "®"; break; + case L'¯': NewData += "¯"; break; + case L'°': NewData += "°"; break; + case L'±': NewData += "±"; break; + case L'²': NewData += "²"; break; + case L'³': NewData += "³"; break; + case L'´': NewData += "´"; break; + case L'µ': NewData += "µ"; break; + case L'¶': NewData += "¶"; break; + case L'·': NewData += "·"; break; + case L'¸': NewData += "¸"; break; + case L'¹': NewData += "¹"; break; + case L'º': NewData += "º"; break; + case L'»': NewData += "»"; break; + case L'«': NewData += "«"; break; + case L'¼': NewData += "¼"; break; + case L'½': NewData += "½"; break; + case L'¾': NewData += "¾"; break; + case L'¿': NewData += "¿"; break; + case L'À': NewData += "À"; break; + case L'Á': NewData += "Á"; break; + case L'Â': NewData += "Â"; break; + case L'Ã': NewData += "Ã"; break; + case L'Ä': NewData += "Ä"; break; + case L'Å': NewData += "Å"; break; + case L'Æ': NewData += "Æ"; break; + case L'Ç': NewData += "Ç"; break; + case L'È': NewData += "È"; break; + case L'É': NewData += "É"; break; + case L'Ê': NewData += "Ê"; break; + case L'Ë': NewData += "Ë"; break; + case L'Ì': NewData += "Ì"; break; + case L'Í': NewData += "Í"; break; + case L'Î': NewData += "Î"; break; + case L'Ï': NewData += "Ï"; break; + case L'Ð': NewData += "Ð"; break; + case L'Ñ': NewData += "Ñ"; break; + case L'Ò': NewData += "Ò"; break; + case L'Ó': NewData += "Ó"; break; + case L'Ô': NewData += "Ô"; break; + case L'Õ': NewData += "Õ"; break; + case L'Ö': NewData += "Ö"; break; + case L'×': NewData += "×"; break; + case L'Ø': NewData += "Ø"; break; + case L'Ù': NewData += "Ù"; break; + case L'Ú': NewData += "Ú"; break; + case L'Û': NewData += "Û"; break; + case L'Ü': NewData += "Ü"; break; + case L'Ý': NewData += "Ý"; break; + case L'Þ': NewData += "Þ"; break; + case L'ß': NewData += "ß"; break; + case L'à': NewData += "à"; break; + case L'á': NewData += "á"; break; + case L'â': NewData += "â"; break; + case L'ã': NewData += "ã"; break; + case L'ä': NewData += "ä"; break; + case L'å': NewData += "å"; break; + case L'æ': NewData += "æ"; break; + case L'ç': NewData += "ç"; break; + case L'è': NewData += "è"; break; + case L'é': NewData += "é"; break; + case L'ê': NewData += "ê"; break; + case L'ë': NewData += "ë"; break; + case L'ì': NewData += "ì"; break; + case L'í': NewData += "í"; break; + case L'î': NewData += "î"; break; + case L'ï': NewData += "ï"; break; + case L'ð': NewData += "ð"; break; + case L'ñ': NewData += "ñ"; break; + case L'ò': NewData += "ò"; break; + case L'ó': NewData += "ó"; break; + case L'ô': NewData += "ô"; break; + case L'õ': NewData += "õ"; break; + case L'ö': NewData += "ö"; break; + case L'÷': NewData += "÷"; break; + case L'ø': NewData += "ø"; break; + case L'ù': NewData += "ù"; break; + case L'ú': NewData += "ú"; break; + case L'û': NewData += "û"; break; + case L'ü': NewData += "ü"; break; + case L'ý': NewData += "ý"; break; + case L'þ': NewData += "þ"; break; + default: NewData += Data[i]; break; + } + } + *Buf = strdup(NewData.c_str()); + return (*Buf); +} + +//Function copied from Intel SDK +/////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2000-2003 Intel Corporation +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither name of Intel Corporation nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////// +/******************************************************************************** + * SampleUtil_GetFirstDocumentItem + * + * Description: + * Given a document node, this routine searches for the first element + * named by the input string item, and returns its value as a string. + * String must be freed by caller using free. + * Parameters: + * doc -- The DOM document from which to extract the value + * item -- The item to search for + * + * + ********************************************************************************/ +char* ixmlGetFirstDocumentItem( IN IXML_Document * doc, IN const char *item, int* error ) { + IXML_NodeList *nodeList = NULL; + IXML_Node *textNode = NULL; + IXML_Node *tmpNode = NULL; + + char *ret = NULL; + *error = 0; + + nodeList = ixmlDocument_getElementsByTagName( doc, ( char * )item ); + + if( nodeList != NULL ) { + if( ( tmpNode = ixmlNodeList_item( nodeList, 0 ) ) ) { + + textNode = ixmlNode_getFirstChild( tmpNode ); + + if(textNode != NULL){ + ret = strdup( ixmlNode_getNodeValue( textNode ) ); + } + } + } else { + *error = -1; + } + + if( nodeList != NULL) { + ixmlNodeList_free( nodeList ); + } + + + return ret; +} + +int ixmlAddProperty(IXML_Document* document, IXML_Element* node, const char* upnpproperty, const char* value){ + if(!node) return -1; + IXML_Element* PropertyNode = NULL; + + const char* attribute = att(upnpproperty); + const char* property = prop(upnpproperty); + if(attribute){ + if(strcasecmp(property,"")){ + ixmlElement_setAttribute(node, attribute, value); + } + else { + IXML_NodeList* NodeList = ixmlElement_getElementsByTagName(node, property); + if(NodeList!=NULL){ + IXML_Node* Node = ixmlNodeList_item(NodeList, 0); + PropertyNode = (IXML_Element*) ixmlNode_getFirstChild(Node); + if(PropertyNode){ + ixmlElement_setAttribute(PropertyNode, attribute, value); + } + else { + ixmlNodeList_free(NodeList); + return -1; + } + } + else { + return -1; + } + } + } + else { + PropertyNode = ixmlDocument_createElement(document, property); + IXML_Node* PropertyText = ixmlDocument_createTextNode(document, value); + ixmlNode_appendChild((IXML_Node*) PropertyNode, PropertyText); + ixmlNode_appendChild((IXML_Node*) node, (IXML_Node*) PropertyNode); + } + return 0; +} diff --git a/misc/util.h b/misc/util.h new file mode 100644 index 0000000..ffedd67 --- /dev/null +++ b/misc/util.h @@ -0,0 +1,45 @@ +/* + * File: util.h + * Author: savop + * + * Created on 21. Mai 2009, 21:25 + */ + +#ifndef _UTIL_H +#define _UTIL_H + +#include <vdr/tools.h> +#include <vdr/plugin.h> +#include <upnp/ixml.h> + +#ifdef __cplusplus +extern "C" { +struct strCmp { bool operator()(const char* s1, const char* s2) const { return (strcmp(s1,s2) < 0); }}; +const sockaddr_in* getIPFromInterface(const char* Interface); +const char* getMACFromInterface(const char* Interface); +char** getNetworkInterfaces(int *count); +char* ixmlGetFirstDocumentItem( IN IXML_Document * doc, IN const char *item, int* error ); +int ixmlAddProperty(IN IXML_Document* document, IN IXML_Element* node, const char* upnpproperty, const char* value ); +char* substr(const char* str, unsigned int offset, unsigned int length); +} +#endif + +const char* escapeSQLite(const char* Data, char** Buf); +const char* escapeXMLCharacters(const char* Data, char** Buf); + +class cMenuEditIpItem: public cMenuEditItem { +private: + char *value; + int curNum; + int pos; + bool step; +protected: + virtual void Set(void); +public: + cMenuEditIpItem(const char *Name, char *Value); // Value must be 16 bytes + ~cMenuEditIpItem(); + virtual eOSState ProcessKey(eKeys Key); +}; + +#endif /* _UTIL_H */ + diff --git a/receiver/filehandle.cpp b/receiver/filehandle.cpp new file mode 100644 index 0000000..bc24f53 --- /dev/null +++ b/receiver/filehandle.cpp @@ -0,0 +1,8 @@ +/* + * File: filehandle.cpp + * Author: savop + * + * Created on 15. Oktober 2009, 10:49 + */ + +#include "filehandle.h"
\ No newline at end of file diff --git a/receiver/filehandle.h b/receiver/filehandle.h new file mode 100644 index 0000000..37f06e8 --- /dev/null +++ b/receiver/filehandle.h @@ -0,0 +1,26 @@ +/* + * File: filehandle.h + * Author: savop + * + * Created on 15. Oktober 2009, 10:49 + */ + +#ifndef _FILEHANDLE_H +#define _FILEHANDLE_H + +#include <upnp/upnp.h> +#include "../common.h" + +class cFileHandle { +public: + virtual void open(UpnpOpenFileMode mode) = 0; + virtual int read(char* buf, size_t buflen) = 0; + virtual int write(char* buf, size_t buflen) = 0; + virtual int seek(off_t offset, int whence) = 0; + virtual void close() = 0; + virtual ~cFileHandle(){}; +private: +}; + +#endif /* _FILEHANDLE_H */ + diff --git a/receiver/livereceiver.cpp b/receiver/livereceiver.cpp new file mode 100644 index 0000000..593853f --- /dev/null +++ b/receiver/livereceiver.cpp @@ -0,0 +1,175 @@ +/* + * File: livereceiver.cpp + * Author: savop + * + * Created on 4. Juni 2009, 13:28 + */ + +#include <vdr/thread.h> +#include <vdr/remux.h> +#include <vdr/device.h> +#include <vdr/channels.h> +#include <vdr/ringbuffer.h> +#include "livereceiver.h" + +cLiveReceiver* cLiveReceiver::newInstance(cChannel* Channel, int Priority){ + cDevice *Device = cDevice::GetDevice(Channel, Priority, true); + + if(!Device){ + ERROR("No matching device found to serve this channel!"); + return NULL; + } + + cLiveReceiver *Receiver = new cLiveReceiver(Channel, Device); + if(Receiver){ + MESSAGE("Receiver for channel \"%s\" created successfully.", Channel->Name()); + return Receiver; + } + else { + ERROR("Failed to create receiver!"); + return NULL; + } +} + +cLiveReceiver::cLiveReceiver(cChannel *Channel, cDevice *Device) +: cReceiver( Channel->GetChannelID(), 0, Channel->Vpid(), Channel->Apids(), Channel->Dpids(), Channel->Spids()), + mDevice(Device), mChannel(Channel){ + this->mLiveBuffer = NULL; + this->mOutputBuffer = NULL; + this->mFrameDetector = NULL; +} + +cLiveReceiver::~cLiveReceiver(void){ + if(this->IsAttached()) + this->Detach(); +} + +void cLiveReceiver::open(UpnpOpenFileMode){ + this->mLiveBuffer = new cRingBufferLinear(RECEIVER_LIVEBUFFER_SIZE, RECEIVER_RINGBUFFER_MARGIN, true, "Live TV buffer"); + this->mOutputBuffer = new cRingBufferLinear(RECEIVER_OUTPUTBUFFER_SIZE, RECEIVER_RINGBUFFER_MARGIN, true, "Streaming buffer"); + + this->mLiveBuffer->SetTimeouts(0, 100); + this->mOutputBuffer->SetTimeouts(0, 500); + + this->mFrameDetector = new cFrameDetector(this->mChannel->Vpid(), this->mChannel->Vtype()); + + this->mPatPmtGenerator.SetChannel(this->mChannel); + + this->mDevice->SwitchChannel(this->mChannel, false); + this->mDevice->AttachReceiver(this); +} + +void cLiveReceiver::Activate(bool On){ + if(On){ + this->Start(); + MESSAGE("Live receiver started."); + } + else { + if(this->Running()){ + this->Cancel(2); + } + MESSAGE("Live receiver stopped"); + } +} + +void cLiveReceiver::Receive(uchar* Data, int Length){ + if(this->Running()){ + int bytesWrote = this->mLiveBuffer->Put(Data, Length); + if(bytesWrote != Length && this->Running()){ + this->mLiveBuffer->ReportOverflow(Length - bytesWrote); + } + } +} + +void cLiveReceiver::Action(void){ + MESSAGE("Started buffering..."); + while(this->Running()){ + int bytesRead; + //MESSAGE("Buffer is filled with %d bytes", this->mLiveBuffer->Available()); + uchar* bytes = this->mLiveBuffer->Get(bytesRead); + if(bytes){ + int count = this->mFrameDetector->Analyze(bytes, bytesRead); + if(count){ + //MESSAGE("%d bytes analyzed", count); + //MESSAGE("%2.2f FPS", this->mFrameDetector->FramesPerSecond()); + if(!this->Running() && this->mFrameDetector->IndependentFrame()) + break; + if(this->mFrameDetector->Synced()){ + //MESSAGE("Frame detector synced to data stream"); + if(this->mFrameDetector->IndependentFrame()){ + this->mOutputBuffer->Put(this->mPatPmtGenerator.GetPat(), TS_SIZE); + int i = 0; + while(uchar* pmt = this->mPatPmtGenerator.GetPmt(i)){ + this->mOutputBuffer->Put(pmt, TS_SIZE); + } + } + int bytesWrote = this->mOutputBuffer->Put(bytes, count); + if(bytesWrote != count){ + this->mLiveBuffer->ReportOverflow(count - bytesWrote); + } + //MESSAGE("Wrote %d to output buffer", bytesWrote); + if(bytesWrote){ + this->mLiveBuffer->Del(bytesWrote); + } + else { + cCondWait::SleepMs(100); + } + } + else { + ERROR("Cannot sync to stream"); + } + } + } + } + MESSAGE("Receiver was detached from device"); +} + +int cLiveReceiver::read(char* buf, size_t buflen){ + int bytesRead; + if(!this->IsAttached()) + bytesRead = -1; + else { + while(!this->mOutputBuffer->Available()){ + WARNING("No data, waiting..."); + cCondWait::SleepMs(50); + if(!this->IsAttached()){ + MESSAGE("Lost device..."); + return 0; + } + } + + uchar* buffer = this->mOutputBuffer->Get(bytesRead); + if(buffer){ + if(buflen > (size_t)bytesRead){ + memcpy(buf,(char*)buffer,bytesRead); + this->mOutputBuffer->Del(bytesRead); + } + else { + memcpy(buf,(char*)buffer,buflen); + this->mOutputBuffer->Del(buflen); + } + } + + } + MESSAGE("Read %d bytes from live feed", bytesRead); + return bytesRead; +} + +int cLiveReceiver::seek(off_t, int){ + ERROR("Seeking not supported on broadcasts"); + return 0; +} + +int cLiveReceiver::write(char*, size_t){ + ERROR("Writing not allowed on broadcasts"); + return 0; +} + +void cLiveReceiver::close(){ + MESSAGE("Closing live receiver"); + this->Detach(); + delete this->mOutputBuffer; this->mOutputBuffer = NULL; + delete this->mLiveBuffer; this->mLiveBuffer = NULL; + this->mFrameDetector = NULL; + MESSAGE("Live receiver closed."); +}
\ No newline at end of file diff --git a/receiver/livereceiver.h b/receiver/livereceiver.h new file mode 100644 index 0000000..4632733 --- /dev/null +++ b/receiver/livereceiver.h @@ -0,0 +1,40 @@ +/* + * File: livereceiver.h + * Author: savop + * + * Created on 4. Juni 2009, 13:28 + */ + +#ifndef _LIVERECEIVER_H +#define _LIVERECEIVER_H + +#include "../common.h" +#include "filehandle.h" +#include <vdr/thread.h> +#include <vdr/receiver.h> + +class cLiveReceiver : public cReceiver, public cThread, public cFileHandle { +public: + static cLiveReceiver* newInstance(cChannel *Channel, int Priority); + virtual ~cLiveReceiver(void); + virtual void open(UpnpOpenFileMode mode); + virtual int read(char* buf, size_t buflen); + virtual int write(char* buf, size_t buflen); + virtual int seek(off_t offset, int whence); + virtual void close(); +protected: + virtual void Receive(uchar *Data, int Length); + virtual void Activate(bool On); + virtual void Action(void); +private: + cLiveReceiver(cChannel *Channel, cDevice *Device); + cDevice *mDevice; + cChannel *mChannel; + cRingBufferLinear *mLiveBuffer; + cRingBufferLinear *mOutputBuffer; + cFrameDetector *mFrameDetector; + cPatPmtGenerator mPatPmtGenerator; +}; + +#endif /* _LIVERECEIVER_H */ + diff --git a/receiver/recplayer.cpp b/receiver/recplayer.cpp new file mode 100644 index 0000000..d968ae7 --- /dev/null +++ b/receiver/recplayer.cpp @@ -0,0 +1,171 @@ +/* + * File: recplayer.cpp + * Author: savop + * + * Created on 8. Juni 2009, 11:57 + */ + +#include <stdio.h> +#include <fcntl.h> +#include <vdr/recording.h> +#include <vdr/tools.h> +#include "recplayer.h" + +cRecordingPlayer *cRecordingPlayer::newInstance(cRecording* Recording){ + if(Recording->IsPesRecording()){ + ERROR("Sorry, but only TS is supported, yet!"); + return NULL; + } + + cRecordingPlayer *Player = new cRecordingPlayer(Recording); + return Player; +} +cRecordingPlayer::~cRecordingPlayer() { + delete this->mOffsets; +} + +cRecordingPlayer::cRecordingPlayer(cRecording *Recording) { + MESSAGE("Created Recplayer"); + this->mFile = NULL; + this->mTotalLenght = 0; + this->mRecording = Recording; + this->mOffsets = new off_t[VDR_MAX_FILES_PER_RECORDING]; + this->mOffset = 0; + this->mIndex = 1; + +} + +void cRecordingPlayer::open(UpnpOpenFileMode){ + this->Scan(); +} + +void cRecordingPlayer::close(){ + delete [] this->mOffsets; + if(this->mFile) fclose(this->mFile); +} + +int cRecordingPlayer::write(char*, size_t){ + ERROR("Writing not allowed on recordings"); + return 0; +} + +int cRecordingPlayer::read(char* buf, size_t buflen){ + FILE *File; + off_t fileEndOffset = this->mOffsets[this->mIndex]; + if(this->mOffset > fileEndOffset){ + File = this->NextFile(); + } + else { + File = this->GetFile(); + } + // do not read more bytes than the actual file has + size_t bytesToRead = ((fileEndOffset - this->mOffset) < (off_t)buflen)? fileEndOffset - this->mOffset : buflen; + size_t bytesRead = fread((char*)buf, sizeof(char), bytesToRead, File); + + this->mOffset += (off_t)bytesRead; + + return (int)bytesRead; +} + +int cRecordingPlayer::seek(off_t offset, int origin){ + // Calculate the new offset + switch(origin){ + case SEEK_CUR: + if(this->mOffset + offset > this->mTotalLenght){ + ERROR("Can't read behind end of file!"); + return -1; + } + this->mOffset += (off_t)offset; + break; + case SEEK_END: + if(offset > 0){ + ERROR("Can't read behind end of file!"); + return -1; + } + this->mOffset = this->mTotalLenght + offset; + break; + case SEEK_SET: + if(offset > this->mTotalLenght){ + ERROR("Can't read behind end of file!"); + return -1; + } + this->mOffset = (off_t)offset; + break; + default: + ERROR("Unknown seek mode (%d)!", origin); + return -1; + } + // Seek to the very first file; + this->SeekInFile(1,0); + off_t fileEndOffset = this->mOffsets[this->mIndex]; + // Spin until the new offset is in range of a specific file + while(this->mOffset > (fileEndOffset = this->mOffsets[this->mIndex])){ + // If its not possible to switch to next file, there was an error + if(!this->NextFile()){ + ERROR("Offset %ld not in the range of a file!", offset); + return -1; + } + } + off_t relativeOffset = + this->mOffset - (this->mOffsets[this->mIndex-1]) + ? this->mOffsets[this->mIndex-1] + : 0; + if(!this->SeekInFile(this->mIndex, relativeOffset)){ + ERROR("Cannot set offset!"); + return -1; + } + return 0; +} + +void cRecordingPlayer::Scan(){ + MESSAGE("Scanning video files..."); + // Reset the offsets + int i = 1; + while(this->mOffsets[i++]) this->mOffsets[i] = 0; + MESSAGE("Offsets reseted."); + + i = 0; + FILE *File; + while((File = this->GetFile(i))){ + if(VDR_MAX_FILES_PER_RECORDING < i+1){ + ERROR("Maximum file offsets exceeded!"); + break; + } + fseek(File, 0, SEEK_END); + off_t offset = ftell(File); + MESSAGE("File %d has its last offset at %ld", i, offset); + this->mOffsets[i+1] = this->mOffsets[i] + offset; + this->mTotalLenght = this->mOffsets[i+1]; + i++; + } +} + +FILE *cRecordingPlayer::GetFile(int Index){ + // No Index given: set current index + if(Index == 0) Index = this->mIndex; + // Index not changed: return current file + if(this->mIndex == Index && this->mFile) return this->mFile; + + // Index changed: close open file and open new file + if(this->mFile) fclose(this->mFile); + char *filename = new char[VDR_FILENAME_BUFSIZE]; + snprintf(filename, VDR_FILENAME_BUFSIZE, VDR_RECORDFILE_PATTERN_TS, this->mRecording->FileName(), Index ); + MESSAGE("Filename: %s", filename); + this->mFile = NULL; + if(this->mFile = fopen(filename, "r")){ + this->mIndex = Index; + return this->mFile; + } + return NULL; +} + +FILE *cRecordingPlayer::NextFile(void){ + return this->GetFile(this->mIndex++); +} + +int cRecordingPlayer::SeekInFile(int Index, off_t Offset){ + FILE *File = this->GetFile(Index); + fseek(File, Offset, SEEK_SET); + return ftell(File); +} + diff --git a/receiver/recplayer.h b/receiver/recplayer.h new file mode 100644 index 0000000..8295539 --- /dev/null +++ b/receiver/recplayer.h @@ -0,0 +1,39 @@ +/* + * File: recplayer.h + * Author: savop + * + * Created on 8. Juni 2009, 11:57 + */ + +#ifndef _RECPLAYER_H +#define _RECPLAYER_H + +#include "../common.h" +#include "filehandle.h" +#include <vdr/recording.h> + +class cRecordingPlayer : cFileHandle { +public: + static cRecordingPlayer *newInstance(cRecording *Recording); + virtual ~cRecordingPlayer(); + virtual void open(UpnpOpenFileMode mode); + virtual int read(char* buf, size_t buflen); + virtual int write(char* buf, size_t buflen); + virtual int seek(off_t offset, int origin); + virtual void close(); +private: + void Scan(void); + cRecordingPlayer(cRecording *Recording); + FILE* GetFile(int Index = 0); + FILE* NextFile(void); + int SeekInFile(int Index, off_t Offset); + cRecording *mRecording; + off_t* mOffsets; + off_t mOffset; + off_t mTotalLenght; + int mIndex; + FILE *mFile; +}; + +#endif /* _RECPLAYER_H */ + diff --git a/server/server.cpp b/server/server.cpp new file mode 100644 index 0000000..56afcaa --- /dev/null +++ b/server/server.cpp @@ -0,0 +1,366 @@ +/* + * File: server.cpp + * Author: savop + * + * Created on 19. April 2009, 17:42 + */ + +#include <vdr/plugin.h> +#include <stdlib.h> +#include <string.h> +#include <arpa/inet.h> +#include <upnp/upnp.h> +#include "server.h" +#include "../misc/util.h" +#include "../misc/config.h" +#include "../common.h" +#include "../upnpcomponents/dlna.h" +#include "../database/object.h" + +/**************************************************** + * + * The UPnP Server + * + * Handles incoming messages, UPnP connections + * and so on. + * + ****************************************************/ + +cConnectionManager* cUPnPServer::mConnectionManager = NULL; +cContentDirectory* cUPnPServer::mContentDirectory = NULL; + +cUPnPServer::cUPnPServer() { + this->mServerAddr = new sockaddr_in; + // Bugfix: this was necessary because there were + // some uninitialised bytes in the structure (Please recheck this!) + memset(this->mServerAddr,0,sizeof(sockaddr_in)); + this->mServerAddr->sin_family = AF_INET; + this->mServerAddr->sin_port = 0; + this->mIsRunning = false; + this->mIsAutoDetectionEnabled = true; + this->mIsEnabled = false; + this->mDeviceHandle = NULL; + this->mMediaDatabase = NULL; +} + +cUPnPServer::~cUPnPServer() { + delete this->mServerAddr; this->mServerAddr = NULL; + delete this->mMediaDatabase; +} + +bool cUPnPServer::init(void){ + + MESSAGE("Loading configuration..."); + cUPnPConfig* config = cUPnPConfig::get(); + this->enable(config->mEnable == 1 ? true : false); + if(!config->mAutoSetup){ + if(config->mInterface) + if(!this->setInterface(config->mInterface)){ + ERROR("Invalid network interface: %s", config->mInterface); + return false; + } + if(config->mAddress) + if(!this->setAddress(config->mAddress)){ + ERROR("Invalid IP address: %s", config->mAddress); + return false; + } + if(!this->setServerPort((short)config->mPort)){ + ERROR("Invalid port: %d", config->mPort); + return false; + } + } + else { + if(!this->setAutoDetection(config->mAutoSetup == 1 ? true : false)){ + ERROR("Invalid auto detection setting: %d", config->mAutoSetup); + return false; + } + if(!this->autoDetectSettings()){ + ERROR("Error while auto detecting settings."); + return false; + } + } + + MESSAGE("Initializing Intel UPnP SDK on %s:%d",inet_ntoa(this->mServerAddr->sin_addr), ntohs(this->mServerAddr->sin_port)); + int ret = 0; + ret = UpnpInit(inet_ntoa(this->mServerAddr->sin_addr), ntohs(this->mServerAddr->sin_port)); + + if (ret != UPNP_E_SUCCESS) { + // test if SDK was allready initiated + if (ret == UPNP_E_INIT) { + WARNING("SDK was allready initiated (no problem) - Errorcode: %d", ret); + } else { + ERROR("Error while init Intel SDK - Errorcode: %d", ret); + return false; + } + } + else { + if(!inet_aton(UpnpGetServerIpAddress(),&this->mServerAddr->sin_addr)){ + ERROR("Unable to set IP address"); + } + this->mServerAddr->sin_port = htons(UpnpGetServerPort()); + MESSAGE("Initializing succesfully at %s:%d", UpnpGetServerIpAddress(), UpnpGetServerPort()); + } + + MESSAGE("Setting maximum packet size for SOAP requests"); + UpnpSetMaxContentLength(UPNP_SOAP_MAX_LEN); + + //set the root directory of the webserver + cString WebserverRootDir = cString::sprintf("%s%s", cPluginUpnp::getConfigDirectory(), UPNP_WEB_SERVER_ROOT_DIR); + + MESSAGE("Set web server root dir: %s", *WebserverRootDir ); + this->mWebServer = cUPnPWebServer::getInstance(WebserverRootDir); + MESSAGE("Initializing web server."); + if (!this->mWebServer->init()) { + ERROR("Error while setting web server root dir - Errorcode: %d", ret); + return false; + } + + //register media server device to SDK + cString URLBase = cString::sprintf("http://%s:%d", UpnpGetServerIpAddress(), UpnpGetServerPort()); + + this->mDeviceDescription = cDlna::getInstance()->getDeviceDescription(URLBase); + + MESSAGE("Register Media Server Device"); + ret = UpnpRegisterRootDevice2(UPNPREG_BUF_DESC, + this->mDeviceDescription, sizeof(this->mDeviceDescription), 1, + &cUPnPServer::upnpActionCallback, + &this->mDeviceHandle, + &this->mDeviceHandle); + if (ret != UPNP_E_SUCCESS) { + ERROR("Error while registering device - Errorcode: %d", ret); + return false; + } + + MESSAGE("Unregister server to cleanup previously started servers"); + ret = UpnpUnRegisterRootDevice(this->mDeviceHandle); + if (ret != UPNP_E_SUCCESS) { + WARNING("Unregistering old devices failed"); + return false; + } + + MESSAGE("Register Media Server Device"); + ret = UpnpRegisterRootDevice2(UPNPREG_BUF_DESC, + this->mDeviceDescription, sizeof(this->mDeviceDescription), 1, + &cUPnPServer::upnpActionCallback, + &this->mDeviceHandle, + &this->mDeviceHandle); + if (ret != UPNP_E_SUCCESS) { + ERROR("Error while registering device - Errorcode: %d", ret); + return false; + } + + MESSAGE("Initializing media database"); + this->mMediaDatabase = new cMediaDatabase; + if(!this->mMediaDatabase->init()){ + ERROR("Error while initializing database"); + return false; + } + + MESSAGE("Initializing connection manager"); + cUPnPServer::mConnectionManager = new cConnectionManager(this->mDeviceHandle); + MESSAGE("Initializing content directory"); + cUPnPServer::mContentDirectory = new cContentDirectory(this->mDeviceHandle, this->mMediaDatabase); + if(!cUPnPServer::mContentDirectory->Start()){ + ERROR("Unable to start content directory thread"); + return false; + } + + //send first advertisments + MESSAGE("Send first advertisements to publish start in network"); + ret = UpnpSendAdvertisement(this->mDeviceHandle, UPNP_ANNOUNCE_MAX_AGE); + if (ret != UPNP_E_SUCCESS) { + ERROR("Error while sending first advertisments - Errorcode: %d", ret); + return false; + } + + return true; +} + +bool cUPnPServer::uninit(void) { + MESSAGE("Shuting down content directory"); + delete cUPnPServer::mContentDirectory; cUPnPServer::mContentDirectory = NULL; + + MESSAGE("Shuting down connection manager"); + delete cUPnPServer::mConnectionManager; cUPnPServer::mConnectionManager = NULL; + + MESSAGE("Closing metadata database"); + delete this->mMediaDatabase; this->mMediaDatabase = NULL; + + MESSAGE("Close Intel SDK"); + // unregiser media server device from UPnP SDK + int ret = UpnpUnRegisterRootDevice(this->mDeviceHandle); + if (ret != UPNP_E_SUCCESS) { + WARNING("No device registered"); + } + // send intel sdk message to shutdown + ret = UpnpFinish(); + + if (ret == UPNP_E_SUCCESS) { + MESSAGE("Close Intel SDK Successfull"); + return true; + } else { + ERROR("Intel SDK unintialized or already closed - Errorcode: %d", ret); + return false; + } +} + +int cUPnPServer::upnpActionCallback(Upnp_EventType eventtype, void *event, void *cookie) { + // only to remove warning while compiling because cookie is unused + cookie = NULL; + Upnp_Subscription_Request* eventRequest = NULL; + Upnp_Action_Request* actionRequest = NULL; + + //check committed event variable + if (event == NULL) { + ERROR("UPnP Callback - NULL request"); + return UPNP_E_BAD_REQUEST; + } + + switch (eventtype) { + case UPNP_CONTROL_ACTION_REQUEST: + actionRequest = (Upnp_Action_Request*) event; + + //check that request is for this device + if (strcmp(actionRequest->DevUDN, UPNP_DEVICE_UDN) != 0) { + ERROR("UPnP Callback - actions request not for this device"); + return UPNP_E_BAD_REQUEST; + } + + //find out which service was called + if (strcmp(actionRequest->ServiceID, UPNP_CMS_SERVICE_ID) == 0) { + // proceed action + return cUPnPServer::mConnectionManager->execute(actionRequest); + + } else if (strcmp(actionRequest->ServiceID, UPNP_CDS_SERVICE_ID) == 0) { + // proceed action + return cUPnPServer::mContentDirectory->execute(actionRequest); + } else { + ERROR("UPnP Callback - unsupported service called for control"); + return UPNP_E_BAD_REQUEST; + } + case UPNP_EVENT_SUBSCRIPTION_REQUEST: + eventRequest = (Upnp_Subscription_Request*) event; + + //check that request is for this device + if (strcmp(eventRequest->UDN, UPNP_DEVICE_UDN) != 0) { + ERROR("UPnP Callback - event request not for this device"); + return UPNP_E_BAD_REQUEST; + } + + if (strcmp(eventRequest->ServiceId, UPNP_CMS_SERVICE_ID) == 0) { + // handle event request + return cUPnPServer::mConnectionManager->subscribe(eventRequest); + + } else if (strcmp(eventRequest->ServiceId, UPNP_CDS_SERVICE_ID) == 0) { + // handle event request + return cUPnPServer::mContentDirectory->subscribe(eventRequest); + } else { + ERROR("UPnP Callback - unsupported service called for eventing"); + return UPNP_E_BAD_REQUEST; + } + + return UPNP_E_BAD_REQUEST; + default: + ERROR("UPnP Action Callback - Unsupported Event"); + return UPNP_E_BAD_REQUEST; + } + + return UPNP_E_BAD_REQUEST; +} + +bool cUPnPServer::autoDetectSettings(void){ + int count; + char** Ifaces = getNetworkInterfaces(&count); + int i=0; + bool ret = false; + MESSAGE("AUTODETECT: Found %d possible interfaces.", sizeof(Ifaces)); + while(Ifaces[i]){ + if(strcmp(Ifaces[i],"lo")!=0){ + // true || false == true + // false || false == false + ret = this->setInterface(strdup(Ifaces[i])) || ret; + } + i++; + } + delete [] Ifaces; + if(!ret){ + MESSAGE("AUTODETECT: No suitable interface. Giving up."); + return false; + } + this->setServerPort(0); + return true; +} + +bool cUPnPServer::start(void){ + if(!this->isRunning()){ + // Put all the stuff which shall be started with the server in here + // if the startup failed due any reason return false! + MESSAGE("Starting UPnP Server on %s:%d",inet_ntoa(this->getServerAddress()->sin_addr), ntohs(this->getServerAddress()->sin_port)); + MESSAGE("Using DLNA version: %s", DLNA_PROTOCOL_VERSION_STR); + this->mIsRunning = true; + // Start Media database thread + this->mMediaDatabase->Start(); + } + return true; +} + +void cUPnPServer::stop(void){ + if(this->isRunning()){ + MESSAGE("Call upnpServer STOP"); + this->uninit(); + this->mIsRunning = false; + } + return; +} + +bool cUPnPServer::restart(void){ + MESSAGE("Call upnpServer RESTART"); + this->stop(); + return this->start(); +} + +void cUPnPServer::enable(bool enabled){ + this->mIsEnabled = enabled; +} + +bool cUPnPServer::setInterface(const char* Interface){ + if(Interface != NULL) this->mInterface = Interface; + + if(*this->mInterface!=NULL){ + MESSAGE("Try to retrieve address for NIC %s",Interface); + const sockaddr_in* ipAddress = getIPFromInterface(Interface); + if(ipAddress!=NULL){ + memcpy(&this->mServerAddr->sin_addr,&ipAddress->sin_addr,sizeof(ipAddress->sin_addr)); + MESSAGE("NIC %s has the following IP: %s", *this->mInterface, inet_ntoa(this->mServerAddr->sin_addr)); + this->stop(); + return true; + } + delete ipAddress; + ERROR("Unable to obtain a valid IP address for NIC %s!",Interface); + } + this->mServerAddr = NULL; + return false; +} + +bool cUPnPServer::setServerPort(unsigned short port){ + // check if the port is in user range or 0 + if(port != 0 && port < SERVER_MIN_PORT) return false; + this->stop(); + this->mServerAddr->sin_port = htons(port); + return true; +} + +bool cUPnPServer::setAddress(const char* Address){ + if(inet_aton(Address, &this->mServerAddr->sin_addr) == 0) return false; + this->stop(); + return true; +} + +bool cUPnPServer::setAutoDetection(bool enable){ + this->mIsAutoDetectionEnabled = enable; + return true; +} + +sockaddr_in* cUPnPServer::getServerAddress() { + return this->mServerAddr; +}
\ No newline at end of file diff --git a/server/server.h b/server/server.h new file mode 100644 index 0000000..72ed2c8 --- /dev/null +++ b/server/server.h @@ -0,0 +1,172 @@ +/* + * File: server.h + * Author: savop + * + * Created on 19. April 2009, 17:42 + */ + +#ifndef _SERVER_H +#define _SERVER_H + +#include <netinet/in.h> +#include <vdr/recording.h> +#include <vdr/thread.h> +#include <upnp/upnp.h> +#include "../misc/util.h" +#include "../common.h" +#include "../upnpcomponents/upnpwebserver.h" +#include "../database/metadata.h" +#include "../upnpcomponents/connectionmanager.h" +#include "../upnpcomponents/contentdirectory.h" +#include "../upnp.h" + +class cUPnPServer { + friend class cPluginUpnp; +public: + /** + * Constructor + * + * This will create a new server and initializes the main functionalities + * The server has to be started manually by invoking cUPnPServer::start(). + */ + cUPnPServer(); + /** + * Destructor + * + * This will destroy the server object. Open ports and connections will be + * closed. + */ + virtual ~cUPnPServer(); + /** + * Enable the server + * + * This switch indicates if the server is startable or not + * + * If it is set to FALSE, any invocation of start() will do nothing. + */ + void enable(bool enabled); + /** + * Start the UPnP server + * + * This will start the UPnP server activities as a background task. + * + * @return 1 when the server started successfully, 0 otherwise + */ + bool start(void); + /** + * Restart the server + * + * When the server is not operating properly it can be restarted. + * It will stop the server functionalities, clear everything and + * start it again. + * + * @return 1 when the server restarted successfully, 0 otherwise + */ + bool restart(void); + /** + * Stop the server + * + * This will stop the server. This means that open connections to + * any clients and open ports will be closed. + */ + void stop(void); + bool autoDetectSettings(void); + /** + * Get the server address + * + * Returns a server address structure including IP address and port + * + * @return The server socket address + */ + sockaddr_in* getServerAddress(void); + /** + * Get the interface the server listens to + * + * Returns the network interface + */ + const char* getInterface(void) const { return this->mInterface; } + /** + * Set the server port + * + * The port must be in the scope of user definied ports (49152 - 65535). If + * the port is 0, it is autoassigned. You can retrieve the actual port by + * calling getServerAddress(), which will give you a structure with the port + * in it. + * + * The server must be restarted if the IP or port changes. + * + * Returns 1 when the port is valid, 0 otherwise + * + * @param port The port of the server + * @return 1 if the new server address is set, 0 otherwise + */ + bool setServerPort(unsigned short port); + /** + * The Interface to listen on + * + * Sets the listener interface, for instance 'eth1' or 'wlan0' + */ + bool setInterface(const char* Interface); + /** + * Set the server address + * + * Specifies the servers IP address. The server needs to restart + * when the IP is changed. However, it's not possible to detect + * changes through the system. + * + * This method should only be used in cases of fixed IP addresses + * for example when no DHCP server is available. + */ + bool setAddress(const char* Address); + /** + * Enables oder Disables auto detection mode + * + * If this is set to true, the setup will get it's information via + * auto detection + */ + bool setAutoDetection(bool enable); + /** + * Checks if the server is enabled + * + * This indicates if the server is currently enabled. + * + * @return 1 if the server is enabled, 0 otherwise + */ + bool isEnabled(void) const { return this->mIsEnabled; } + /** + * Checks if the server is running + * + * If the server is enabled, this indicates if it is running. + * + * @return 1 if the server is running, 0 otherwise + */ + bool isRunning(void) const { return this->mIsRunning; } + /** + * Is auto detection enabled or not + * + * Returns true or false if auto detection is enabled or not + */ + bool isAutoDetectionEnabled() { return this->mIsAutoDetectionEnabled; } +protected: +private: + /** + * Inits the server + * + * This method initializes all member variables with default values + */ + bool init(void); + bool uninit(void); + static int upnpActionCallback(Upnp_EventType eventtype, void *event, void *cookie); + bool mIsRunning; + bool mIsEnabled; + sockaddr_in* mServerAddr; + cString mInterface; + bool mIsAutoDetectionEnabled; + cString mDeviceDescription; + cUPnPWebServer* mWebServer; + cMediaDatabase* mMediaDatabase; + UpnpDevice_Handle mDeviceHandle; + static cConnectionManager* mConnectionManager; + static cContentDirectory* mContentDirectory; +}; +#endif /* _SERVER_H */
\ No newline at end of file diff --git a/upnp.cpp b/upnp.cpp new file mode 100644 index 0000000..0aadcf0 --- /dev/null +++ b/upnp.cpp @@ -0,0 +1,129 @@ +/* + * upnp.c: A plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + * $Id$ + */ + +#include <stdio.h> +#include "upnp.h" +#include "misc/menusetup.h" +#include "misc/config.h" + +cCondWait DatabaseLocker; + +static const char *VERSION = "0.0.1"; +static const char *DESCRIPTION = PLUGIN_DESCRIPTION; + +const char* cPluginUpnp::mConfigDirectory = NULL; + +cPluginUpnp::cPluginUpnp(void) +{ + // Initialize any member variables here. + // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL + // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT! + this->mUpnpServer = new cUPnPServer(); +} + +cPluginUpnp::~cPluginUpnp() +{ + // Clean up after yourself! + delete this->mUpnpServer; +} + +const char* cPluginUpnp::getConfigDirectory(){ + return cPluginUpnp::mConfigDirectory; +} + +const char *cPluginUpnp::Version(void){ + return VERSION; +} + +const char *cPluginUpnp::Description(void) { + return DESCRIPTION; +} + +const char *cPluginUpnp::CommandLineHelp(void) +{ + // Return a string that describes all known command line options. + cString cmdHelp; + + cmdHelp = cString::sprintf( + " The server can automatically detect both IP address and an\n" + " appropriate port, assuming that the first network interface\n" + " is connected to the internal network. However, it is possible\n" + " to specify alternative settings with the following options:\n\n" + " -i <interface> --int=<interface> The server network\n" + " interface\n" + " e.g: eth0, wlan1 etc.\n" + " If given option '-a' must\n" + " be absent.\n" + " -a <address> --address=<address> The server IPv4 address.\n" + " If given option '-i' must\n" + " be absent.\n" + " -p <port> --port=<port> The server port\n" + " Supported ports:\n" + " %5d (auto detect)\n" + " %5d-%5d (user defined)\n", + 0, + SERVER_MIN_PORT, + SERVER_MAX_PORT + ); + return cmdHelp; +} + +bool cPluginUpnp::ProcessArgs(int argc, char *argv[]) +{ + return cUPnPConfig::get()->processArgs(argc, argv); +} + +bool cPluginUpnp::Initialize(void) +{ + // Initialize any background activities the plugin shall perform. + MESSAGE("######### LETS GET READY TO RUMBLE #########"); + + cPluginUpnp::mConfigDirectory = strdup(cPlugin::ConfigDirectory(this->Name())); + if(!cPluginUpnp::getConfigDirectory()){ + ERROR("Cannot set configuration directory"); + return false; + } + MESSAGE("Configuration directory: %s", cPluginUpnp::getConfigDirectory()); + DatabaseLocker.Signal(); + return this->mUpnpServer->init(); +} + +bool cPluginUpnp::Start(void) +{ + MESSAGE("Call plugin START"); + // Start any background activities the plugin shall perform. + return this->mUpnpServer->start(); + //return true; +} + +void cPluginUpnp::Stop(void) +{ + MESSAGE("Call plugin STOP"); + // Stop any background activities the plugin is performing. + this->mUpnpServer->stop(); +} + +cString cPluginUpnp::Active(void) +{ + // Return a message string if shutdown should be postponed + return this->mUpnpServer->isRunning() ? _("The UPnP server is still running."): NULL; +} + +cMenuSetupPage *cPluginUpnp::SetupMenu(void) +{ + // Return a setup menu in case the plugin supports one. + return new cMenuSetupUPnP(); +} + +bool cPluginUpnp::SetupParse(const char *Name, const char *Value) +{ + return cUPnPConfig::get()->parseSetup(Name, Value); +} + +VDRPLUGINCREATOR(cPluginUpnp); // Don't touch this! + @@ -0,0 +1,42 @@ +/* + * File: upnp.h + * Author: savop + * + * Created on 17. April 2009, 20:53 + */ + +#ifndef _UPNP_H +#define _UPNP_H + +#include <vdr/thread.h> +#include <vdr/plugin.h> +#include "common.h" +#include "server/server.h" + +class cUPnPServer; + +class cPluginUpnp : public cPlugin { +private: + // Add any member variables or functions you may need here. + cUPnPServer* mUpnpServer; + static const char* mConfigDirectory; +public: + cPluginUpnp(void); + virtual ~cPluginUpnp(); + virtual const char *Version(void); + virtual const char *Description(void); + 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 cString Active(void); + virtual cMenuSetupPage *SetupMenu(void); + virtual bool SetupParse(const char *Name, const char *Value); + static const char* getConfigDirectory(); +}; + +extern cCondWait DatabaseLocker; + +#endif /* _UPNP_H */ + diff --git a/upnpcomponents/connectionmanager.cpp b/upnpcomponents/connectionmanager.cpp new file mode 100644 index 0000000..b2c7149 --- /dev/null +++ b/upnpcomponents/connectionmanager.cpp @@ -0,0 +1,393 @@ +/* + * File: connectionmanager.cpp + * Author: savop + * + * Created on 21. August 2009, 18:35 + */ + +#include "upnpservice.h" +#include <string.h> +#include <upnp/ixml.h> +#include <upnp/upnptools.h> +#include <vdr/tools.h> +#include "connectionmanager.h" +#include "dlna.h" + +cVirtualConnection::cVirtualConnection() : mRcsID(-1) {} + +cConnectionManager::cConnectionManager(UpnpDevice_Handle DeviceHandle) : cUpnpService(DeviceHandle) { + this->mVirtualConnections = new cList<cVirtualConnection>; + this->mDefaultConnection = this->createVirtualConnection(); + this->mSupportedProtocols = cDlna::getInstance()->getSupportedProtocols(); +} + +cConnectionManager::~cConnectionManager() { + delete this->mDefaultConnection; + delete this->mVirtualConnections; +} + +int cConnectionManager::subscribe(Upnp_Subscription_Request* Request){ + IXML_Document* PropertySet = NULL; + /* The protocol infos which this server supports */ + UpnpAddToPropertySet(&PropertySet, "SourceProtocolInfo", this->mSupportedProtocols); + /* Not set, this field is only used by Media Renderers */ + UpnpAddToPropertySet(&PropertySet, "SinkProtocolInfo", ""); + /* The current connection IDs of all virtual connections */ + const char* IDs = this->getConnectionIDsCVS(); + if(!IDs){ + return UPNP_E_INTERNAL_ERROR; + } + UpnpAddToPropertySet(&PropertySet, "CurrentConnectionIDs", IDs); + // Accept subscription + int ret = UpnpAcceptSubscriptionExt(this->mDeviceHandle, Request->UDN, Request->ServiceId, PropertySet, Request->Sid); + + if(ret != UPNP_E_SUCCESS){ + ERROR("Subscription failed (Error code: %d)", ret); + } + + ixmlDocument_free(PropertySet); + return ret; +} + +int cConnectionManager::execute(Upnp_Action_Request* Request){ + if (Request == NULL) { + ERROR("CMS Action Handler - request is null"); + return UPNP_E_BAD_REQUEST; + } + + if(!strcmp(Request->ActionName, UPNP_CMS_ACTION_GETPROTOCOLINFO)) + return this->getProtocolInfo(Request); + if(!strcmp(Request->ActionName, UPNP_CMS_ACTION_GETCURRENTCONNECTIONIDS)) + return this->getCurrentConnectionIDs(Request); + if(!strcmp(Request->ActionName, UPNP_CMS_ACTION_GETCURRENTCONNECTIONINFO)) + return this->getCurrentConnectionInfo(Request); + if(!strcmp(Request->ActionName, UPNP_CMS_ACTION_PREPAREFORCONNECTION)) + return this->prepareForConnection(Request); + if(!strcmp(Request->ActionName, UPNP_CMS_ACTION_CONNECTIONCOMPLETE)) + return this->connectionComplete(Request); + + return UPNP_E_BAD_REQUEST; +} + +int cConnectionManager::getProtocolInfo(Upnp_Action_Request* Request){ + MESSAGE("Protocol info requested by %s.", inet_ntoa(Request->CtrlPtIPAddr)); + cString Result = cString::sprintf( + "<u:%sResponse xmlns:u=\"%s\"> \ + <Source>%s</Source> \ + <Sink></Sink> \ + </u:%sResponse>", + Request->ActionName, + UPNP_CMS_SERVICE_TYPE, + *this->mSupportedProtocols, + Request->ActionName + ); + Request->ActionResult = ixmlParseBuffer(Result); + Request->ErrCode = UPNP_E_SUCCESS; + return Request->ErrCode; +} + +int cConnectionManager::getCurrentConnectionIDs(Upnp_Action_Request* Request){ + MESSAGE("Current connection IDs requested by %s.", inet_ntoa(Request->CtrlPtIPAddr)); + cString Result; + const char* IDs = this->getConnectionIDsCVS(); + if(!IDs){ + Request->ErrCode = UPNP_E_INTERNAL_ERROR; + return Request->ErrCode; + } + Result = cString::sprintf( + "<u:%sResponse xmlns:u=\"%s\"> \ + <ConnectionIDs>%s</ConnectionIDs> \ + </u:%sResponse>", + Request->ActionName, + UPNP_CMS_SERVICE_TYPE, + IDs, + Request->ActionName + ); + Request->ActionResult = ixmlParseBuffer(Result); + Request->ErrCode = UPNP_E_SUCCESS; + return Request->ErrCode; +} + +int cConnectionManager::getCurrentConnectionInfo(Upnp_Action_Request* Request){ + MESSAGE("Current connection info requested by %s.", inet_ntoa(Request->CtrlPtIPAddr)); + int ConnectionID; + + if(this->parseIntegerValue(Request->ActionRequest, "ConnectionID", &ConnectionID) != 0){ + ERROR("Invalid arguments. ConnectionID missing or wrong"); + this->setError(Request, 402); + return Request->ErrCode; + } + + cVirtualConnection* Connection; + for(Connection = this->mVirtualConnections->First(); Connection && Connection->mConnectionID != ConnectionID; Connection = this->mVirtualConnections->Next(Connection)){} + + if(Connection){ + cString Result = cString::sprintf( + "<u:%sResponse xmlns:u=\"%s\">\ + <ProtocolInfo>%s</ProtocolInfo>\ + <PeerConnectionManager>%s</PeerConnectionManager>\ + <PeerConnectionID>%d</PeerConnectionID>\ + <Direction>%s</Direction>\ + <RcsID>%d</RcsID>\ + <AVTransportID>%d</AVTransportID>\ + <Status>%s</Status>\ + </u:%sResponse>", + Request->ActionName, + UPNP_CMS_SERVICE_TYPE, + *Connection->mRemoteProtocolInfo, + *Connection->mRemoteConnectionManager, + -1, + cVirtualConnection::getDirectionString(OUTPUT), + Connection->mRcsID, + Connection->mAVTransportID, + cVirtualConnection::getStatusString(Connection->mStatus), + Request->ActionName + ); + Request->ActionResult = ixmlParseBuffer(Result); + Request->ErrCode = UPNP_E_SUCCESS; + } + else { + ERROR("No valid connection found with given ID=%d!", ConnectionID); + this->setError(Request, 706); + } + + return Request->ErrCode; + +} + +int cConnectionManager::prepareForConnection(Upnp_Action_Request* Request){ + MESSAGE("Request for a new connection by %s.", inet_ntoa(Request->CtrlPtIPAddr)); + //char* Result = NULL; + char* RemoteProtocolInfo = NULL; + char* PeerConnectionManager = NULL; + int PeerConnectionID = 0; + char* DirectionStr = NULL; + int Direction; + + if(this->parseStringValue(Request->ActionRequest, "RemoteProtocolInfo", &RemoteProtocolInfo) != 0){ + ERROR("Invalid argument RemoteProtocolInfo: Missing or wrong"); + this->setError(Request, 402); + return Request->ErrCode; + } + + if(this->parseStringValue(Request->ActionRequest, "PeerConnectionManager", &PeerConnectionManager) != 0){ + ERROR("Invalid argument PeerConnectionManager: Missing or wrong"); + this->setError(Request, 402); + return Request->ErrCode; + } + + if(this->parseStringValue(Request->ActionRequest, "Direction", &DirectionStr) != 0 && (Direction = cVirtualConnection::getDirection(DirectionStr)) == -1){ + ERROR("Invalid argument Direction: Missing or wrong"); + this->setError(Request, 402); + return Request->ErrCode; + } + + if(this->parseIntegerValue(Request->ActionRequest, "PeerConnectionID", &PeerConnectionID) != 0){ + ERROR("Invalid argument PeerConnectionID: Missing or wrong"); + this->setError(Request, 402); + return Request->ErrCode; + } + + + /* TODO: + Create Connection + Notify AVTransport that a new connection was established + Send back the response */ + this->setError(Request, UPNP_SOAP_E_ACTION_NOT_IMPLEMENTED); + return Request->ErrCode; +} + +int cConnectionManager::connectionComplete(Upnp_Action_Request* Request){ + MESSAGE("Request for closing an open connection by %s.", inet_ntoa(Request->CtrlPtIPAddr)); + //char* Result = NULL; + int ConnectionID; + + if(this->parseIntegerValue(Request->ActionRequest, "ConnectionID", &ConnectionID) != 0){ + ERROR("Invalid argument ConnectionID: Missing or wrong"); + this->setError(Request, 402); + return Request->ErrCode; + } + + // TODO: + // Close and clear any open resources + // Close and delete the connection + // Free other resources left + this->setError(Request, UPNP_SOAP_E_ACTION_NOT_IMPLEMENTED); + return Request->ErrCode; +} + +bool cConnectionManager::setProtocolInfo(const char* ProtocolInfo){ + if(strcmp(this->mSupportedProtocols, ProtocolInfo)){ + // ProtocolInfo changed, save and invoke a event notification + this->mSupportedProtocols = ProtocolInfo; + + IXML_Document* PropertySet = NULL; + UpnpAddToPropertySet(&PropertySet, "SourceProtocolInfo", this->mSupportedProtocols); + int ret = UpnpNotifyExt(this->mDeviceHandle, UPNP_DEVICE_UDN, UPNP_CMS_SERVICE_ID, PropertySet); + ixmlDocument_free(PropertySet); + + if(ret != UPNP_E_SUCCESS){ + ERROR("State change notification failed (Error code: %d)",ret); + return false; + } + } + return true; +} + +cVirtualConnection* cConnectionManager::createVirtualConnection(const char* RemoteProtocolInfo, const char* RemoteConnectionManager, int RemoteConnectionID, eDirection Direction){ + static int lastConnectionID = 0; + MESSAGE("Create virtual connection"); + if(lastConnectionID == 2147483647) lastConnectionID = 1; + cVirtualConnection* Connection = new cVirtualConnection; + // AVT is available + Connection->mAVTransportID = 0; + // The ProtocolInfo of the remote device (i.e. Media Renderer) + Connection->mRemoteProtocolInfo = RemoteProtocolInfo; + // The responsible connection manager + Connection->mRemoteConnectionManager = RemoteConnectionManager; + // The virtual connection direction is output + Connection->mDirection = Direction; + // The remote connection ID, -1 says ID is unknown + Connection->mRemoteConnectionID = RemoteConnectionID; + // Connection status, assume that its ok. + Connection->mStatus = OK; + // new assigned ConnectionID + Connection->mConnectionID = lastConnectionID++; + + // Notify the subscribers + IXML_Document* PropertySet = NULL; + const char* IDs = this->getConnectionIDsCVS(); + if(!IDs){ + return NULL; + } + UpnpAddToPropertySet(&PropertySet, "CurrentConnectionIDs", IDs); + int ret = UpnpNotifyExt(this->mDeviceHandle, UPNP_DEVICE_UDN, UPNP_CMS_SERVICE_ID, PropertySet); + ixmlDocument_free(PropertySet); + + if(ret != UPNP_E_SUCCESS){ + ERROR("State change notification failed (Error code: %d)",ret); + return NULL; + } + MESSAGE("Notification of connection creation sent"); + this->mVirtualConnections->Add(Connection); + return Connection; +} + +bool cConnectionManager::destroyVirtualConnection(int ConnectionID){ + if(ConnectionID == 0){ + ERROR("Cannot delete default connection with ID 0!"); + return false; + } + + cVirtualConnection* Connection; + for(Connection = this->mVirtualConnections->First(); Connection && Connection->mConnectionID != ConnectionID; Connection = this->mVirtualConnections->Next(Connection)){} + + if(Connection){ + this->mVirtualConnections->Del(Connection); + // Notify the subscribers + IXML_Document* PropertySet = NULL; + const char* IDs = this->getConnectionIDsCVS(); + if(!IDs){ + return false; + } + UpnpAddToPropertySet(&PropertySet, "CurrentConnectionIDs", IDs); + int ret = UpnpNotifyExt(this->mDeviceHandle, UPNP_DEVICE_UDN, UPNP_CMS_SERVICE_ID, PropertySet); + ixmlDocument_free(PropertySet); + + if(ret != UPNP_E_SUCCESS){ + ERROR("State change notification failed (Error code: %d)",ret); + return false; + } + return true; + } + ERROR("No connection with ID=%d found!", ConnectionID); + return false; +} + +const char* cConnectionManager::getConnectionIDsCVS(){ + cString IDs; + for(cVirtualConnection* Connection = this->mVirtualConnections->First(); Connection; Connection = this->mVirtualConnections->Next(Connection)){ + IDs = cString::sprintf("%s,%d", (*IDs)?*IDs:"", Connection->mConnectionID); + } + return IDs; +} + +void cConnectionManager::setError(Upnp_Action_Request* Request, int Error){ + Request->ErrCode = Error; + switch(Error){ + case 701: + strn0cpy(Request->ErrStr,_("Incompatible protocol info"),LINE_SIZE); + break; + case 702: + strn0cpy(Request->ErrStr,_("Incompatible directions"),LINE_SIZE); + break; + case 703: + strn0cpy(Request->ErrStr,_("Insufficient network resources"),LINE_SIZE); + break; + case 704: + strn0cpy(Request->ErrStr,_("Local restrictions"),LINE_SIZE); + break; + case 705: + strn0cpy(Request->ErrStr,_("Access denied"),LINE_SIZE); + break; + case 706: + strn0cpy(Request->ErrStr,_("Invalid connection reference"),LINE_SIZE); + break; + case 707: + strn0cpy(Request->ErrStr,_("Not in network"),LINE_SIZE); + break; + default: + cUpnpService::setError(Request, Error); + break; + } +} + +const char* cVirtualConnection::getDirectionString(eDirection Direction){ + switch(Direction){ + case INPUT: + return "Input"; + case OUTPUT: + return "Output"; + default: + return NULL; + } +} + +const char* cVirtualConnection::getStatusString(eConnectionStatus Status){ + switch(Status){ + case OK: + return "OK"; + case CONTENT_FORMAT_MISMATCH: + return "ContentFormatMismatch"; + case INSUFFICIENT_BANDWIDTH: + return "InsufficientBandwidth"; + case UNRELIABLE_CHANNEL: + return "UnreliableChannel"; + case UNKNOWN: + return "Unknown"; + default: + return NULL; + } +} + +int cVirtualConnection::getConnectionStatus(const char* eConnectionStatus){ + if(!strcasecmp(eConnectionStatus,"OK")) + return OK; + if(!strcasecmp(eConnectionStatus,"ContentFormatMismatch")) + return CONTENT_FORMAT_MISMATCH; + if(!strcasecmp(eConnectionStatus,"InsufficientBandwidth")) + return INSUFFICIENT_BANDWIDTH; + if(!strcasecmp(eConnectionStatus,"UnreliableChannel")) + return UNRELIABLE_CHANNEL; + if(!strcasecmp(eConnectionStatus,"Unknown")) + return UNKNOWN; + return -1; +} + +int cVirtualConnection::getDirection(const char* Direction){ + if(!strcasecmp(Direction, "Output")) + return OUTPUT; + if(!strcasecmp(Direction, "Input")) + return INPUT; + return -1; +}
\ No newline at end of file diff --git a/upnpcomponents/connectionmanager.h b/upnpcomponents/connectionmanager.h new file mode 100644 index 0000000..202df59 --- /dev/null +++ b/upnpcomponents/connectionmanager.h @@ -0,0 +1,67 @@ +/* + * File: connectionmanager.h + * Author: savop + * + * Created on 21. August 2009, 18:35 + */ + +#ifndef _CONNECTIONMANAGER_H +#define _CONNECTIONMANAGER_H + +#include "upnpservice.h" + +enum eConnectionStatus { + OK, + CONTENT_FORMAT_MISMATCH, + INSUFFICIENT_BANDWIDTH, + UNRELIABLE_CHANNEL, + UNKNOWN +}; + +enum eDirection { + OUTPUT, + INPUT +}; + +class cVirtualConnection : public cListObject { + friend class cConnectionManager; +private: + cString mRemoteProtocolInfo; + cString mRemoteConnectionManager; + eDirection mDirection; + int mRemoteConnectionID; + int mConnectionID; + int mAVTransportID; + const int mRcsID; + eConnectionStatus mStatus; + cVirtualConnection(); + static const char* getStatusString(eConnectionStatus Status); + static const char* getDirectionString(eDirection Direction); + static int getDirection(const char* Direction); + static int getConnectionStatus(const char* ConnectionStatus); +}; + +class cConnectionManager : public cUpnpService { +public: + cConnectionManager(UpnpDevice_Handle DeviceHandle); + virtual ~cConnectionManager(); + virtual int execute(Upnp_Action_Request* Request); + virtual int subscribe(Upnp_Subscription_Request* Request); + bool setProtocolInfo(const char* ProtocolInfo); +private: + virtual void setError(Upnp_Action_Request* Request, int Error); + int getProtocolInfo(Upnp_Action_Request* Request); + int getCurrentConnectionIDs(Upnp_Action_Request* Request); + int getCurrentConnectionInfo(Upnp_Action_Request* Request); + int prepareForConnection(Upnp_Action_Request* Request); + int connectionComplete(Upnp_Action_Request* Request); + cVirtualConnection* createVirtualConnection(const char* RemoteProtocolInfo = NULL, const char* RemoteConnectionManager = NULL, int RemoteConnectionID = -1, eDirection Direction = OUTPUT); + bool destroyVirtualConnection(int ConnectionID); + const char* getConnectionIDsCVS(); + cVirtualConnection* mDefaultConnection; + cList<cVirtualConnection>* mVirtualConnections; + cString mSupportedProtocols; +}; + +#endif /* _CONNECTIONMANAGER_H */ + diff --git a/upnpcomponents/contentdirectory.cpp b/upnpcomponents/contentdirectory.cpp new file mode 100644 index 0000000..a9afdfa --- /dev/null +++ b/upnpcomponents/contentdirectory.cpp @@ -0,0 +1,306 @@ +/* + * File: contentdirectory.cpp + * Author: savop + * + * Created on 21. August 2009, 16:12 + */ + +#include <upnp/ixml.h> +#include <upnp/upnptools.h> +#include "contentdirectory.h" +#include "../common.h" +#include "../misc/util.h" + +cContentDirectory::cContentDirectory(UpnpDevice_Handle DeviceHandle, cMediaDatabase* MediaDatabase) +: cUpnpService(DeviceHandle) { + this->mMediaDatabase = MediaDatabase; +} + +cContentDirectory::~cContentDirectory() {} + +int cContentDirectory::subscribe(Upnp_Subscription_Request* Request){ + IXML_Document* PropertySet = NULL; + + /* The system update ID */ + UpnpAddToPropertySet(&PropertySet, "SystemUpdateID", itoa(this->mMediaDatabase->getSystemUpdateID())); + /* The container update IDs as CSV list */ + UpnpAddToPropertySet(&PropertySet, "ContainerUpdateIDs", this->mMediaDatabase->getContainerUpdateIDs()); + /* The transfer IDs, which are not supported, i.e. empty */ + UpnpAddToPropertySet(&PropertySet, "TransferIDs", ""); + // Accept subscription + int ret = UpnpAcceptSubscriptionExt(this->mDeviceHandle, Request->UDN, Request->ServiceId, PropertySet, Request->Sid); + + if(ret != UPNP_E_SUCCESS){ + ERROR("Subscription failed (Error code: %d)", ret); + } + + ixmlDocument_free(PropertySet); + return ret; +} + +void cContentDirectory::Action(){ + static int Retry = 5; + MESSAGE("Start Content directory thread"); + while(this->Running()){ + IXML_Document* PropertySet = NULL; + UpnpAddToPropertySet(&PropertySet, "SystemUpdateID", itoa(this->mMediaDatabase->getSystemUpdateID())); + int ret = UpnpNotifyExt(this->mDeviceHandle, UPNP_DEVICE_UDN, UPNP_CMS_SERVICE_ID, PropertySet); + ixmlDocument_free(PropertySet); + + if(ret != UPNP_E_SUCCESS){ + Retry--; + ERROR("State change notification failed (Error code: %d)",ret); + ERROR("%d of %d notifications failed", (5-Retry), 5); + } + else { + Retry = 5; + } + if (!Retry){ + ERROR("Maximum retries of notifications reached. Stopping..."); + this->Cancel(); + } + // Sleep 2 seconds + cCondWait::SleepMs(2000); + } +} + +int cContentDirectory::execute(Upnp_Action_Request* Request){ + if (Request == NULL) { + ERROR("CMS Action Handler - request is null"); + return UPNP_E_BAD_REQUEST; + } + + if(!strcmp(Request->ActionName, UPNP_CDS_ACTION_BROWSE)) + return this->browse(Request); + if(!strcmp(Request->ActionName, UPNP_CDS_ACTION_SEARCHCAPABILITIES)) + return this->getSearchCapabilities(Request); + if(!strcmp(Request->ActionName, UPNP_CDS_ACTION_SORTCAPABILITIES)) + return this->getSortCapabilities(Request); + if(!strcmp(Request->ActionName, UPNP_CDS_ACTION_SYSTEMUPDATEID)) + return this->getSystemUpdateID(Request); + + return UPNP_E_BAD_REQUEST; +} + + +int cContentDirectory::browse(Upnp_Action_Request* Request){ + MESSAGE("Browse requested by %s.", inet_ntoa(Request->CtrlPtIPAddr)); + + char* ObjectID = NULL; + if(this->parseStringValue(Request->ActionRequest, "ObjectID", &ObjectID)){ + ERROR("Invalid arguments. ObjectID missing or wrong"); + this->setError(Request, UPNP_SOAP_E_INVALID_ARGS); + return Request->ErrCode; + } + + char* BrowseFlag = NULL; + bool BrowseMetadata = false; + if(this->parseStringValue(Request->ActionRequest, "BrowseFlag", &BrowseFlag)){ + ERROR("Invalid arguments. Browse flag missing or wrong"); + this->setError(Request, UPNP_SOAP_E_INVALID_ARGS); + return Request->ErrCode; + } + if(!strcasecmp(BrowseFlag, "BrowseMetadata")){ + BrowseMetadata = true; + } + else if(!strcasecmp(BrowseFlag, "BrowseDirectChildren")){ + BrowseMetadata = false; + } + else { + ERROR("Invalid argument. Browse flag invalid"); + this->setError(Request, UPNP_SOAP_E_INVALID_ARGS); + return Request->ErrCode; + } + + char* Filter = NULL; + if(this->parseStringValue(Request->ActionRequest, "Filter", &Filter)){ + ERROR("Invalid arguments. Filter missing or wrong"); + this->setError(Request, UPNP_SOAP_E_INVALID_ARGS); + return Request->ErrCode; + } + + int StartingIndex = 0; + if(this->parseIntegerValue(Request->ActionRequest, "StartingIndex", &StartingIndex)){ + ERROR("Invalid arguments. Starting index missing or wrong"); + this->setError(Request, UPNP_SOAP_E_INVALID_ARGS); + return Request->ErrCode; + } + + int RequestedCount = 0; + if(this->parseIntegerValue(Request->ActionRequest, "RequestedCount", &RequestedCount)){ + ERROR("Invalid arguments. Requested count missing or wrong"); + this->setError(Request, UPNP_SOAP_E_INVALID_ARGS); + return Request->ErrCode; + } + + char* SortCriteria = NULL; + if(this->parseStringValue(Request->ActionRequest, "SortCriteria", &SortCriteria)){ + ERROR("Invalid arguments. Sort criteria missing or wrong"); + this->setError(Request, UPNP_SOAP_E_INVALID_ARGS); + return Request->ErrCode; + } + + cUPnPResultSet* ResultSet; + + int ret = this->mMediaDatabase->browse(&ResultSet, ObjectID, BrowseMetadata, Filter, StartingIndex, RequestedCount, SortCriteria); + if(ret!=UPNP_E_SUCCESS){ + ERROR("Error while browsing. Code: %d", ret); + this->setError(Request, ret); + return Request->ErrCode; + } + + char* escapedResult = NULL; + escapeXMLCharacters(ResultSet->mResult, &escapedResult); + + if(!escapedResult){ + ERROR("Escaping XML data failed"); + this->setError(Request, UPNP_SOAP_E_ACTION_FAILED); + return Request->ErrCode; + } + + cString Result = cString::sprintf( + "<u:%sResponse xmlns:u=\"%s\"> \ + <Result>%s</Result> \ + <NumberReturned>%d</NumberReturned> \ + <TotalMatches>%d</TotalMatches> \ + <UpdateID>%d</UpdateID> \ + </u:%sResponse>", + Request->ActionName, + UPNP_CDS_SERVICE_TYPE, + escapedResult, + ResultSet->mNumberReturned, + ResultSet->mTotalMatches, + this->mMediaDatabase->getSystemUpdateID(), + Request->ActionName + ); + + Request->ActionResult = ixmlParseBuffer(Result); + Request->ErrCode = UPNP_E_SUCCESS; + + free(escapedResult); + + return Request->ErrCode; + +} + +int cContentDirectory::getSystemUpdateID(Upnp_Action_Request* Request){ + cString Result = cString::sprintf( + "<u:%sResponse xmlns:u=\"%s\"> \ + <Id>%d</Id> \ + </u:%sResponse>", + Request->ActionName, + UPNP_CDS_SERVICE_TYPE, + this->mMediaDatabase->getSystemUpdateID(), + Request->ActionName + ); + + Request->ActionResult = ixmlParseBuffer(Result); + Request->ErrCode = UPNP_E_SUCCESS; + + return Request->ErrCode; +} + +int cContentDirectory::getSearchCapabilities(Upnp_Action_Request* Request){ + MESSAGE("Sorry, no search capabilities yet"); + + cString Result = cString::sprintf( + "<u:%sResponse xmlns:u=\"%s\"> \ + <SearchCaps>%s</SearchCaps> \ + </u:%sResponse>", + Request->ActionName, + UPNP_CDS_SERVICE_TYPE, + UPNP_CDS_SEARCH_CAPABILITIES, + Request->ActionName + ); + + Request->ActionResult = ixmlParseBuffer(Result); + Request->ErrCode = UPNP_E_SUCCESS; + + return Request->ErrCode; +} + +int cContentDirectory::getSortCapabilities(Upnp_Action_Request* Request){ + MESSAGE("Sorry, no sort capabilities yet"); + + cString Result = cString::sprintf( + "<u:%sResponse xmlns:u=\"%s\"> \ + <SortCaps>%s</SortCaps> \ + </u:%sResponse>", + Request->ActionName, + UPNP_CDS_SERVICE_TYPE, + UPNP_CDS_SORT_CAPABILITIES, + Request->ActionName + ); + + Request->ActionResult = ixmlParseBuffer(Result); + Request->ErrCode = UPNP_E_SUCCESS; + + return Request->ErrCode; +} + +void cContentDirectory::setError(Upnp_Action_Request* Request, int Error){ + Request->ErrCode = Error; + switch(Error){ + case UPNP_CDS_E_BAD_METADATA: + strn0cpy(Request->ErrStr,_("Bad metadata"),LINE_SIZE); + break; + case UPNP_CDS_E_CANT_PROCESS_REQUEST: + strn0cpy(Request->ErrStr,_("Cannot process the request"),LINE_SIZE); + break; + case UPNP_CDS_E_DEST_RESOURCE_ACCESS_DENIED: + strn0cpy(Request->ErrStr,_("Destination resource access denied"),LINE_SIZE); + break; + case UPNP_CDS_E_INVALID_CURRENT_TAG: + strn0cpy(Request->ErrStr,_("Invalid current tag"),LINE_SIZE); + break; + case UPNP_CDS_E_INVALID_NEW_TAG: + strn0cpy(Request->ErrStr,_("Invalid new tag"),LINE_SIZE); + break; + case UPNP_CDS_E_INVALID_SEARCH_CRITERIA: + strn0cpy(Request->ErrStr,_("Invalid or unsupported search criteria"),LINE_SIZE); + break; + case UPNP_CDS_E_INVALID_SORT_CRITERIA: + strn0cpy(Request->ErrStr,_("Invalid or unsupported sort criteria"),LINE_SIZE); + break; + case UPNP_CDS_E_NO_SUCH_CONTAINER: + strn0cpy(Request->ErrStr,_("No such container"),LINE_SIZE); + break; + case UPNP_CDS_E_NO_SUCH_DESTINATION_RESOURCE: + strn0cpy(Request->ErrStr,_("No such destination resource"),LINE_SIZE); + break; + case UPNP_CDS_E_NO_SUCH_FILE_TRANSFER: + strn0cpy(Request->ErrStr,_("No such file transfer"),LINE_SIZE); + break; + case UPNP_CDS_E_NO_SUCH_OBJECT: + strn0cpy(Request->ErrStr,_("No such objectID"),LINE_SIZE); + break; + case UPNP_CDS_E_NO_SUCH_SOURCE_RESOURCE: + strn0cpy(Request->ErrStr,_("No such source resource"),LINE_SIZE); + break; + case UPNP_CDS_E_PARAMETER_MISMATCH: + strn0cpy(Request->ErrStr,_("Parameter mismatch"),LINE_SIZE); + break; + case UPNP_CDS_E_READ_ONLY_TAG: + strn0cpy(Request->ErrStr,_("Read only tag"),LINE_SIZE); + break; + case UPNP_CDS_E_REQUIRED_TAG: + strn0cpy(Request->ErrStr,_("Required tag"),LINE_SIZE); + break; + case UPNP_CDS_E_RESOURCE_ACCESS_DENIED: + strn0cpy(Request->ErrStr,_("Resource access denied"),LINE_SIZE); + break; + case UPNP_CDS_E_RESTRICTED_OBJECT: + strn0cpy(Request->ErrStr,_("Restricted object"),LINE_SIZE); + break; + case UPNP_CDS_E_RESTRICTED_PARENT: + strn0cpy(Request->ErrStr,_("Restricted parent"),LINE_SIZE); + break; + case UPNP_CDS_E_TRANSFER_BUSY: + strn0cpy(Request->ErrStr,_("Transfer busy"),LINE_SIZE); + break; + default: + cUpnpService::setError(Request, Error); + break; + } + return; +}
\ No newline at end of file diff --git a/upnpcomponents/contentdirectory.h b/upnpcomponents/contentdirectory.h new file mode 100644 index 0000000..a504fdc --- /dev/null +++ b/upnpcomponents/contentdirectory.h @@ -0,0 +1,38 @@ +/* + * File: contentdirectory.h + * Author: savop + * + * Created on 21. August 2009, 16:12 + */ + +#ifndef _CONTENTDIRECTORY_H +#define _CONTENTDIRECTORY_H + +#include <upnp/upnp.h> +#include "upnpservice.h" +#include "../database/metadata.h" + +class cContentDirectory : public cUpnpService, public cThread { +public: + cContentDirectory(UpnpDevice_Handle DeviceHandle, cMediaDatabase* MediaDatabase); + virtual ~cContentDirectory(); + virtual int subscribe(Upnp_Subscription_Request* Request); + virtual int execute(Upnp_Action_Request* Request); + virtual void setError(Upnp_Action_Request* Request, int Error); +private: + cMediaDatabase* mMediaDatabase; + void Action(); + int getSearchCapabilities(Upnp_Action_Request* Request); + int getSortCapabilities(Upnp_Action_Request* Request); + int getSystemUpdateID(Upnp_Action_Request* Request); + int browse(Upnp_Action_Request* Request); +// int search(Upnp_Action_Request* Request); +// int createObject(Upnp_Action_Request* Request); +// int destroyObject(Upnp_Action_Request* Request); +// int updateObject(Upnp_Action_Request* Request); +// int deleteResource(Upnp_Action_Request* Request); +// int createReference(Upnp_Action_Request* Request); +}; + +#endif /* _CONTENTDIRECTORY_H */ + diff --git a/upnpcomponents/dlna.cpp b/upnpcomponents/dlna.cpp new file mode 100644 index 0000000..a68c6db --- /dev/null +++ b/upnpcomponents/dlna.cpp @@ -0,0 +1,235 @@ +/* + * File: dlna.cpp + * Author: savop + * + * Created on 18. April 2009, 23:27 + */ + +#include <stdio.h> +#include <vdr/tools.h> +#include "dlna.h" + +cDlna* cDlna::mInstance = NULL; + +cDlna* cDlna::getInstance(void){ + if(cDlna::mInstance == NULL) + cDlna::mInstance = new cDlna; + + if(cDlna::mInstance != NULL) + return cDlna::mInstance; + else return NULL; +} + +cDlna::cDlna() { + this->mRegisteredProfiles = new cRegisteredProfiles; + this->init(); +} + +cDlna::~cDlna() { + delete this->mRegisteredProfiles; +} + + +void cDlna::init(void){ + this->registerMainProfiles(); +} + +void cDlna::registerProfile(DLNAProfile* Profile, int Op, const char* Ps, int Ci, unsigned int Flags){ + cRegisteredProfile *RegisteredProfile = new cRegisteredProfile(); + RegisteredProfile->Profile = Profile; + RegisteredProfile->Operation = Op; + RegisteredProfile->PlaySpeeds = Ps; + RegisteredProfile->Conversion = Ci; + RegisteredProfile->PrimaryFlags = Flags; + this->mRegisteredProfiles->Add(RegisteredProfile); +} + +void cDlna::registerMainProfiles(){ + this->registerProfile(&DLNA_PROFILE_MPEG2_TS_SD_EU, -1, NULL, -1, DLNA_FLAGS_PLUGIN_SUPPORT); + this->registerProfile(&DLNA_PROFILE_AVC_TS_HD_EU, -1, NULL, -1, DLNA_FLAGS_PLUGIN_SUPPORT); +} + +const char* cDlna::getSupportedProtocols(){ + cString Protocols; + cRegisteredProfile* Profile; + for(Profile = this->mRegisteredProfiles->First(); Profile; Profile = this->mRegisteredProfiles->Next(Profile)){ + Protocols = cString::sprintf("%s%s%s",(*Protocols)?*Protocols:"",(*Protocols)?",":"",this->getRegisteredProtocolInfoString(Profile)); + } + return Protocols; +} + +const char* cDlna::getProtocolInfo(DLNAProfile *Prof){ + cRegisteredProfile* Profile; + for(Profile = this->mRegisteredProfiles->First(); Profile && Profile->Profile != Prof; Profile = this->mRegisteredProfiles->Next(Profile)){} + if(Profile){ + return this->getRegisteredProtocolInfoString(Profile); + } + return NULL; +} + +DLNAProfile* cDlna::getProfileOfChannel(cChannel* Channel){ + if(Channel == NULL) return NULL; + // Switching the video types of the DVB-Stream + switch(Channel->Vtype()){ + case 0x02: + return &DLNA_PROFILE_MPEG2_TS_SD_EU; + case 0x1B: + return &DLNA_PROFILE_AVC_TS_HD_EU; + default: + ERROR("Unknown video type %d for channel %s!", Channel->Vtype(), Channel->Name()); + return NULL; + } +} + +DLNAProfile* cDlna::getProfileOfRecording(cRecording* Recording){ + // Get the data of the first file of the recording + cString File = cString::sprintf(VDR_RECORDFILE_PATTERN_TS, Recording->FileName(), 1); + return this->getProfileOfFile(File); +} + +DLNAProfile* cDlna::getProfileOfFile(cString){ + WARNING("Not yet supported"); + return NULL; +} + +const char* cDlna::getRegisteredProtocolInfoString(cRegisteredProfile *Profile){ + cString DLNA4thField = NULL; + DLNA4thField = cString::sprintf("DLNA.ORG_PN=%s", Profile->Profile->ID); + if(Profile->Operation != -1) + DLNA4thField = cString::sprintf("%s;DLNA.ORG_OP=%d",*DLNA4thField,Profile->Operation); + if(Profile->PlaySpeeds != NULL) + DLNA4thField = cString::sprintf("%s;DLNA.ORG_PS=%s",*DLNA4thField,Profile->PlaySpeeds); + if(Profile->Conversion != -1) + DLNA4thField = cString::sprintf("%s;DLNA.ORG_CI=%d",*DLNA4thField,Profile->Conversion); + if(Profile->PrimaryFlags != 0) + DLNA4thField = cString::sprintf("%s;DLNA.ORG_FLAGS=%.8x%.24x",*DLNA4thField,Profile->PrimaryFlags,0); + + char* Protocol = strdup(cString::sprintf("http-get:*:%s:%s", Profile->Profile->mime, *DLNA4thField)); + return Protocol; +} + +const char* cDlna::getDeviceDescription(const char* URLBase){ + cString description = cString::sprintf( + "<?xml version = \"1.0\" encoding = \"utf-8\"?> \ + <root xmlns=\"%s\" xmlns:%s=\"%s\"> \ + <specVersion> \ + <major>1</major> \ + <minor>0</minor> \ + </specVersion> \ + <URLBase>%s</URLBase> \ + <device> \ + <deviceType>%s</deviceType> \ + <friendlyName>%s</friendlyName> \ + <manufacturer>%s</manufacturer> \ + <manufacturerURL>%s</manufacturerURL> \ + <modelDescription>%s</modelDescription> \ + <modelName>%s</modelName> \ + <modelNumber>%s</modelNumber> \ + <modelURL>%s</modelURL> \ + <serialNumber>%s</serialNumber> \ + <UDN>%s</UDN> \ + <iconList> \ + <icon> \ + <mimetype>%s</mimetype> \ + <width>%d</width> \ + <height>%d</height> \ + <depth>%d</depth> \ + <url>%s</url> \ + </icon> \ + <icon> \ + <mimetype>%s</mimetype> \ + <width>%d</width> \ + <height>%d</height> \ + <depth>%d</depth> \ + <url>%s</url> \ + </icon> \ + <icon> \ + <mimetype>%s</mimetype> \ + <width>%d</width> \ + <height>%d</height> \ + <depth>%d</depth> \ + <url>%s</url> \ + </icon> \ + <icon> \ + <mimetype>%s</mimetype> \ + <width>%d</width> \ + <height>%d</height> \ + <depth>%d</depth> \ + <url>%s</url> \ + </icon> \ + </iconList> \ + <presentationURL>%s</presentationURL> \ + <%s:X_DLNADOC>%s</dlna:X_DLNADOC> \ + <serviceList> \ + <service> \ + <serviceType>%s</serviceType> \ + <serviceId>%s</serviceId> \ + <SCPDURL>%s</SCPDURL> \ + <controlURL>%s</controlURL> \ + <eventSubURL>%s</eventSubURL> \ + </service> \ + <service> \ + <serviceType>%s</serviceType> \ + <serviceId>%s</serviceId> \ + <SCPDURL>%s</SCPDURL> \ + <controlURL>%s</controlURL> \ + <eventSubURL>%s</eventSubURL> \ + </service> \ + </serviceList> \ + </device> \ + </root>", + UPNP_XMLNS_UPNP_DEV, // UPnP Device Namespace (2) + UPNP_XMLNS_PREFIX_DLNA, // DLNA Namespace prefix (2) + UPNP_XMLNS_DLNA_DEV, // DLNA Device Namespace (2) + URLBase, // URLBase (IP:PORT) (7) + UPNP_DEVICE_TYPE, // UPnP Device Type (MediaServer:1) (9) + UPNP_DEVICE_FRIENDLY_NAME, // UPnP Device Friendly Name (10) + UPNP_DEVICE_MANUFACTURER, // UPnP Device Manufacturer (11) + UPNP_DEVICE_MANUFACTURER_URL, // UPnP Device Manufacturer URL (12) + UPNP_DEVICE_MODEL_DESCRIPTION, // UPnP Device Model Description (13) + UPNP_DEVICE_MODEL_NAME, // UPnP Device Model Name (14) + UPNP_DEVICE_MODEL_NUMBER, // UPnP Device Model Number (15) + UPNP_DEVICE_MODEL_URL, // UPnP Device Model URL (16) + UPNP_DEVICE_SERIAL_NUMBER, // UPnP Device Serialnumber (17) + UPNP_DEVICE_UDN, // UPnP Device UDN (18) + DLNA_ICON_JPEG_LRG_24.mime, // UPnP Device Large Icon JPEG Mimetype (21) + DLNA_ICON_JPEG_LRG_24.width, // UPnP Device Large Icon Width (22) + DLNA_ICON_JPEG_LRG_24.height, // UPnP Device Large Icon Height (23) + DLNA_ICON_JPEG_LRG_24.bitDepth, // UPnP Device Large Icon Bit Depth (24) + UPNP_DEVICE_ICON_JPEG_LRG, // UPnP Device Large Icon Path (25) + DLNA_ICON_JPEG_SM_24.mime, // UPnP Device Small Icon JPEG Mimetype (28) + DLNA_ICON_JPEG_SM_24.width, // UPnP Device Small Icon Width (29) + DLNA_ICON_JPEG_SM_24.height, // UPnP Device Small Icon Height (30) + DLNA_ICON_JPEG_SM_24.bitDepth, // UPnP Device Small Icon Bit Depth (31) + UPNP_DEVICE_ICON_JPEG_SM, // UPnP Device Small Icon Path (32) + DLNA_ICON_PNG_SM_24A.mime, // UPnP Device Small Icon PNG Mimetype (35) + DLNA_ICON_PNG_SM_24A.width, // UPnP Device Small Icon Width (36) + DLNA_ICON_PNG_SM_24A.height, // UPnP Device Small Icon Height (37) + DLNA_ICON_PNG_SM_24A.bitDepth, // UPnP Device Small Icon Bit Depth (38) + UPNP_DEVICE_ICON_PNG_SM, // UPnP Device Small Icon Path (39) + DLNA_ICON_PNG_LRG_24A.mime, // UPnP Device Large Icon PNG Mimetype (42) + DLNA_ICON_PNG_LRG_24A.width, // UPnP Device Large Icon Width (43) + DLNA_ICON_PNG_LRG_24A.height, // UPnP Device Large Icon Height (44) + DLNA_ICON_PNG_LRG_24A.bitDepth, // UPnP Device Large Icon Bit Depth (45) + UPNP_DEVICE_ICON_PNG_LRG, // UPnP Device Large Icon Path (46) + UPNP_WEB_PRESENTATION_URL, // UPnP Presentation URL (49) + UPNP_XMLNS_PREFIX_DLNA, // DLNA Namespace prefix (50) + DLNA_DEVICE_DMS_1_5, // DLNA Device Type/Version (50) + UPNP_CMS_SERVICE_TYPE, // UPnP CMS Service Type + UPNP_CMS_SERVICE_ID, // UPnP CMS Service ID + UPNP_CMS_SCPD_URL, // UPnP CMS Service Description + UPNP_CMS_CONTROL_URL, // UPnP CMS Control URL + UPNP_CMS_EVENT_URL, // UPnP CMS Event URL + UPNP_CDS_SERVICE_TYPE, // UPnP CDS Service Type + UPNP_CDS_SERVICE_ID, // UPnP CDS Service ID + UPNP_CDS_SCPD_URL, // UPnP CDS Service Description + UPNP_CDS_CONTROL_URL, // UPnP CDS Control URL + UPNP_CDS_EVENT_URL // UPnP CDS Event URL +// UPNP_AVT_SERVICE_TYPE, // UPnP AVT Service Type +// UPNP_AVT_SERVICE_ID, // UPnP AVT Service ID +// UPNP_AVT_SCPD_URL, // UPnP AVT Service Description +// UPNP_AVT_CONTROL_URL, // UPnP AVT Control URL +// UPNP_AVT_EVENT_URL // UPnP AVT Event URL + ); + return description; +}
\ No newline at end of file diff --git a/upnpcomponents/dlna.h b/upnpcomponents/dlna.h new file mode 100644 index 0000000..c05d69a --- /dev/null +++ b/upnpcomponents/dlna.h @@ -0,0 +1,64 @@ +/* + * File: dlna.h + * Author: savop + * + * Created on 18. April 2009, 23:27 + */ + +#ifndef _DLNA_H +#define _DLNA_H + +#include "../common.h" +#include <vdr/channels.h> +#include <vdr/recording.h> + +class cDlna; + +class cRegisteredProfile : public cListObject { + friend class cDlna; +private: + DLNAProfile* Profile; + int Operation; + const char* PlaySpeeds; + int Conversion; + int PrimaryFlags; +public: + cRegisteredProfile(){}; + virtual ~cRegisteredProfile(){}; +}; + +class cRegisteredProfiles : public cList<cRegisteredProfile> { + friend class cDlna; +}; + +/** + * Enable DLNA compliant media transfer + * + * This class enables media transmission with DLNA conformity. Its compliant with + * version 1.5 of the DLNA guidelines. + * + */ +class cDlna { + friend class cUPnPServer; +public: + static cDlna* getInstance(void); + virtual ~cDlna(); + //const char* getProtocolInfo(UPnPObjectID OID); + const char* getDeviceDescription(const char* URLBase); + void registerProfile(DLNAProfile* Profile, int Op = -1, const char* Ps = NULL, int Ci = -1, unsigned int Flags = 0); + void registerMainProfiles(); + const char* getSupportedProtocols(); + const char* getProtocolInfo(DLNAProfile *Prof); + DLNAProfile* getProfileOfChannel(cChannel* Channel); + DLNAProfile* getProfileOfRecording(cRecording* Recording); + DLNAProfile* getProfileOfFile(cString File); +private: + const char* getRegisteredProtocolInfoString(cRegisteredProfile *Profile); + cDlna(); + void init(void); + static cDlna* mInstance; + cRegisteredProfiles* mRegisteredProfiles; +}; + +#endif /* _DLNA_H */ + diff --git a/upnpcomponents/upnpservice.cpp b/upnpcomponents/upnpservice.cpp new file mode 100644 index 0000000..a1d6a47 --- /dev/null +++ b/upnpcomponents/upnpservice.cpp @@ -0,0 +1,118 @@ +/* + * File: upnpservice.cpp + * Author: savop + * + * Created on 21. August 2009, 18:38 + */ + +#include "upnpservice.h" +#include "../common.h" +#include "../misc/util.h" + +cUpnpService::cUpnpService(UpnpDevice_Handle DeviceHandle) { + this->mDeviceHandle = DeviceHandle; +} + +int cUpnpService::parseIntegerValue(IXML_Document* Document, const char* Item, int* Value){ + char* Val = NULL; + int Error = 0; + + Val = ixmlGetFirstDocumentItem(Document, Item, &Error); + + if(Error != 0){ + ERROR("Error while parsing integer value for item=%s", Item); + Error = -1; + } + else if(!Value){ + WARNING("Value %s empty!", Item); + *Value = 0; + } + else { + *Value = atoi(Val); + free(Val); + } + return Error; +} + +int cUpnpService::parseStringValue(IXML_Document* Document, const char* Item, char** Value){ + char* Val = NULL; + int Error = 0; + + Val = ixmlGetFirstDocumentItem(Document, Item, &Error); + + if(Error != 0){ + ERROR("Error while parsing string value for item=%s", Item); + Error = -1; + } + else if(!Val){ + WARNING("Value %s empty!", Item); + *Value = NULL; + } + else { + *Value = strdup(Val); + free(Val); + } + + return Error; +} + +void cUpnpService::setError(Upnp_Action_Request* Request, int Error){ + Request->ErrCode = Error; + switch(Error){ + case UPNP_SOAP_E_INVALID_ACTION: + strn0cpy(Request->ErrStr,_("Invalid action"),LINE_SIZE); + break; + case UPNP_SOAP_E_INVALID_ARGS: + strn0cpy(Request->ErrStr,_("Invalid args"),LINE_SIZE); + break; + case UPNP_SOAP_E_INVALID_VAR: + strn0cpy(Request->ErrStr,_("Invalid var"),LINE_SIZE); + break; + case UPNP_SOAP_E_ACTION_FAILED: + strn0cpy(Request->ErrStr,_("Action failed"),LINE_SIZE); + break; + case UPNP_SOAP_E_ARGUMENT_INVALID: + strn0cpy(Request->ErrStr,_("Argument value invalid"),LINE_SIZE); + break; + case UPNP_SOAP_E_ARGUMENT_OUT_OF_RANGE: + strn0cpy(Request->ErrStr,_("Argument value out of range"),LINE_SIZE); + break; + case UPNP_SOAP_E_ACTION_NOT_IMPLEMENTED: + strn0cpy(Request->ErrStr,_("Optional action not implemented"),LINE_SIZE); + break; + case UPNP_SOAP_E_OUT_OF_MEMORY: + strn0cpy(Request->ErrStr,_("Out of memory"),LINE_SIZE); + break; + case UPNP_SOAP_E_HUMAN_INTERVENTION: + strn0cpy(Request->ErrStr,_("Human intervention required"),LINE_SIZE); + break; + case UPNP_SOAP_E_STRING_TO_LONG: + strn0cpy(Request->ErrStr,_("String argument to long"),LINE_SIZE); + break; + case UPNP_SOAP_E_NOT_AUTHORIZED: + strn0cpy(Request->ErrStr,_("Action not authorized"),LINE_SIZE); + break; + case UPNP_SOAP_E_SIGNATURE_FAILURE: + strn0cpy(Request->ErrStr,_("Signature failure"),LINE_SIZE); + break; + case UPNP_SOAP_E_SIGNATURE_MISSING: + strn0cpy(Request->ErrStr,_("Signature missing"),LINE_SIZE); + break; + case UPNP_SOAP_E_NOT_ENCRYPTED: + strn0cpy(Request->ErrStr,_("Not encrypted"),LINE_SIZE); + break; + case UPNP_SOAP_E_INVALID_SEQUENCE: + strn0cpy(Request->ErrStr,_("Invalid sequence"),LINE_SIZE); + break; + case UPNP_SOAP_E_INVALID_CONTROL_URL: + strn0cpy(Request->ErrStr,_("Invalid control URL"),LINE_SIZE); + break; + case UPNP_SOAP_E_NO_SUCH_SESSION: + strn0cpy(Request->ErrStr,_("No such session"),LINE_SIZE); + break; + case UPNP_SOAP_E_OUT_OF_SYNC: + default: + strn0cpy(Request->ErrStr,_("Unknown error code. Contact the device manufacturer"),LINE_SIZE); + break; + } +}
\ No newline at end of file diff --git a/upnpcomponents/upnpservice.h b/upnpcomponents/upnpservice.h new file mode 100644 index 0000000..c8630b5 --- /dev/null +++ b/upnpcomponents/upnpservice.h @@ -0,0 +1,27 @@ +/* + * File: upnpservice.h + * Author: savop + * + * Created on 21. August 2009, 18:38 + */ + +#ifndef _UPNPSERVICE_H +#define _UPNPSERVICE_H + +#include <upnp/upnp.h> + +class cUpnpService { +public: + cUpnpService(UpnpDevice_Handle DeviceHandle); + virtual ~cUpnpService(){}; + virtual int subscribe(Upnp_Subscription_Request* Request) = 0; + virtual int execute(Upnp_Action_Request* Request) = 0; +protected: + virtual void setError(Upnp_Action_Request* Request, int Error); + int parseIntegerValue(IN IXML_Document* Document, IN const char* Item, OUT int* Value); + int parseStringValue(IN IXML_Document* Document, IN const char* Item, OUT char** Value); + UpnpDevice_Handle mDeviceHandle; +}; + +#endif /* _UPNPSERVICE_H */ + diff --git a/upnpcomponents/upnpwebserver.cpp b/upnpcomponents/upnpwebserver.cpp new file mode 100644 index 0000000..9be3d6a --- /dev/null +++ b/upnpcomponents/upnpwebserver.cpp @@ -0,0 +1,335 @@ +/* + * File: upnpwebserver.cpp + * Author: savop + * + * Created on 30. Mai 2009, 18:13 + */ + +#include <time.h> +#include <vdr/channels.h> +#include <map> +#include <upnp/upnp.h> +#include "upnpwebserver.h" +#include "../server/server.h" +#include "../receiver/livereceiver.h" +#include "../receiver/recplayer.h" +#include "../misc/search.h" + +/* COPIED FROM INTEL UPNP TOOLS */ +/******************************************************************************* + * + * Copyright (c) 2000-2003 Intel Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ******************************************************************************/ +struct File_Info_ +{ + /** The length of the file. A length less than 0 indicates the size + * is unknown, and data will be sent until 0 bytes are returned from + * a read call. */ + off64_t file_length; + + /** The time at which the contents of the file was modified; + * The time system is always local (not GMT). */ + time_t last_modified; + + /** If the file is a directory, {\bf is_directory} contains + * a non-zero value. For a regular file, it should be 0. */ + int is_directory; + + /** If the file or directory is readable, this contains + * a non-zero value. If unreadable, it should be set to 0. */ + int is_readable; + + /** The content type of the file. This string needs to be allocated + * by the caller using {\bf ixmlCloneDOMString}. When finished + * with it, the SDK frees the {\bf DOMString}. */ + + DOMString content_type; + +}; + +struct cWebFileHandle { + cString Filename; + off64_t Size; + cFileHandle* FileHandle; +}; + +/**************************************************** + * + * The web server + * + * Handles the virtual directories and the + * provision of data + * + * Interface between the channels/recordings of the + * VDR and the outer world + * + ****************************************************/ + +cUPnPWebServer::cUPnPWebServer(const char* root) : mRootdir(root) { +} + +cUPnPWebServer::~cUPnPWebServer(){ + delete this->mRootdir; +} + +cUPnPWebServer* cUPnPWebServer::mInstance = NULL; + +UpnpVirtualDirCallbacks cUPnPWebServer::mVirtualDirCallbacks = { + cUPnPWebServer::getInfo, + cUPnPWebServer::open, + cUPnPWebServer::read, + cUPnPWebServer::write, + cUPnPWebServer::seek, + cUPnPWebServer::close +}; + +bool cUPnPWebServer::init(){ + MESSAGE("Initialize callbacks for virtual directories."); + + if(UpnpSetWebServerRootDir(this->mRootdir) == UPNP_E_INVALID_ARGUMENT){ + ERROR("The root directory of the webserver is invalid."); + return false; + } + MESSAGE("Setting up callbacks"); + + if(UpnpSetVirtualDirCallbacks(&cUPnPWebServer::mVirtualDirCallbacks) == UPNP_E_INVALID_ARGUMENT){ + ERROR("The virtual directory callbacks are invalid."); + return false; + } + + if(UpnpIsWebserverEnabled() == FALSE){ + WARNING("The webserver has not been started. For whatever reason..."); + return false; + } + + MESSAGE("Add virtual directories."); + if(UpnpAddVirtualDir(UPNP_DIR_SHARES) == UPNP_E_INVALID_ARGUMENT){ + ERROR("The virtual directory %s is invalid.",UPNP_DIR_SHARES); + return false; + } + return true; +} + +cUPnPWebServer* cUPnPWebServer::getInstance(const char* rootdir){ + if(cUPnPWebServer::mInstance == NULL) + cUPnPWebServer::mInstance = new cUPnPWebServer(rootdir); + + if(cUPnPWebServer::mInstance){ + return cUPnPWebServer::mInstance; + } + else return NULL; +} + +void cUPnPWebServer::free(){ + delete cUPnPWebServer::mInstance; +} + +int cUPnPWebServer::getInfo(const char* filename, File_Info* info){ + MESSAGE("Getting information of file '%s'", filename); + + propertyMap Properties; + int Method; + int Section; + + if(cPathParser::parse(filename, &Section, &Method, &Properties)){ + switch(Section){ + case 0: + switch(Method){ + case UPNP_WEB_METHOD_STREAM: + { + MESSAGE("Stream request"); + propertyMap::iterator It = Properties.find("resId"); + unsigned int ResourceID = 0; + if(It == Properties.end()){ + ERROR("No resourceID for stream request"); + return -1; + } + else { + ResourceID = (unsigned)atoi(It->second); + cUPnPResource* Resource = cUPnPResources::getInstance()->getResource(ResourceID); + if(!Resource){ + ERROR("No such resource with ID (%d)", ResourceID); + return -1; + } + else { + File_Info_ finfo; + + finfo.content_type = ixmlCloneDOMString(Resource->getContentType()); + finfo.file_length = Resource->getFileSize(); + finfo.is_directory = 0; + finfo.is_readable = 1; + finfo.last_modified = Resource->getLastModification(); + memcpy(info, &finfo, sizeof(File_Info_)); + + MESSAGE("==== File info of Resource #%d ====", Resource->getID()); + MESSAGE("Size: %lld", finfo.file_length); + MESSAGE("Dir: %s", finfo.is_directory?"yes":"no"); + MESSAGE("Read: %s", finfo.is_readable?"allowed":"not allowed"); + MESSAGE("Last modified: %s", ctime(&(finfo.last_modified))); + MESSAGE("Content-type: %s", finfo.content_type); + } + } + } + break; + case UPNP_WEB_METHOD_BROWSE: + // break; + case UPNP_WEB_METHOD_SHOW: + // break; + case UPNP_WEB_METHOD_SEARCH: + case UPNP_WEB_METHOD_DOWNLOAD: + default: + ERROR("Unknown or unsupported method ID (%d)", Method); + return -1; + } + break; + default: + ERROR("Unknown or unsupported section ID (%d).", Section); + return -1; + } + } + else { + return -1; + } + + return 0; +} + +UpnpWebFileHandle cUPnPWebServer::open(const char* filename, UpnpOpenFileMode mode){ + MESSAGE("File %s was opened for %s.",filename,mode==UPNP_READ ? "reading" : "writing"); + + propertyMap Properties; + int Method; + int Section; + cWebFileHandle* WebFileHandle = NULL; + + if(cPathParser::parse(filename, &Section, &Method, &Properties)){ + switch(Section){ + case 0: + switch(Method){ + case UPNP_WEB_METHOD_STREAM: + { + MESSAGE("Stream request"); + propertyMap::iterator It = Properties.find("resId"); + unsigned int ResourceID = 0; + if(It == Properties.end()){ + ERROR("No resourceID for stream request"); + return NULL; + } + else { + ResourceID = (unsigned)atoi(It->second); + cUPnPResource* Resource = cUPnPResources::getInstance()->getResource(ResourceID); + if(!Resource){ + ERROR("No such resource with ID (%d)", ResourceID); + return NULL; + } + else { + WebFileHandle = new cWebFileHandle; + WebFileHandle->Filename = Resource->getResource(); + WebFileHandle->Size = Resource->getFileSize(); + switch(Resource->getResourceType()){ + case UPNP_RESOURCE_CHANNEL: + { + char* ChannelID = strtok(strdup(Resource->getResource()),":"); + int AudioID = atoi(strtok(NULL,":")); + MESSAGE("Try to create Receiver for Channel %s with Audio ID %d", ChannelID, AudioID); + cChannel* Channel = Channels.GetByChannelID(tChannelID::FromString(ChannelID)); + if(!Channel){ + ERROR("No such channel with ID %s", ChannelID); + return NULL; + } + cLiveReceiver* Receiver = cLiveReceiver::newInstance(Channel,0); + if(!Receiver){ + ERROR("Unable to tune channel. No available tuners?"); + return NULL; + } + WebFileHandle->FileHandle = Receiver; + } + break; + case UPNP_RESOURCE_RECORDING: + // break; + case UPNP_RESOURCE_FILE: + // break; + case UPNP_RESOURCE_URL: + default: + return NULL; + } + } + } + } + break; + case UPNP_WEB_METHOD_BROWSE: + // break; + case UPNP_WEB_METHOD_SHOW: + // break; + case UPNP_WEB_METHOD_SEARCH: + case UPNP_WEB_METHOD_DOWNLOAD: + default: + ERROR("Unknown or unsupported method ID (%d)", Method); + return NULL; + } + break; + default: + ERROR("Unknown or unsupported section ID (%d).", Section); + return NULL; + } + } + else { + return NULL; + } + MESSAGE("Open the file handle"); + WebFileHandle->FileHandle->open(mode); + return (UpnpWebFileHandle)WebFileHandle; +} + +int cUPnPWebServer::write(UpnpWebFileHandle fh, char* buf, size_t buflen){ + cWebFileHandle* FileHandle = (cWebFileHandle*)fh; + MESSAGE("Writing to %s", *FileHandle->Filename); + return FileHandle->FileHandle->write(buf, buflen); +} + +int cUPnPWebServer::read(UpnpWebFileHandle fh, char* buf, size_t buflen){ + cWebFileHandle* FileHandle = (cWebFileHandle*)fh; + MESSAGE("Reading from %s", *FileHandle->Filename); + return FileHandle->FileHandle->read(buf, buflen); +} + +int cUPnPWebServer::seek(UpnpWebFileHandle fh, off_t offset, int origin){ + cWebFileHandle* FileHandle = (cWebFileHandle*)fh; + MESSAGE("Seeking on %s", *FileHandle->Filename); + return FileHandle->FileHandle->seek(offset, origin); +} + +int cUPnPWebServer::close(UpnpWebFileHandle fh){ + cWebFileHandle *FileHandle = (cWebFileHandle *)fh; + MESSAGE("Closing file %s", *FileHandle->Filename); + FileHandle->FileHandle->close(); + delete FileHandle->FileHandle; + delete FileHandle; + return 0; +} diff --git a/upnpcomponents/upnpwebserver.h b/upnpcomponents/upnpwebserver.h new file mode 100644 index 0000000..55ef260 --- /dev/null +++ b/upnpcomponents/upnpwebserver.h @@ -0,0 +1,123 @@ +/* + * File: upnpwebserver.h + * Author: savop + * + * Created on 30. Mai 2009, 18:13 + */ + +#ifndef _UPNPWEBSERVER_H +#define _UPNPWEBSERVER_H + +#include "../common.h" +#include <upnp/upnp.h> + +class cUPnPWebServer { + friend class cUPnPServer; +private: + static cUPnPWebServer *mInstance; + static UpnpVirtualDirCallbacks mVirtualDirCallbacks; + const char* mRootdir; + cUPnPWebServer(const char* root = "/"); +protected: + bool enable(bool enable); + static void free(); +public: + bool init(); + static cUPnPWebServer* getInstance(const char* rootdir = "/"); + virtual ~cUPnPWebServer(); +//}; + + /**************************************************** + * + * The callback functions for the webserver + * + ****************************************************/ + /** + * Retrieve file information + * + * Returns file related information for an virtual directory file + * + * @return 0 on success, -1 otherwise + * @param filename The filename of which the information is gathered + * @param info The File_Info structure with the data + */ + static int getInfo(const char* filename, struct File_Info* info); + /** + * Opens a virtual directory file + * + * Opens a file in a virtual directory with the specified mode. + * + * Possible modes are: + * - UPNP_READ : Opens the file for reading + * - UPNP_WRITE: Opens the file for writing + * + * It returns a file handle to the opened file, NULL otherwise + * + * @return FileHandle to the opened file, NULL otherwise + * @param filename The file to open + * @param mode UPNP_WRITE for writing, UPNP_READ for reading. + */ + static UpnpWebFileHandle open(const char* filename, UpnpOpenFileMode mode); + /** + * Reads from the opened file + * + * Reads <code>buflen</code> bytes from the file and stores the content + * to the buffer + * + * Returns 0 no more bytes read (EOF) + * >0 bytes read from file + * + * @return number of bytes read, 0 on EOF + * @param fh the file handle of the opened file + * @param buf the buffer to write the bytes to + * @param buflen the maximum count of bytes to read + * + */ + static int read(UpnpWebFileHandle fh, char* buf, size_t buflen); + /** + * Writes to the opened file + * + * Writes <code>buflen</code> bytes from the buffer and stores the content + * in the file + * + * Returns >0 bytes wrote to file, maybe less the buflen in case of write + * errors + * + * @return number of bytes read, 0 on EOF + * @param fh the file handle of the opened file + * @param buf the buffer to read the bytes from + * @param buflen the maximum count of bytes to write + * + */ + static int write(UpnpWebFileHandle fh, char* buf, size_t buflen); + /** + * Seek in the file + * + * Seeks in the opened file and sets the file pointer to the specified offset + * + * Returns 0 on success, non-zero value otherwise + * + * @return 0 on success, non-zero value otherwise + * @param fh the file handle of the opened file + * @param offset a negative oder positive value which moves the pointer + * forward or backward + * @param origin SEEK_CUR, SEEK_END or SEEK_SET + * + */ + static int seek(UpnpWebFileHandle fh, off_t offset, int origin); + /** + * Closes the file + * + * closes the opened file + * + * Returns 0 on success, non-zero value otherwise + * + * @return 0 on success, non-zero value otherwise + * @param fh the file handle of the opened file + * + */ + static int close(UpnpWebFileHandle fh); +}; + +#endif /* _UPNPWEBSERVER_H */ + diff --git a/web/xml/cds_scpd.xml b/web/xml/cds_scpd.xml new file mode 100644 index 0000000..dff4252 --- /dev/null +++ b/web/xml/cds_scpd.xml @@ -0,0 +1,145 @@ +<?xml version = "1.0" encoding="UTF-8"?> +<scpd xmlns="urn:schemas-upnp-org:service-1-0"> +<specVersion> +<major>1</major> +<minor>0</minor> +</specVersion> +<actionList> +<action> +<name>GetSearchCapabilities</name> +<argumentList> +<argument> +<name>SearchCaps</name> +<direction>out</direction> +<relatedStateVariable>SearchCapabilities</relatedStateVariable> +</argument> +</argumentList> +</action> +<action> +<name>GetSortCapabilities</name> +<argumentList> +<argument> +<name>SortCaps</name> +<direction>out</direction> +<relatedStateVariable>SortCapabilities</relatedStateVariable> +</argument> +</argumentList> +</action> +<action> +<name>GetSystemUpdateID</name> +<argumentList> +<argument> +<name>Id</name> +<direction>out</direction> +<relatedStateVariable>SystemUpdateID</relatedStateVariable> +</argument> +</argumentList> +</action> +<action> +<name>Browse</name> +<argumentList> +<argument> +<name>ObjectID</name> +<direction>in</direction> +<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable> +</argument> +<argument> +<name>BrowseFlag</name> +<direction>in</direction> +<relatedStateVariable>A_ARG_TYPE_BrowseFlag</relatedStateVariable> +</argument> +<argument> +<name>Filter</name> +<direction>in</direction> +<relatedStateVariable>A_ARG_TYPE_Filter</relatedStateVariable> +</argument> +<argument> +<name>StartingIndex</name> +<direction>in</direction> +<relatedStateVariable>A_ARG_TYPE_Index</relatedStateVariable> +</argument> +<argument> +<name>RequestedCount</name> +<direction>in</direction> +<relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable> +</argument> +<argument> +<name>SortCriteria</name> +<direction>in</direction> +<relatedStateVariable>A_ARG_TYPE_SortCriteria</relatedStateVariable> +</argument> +<argument> +<name>Result</name> +<direction>out</direction> +<relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable> +</argument> +<argument> +<name>NumberReturned</name> +<direction>out</direction> +<relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable> +</argument> +<argument> +<name>TotalMatches</name> +<direction>out</direction> +<relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable> +</argument> +<argument> +<name>UpdateID</name> +<direction>out</direction> +<relatedStateVariable>A_ARG_TYPE_UpdateID</relatedStateVariable> +</argument> +</argumentList> +</action> +</actionList> +<serviceStateTable> +<stateVariable sendEvents="no"> +<name>A_ARG_TYPE_ObjectID</name> +<dataType>string</dataType> +</stateVariable> +<stateVariable sendEvents="no"> +<name>A_ARG_TYPE_Result</name> +<dataType>string</dataType> +</stateVariable> +<stateVariable sendEvents="no"> +<name>A_ARG_TYPE_BrowseFlag</name> +<dataType>string</dataType> +<allowedValueList> +<allowedValue>BrowseMetadata</allowedValue> +<allowedValue>BrowseDirectChildren</allowedValue> +</allowedValueList> +</stateVariable> +<stateVariable sendEvents="no"> +<name>A_ARG_TYPE_Filter</name> +<dataType>string</dataType> +</stateVariable> +<stateVariable sendEvents="no"> +<name>A_ARG_TYPE_SortCriteria</name> +<dataType>string</dataType> +</stateVariable> +<stateVariable sendEvents="no"> +<name>A_ARG_TYPE_Index</name> +<dataType>ui4</dataType> +</stateVariable> +<stateVariable sendEvents="no"> +<name>A_ARG_TYPE_Count</name> +<dataType>ui4</dataType> +</stateVariable> +<stateVariable sendEvents="no"> +<name>A_ARG_TYPE_UpdateID</name> +<dataType>ui4</dataType> +</stateVariable> +<stateVariable sendEvents="no"> +<name>SearchCapabilities</name> +<dataType>string</dataType> +</stateVariable> +<stateVariable sendEvents="no"> +<name>SortCapabilities</name> +<dataType>string</dataType> +</stateVariable> +<stateVariable sendEvents="yes"> +<name>SystemUpdateID</name> +<dataType>ui4</dataType> +</stateVariable> +</serviceStateTable> +</scpd> + diff --git a/web/xml/cms_scpd.xml b/web/xml/cms_scpd.xml new file mode 100644 index 0000000..e4071f9 --- /dev/null +++ b/web/xml/cms_scpd.xml @@ -0,0 +1,133 @@ +<?xml version="1.0" encoding="UTF-8"?> +<scpd xmlns="urn:schemas-upnp-org:service-1-0"> + <specVersion> + <major>1</major> + <minor>0</minor> + </specVersion> + <actionList> + <action> + <name>GetProtocolInfo</name> + <argumentList> + <argument> + <name>Source</name> + <direction>out</direction> + <relatedStateVariable>SourceProtocolInfo</relatedStateVariable> + </argument> + <argument> + <name>Sink</name> + <direction>out</direction> + <relatedStateVariable>SinkProtocolInfo</relatedStateVariable> + </argument> + </argumentList> + </action> + <action> + <name>GetCurrentConnectionIDs</name> + <argumentList> + <argument> + <name>ConnectionIDs</name> + <direction>out</direction> + <relatedStateVariable>CurrentConnectionIDs</relatedStateVariable> + </argument> + </argumentList> + </action> + <action> + <name>GetCurrentConnectionInfo</name> + <argumentList> + <argument> + <name>ConnectionID</name> + <direction>in</direction> + <relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable> + </argument> + <argument> + <name>RcsID</name> + <direction>out</direction> + <relatedStateVariable>A_ARG_TYPE_RcsID</relatedStateVariable> + </argument> + <argument> + <name>AVTransportID</name> + <direction>out</direction> + <relatedStateVariable>A_ARG_TYPE_AVTransportID</relatedStateVariable> + </argument> + <argument> + <name>ProtocolInfo</name> + <direction>out</direction> + <relatedStateVariable>A_ARG_TYPE_ProtocolInfo</relatedStateVariable> + </argument> + <argument> + <name>PeerConnectionManager</name> + <direction>out</direction> + <relatedStateVariable>A_ARG_TYPE_ConnectionManager</relatedStateVariable> + </argument> + <argument> + <name>PeerConnectionID</name> + <direction>out</direction> + <relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable> + </argument> + <argument> + <name>Direction</name> + <direction>out</direction> + <relatedStateVariable>A_ARG_TYPE_Direction</relatedStateVariable> + </argument> + <argument> + <name>Status</name> + <direction>out</direction> + <relatedStateVariable>A_ARG_TYPE_ConnectionStatus</relatedStateVariable> + </argument> + </argumentList> + </action> + </actionList> + <serviceStateTable> + <stateVariable sendEvents="yes"> + <name>SourceProtocolInfo</name> + <dataType>string</dataType> + </stateVariable> + <stateVariable sendEvents="yes"> + <name>SinkProtocolInfo</name> + <dataType>string</dataType> + </stateVariable> + <stateVariable sendEvents="yes"> + <name>CurrentConnectionIDs</name> + <dataType>string</dataType> + </stateVariable> + <stateVariable sendEvents="no"> + <name>A_ARG_TYPE_ConnectionStatus</name> + <dataType>string</dataType> + <allowedValueList> + <allowedValue>OK</allowedValue> + <allowedValue>ContentFormatMismatch</allowedValue> + <allowedValue>InsufficientBandwidth</allowedValue> + <allowedValue>UnreliableChannel</allowedValue> + <allowedValue>Unknown</allowedValue> + </allowedValueList> + </stateVariable> + <stateVariable sendEvents="no"> + <name>A_ARG_TYPE_ConnectionManager</name> + <dataType>string</dataType> + </stateVariable> + <stateVariable sendEvents="no"> + <name>A_ARG_TYPE_Direction</name> + <dataType>string</dataType> + <allowedValueList> + <allowedValue>Input</allowedValue> + <allowedValue>Output</allowedValue> + </allowedValueList> + </stateVariable> + <stateVariable sendEvents="no"> + <name>A_ARG_TYPE_ProtocolInfo</name> + <dataType>string</dataType> + </stateVariable> + <stateVariable sendEvents="no"> + <name>A_ARG_TYPE_ConnectionID</name> + <dataType>i4</dataType> + </stateVariable> + <stateVariable sendEvents="no"> + <name>A_ARG_TYPE_AVTransportID</name> + <dataType>i4</dataType> + </stateVariable> + <stateVariable sendEvents="no"> + <name>A_ARG_TYPE_RcsID</name> + <dataType>i4</dataType> + </stateVariable> + </serviceStateTable> +</scpd> + |