From 1cf955a715830130b7add8c1183d65b0f442fd23 Mon Sep 17 00:00:00 2001 From: Denis Loh Date: Sat, 24 Oct 2009 14:24:17 +0200 Subject: Initial commit --- COPYING | 340 +++++++ HISTORY | 16 + Makefile | 141 +++ README | 74 ++ channelmap.conf.example | 30 + common.cpp | 29 + common.h | 773 +++++++++++++++ database/database.cpp | 274 ++++++ database/database.h | 834 +++++++++++++++++ database/metadata.cpp | 389 ++++++++ database/metadata.h | 60 ++ database/object.cpp | 1702 ++++++++++++++++++++++++++++++++++ database/object.h | 397 ++++++++ database/resources.cpp | 282 ++++++ database/resources.h | 51 + misc/config.cpp | 109 +++ misc/config.h | 33 + misc/menusetup.cpp | 205 ++++ misc/menusetup.h | 78 ++ misc/search.cpp | 795 ++++++++++++++++ misc/search.h | 90 ++ misc/util.cpp | 488 ++++++++++ misc/util.h | 45 + receiver/filehandle.cpp | 8 + receiver/filehandle.h | 26 + receiver/livereceiver.cpp | 175 ++++ receiver/livereceiver.h | 40 + receiver/recplayer.cpp | 171 ++++ receiver/recplayer.h | 39 + server/server.cpp | 366 ++++++++ server/server.h | 172 ++++ upnp.cpp | 129 +++ upnp.h | 42 + upnpcomponents/connectionmanager.cpp | 393 ++++++++ upnpcomponents/connectionmanager.h | 67 ++ upnpcomponents/contentdirectory.cpp | 306 ++++++ upnpcomponents/contentdirectory.h | 38 + upnpcomponents/dlna.cpp | 235 +++++ upnpcomponents/dlna.h | 64 ++ upnpcomponents/upnpservice.cpp | 118 +++ upnpcomponents/upnpservice.h | 27 + upnpcomponents/upnpwebserver.cpp | 335 +++++++ upnpcomponents/upnpwebserver.h | 123 +++ web/xml/cds_scpd.xml | 145 +++ web/xml/cms_scpd.xml | 133 +++ 45 files changed, 10387 insertions(+) create mode 100644 COPYING create mode 100644 HISTORY create mode 100644 Makefile create mode 100644 README create mode 100644 channelmap.conf.example create mode 100644 common.cpp create mode 100644 common.h create mode 100644 database/database.cpp create mode 100644 database/database.h create mode 100644 database/metadata.cpp create mode 100644 database/metadata.h create mode 100644 database/object.cpp create mode 100644 database/object.h create mode 100644 database/resources.cpp create mode 100644 database/resources.h create mode 100644 misc/config.cpp create mode 100644 misc/config.h create mode 100644 misc/menusetup.cpp create mode 100644 misc/menusetup.h create mode 100644 misc/search.cpp create mode 100644 misc/search.h create mode 100644 misc/util.cpp create mode 100644 misc/util.h create mode 100644 receiver/filehandle.cpp create mode 100644 receiver/filehandle.h create mode 100644 receiver/livereceiver.cpp create mode 100644 receiver/livereceiver.h create mode 100644 receiver/recplayer.cpp create mode 100644 receiver/recplayer.h create mode 100644 server/server.cpp create mode 100644 server/server.h create mode 100644 upnp.cpp create mode 100644 upnp.h create mode 100644 upnpcomponents/connectionmanager.cpp create mode 100644 upnpcomponents/connectionmanager.h create mode 100644 upnpcomponents/contentdirectory.cpp create mode 100644 upnpcomponents/contentdirectory.h create mode 100644 upnpcomponents/dlna.cpp create mode 100644 upnpcomponents/dlna.h create mode 100644 upnpcomponents/upnpservice.cpp create mode 100644 upnpcomponents/upnpservice.h create mode 100644 upnpcomponents/upnpwebserver.cpp create mode 100644 upnpcomponents/upnpwebserver.h create mode 100644 web/xml/cds_scpd.xml create mode 100644 web/xml/cms_scpd.xml diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..f90922e --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/HISTORY b/HISTORY new file mode 100644 index 0000000..854250e --- /dev/null +++ b/HISTORY @@ -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='' -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 diff --git a/README b/README new file mode 100644 index 0000000..1557c0b --- /dev/null +++ b/README @@ -0,0 +1,74 @@ +This is a "plugin" for the Video Disk Recorder (VDR). + +Written by: Denis Loh + Andreas Günther + +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 --int= The server network + interface + e.g: eth0, wlan1 etc. + If given option '-a' must + be absent. + -a
--address=
The server IPv4 address. + If given option '-i' must + be absent. + -p --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) +# :[:] +# +# 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 +# - +# +# 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 +#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 +#include +#include +#include +#include + +/**************************************************** + * + * 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 "" + +/**************************************************** + * + * 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 +#include +#include +#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;iColCount;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 +#include +#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 { + 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 +#include +#include +#include "object.h" +#include "resources.h" +#include "metadata.h" +#include "../common.h" +#include "../misc/search.h" +#include +#include +#include + +#define KEY_SYSTEM_UPDATE_ID "SystemUpdateID" + + /**********************************************\ + * * + * Media database * + * * + \**********************************************/ + +cMediaDatabase::cMediaDatabase(){ + this->mSystemUpdateID = 0; + this->mLastInsertObjectID = 0; + this->mDatabase = cSQLiteDatabase::getInstance(); + this->mObjects = new cHash; + 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* 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* 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* 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 +#include +#include +#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* 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 +#include +#include +#include +#include +#include "database.h" +#include +#include +#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; + this->mResourcesID = new cHash; + 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; + 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; imSearchClasses.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; imCreateClasses.size(); i++){ + Class = this->mCreateClasses[i]; + if(Class == CreateClass){ + this->mCreateClasses.erase(it+i); + return 0; + } + } + return -1; +} + +int cUPnPClassContainer::setSearchClasses(std::vector SearchClasses){ + this->mSearchClasses = SearchClasses; + return 0; +} + +int cUPnPClassContainer::setCreateClasses(std::vector 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* 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; igetSearchClasses()->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 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 +#include +#include +#include +#include + +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 { +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* mResources; // The resources of this object + cHash* 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* 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* 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 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* 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 SearchClasses); + int setCreateClasses(std::vector CreateClasses); + int setSearchable(bool Searchable); + /******** Getter ********/ + const char* getContainerType() const { return this->mContainerType; } + const std::vector* getSearchClasses() const { return &(this->mSearchClasses); } + const std::vector* 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 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 +#include +#include "../upnpcomponents/dlna.h" +#include +#include "resources.h" + +cUPnPResources* cUPnPResources::mInstance = NULL; + +cUPnPResources::cUPnPResources(){ + this->mResources = new cHash; + 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 +#include + +class cUPnPResourceMediator; +class cMediaDatabase; + +class cUPnPResources { +private: + cHash* 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 +#include +#include +#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 +#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 +#include "menusetup.h" +#include "../common.h" +#include "util.h" +#include +#include +#include +#include + + +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 +#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 +#include "search.h" +#include "../common.h" +#include +#include +#include + +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 expCallback; +typedef function1 propCallback; +typedef function1 opCallback; +typedef function1 charCallback; +typedef function1 intCallback; + +// The defined ColumnNames +struct cProperties : symbols { +//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 { + cOperators(){ + add + ("=", "==") + ("!=", "!=") + ("<", "<") + (">", ">") + ("<=", "<=") + (">=", ">=") + ("contains", "LIKE") + ("doesNotContain", "NOT LIKE") + ("derivedfrom", "derivedFrom") + ; + } +} Operators; + +struct cConcatOperators : symbols { + cConcatOperators(){ + add + ("and", "AND") + ("or", "OR") + ; + } +} ConcatOperators; + +struct cExistanceOperator : symbols { + 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 { + // 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 + struct definition { + boost::spirit::rule searchCrit, searchExp, logOp, \ + relExp, binOp, relOp, stringOp, \ + existsOp, boolVal, quotedVal, \ + wChar, property, brackedExp, exp; + const boost::spirit::rule &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 { + // The callback members + propCallback &pushProperty; + charCallback &pushDirection; + + cSortGrammar( + propCallback &pushProperty, + charCallback &pushDirection): + pushProperty(pushProperty), + pushDirection(pushDirection){} + + template + struct definition { + boost::spirit::rule sortCrit, sortExp, property, direction; + + const boost::spirit::rule &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 { + // The callback members + propCallback &pushProperty; + charCallback &pushAsterisk; + + cFilterGrammar( + propCallback &pushProperty, + charCallback &pushAsterisk): + pushProperty(pushProperty), + pushAsterisk(pushAsterisk){} + + template + struct definition { + boost::spirit::rule filterCrit, filterExp, property; + + const boost::spirit::rule &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; + this->mCurrentCrit = NULL; +} + +cSortCriteria::~cSortCriteria(){} + +cList* cSortCriteria::parse(const char* Sort){ + cSortCriteria* SortParser = new cSortCriteria; + cList* 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 { + 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 { + + 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 + struct definition { + boost::spirit::rule pathExp, section, method, methodProperties, + property, key, value, uncriticalChar; + + const boost::spirit::rule &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 +#include +#include "util.h" + +struct cSortCrit : public cListObject { + const char* Property; + bool SortDescending; +}; + +typedef std::map 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* mCriteriaList; + bool parseSort(const char* Sort); + void pushProperty(const char* Property); + void pushDirection(const char Direction); + cList* getSortList() const { return this->mCriteriaList; } + cSortCriteria(); +public: + virtual ~cSortCriteria(); + static cList* 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 +#include +#include +#include +#include +#include +#include + +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 +#include +#include + +#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 +#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 +#include +#include +#include +#include +#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 +#include + +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 +#include +#include +#include +#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 + +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 +#include +#include +#include +#include +#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 +#include +#include +#include +#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 +#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 --int= The server network\n" + " interface\n" + " e.g: eth0, wlan1 etc.\n" + " If given option '-a' must\n" + " be absent.\n" + " -a
--address=
The server IPv4 address.\n" + " If given option '-i' must\n" + " be absent.\n" + " -p --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! + diff --git a/upnp.h b/upnp.h new file mode 100644 index 0000000..5baa1cc --- /dev/null +++ b/upnp.h @@ -0,0 +1,42 @@ +/* + * File: upnp.h + * Author: savop + * + * Created on 17. April 2009, 20:53 + */ + +#ifndef _UPNP_H +#define _UPNP_H + +#include +#include +#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 +#include +#include +#include +#include "connectionmanager.h" +#include "dlna.h" + +cVirtualConnection::cVirtualConnection() : mRcsID(-1) {} + +cConnectionManager::cConnectionManager(UpnpDevice_Handle DeviceHandle) : cUpnpService(DeviceHandle) { + this->mVirtualConnections = new cList; + 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( + " \ + %s \ + \ + ", + 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( + " \ + %s \ + ", + 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( + "\ + %s\ + %s\ + %d\ + %s\ + %d\ + %d\ + %s\ + ", + 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* 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 +#include +#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( + " \ + %s \ + %d \ + %d \ + %d \ + ", + 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( + " \ + %d \ + ", + 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( + " \ + %s \ + ", + 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( + " \ + %s \ + ", + 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 +#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 +#include +#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( + " \ + \ + \ + 1 \ + 0 \ + \ + %s \ + \ + %s \ + %s \ + %s \ + %s \ + %s \ + %s \ + %s \ + %s \ + %s \ + %s \ + \ + \ + %s \ + %d \ + %d \ + %d \ + %s \ + \ + \ + %s \ + %d \ + %d \ + %d \ + %s \ + \ + \ + %s \ + %d \ + %d \ + %d \ + %s \ + \ + \ + %s \ + %d \ + %d \ + %d \ + %s \ + \ + \ + %s \ + <%s:X_DLNADOC>%s \ + \ + \ + %s \ + %s \ + %s \ + %s \ + %s \ + \ + \ + %s \ + %s \ + %s \ + %s \ + %s \ + \ + \ + \ + ", + 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 +#include + +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 { + 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 + +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 +#include +#include +#include +#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 + +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 buflen 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 buflen 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 @@ + + + +1 +0 + + + +GetSearchCapabilities + + +SearchCaps +out +SearchCapabilities + + + + +GetSortCapabilities + + +SortCaps +out +SortCapabilities + + + + +GetSystemUpdateID + + +Id +out +SystemUpdateID + + + + +Browse + + +ObjectID +in +A_ARG_TYPE_ObjectID + + +BrowseFlag +in +A_ARG_TYPE_BrowseFlag + + +Filter +in +A_ARG_TYPE_Filter + + +StartingIndex +in +A_ARG_TYPE_Index + + +RequestedCount +in +A_ARG_TYPE_Count + + +SortCriteria +in +A_ARG_TYPE_SortCriteria + + +Result +out +A_ARG_TYPE_Result + + +NumberReturned +out +A_ARG_TYPE_Count + + +TotalMatches +out +A_ARG_TYPE_Count + + +UpdateID +out +A_ARG_TYPE_UpdateID + + + + + + +A_ARG_TYPE_ObjectID +string + + +A_ARG_TYPE_Result +string + + +A_ARG_TYPE_BrowseFlag +string + +BrowseMetadata +BrowseDirectChildren + + + +A_ARG_TYPE_Filter +string + + +A_ARG_TYPE_SortCriteria +string + + +A_ARG_TYPE_Index +ui4 + + +A_ARG_TYPE_Count +ui4 + + +A_ARG_TYPE_UpdateID +ui4 + + +SearchCapabilities +string + + +SortCapabilities +string + + +SystemUpdateID +ui4 + + + + 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 @@ + + + + 1 + 0 + + + + GetProtocolInfo + + + Source + out + SourceProtocolInfo + + + Sink + out + SinkProtocolInfo + + + + + GetCurrentConnectionIDs + + + ConnectionIDs + out + CurrentConnectionIDs + + + + + GetCurrentConnectionInfo + + + ConnectionID + in + A_ARG_TYPE_ConnectionID + + + RcsID + out + A_ARG_TYPE_RcsID + + + AVTransportID + out + A_ARG_TYPE_AVTransportID + + + ProtocolInfo + out + A_ARG_TYPE_ProtocolInfo + + + PeerConnectionManager + out + A_ARG_TYPE_ConnectionManager + + + PeerConnectionID + out + A_ARG_TYPE_ConnectionID + + + Direction + out + A_ARG_TYPE_Direction + + + Status + out + A_ARG_TYPE_ConnectionStatus + + + + + + + SourceProtocolInfo + string + + + SinkProtocolInfo + string + + + CurrentConnectionIDs + string + + + A_ARG_TYPE_ConnectionStatus + string + + OK + ContentFormatMismatch + InsufficientBandwidth + UnreliableChannel + Unknown + + + + A_ARG_TYPE_ConnectionManager + string + + + A_ARG_TYPE_Direction + string + + Input + Output + + + + A_ARG_TYPE_ProtocolInfo + string + + + A_ARG_TYPE_ConnectionID + i4 + + + A_ARG_TYPE_AVTransportID + i4 + + + A_ARG_TYPE_RcsID + i4 + + + + -- cgit v1.2.3