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