commit 389dae52b65b306b06a95b0f409ae31fa7847503 Author: Edouard DUPIN Date: Tue Nov 10 12:54:06 2015 +0100 [DEV] import first version 1.1a diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..d232fa3 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,20 @@ +Author +====== + +Benjamin Zores + +With many contributions from : +- Alexis Saettler +- Amir Shalem +- Rémi Turboult for some UPnP standard compliance check. +- Bernd Loske for some buffer overrun fix. +- Eric Tanguy for Fedora RPM build and startup scripts. +- Julien Lincy for a better UPnP CDS respect and + commercial UPnP players compliance. +- Jonathan (no_dice at users.sourceforge.net) for large files support. +- Mostafa Hosseini for XboX 360 compliance, several UPnP compliance fixes and basic 'Search' support. +- Phil Chandler for RedBlack balanced tree sorting algorithm and some memory optimizations. +- Navaho Gunleg for MacOSX build fix. +- Sven Almgren for Telnet Control interface. + +Please send remarks, questions, bug reports to . diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..623b625 --- /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 Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..9e70ac2 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,198 @@ +2007-12-09 Benjamin Zores + + * new public release (Version 1.1a). + +2007-12-04 Benjamin Zores + + * new public release (Version 1.1). + +2007-11-04 Benjamin Zores + + * much complete DLNA support through libdlna. + +2007-07-30 Benjamin Zores + + * MacOSX build fix. + +2007-07-05 Alexis Saettler + + * new public release (Version 1.0). + +2007-06-26 Benjamin Zores + + * fix for XboX 360 file discovery (patch by Keith Brindley). + +2007-06-25 Benjamin Zores + + * added support for DLNA (Digital Living Network Alliance), + the wonderful closed-source marketing UPnP++ specification. + uShare is now compliant with PlayStation 3 UPnP/DLNA Media Renderer. + +2007-02-26 Alexis Saettler + + * new public release (Version 0.9.10). + * fixed http 404 error which prevented ushare 0.9.9 release to work. + +2007-02-25 Alexis Saettler + + * new public release (Version 0.9.9). + * fixed bug with localization, use now --localedir configure's + option to set location of .mo files. + +2007-01-23 Benjamin Zores + + * make libupnp >= 1.4.2 a requirement. + +2007-01-20 Benjamin Zores + + * added support for FreeBSD. + +2007-01-19 Benjamin Zores + + * use a RedBlack balanced tree searching and sorting algorithm + instead of the recursive one. It avoids recursive calls, takes + less memory (perfect for embedded systems) and executes in more + "constant time" than the logarithmic style. + Original patch by Phil Chandler. + * massive memory optimization (uShare now takes 75% less RAM ;-) + only not using fixed size URL allocation buffer (a 16000+ files + collection now only takes 5MB where it used to take 19MB). + * improved search parsing and matching mechanism (patch by Mostafa + Hosseini). XboX360 support is now complete. + Please note that to stream video to the XboX, the console should + be running dashboard version 2.0.4552.0 and only unprotected + .wmv files are supported. + +2007-01-14 Benjamin Zores + + * make libupnp >= 1.4.1 a requirement. + * fixed support for 2GB+ files (see http://sourceforge.net/tracker/index.php?func=detail&aid=1634927&group_id=151880&atid=782410) + * fixed support for XboX 360 (thanks to Mostafa Hosseini). + * added basic support for 'Search' action. + +2006-12-10 Alexis Saettler + + * new public release (Version 0.9.8). + +2006-10-19 Alexis Saettler + + * add German translation (thanks to Robin H.). + * fix filtering and browse capabilities : now uShare is conform to + ContentDirectory:1 Service Template Standard. + +2006-09-17 Alexis Saettler + + * add iso and bin mime types. + * convert special xml characters to xml (like & => &) + (reported by Frank Scholz). + +2006-09-10 Alexis Saettler + + * Use sysconfdir (from configure) to set /etc destination + * update init script to use lsb functions + +2006-03-12 Benjamin Zores + + * new public release (Version 0.9.7) + +2006-03-07 Benjamin Zores + + * modify configure.ac to check for functions in libupnp that + only comes with releases >= 1.3.1 + (previous versions of the library are buggy and should not be used). + * added new Microsoft Registrar service which fakes the authorization + required to be able to stream to MS compliant Media Renderers + (i.e now usable with XboX 360 and probably others). + +2006-02-27 Benjamin Zores + + * applied suggestions from Julien Lincy + * UPnP CDS compliance fix when request count and index are 0. + * removed the ':' characters from UDN anming convention (devices like + the Roku SoundBridge M2000 Network Music Player do not support it). + +2006-02-19 Benjamin Zores + + * new public release (Version 0.9.6) + +2006-01-06 Benjamin Zores + + * added Fedora RPM build file (ushare.spec) and startup script. + (patch by Eric Tanguy ) + +2005-12-28 Benjamin Zores + + * can now specify on which port the HTTP server has to run. + (useful for security and firewall issues). + +2005-12-27 Benjamin Zores + + * fixed a potential buffer overrun when building DIDL messages. + (patch by Bernd Loske ). + +2005-12-19 Benjamin Zores + + * new public release (Version 0.9.5) + +2005-12-17 Benjamin Zores + + * added UPnP presentation URL support. + (you can now update or add contents through a web interface). + +2005-12-10 Benjamin Zores + + * added support for subtitle file formats. + * added support for DVD specific file formats. + +2005-12-08 Benjamin Zores + + * new public bug-fix release (Version 0.9.4) + * fixed some memleaks. + * avoid having empty shared directory name + (crash with djmount, thanks to Remi Turboult for reporting it). + * fixed handling of non-absolute content directories paths. + +2005-11-23 Alexis Saettler + + * add logging with syslog support, in daemon mode. + +2005-11-20 Benjamin Zores + + * new public bug-fix release (Version 0.9.3) + * Added verbose mode. + * Read configuration from /etc/ushare.conf configuration file. + * Support for daemon mode. + * Better MIME types handling. + * Support for new multimedia files extensions. + * Rewrite of some string buffer and integer manipulation tools. + +2005-11-16 Alexis Saettler + + * modify -c parameter habdle, to allow multiple shared directories. + +2005-11-13 Benjamin Zores + + * new public bug-fix release (Version 0.9.2) + +2005-11-13 Benjamin Zores + + * improved UPnP compliance with Browse RequestedCount flag. + * fixed some UPnP Object ID sent in Browse messages. + +2005-11-10 Benjamin Zores + + * new public release (Version 0.9.1) + +2005-11-08 Alexis Saettler + + * Prepare uShare for internationalization + * Add French localization + * Add support of iconv, for UTF-8 filenames conversions + +2005-10-29 Alexis Saettler + + * Added Debian package building scripts + +2005-10-25 Benjamin Zores + + * First public release (Version 0.9.0) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ebd8f91 --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +ifeq (,$(wildcard config.mak)) +$(error "config.mak is not present, run configure !") +endif +include config.mak + +DISTFILE = ushare-$(VERSION).tar.bz2 + +EXTRADIST = AUTHORS \ + ChangeLog \ + configure \ + COPYING \ + NEWS \ + README \ + THANKS \ + TODO \ + +SUBDIRS = po \ + scripts \ + src \ + +all: + for subdir in $(SUBDIRS); do \ + $(MAKE) -C $$subdir $@; \ + done + +clean: + for subdir in $(SUBDIRS); do \ + $(MAKE) -C $$subdir $@; \ + done + +distclean: clean + for subdir in $(SUBDIRS); do \ + $(MAKE) -C $$subdir $@; \ + done + -$(RM) -f config.log + -$(RM) -f config.mak + -$(RM) -f config.h + + +install: + for subdir in $(SUBDIRS); do \ + $(MAKE) -C $$subdir $@; \ + done + +.PHONY: clean distclean install + +dist: + -$(RM) $(DISTFILE) + dist=$(shell pwd)/ushare-$(VERSION) && \ + for subdir in . $(SUBDIRS); do \ + mkdir -p "$$dist/$$subdir"; \ + $(MAKE) -C $$subdir dist-all DIST="$$dist/$$subdir"; \ + done && \ + tar cjf $(DISTFILE) ushare-$(VERSION) + -$(RM) -rf ushare-$(VERSION) + +dist-all: + cp $(EXTRADIST) $(SRCS) Makefile $(DIST) + +.PHONY: dist dist-all diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..986dd1e --- /dev/null +++ b/NEWS @@ -0,0 +1,108 @@ +2007-12-09: GeeXboX uShare 1.1a released. + Support for XboX 360 dashboard Fall Update (.avi and .divx now are supported) + DLNA support is not enabled by default, as not mandatory. + Some configure script fixes and better support for cross-compilation. + Fixed network interface discovery on MacOSX. + +2007-12-04: GeeXboX uShare 1.1 released. + Much more complete DLNA support through external libdlna. + Telnet Control interface. + Support for FLAC and HDMOV files. + FreeBSD 64bit and MacOSX build fix. + Newly written configure script. + +2007-07-05 : GeeXboX uShare 1.0 released. + Fixed XboX 360 file discovery (it finally should work). + Support for Sony PlayStation 3. + DLNA compliance. + +2007-02-26 : GeeXboX uShare 0.9.10 released. + Fixed http 404 error which prevented ushare 0.9.9 release to work. + +2007-02-25 : GeeXboX uShare 0.9.9 released. + Added support for FreeBSD. + Use a RedBlack balanced tree searching and sorting algorithm + instead of the recursive one. It avoids recursive calls, takes + less memory (perfect for embedded systems) and executes in more + "constant time" than the logarithmic style. + Original patch by Phil Chandler. + Massive memory optimization (uShare now takes 75% less RAM ;-) + only not using fixed size URL allocation buffer (a 16000+ files + collection now only takes 5MB where it used to take 19MB). + Improved search parsing and matching mechanism (patch by Mostafa + Hosseini). XboX360 support is now complete. + Please note that to stream video to the XboX, the console should + be running dashboard version 2.0.4552.0 and only unprotected + .wmv files are supported. + Fixed support for 2GB+ files (see http://sourceforge.net/tracker/index.php?func=detail&aid=1634927&group_id=151880&atid=782410) + Fixed support for XboX 360 (thanks to Mostafa Hosseini). + Added basic support for 'Search' action. + Fixed bug with localization, use now --localedir configure's + option to set location of .mo files. + Make libupnp >= 1.4.2 a requirement. + +2006-12-10 : GeeXboX uShare 0.9.8 released. + Added German translation. + Convert special xml characters to xml (like & => &). + Added iso and bin mime types. + Better filtering and browse capabilities: now uShare is conform to + ContentDirectory:1 Service Template Standard. + Updated init script to use lsb functions. + +2006-03-12 : GeeXboX uShare 0.9.7 released. + Added new Microsoft Registrar service which fakes the authorization + required to be able to stream to MS compliant Media Renderers. + Updated AVI file MIME type for better compliance with UPnP devices. + Better UPnP CDS compliance when request count and index are 0. + Removed the nasty characters from UDN naming for compatibility issues. + Force usage of libupnp >= 1.3.1 + (previous versions of the library are buggy and should not be used). + Support for XboX 360, D-Link DSM-320 and Roku SoundBridge M2000 + Network Music Player devices. + +2006-02-19 : GeeXboX uShare 0.9.6 released. + Added Fedora RPM build and startup init scripts. + Support for for large files (2GB+ files). + Can now specify on which port the HTTP server has to run. + Fixed a potential buffer overrun when building DIDL messages. + +2006-01-31 : uShare has been included in the Fedora Project. + See http://fedoraproject.org/extras/4/i386/repodata/repoview/ushare-0-0.9.5-4.fc4.html + +2005-12-19 : GeeXboX uShare 0.9.5 released. + Added UPnP presentation URL support (you can now update or add/remove + contents through a web interface). + Added support for subtitle file formats. + Added support for DVD specific file formats. + +2005-12-07 : GeeXboX uShare 0.9.4 released. + Fixed some memleaks. + Avoid having empty shared directory name. + Fixed handling of non-absolute content directories paths. + Added logging with syslog support, in daemon mode. + +2005-11-20: GeeXboX uShare 0.9.3 released. + Support for multiple directories to be shared. + Added start/stop script. + Added verbose mode. + Read configuration from /etc/ushare.conf configuration file. + Support for daemon mode. + Better MIME types handling. + Support for new multimedia files extensions. + Rewrite of some string buffer and integer manipulation tools. + +2005-11-13: GeeXboX uShare 0.9.2 released. + Improved UPnP compliance with Browse RequestedCount flag. + Fixed some UPnP Object ID sent in Browse messages (terrible mistake). + +2005-11-10: GeeXboX uShare 0.9.1 released. + uShare ready for internationalization. + Added French localization. + Support of iconv, for UTF-8 filenames conversions. + Added Debian package building scripts. + +2005-10-25: GeeXboX uShare 0.9.0 released. + First public release. + Tested with : + - Intel Digital Media Adaptor + - Intel UPnP AV Media Controller diff --git a/README b/README new file mode 100644 index 0000000..d90e919 --- /dev/null +++ b/README @@ -0,0 +1,209 @@ +GeeXboX uShare - Introduction +============================= + +GeeXboX uShare is a UPnP (TM) A/V Media Server. It implements the server +component that provides UPnP media devices with information on available +multimedia files. uShare uses the built-in http server of libupnp to +stream the files to clients. + +GeeXboX uShare is able to provide access to both images, videos, music +or playlists files (see below for a complete file format support list). +It does not act as an UPnP Media Adaptor and thus, can't transcode +streams to fit the client requirements. + +uShare is written in C for the GeeXboX project (see http://www.geexbox.org/). +It is designed to provide access to multimedia contents to GeeXboX but can of +course be used by any other UPnP client device. +It should compile and run on any modern POSIX compatible system such as Linux. + +GeeXboX uShare is free software - it is licensed under the terms of the GNU +General Public License (GPL). + +Copyright and License +===================== + +GeeXboX uShare is copyright (C) 2005-2007 Benjamin Zores. + +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. + +Note that uShare links with libupnp, which is licensed under the +terms of a modified BSD license (i.e. the original BSD license without the +advertising clause). This license is compatible with the GNU GPL. + +Homepage +======== + +Web site and file area for uShare is hosted on GeeXboX server : + + http://ushare.geexbox.org/ + +The latest version of uShare should always be available on this site. + +Requirements +============ + +The following programs are required to build uShare: + + * GNU C Compiler (gcc), 2.95 or later. + + The GNU C Compiler is part of the GNU Compiler Collection which can be + downloaded from http://gcc.gnu.org/. + + * pkg-config. + + pkg-config is a helper tool used when compiling applications and libraries. + It helps you insert the correct compiler options on the command line. + (see http://pkg-config.freedesktop.org/wiki/ ). + +The following UPnP library is required to build and run uShare : + + * Linux SDK for UPnP Devices (libupnp), 1.4.2 or later + + The libupnp library is used to communicate using the UPnP protocol. + libupnp can be downloaded from http://pupnp.sourceforge.net/. + +The following DLNA library is required for proper DLNA support : + + * libdlna, 0.2.1 or later + + The libdlna library is used to provides DLNA profiles informations. + libdlna can be downloaded from http://libdlna.geexbox.org/. + +Building and Installation +========================= + +Compile uShare by running configure and then make. This should +produce an executable ushare in the src subdirectory, which can be +used right away. No extra files need to be installed. + +You can pass the CFLAGS you want to configure including -DDEBUG in order +to activate support for debug messages in uShare. + +Example : + +CFLAGS="-Os" ./configure --prefix=/usr +make + +You can enable DLNA support by doing a: +./configure --enable-dlna + +If you want to install uShare on your system, run : + +make install-strip + +This will copy the executable and manual page into their appropriate +directories (/usr/bin and /usr/man/man1 in this example). + +For more information regarding configure and make, see the INSTALL document. + +Usage +===== + +uShare runs from the console only. It supports the usual --help option +which displays usage and option information. + +Usage: ushare [-n name] [-i interface] [-c directory] [[-c directory] ...] +Options: + -n, --name=NAME Set UPnP Friendly Name (default is 'uShare') + -i, --interface=IFACE Use IFACE Network Interface (default is 'eth0') + -f, --cfg=FILE Config file to be used + -p, --port=PORT Forces the HTTP server to run on PORT + -q, --telnet-port=PORT Forces the TELNET server to run on PORT + -c, --content=DIR Share the content of DIR directory. + -w, --no-web Disable the control web page (enabled by default) + -t, --no-telnet Disable the TELNET control (enabled by default) + -o, --override-iconv-err If iconv fails parsing name, still add to media contents (hoping the renderer can handle it) + -v, --verbose Set verbose display + -x, --xbox Use XboX 360 compliant profile + -d, --dlna Use DLNA compliant profile (PlayStation3 needs this) + -D, --daemon Run as a daemon. + -V, --version Display the version of uShare and exit + -h, --help Display this help + +uShare gets its configuration from the /etc/ushare.conf file. +You can force configuration options through command line. + +uShare expects one or several directory argument (-c argument), +specifying where multimedia files are stored. You should probably also use +the -i option to specify which interface uShare should listen on. + + ushare -c /shares + ushare -c /shares1 --content=/shares2 + +You can also perform remote control of uShare UPnP Media Server through its +web interface. This let you define new content locations at runtime or +update the currently shared one in case the filesystem has changed. +Just go to : + + http://ip_address:port/web/ushare.html + +See the manual page for more details : + + man ushare + +Supported File Formats List +=========================== + +- Video files : asf, avi, dv, divx, wmv, mjpg, mjpeg, mpeg, mpg, mpe, + mp2p, vob, mp2t, m1v, m2v, m4v, m4p, mp4ps, ts, ogm, mkv, + rmvb, mov, qt + +- Audio files : aac, ac3, aif, aiff, at3p, au, snd, dts, rmi, mp1, mp2, mp3, + mp4, mpa, ogg, wav, pcm, lpcm, l16, wma, mka, ra, rm, ram + +- Images files : bmp, ico, gif, jpeg, jpg, jpe, pcd, png, pnm, ppm, + qti, qtf, qtif, tif, tiff + +- Playlist files : pls, m3u, asx + +If you want uShare to support more file formats, simply add its properties +in the src/mime.c table. Do not forget to send a patch to update uShare. + +Feedback +======== + +Please send bug reports, suggestions, ideas, comments or patches to : + + ushare@geexbox.org + +Known bugs and limitations +========================== + +If you need to listen on more than one interface, you will have to start +multiple instances of the media server. + +uShare keeps some information on files in memory. +If your multimedia collection is huge, this might be a problem. + +Thanks +====== + +Many thanks to Oskar Liljeblad for its original work on GMediaServer +(which is much more functionnal in terms of UPnP A/V features that +uShare will ever be). + +References +========== + +Note that this list of references is not complete. + + * UPnP(TM) Standards (http://www.upnp.org/standardizeddcps/default.asp) + +Trademarks +========== + +UPnP(TM) is a trademark of the UPnP(TM) Implementers Corporation. + +- diff --git a/THANKS b/THANKS new file mode 100644 index 0000000..4f1bb92 --- /dev/null +++ b/THANKS @@ -0,0 +1,8 @@ +Many thanks to Oskar Liljeblad for its original work on GMediaServer +(which is much more functionnal in terms of UPnP A/V features +that uShare will ever be). + +Special Thanks to Rémi Turboult for some UPnP standard compliance check. + +Thanks to Jason Woodward for making me aware of most of +DLNA specification internals. diff --git a/TODO b/TODO new file mode 100644 index 0000000..d0c8966 --- /dev/null +++ b/TODO @@ -0,0 +1,3 @@ +- change daemon running user (currently root) +- add static libupnp in sources for easier maintaining and fixes +- add authentication to web interface ?? diff --git a/configure b/configure new file mode 100755 index 0000000..20a08ed --- /dev/null +++ b/configure @@ -0,0 +1,740 @@ +#!/bin/sh +# +# uShare configure script - (c) 2007 Benjamin Zores +# +# (fully inspirated from ffmpeg configure script, thanks to Fabrice Bellard) +# + +# make sure we are running under a compatible shell +unset foo +(: ${foo%%bar}) 2>/dev/null && ! (: ${foo?}) 2>/dev/null +if test "$?" != 0; then + if test "x$USHARE_CONFIGURE_EXEC" = x; then + USHARE_CONFIGURE_EXEC=1 + export USHARE_CONFIGURE_EXEC + exec bash "$0" "$@" + exec ksh "$0" "$@" + exec /usr/xpg4/bin/sh "$0" "$@" + fi + echo "No compatible shell script interpreter found." + exit 1 +fi + +show_help(){ + echo "Usage: configure [options]" + echo "Options: [defaults in brackets after descriptions]" + echo + echo "Standard options:" + echo " --help print this message" + echo " --log[=FILE|yes|no] log tests and output to FILE [config.log]" + echo " --prefix=PREFIX install in PREFIX [$PREFIX]" + echo " --bindir=DIR install binaries in DIR [PREFIX/bin]" + echo " --sysconfdir=DIR configuration files DIR [PREFIX/etc]" + echo " --localedir=DIR use locales from DIR [PREFIX/share/locale]" + echo "" + echo "Extended options:" + echo " --enable-dlna enable DLNA support through libldna" + echo " --disable-dlna disable DLNA support" + echo " --disable-nls do not use Native Language Support" + echo "" + echo "Search paths:" + echo " --with-libupnp-dir=DIR check for libupnp installed in DIR" + echo " --with-libdlna-dir=DIR check for libdlna installed in DIR" + echo "" + echo "Advanced options (experts only):" + echo " --enable-debug enable debugging symbols" + echo " --disable-debug disable debugging symbols" + echo " --disable-strip disable stripping of executables at installation" + echo " --disable-optimize disable compiler optimization" + echo " --cross-prefix=PREFIX use PREFIX for compilation tools [$cross_prefix]" + echo " --cross-compile assume a cross-compiler is used" + exit 1 +} + +log(){ + echo "$@" >>$logfile +} + +log_file(){ + log BEGIN $1 + cat -n $1 >>$logfile + log END $1 +} + +echolog(){ + log "$@" + echo "$@" +} + +clean(){ + rm -f $TMPC $TMPO $TMPE $TMPS +} + +die(){ + echolog "$@" + if enabled logging; then + echo "See file \"$logfile\" produced by configure for more details." + else + echo "Rerun configure with logging enabled (do not use --log=no) for more details." + fi + clean + exit 1 +} + +enabled(){ + eval test "x\$$1" = "xyes" +} + +flags_saved(){ + (: ${SAVE_CFLAGS?}) 2>/dev/null +} + +save_flags(){ + flags_saved && return + SAVE_CFLAGS="$CFLAGS" + SAVE_LDFLAGS="$LDFLAGS" + SAVE_extralibs="$extralibs" +} + +restore_flags(){ + CFLAGS="$SAVE_CFLAGS" + LDFLAGS="$SAVE_LDFLAGS" + extralibs="$SAVE_extralibs" + unset SAVE_CFLAGS + unset SAVE_LDFLAGS + unset SAVE_extralibs +} + +temp_cflags(){ + temp_append CFLAGS "$@" +} + +temp_ldflags(){ + temp_append LDFLAGS "$@" +} + +temp_extralibs(){ + temp_append extralibs "$@" +} + +temp_append(){ + local var + var=$1 + shift + save_flags + append_var "$var" "$@" +} + +append_var(){ + local var f + var=$1 + shift + for f in $@; do + if eval echo \$$var | grep -qv -e "$f"; then + eval "$var=\"\$$var $f\"" + fi + done +} + +append(){ + local var + var=$1 + shift + flags_saved && append_var "SAVE_$var" "$@" + append_var "$var" "$@" +} + +add_cflags(){ + append CFLAGS "$@" +} + +add_ldflags(){ + append LDFLAGS "$@" +} + +add_extralibs(){ + append extralibs "$@" +} + +add_clog(){ + echo "#define $1 $2" >> $CONFIG_H +} + +add_clog_str(){ + echo "#define $1 \"$2\"" >> $CONFIG_H +} + +check_cmd(){ + log "$@" + "$@" >>$logfile 2>&1 +} + +check_cc(){ + log check_cc "$@" + cat >$TMPC + log_file $TMPC + check_cmd $cc $CFLAGS "$@" -c -o $TMPO $TMPC +} + +check_cpp(){ + log check_cpp "$@" + cat >$TMPC + log_file $TMPC + check_cmd $cc $CFLAGS "$@" -E -o $TMPO $TMPC +} + +check_ld(){ + log check_ld "$@" + check_cc || return + check_cmd $cc $LDFLAGS "$@" -o $TMPE $TMPO $extralibs +} + +check_exec(){ + check_ld "$@" && { enabled cross_compile || $TMPE >>$logfile 2>&1; } +} + +check_cflags(){ + log check_cflags "$@" + check_cc "$@" < +int x; +EOF +} + +check_func(){ + local func + log check_func "$@" + func=$1 + shift + check_ld "$@" <> $CONFIGFILE +} + +expand_var(){ + v="$1" + while true; do + eval t="$v" + test "$t" = "$v" && break + v="$t" + done + echo "$v" +} + +# set temporary file name +if test ! -z "$TMPDIR" ; then + TMPDIR1="${TMPDIR}" +elif test ! -z "$TEMPDIR" ; then + TMPDIR1="${TEMPDIR}" +else + TMPDIR1="/tmp" +fi + +TMPC="${TMPDIR1}/ushare-${RANDOM}-$$-${RANDOM}.c" +TMPO="${TMPDIR1}/ushare-${RANDOM}-$$-${RANDOM}.o" +TMPE="${TMPDIR1}/ushare-${RANDOM}-$$-${RANDOM}" +TMPS="${TMPDIR1}/ushare-${RANDOM}-$$-${RANDOM}.S" + +CONFIGFILE="config.mak" +CONFIG_H="config.h" + +################################################# +# set default parameters +################################################# +logging="yes" +logfile="config.log" +PREFIX="/usr/local" +bindir='${PREFIX}/bin' +sysconfdir='${PREFIX}/etc' +localedir='${PREFIX}/share/locale' +dlna="no" +nls="yes" +cc="gcc" +make="make" +strip="strip" +cpu=`uname -m` +optimize="yes" +debug="no" +dostrip="yes" +extralibs="" +installstrip="-s" +cross_compile="no" +INSTALL="/usr/bin/install -c" +VERSION="1.1a" +system_name=`uname -s 2>&1` + +################################################# +# set cpu variable and specific cpu flags +################################################# +case "$cpu" in + i386|i486|i586|i686|i86pc|BePC) + cpu="x86" + ;; + x86_64|amd64) + cpu="x86" + canon_arch="`$cc -dumpmachine | sed -e 's,\([^-]*\)-.*,\1,'`" + if [ x"$canon_arch" = x"x86_64" -o x"$canon_arch" = x"amd64" ]; then + if [ -z "`echo $CFLAGS | grep -- -m32`" ]; then + cpu="x86_64" + fi + fi + ;; +# armv4l is a subset of armv5tel + arm|armv4l|armv5tel) + cpu="armv4l" + ;; + alpha) + cpu="alpha" + ;; + "Power Macintosh"|ppc|ppc64|powerpc) + cpu="powerpc" + ;; + mips|mipsel|IP*) + cpu="mips" + ;; + sun4u|sparc64) + cpu="sparc64" + ;; + sparc) + cpu="sparc" + ;; + sh4) + cpu="sh4" + ;; + parisc|parisc64) + cpu="parisc" + ;; + s390|s390x) + cpu="s390" + ;; + m68k) + cpu="m68k" + ;; + ia64) + cpu="ia64" + ;; + bfin) + cpu="bfin" + ;; + *) + cpu="unknown" + ;; +esac + +# OS test booleans functions +issystem() { + test "`echo $system_name | tr A-Z a-z`" = "`echo $1 | tr A-Z a-z`" +} + +linux() { issystem "Linux" || issystem "uClinux" ; return "$?" ; } +sunos() { issystem "SunOS" ; return "$?" ; } +hpux() { issystem "HP-UX" ; return "$?" ; } +irix() { issystem "IRIX" ; return "$?" ; } +aix() { issystem "AIX" ; return "$?" ; } +cygwin() { issystem "CYGWIN" ; return "$?" ; } +freebsd() { issystem "FreeBSD" || issystem "GNU/kFreeBSD"; return "$?" ; } +netbsd() { issystem "NetBSD" ; return "$?" ; } +bsdos() { issystem "BSD/OS" ; return "$?" ; } +openbsd() { issystem "OpenBSD" ; return "$?" ; } +bsd() { freebsd || netbsd || bsdos || openbsd ; return "$?" ; } +qnx() { issystem "QNX" ; return "$?" ; } +darwin() { issystem "Darwin" ; return "$?" ; } +gnu() { issystem "GNU" ; return "$?" ; } +mingw32() { issystem "MINGW32" ; return "$?" ; } +morphos() { issystem "MorphOS" ; return "$?" ; } +amigaos() { issystem "AmigaOS" ; return "$?" ; } +win32() { cygwin || mingw32 ; return "$?" ; } +beos() { issystem "BEOS" ; return "$?" ; } + +################################################# +# check options +################################################# +for opt do + optval="${opt#*=}" + case "$opt" in + --log) + ;; + --log=*) logging="$optval" + ;; + --prefix=*) PREFIX="$optval"; force_prefix=yes + ;; + --bindir=*) bindir="$optval"; + ;; + --sysconfdir=*) sysconfdir="$optval"; + ;; + --localedir=*) localedir="$optval"; + ;; + --with-libupnp-dir=*) libupnpdir="$optval"; + ;; + --with-libdlna-dir=*) libdlnadir="$optval"; + ;; + --disable-nls) nls="no" + ;; + --enable-dlna) dlna="yes" + ;; + --disable-dlna) dlna="no" + ;; + --enable-debug) debug="yes" + ;; + --disable-debug) debug="no" + ;; + --disable-strip) dostrip="no" + ;; + --disable-optimize) optimize="no" + ;; + --cross-prefix=*) cross_prefix="$optval" + ;; + --cross-compile) cross_compile="yes" + ;; + --help) show_help + ;; + *) + echo "Unknown option \"$opt\"." + echo "See $0 --help for available options." + exit 1 + ;; + esac +done + +if [ -n "$cross_prefix" ]; then + cross_compile="yes" + cc="${cross_prefix}${cc}" + strip="${cross_prefix}${strip}" +else + [ -n "$CC" ] && cc="$CC" + [ -n "$STRIP" ] && strip="$STRIP" +fi +[ -n "$MAKE" ] && make="$MAKE" + +################################################# +# create logging file +################################################# +if test "$logging" != no; then + enabled logging || logfile="$logging" + echo "# $0 $@" >$logfile + set >>$logfile +else + logfile=/dev/null +fi + +################################################# +# compiler sanity check +################################################# +echolog "Checking for compiler available..." +check_exec <&1 | grep version | grep Apple`"; then + add_cflags "-faltivec" + else + add_cflags "-maltivec -mabi=altivec" + fi + fi +fi + +check_header altivec.h && _altivec_h=yes || _altivec_h=no + +# check if our compiler supports Motorola AltiVec C API +if enabled altivec; then + if enabled _altivec_h; then + inc_altivec_h="#include " + else + inc_altivec_h= + fi + check_cc < +int main(void) { +#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2) +return 0; +#else +#error no vector builtins +#endif +} +EOF + +# test for mm3dnow.h +test "$cpu" = "x86_64" && march=k8 || march=athlon +check_cc -march=$march < +int main(void) { +__m64 b1; +b1 = _m_pswapd(b1); +_m_femms(); +return 0; +} +EOF + +# --- +# big/little-endian test +if test "$cross_compile" = "no"; then + check_ld < +int main(int argc, char ** argv){ + volatile uint32_t i=0x01234567; + return (*((uint8_t*)(&i))) == 0x67; +} +EOF +else +# programs cannot be launched if cross compiling, so make a static guess + if test "$cpu" = "powerpc" -o "$cpu" = "mips" ; then + bigendian="yes" + fi +fi + +# add some useful compiler flags if supported +add_cflags -I.. +check_cflags -W +check_cflags -Wall +check_cflags -D_LARGEFILE_SOURCE +check_cflags -D_FILE_OFFSET_BITS=64 +check_cflags -D_REENTRANT +linux && add_cflags -D_GNU_SOURCE + +################################################# +# check for debug symbols +################################################# +if enabled debug; then + add_cflags -g3 + add_cflags -DHAVE_DEBUG + dostrip=no +fi + +if enabled optimize; then + if test -n "`$cc -v 2>&1 | grep xlc`"; then + add_cflags "-O5" + add_ldflags "-O5" + else + add_cflags "-O3" + fi +fi + +################################################# +# check for locales (optional) +################################################# +echolog "Checking for locales ..." +check_header locale.h && add_cflags -DHAVE_LOCALE_H +check_lib locale.h setlocale "" && add_cflags -DHAVE_SETLOCALE + +################################################# +# check for ifaddr (optional) +################################################# +echolog "Checking for ifaddrs ..." +check_lib ifaddrs.h getifaddrs "" && add_cflags -DHAVE_IFADDRS_H + +################################################# +# check for langinfo (optional) +################################################# +echolog "Checking for langinfo ..." +check_header langinfo.h && add_cflags -DHAVE_LANGINFO_H +check_lib langinfo.h nl_langinfo "" && add_cflags -DHAVE_LANGINFO_CODESET + +################################################# +# check for iconv (optional) +################################################# +echolog "Checking for iconv ..." +check_lib iconv.h iconv "" && add_cflags -DHAVE_ICONV + +################################################# +# check for libupnp and friends (mandatory) +################################################# +if [ -n "$libupnpdir" ]; then + check_cflags -I$libupnpdir + check_ldflags -L$libupnpdir +fi + +echolog "Checking for libixml ..." +check_lib upnp/ixml.h ixmlRelaxParser -lixml || die "Error, can't find libixml !" + +echolog "Checking for libthreadutil ..." +check_lib upnp/ThreadPool.h ThreadPoolAdd "-lthreadutil -lpthread" || die "Error, can't find libthreadutil !" +add_extralibs -lpthread + +libupnp_min_version="1.4.2" +echolog "Checking for libupnp >= $libupnp_min_version ..." +check_lib upnp/upnp.h UpnpSetMaxContentLength -lupnp || die "Error, can't find libupnp !" +check_lib_version libupnp $libupnp_min_version || die "Error, libupnp < $libupnp_min_version !" +add_cflags `pkg-config libupnp --cflags` +add_extralibs `pkg-config libupnp --libs` + +################################################# +# check for libdlna (mandatory if enabled) +################################################# +if test "$dlna" = "yes"; then + libdlna_min_version="0.2.1" + echolog "Checking for libdlna >= $libdlna_min_version ..." + if [ -n "$libdlnadir" ]; then + check_cflags -I$libdlnadir + check_ldflags -L$libdlnadir + fi + check_lib dlna.h dlna_register_all_media_profiles -ldlna || die "Error, can't find libdlna (install it or use --disable-dlna) !" + check_lib_version libdlna $libdlna_min_version || die "Error, libdlna < $libdlna_min_version !" + add_cflags -DHAVE_DLNA + add_cflags `pkg-config libdlna --cflags` + add_extralibs `pkg-config libdlna --libs` +fi + +################################################# +# logging result +################################################# +echolog "" +echolog "uShare: configure is OK" +echolog " version $VERSION" +echolog " using libupnp `pkg-config libupnp --modversion`" +test $dlna = yes && echolog " using libdlna `pkg-config libdlna --modversion`" +echolog "configuration:" +echolog " install prefix $PREFIX" +echolog " configuration dir $sysconfdir" +echolog " locales dir $localedir" +echolog " NLS support $nls" +echolog " DLNA support $dlna" +echolog " C compiler $cc" +echolog " STRIP $strip" +echolog " make $make" +echolog " CPU $cpu ($tune)" +echolog " debug symbols $debug" +echolog " strip symbols $dostrip" +echolog " optimize $optimize" +echolog "" +echolog " CFLAGS $CFLAGS" +echolog " LDFLAGS $LDFLAGS" +echolog " extralibs $extralibs" +echolog "" + +################################################# +# save configs attributes +################################################# +echolog "Creating $CONFIGFILE ..." + +echo "# Automatically generated by configure - do not modify!" > $CONFIGFILE + +append_config "VERSION=$VERSION" + +append_config "PREFIX=$PREFIX" +append_config "prefix=\$(DESTDIR)\$(PREFIX)" +append_config "bindir=\$(DESTDIR)$bindir" +append_config "sysconfdir=\$(DESTDIR)$sysconfdir" +append_config "localedir=\$(DESTDIR)$localedir" + +append_config "MAKE=$make" +append_config "CC=$cc" +append_config "LN=ln" +if enabled dostrip; then + append_config "STRIP=$strip" + append_config "INSTALLSTRIP=$installstrip" +else + append_config "STRIP=echo ignoring strip" + append_config "INSTALLSTRIP=" +fi +append_config "EXTRALIBS=$extralibs" + +append_config "OPTFLAGS=$CFLAGS" +append_config "LDFLAGS=$LDFLAGS" +append_config "INSTALL=$INSTALL" + +append_config "DEBUG=$debug" + + +echolog "Creating $CONFIG_H ..." +echo "/* Automatically generated by configure - do not modify! */" > $CONFIG_H +add_clog_str VERSION "$VERSION" +add_clog_str PACKAGE "ushare" +add_clog_str PACKAGE_NAME "uShare" +add_clog_str SYSCONFDIR `expand_var "${sysconfdir}"` +add_clog_str LOCALEDIR `expand_var "${localedir}"` +test $nls = yes && add_clog CONFIG_NLS 1 + +clean +exit 0 diff --git a/po/Makefile b/po/Makefile new file mode 100644 index 0000000..ce70c34 --- /dev/null +++ b/po/Makefile @@ -0,0 +1,89 @@ +ifeq (,$(wildcard ../config.mak)) +$(error "../config.mak is not present, run configure !") +endif +include ../config.mak + +top_srcdir = .. + +DOMAIN = ushare + +# Languages to generate +LANGS = fr de + +GMSGFMT = /usr/bin/msgfmt +XGETTEXT = /usr/bin/xgettext +MSGMERGE = msgmerge +MSGMERGE_UPDATE = /usr/bin/msgmerge --update +MSGINIT = msginit +MSGCONV = msgconv +MSGFILTER = msgfilter + +EXTRADIST = $(POFILES) $(GMOFILES) \ + POTFILES \ + $(DOMAIN).pot \ + +GMOFILES = $(addsuffix .gmo,$(LANGS)) +POFILES = $(addsuffix .po,$(LANGS)) +POTFILES = $(shell for f in `cat POTFILES`; do echo -n "$(top_srcdir)/$$f "; done) + + +all: stamp-po + +stamp-po: $(DOMAIN).pot + $(MAKE) $(GMOFILES) + touch $@ + +.po.gmo: + $(GMSGFMT) -c --statistics -o $@ $< + +.SUFFIXES: .po .gmo + + +$(POFILES): $(DOMAIN).pot + $(MSGMERGE_UPDATE) $@ $(DOMAIN).pot; + + +update-po: + $(MAKE) $(DOMAIN).pot-update + $(MAKE) update-gmo + +update-gmo: $(GMOFILES) + @: + +.PHONY: update-po update-gmo + +# This target rebuilds $(DOMAIN).pot; it is an expensive operation. +# Note that $(DOMAIN).pot is not touched if it doesn't need to be changed. +$(DOMAIN).pot-update: $(POTFILES) POTFILES + $(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \ + --add-comments=TRANSLATORS: \ + --keyword=_ --keyword=N_ \ + --files-from=POTFILES \ + --copyright-holder='http://ushare.geexbox.org' \ + --msgid-bugs-address="ushare@geexbox.org" \ + -o $(DOMAIN).pot + +# This rule has no dependencies: we don't need to update $(DOMAIN).pot at +# every "make" invocation, only create it when it is missing. +# Only "make $(DOMAIN).pot-update" or "make dist" will force an update. +$(DOMAIN).pot: + $(MAKE) $(DOMAIN).pot-update + +clean: + -$(RM) -f stamp-po + +distclean: clean + -$(RM) -f $(GMOFILES) + +install: $(GMOFILES) + for gmo in $(GMOFILES); do \ + lang=`echo $$gmo | sed -e 's/\.gmo$$//'`; \ + $(INSTALL) -d $(localedir)/$$lang/LC_MESSAGES; \ + $(INSTALL) -m 644 $$lang.gmo $(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo; \ + done + + +dist-all: $(GMOFILES) + cp $(EXTRADIST) $(SRCS) Makefile $(DIST) + +.PHONY: dist-all install clean distclean diff --git a/po/POTFILES b/po/POTFILES new file mode 100644 index 0000000..1d8db4f --- /dev/null +++ b/po/POTFILES @@ -0,0 +1,4 @@ +src/ushare.c +src/metadata.c +src/cfgparser.c +src/presentation.c diff --git a/po/de.gmo b/po/de.gmo new file mode 100644 index 0000000..a84cc35 Binary files /dev/null and b/po/de.gmo differ diff --git a/po/de.po b/po/de.po new file mode 100644 index 0000000..1395d19 --- /dev/null +++ b/po/de.po @@ -0,0 +1,308 @@ +# German translations for uShare package. +# Copyright (C) 2006 http://ushare.geexbox.org +# This file is distributed under the same license as the uShare package. +# uShare Team , 2006. +# +msgid "" +msgstr "" +"Project-Id-Version: uShare 0.9.8\n" +"Report-Msgid-Bugs-To: ushare@geexbox.org\n" +"POT-Creation-Date: 2007-11-18 16:25+0100\n" +"PO-Revision-Date: 2006-09-20 22:12+0200\n" +"Last-Translator: German uShare Team \n" +"Language-Team: German uShare Team \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/ushare.c:263 +msgid "Stopping UPnP Service ...\n" +msgstr "UPnP Service wird gestoppt ...\n" + +#: src/ushare.c:316 +msgid "Initializing UPnP subsystem ...\n" +msgstr "Initialisiere UPnP Subsystem ...\n" + +#: src/ushare.c:320 +msgid "Cannot initialize UPnP subsystem\n" +msgstr "Kann UPnP Subsystem nicht initialisieren\n" + +#: src/ushare.c:325 +msgid "Could not set Max content UPnP\n" +msgstr "" + +#: src/ushare.c:328 +msgid "Starting in XboX 360 compliant profile ...\n" +msgstr "" + +#: src/ushare.c:333 +msgid "Starting in DLNA compliant profile ...\n" +msgstr "" + +#: src/ushare.c:342 +#, c-format +msgid "UPnP MediaServer listening on %s:%d\n" +msgstr "UPnP MediaServer lauscht auf %s:%d\n" + +#: src/ushare.c:350 +msgid "Cannot set virtual directory callbacks\n" +msgstr "Kann virtuelle Verzeichnis-Callbacks nicht setzen\n" + +#: src/ushare.c:358 +msgid "Cannot add virtual directory for web server\n" +msgstr "Kann virtuelles Verzeichnis für den Webserver nicht zurücksetzen\n" + +#: src/ushare.c:368 src/ushare.c:386 +msgid "Cannot register UPnP device\n" +msgstr "Kann Upnp Gerät nicht registrieren\n" + +#: src/ushare.c:376 +msgid "Cannot unregister UPnP device\n" +msgstr "Kann UpnP Gerät nicht deregistrieren\n" + +#: src/ushare.c:391 +msgid "Sending UPnP advertisement for device ...\n" +msgstr "Sende UpnP Advertisement für Gerät...\n" + +#: src/ushare.c:394 +msgid "Listening for control point connections ...\n" +msgstr "Lausche auf Control Point Verbindungen...\n" + +#: src/ushare.c:450 +#, c-format +msgid "Interface %s is down.\n" +msgstr "Interface %s ist down.\n" + +#: src/ushare.c:451 src/ushare.c:462 +msgid "Recheck uShare's configuration and try again !\n" +msgstr "Überprüfe uShare's Konfiguration und versuche es erneut!\n" + +#: src/ushare.c:461 +#, c-format +msgid "Can't find interface %s.\n" +msgstr "Kann Interface %s nicht finden.\n" + +#: src/ushare.c:622 +msgid "Reloading configuration...\n" +msgstr "Lade Konfiguration erneut...\n" + +#: src/ushare.c:683 src/ushare.c:771 +msgid "Error: no content directory to be shared.\n" +msgstr "Fehler: Kein Content Verzeichnis zum sharen.\n" + +#: src/ushare.c:691 +#, c-format +msgid "%s (version %s), a lightweight UPnP Media Server.\n" +msgstr "%s (Version %s), ein leichtgewichtiger UPnP Media Server.\n" + +#: src/ushare.c:693 +#, c-format +msgid "Benjamin Zores (C) 2005-2007, for GeeXboX Team.\n" +msgstr "Benjamin Zores (C) 2005-2007, für das GeeXboX Team.\n" + +#: src/ushare.c:694 +#, c-format +msgid "See http://ushare.geexbox.org/ for updates.\n" +msgstr "Für Updates gehe auf http://ushare.geexbox.org/.\n" + +#: src/ushare.c:711 +msgid "" +"Server is shutting down: other clients will be notified soon, Bye bye ...\n" +msgstr "" + +#: src/ushare.c:746 +#, c-format +msgid "Warning: can't parse file \"%s\".\n" +msgstr "Warnung: Kann Datei nicht parsen \"%s\".\n" + +#: src/ushare.c:802 +#, c-format +msgid "Error: failed to daemonize program : %s\n" +msgstr "Fehler: Daemonisierung des Programms nicht möglich: %s\n" + +#: src/ushare.c:825 +msgid "Terminates the uShare server" +msgstr "" + +#: src/metadata.c:402 +msgid "Failed to add the RB lookup tree\n" +msgstr "" + +#: src/metadata.c:530 +msgid "Cannot create RB tree for lookups\n" +msgstr "" + +#: src/metadata.c:537 +msgid "Building Metadata List ...\n" +msgstr "Erzeuge Metadata List ...\n" + +#: src/metadata.c:550 +#, c-format +msgid "Looking for files in content directory : %s\n" +msgstr "Suche nach Dateien im Content Verzeichnis : %s\n" + +#: src/metadata.c:574 +#, c-format +msgid "Found %d files and subdirectories.\n" +msgstr "Fand %d Dateien und Unterverzeichnisse.\n" + +#: src/cfgparser.c:166 +#, c-format +msgid "Warning: port doesn't fit IANA port assignements.\n" +msgstr "Warnung: Port passt nicht zur IANA Port Zuordnung.\n" + +#: src/cfgparser.c:168 +#, c-format +msgid "" +"Warning: Only Dynamic or Private Ports can be used (from 49152 through " +"65535)\n" +msgstr "" +"Warnung: Nur dynamische und private Ports können benutzt werden (von 49152 " +"bis 65535)\n" + +#: src/cfgparser.c:318 +#, c-format +msgid "" +"Usage: ushare [-n name] [-i interface] [-p port] [-c directory] [[-c " +"directory]...]\n" +msgstr "" +"Usage: ushare [-n Name] [-i Interface] [-p Port] [-c Verzeichnis] [[-c " +"Verzeichnis]...]\n" + +#: src/cfgparser.c:319 +#, c-format +msgid "Options:\n" +msgstr "Optionen:\n" + +#: src/cfgparser.c:320 +#, c-format +msgid " -n, --name=NAME\tSet UPnP Friendly Name (default is '%s')\n" +msgstr " -n, --name=NAME\tSet UPnP-freundlicher Name (vorgegeben ist '%s')\n" + +#: src/cfgparser.c:322 +#, c-format +msgid " -i, --interface=IFACE\tUse IFACE Network Interface (default is '%s')\n" +msgstr "" +" -i, --interface=IFACE\tBenutze IFACE Netzwerk Interface (vorgegeben ist '%" +"s')\n" + +#: src/cfgparser.c:324 +#, c-format +msgid " -f, --cfg=FILE\t\tConfig file to be used\n" +msgstr "" + +#: src/cfgparser.c:325 +#, c-format +msgid " -p, --port=PORT\tForces the HTTP server to run on PORT\n" +msgstr "" +" -p, --port=PORT\tLegt das PORT fest, auf dem der HTTP Server laufen soll\n" + +#: src/cfgparser.c:326 +#, fuzzy, c-format +msgid " -q, --telnet-port=PORT\tForces the TELNET server to run on PORT\n" +msgstr "" +" -p, --port=PORT\tLegt das PORT fest, auf dem der HTTP Server laufen soll\n" + +#: src/cfgparser.c:327 +#, c-format +msgid " -c, --content=DIR\tShare the content of DIR directory\n" +msgstr "" +" -c, --content=DIR\tShare den Inhalt des folgenden Content Verzeichnisses" +"(DIR)\n" + +#: src/cfgparser.c:328 +#, c-format +msgid " -w, --no-web\t\tDisable the control web page (enabled by default)\n" +msgstr "" +" -w, --no-web\t\tDeaktiviere die Kontrollwebseite (Vorgabe: Webseite ist " +"aktiviert)\n" + +#: src/cfgparser.c:329 +#, fuzzy, c-format +msgid " -t, --no-telnet\tDisable the TELNET control (enabled by default)\n" +msgstr "" +" -w, --no-web\t\tDeaktiviere die Kontrollwebseite (Vorgabe: Webseite ist " +"aktiviert)\n" + +#: src/cfgparser.c:330 +#, c-format +msgid "" +" -o, --override-iconv-err\tIf iconv fails parsing name, still add to media " +"contents (hoping the renderer can handle it)\n" +msgstr "" + +#: src/cfgparser.c:331 +#, c-format +msgid " -v, --verbose\t\tSet verbose display\n" +msgstr " -v, --verbose\t\tSetze Verbose Display\n" + +#: src/cfgparser.c:332 +#, c-format +msgid " -x, --xbox\t\tUse XboX 360 compliant profile\n" +msgstr "" + +#: src/cfgparser.c:334 +#, c-format +msgid " -d, --dlna\t\tUse DLNA compliant profile (PlayStation3 needs this)\n" +msgstr "" + +#: src/cfgparser.c:336 +#, c-format +msgid " -D, --daemon\t\tRun as a daemon\n" +msgstr " -D, --daemon\t\tAusführen des Porgramms in Daemon Mode\n" + +#: src/cfgparser.c:337 +#, c-format +msgid " -V, --version\t\tDisplay the version of uShare and exit\n" +msgstr "" +" -V, --version\t\tZeigt die uShare Version an und beendet uShare danach\n" + +#: src/cfgparser.c:338 +#, c-format +msgid " -h, --help\t\tDisplay this help\n" +msgstr " -h, --help\t\tZeigt diese Hilfe an\n" + +#: src/presentation.c:108 src/presentation.c:143 +msgid "uShare Information Page" +msgstr "uShare Informationsseite" + +#: src/presentation.c:155 +msgid "uShare UPnP A/V Media Server" +msgstr "uShare UPnP A/V Media Server" + +#: src/presentation.c:156 +msgid "Information Page" +msgstr "Informationsseite" + +#: src/presentation.c:163 +msgid "Version" +msgstr "Version" + +#: src/presentation.c:166 +msgid "Device UDN" +msgstr "Gerät UDN" + +#: src/presentation.c:168 +msgid "Number of shared files and directories" +msgstr "Anzahl der geshareten Dateien und Verzeichnisse" + +#: src/presentation.c:178 +msgid "Share" +msgstr "Share" + +#: src/presentation.c:184 +msgid "unShare!" +msgstr "unShare!" + +#: src/presentation.c:190 +msgid "Add a new share : " +msgstr "Füge neues Share hinzu : " + +#: src/presentation.c:196 +msgid "Share!" +msgstr "Share!" + +#: src/presentation.c:207 +msgid "Refresh Shares ..." +msgstr "Erneuere Shares ..." diff --git a/po/fr.gmo b/po/fr.gmo new file mode 100644 index 0000000..5835ed1 Binary files /dev/null and b/po/fr.gmo differ diff --git a/po/fr.po b/po/fr.po new file mode 100644 index 0000000..6aa823e --- /dev/null +++ b/po/fr.po @@ -0,0 +1,307 @@ +# French translations for uShare package. +# Copyright (C) 2005-2007 http://ushare.geexbox.org +# This file is distributed under the same license as the ushare package. +# Alexis Saettler , 2005-2007. +# +msgid "" +msgstr "" +"Project-Id-Version: uShare 1.0\n" +"Report-Msgid-Bugs-To: ushare@geexbox.org\n" +"POT-Creation-Date: 2007-11-18 16:25+0100\n" +"PO-Revision-Date: 2007-07-05 20:55+0200\n" +"Last-Translator: Alexis Saettler \n" +"Language-Team: uShare French trad \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: src/ushare.c:263 +msgid "Stopping UPnP Service ...\n" +msgstr "Arrêt du service UPnP ...\n" + +#: src/ushare.c:316 +msgid "Initializing UPnP subsystem ...\n" +msgstr "Initialisation du système UPnP ...\n" + +#: src/ushare.c:320 +msgid "Cannot initialize UPnP subsystem\n" +msgstr "Impossible d'initialiser le système UPnP ...\n" + +#: src/ushare.c:325 +msgid "Could not set Max content UPnP\n" +msgstr "Impossible de configurer la taille max pour UPnP\n" + +#: src/ushare.c:328 +msgid "Starting in XboX 360 compliant profile ...\n" +msgstr "Démarre en mode compatibilité pour XboX 360 ...\n" + +#: src/ushare.c:333 +msgid "Starting in DLNA compliant profile ...\n" +msgstr "Démarre en mode compatibilité DLNA ...\n" + +#: src/ushare.c:342 +#, c-format +msgid "UPnP MediaServer listening on %s:%d\n" +msgstr "Serveur Multimédia UPnP en écoute sur %s:%d\n" + +#: src/ushare.c:350 +msgid "Cannot set virtual directory callbacks\n" +msgstr "Impossible de créer les retours des dossiers virtuels\n" + +#: src/ushare.c:358 +msgid "Cannot add virtual directory for web server\n" +msgstr "Impossible d'ajouter le dossier virtuel du serveur web\n" + +#: src/ushare.c:368 src/ushare.c:386 +msgid "Cannot register UPnP device\n" +msgstr "Impossible d'enregistrer le périphérique UPnP\n" + +#: src/ushare.c:376 +msgid "Cannot unregister UPnP device\n" +msgstr "Impossible de supprimer le périphérique UPnP\n" + +#: src/ushare.c:391 +msgid "Sending UPnP advertisement for device ...\n" +msgstr "Envoit des informations UPnP du périphérique ...\n" + +#: src/ushare.c:394 +msgid "Listening for control point connections ...\n" +msgstr "Attente de connexions ...\n" + +#: src/ushare.c:450 +#, c-format +msgid "Interface %s is down.\n" +msgstr "L'interface %s est arrêtée.\n" + +#: src/ushare.c:451 src/ushare.c:462 +msgid "Recheck uShare's configuration and try again !\n" +msgstr "Revérifiez la configuration de uShare et recommencez !\n" + +#: src/ushare.c:461 +#, c-format +msgid "Can't find interface %s.\n" +msgstr "Impossible de trouver l'interface %s.\n" + +#: src/ushare.c:622 +msgid "Reloading configuration...\n" +msgstr "Rechargement de la configuration...\n" + +#: src/ushare.c:683 src/ushare.c:771 +msgid "Error: no content directory to be shared.\n" +msgstr "Erreur : aucun répertoire de contenus à partager.\n" + +#: src/ushare.c:691 +#, c-format +msgid "%s (version %s), a lightweight UPnP Media Server.\n" +msgstr "%s (version %s), un serveur mutlimédia UPnP léger.\n" + +#: src/ushare.c:693 +#, c-format +msgid "Benjamin Zores (C) 2005-2007, for GeeXboX Team.\n" +msgstr "Benjamin Zores (C) 2005-2007, pour l'équipe GeeXboX.\n" + +#: src/ushare.c:694 +#, c-format +msgid "See http://ushare.geexbox.org/ for updates.\n" +msgstr "Voir http://ushare.geexbox.org/ pour les mises à jour.\n" + +#: src/ushare.c:711 +msgid "" +"Server is shutting down: other clients will be notified soon, Bye bye ...\n" +msgstr "" +"Arrêt du serveur en cours : les autres clients seront notifié bientôt. Au " +"revoir !\n" + +#: src/ushare.c:746 +#, c-format +msgid "Warning: can't parse file \"%s\".\n" +msgstr "Attention : impossible de lire le fichier \"%s\".\n" + +#: src/ushare.c:802 +#, c-format +msgid "Error: failed to daemonize program : %s\n" +msgstr "Erreur : impossible de démoniser le programme : %s\n" + +#: src/ushare.c:825 +msgid "Terminates the uShare server" +msgstr "Arrêt du serveur uShare" + +#: src/metadata.c:402 +msgid "Failed to add the RB lookup tree\n" +msgstr "Impossible d'ajouter l'arbre de recherche RB\n" + +#: src/metadata.c:530 +msgid "Cannot create RB tree for lookups\n" +msgstr "Impossible de créer l'arbre RB pour les recherches\n" + +#: src/metadata.c:537 +msgid "Building Metadata List ...\n" +msgstr "Création de la liste de données\n" + +#: src/metadata.c:550 +#, c-format +msgid "Looking for files in content directory : %s\n" +msgstr "Recherche des fichiers dans le répertoire de contenus : %s\n" + +#: src/metadata.c:574 +#, c-format +msgid "Found %d files and subdirectories.\n" +msgstr "%d fichiers et sous-répertoires trouvés.\n" + +#: src/cfgparser.c:166 +#, c-format +msgid "Warning: port doesn't fit IANA port assignements.\n" +msgstr "Attention : ce port ne suit pas les recommendations IANA.\n" + +#: src/cfgparser.c:168 +#, c-format +msgid "" +"Warning: Only Dynamic or Private Ports can be used (from 49152 through " +"65535)\n" +msgstr "" +"Attention : seuls les ports dynamiques ou privés peuvent être utilisés (de " +"49152 à 65535)\n" + +#: src/cfgparser.c:318 +#, c-format +msgid "" +"Usage: ushare [-n name] [-i interface] [-p port] [-c directory] [[-c " +"directory]...]\n" +msgstr "" +"Usage : ushare [-n nom] [-i interface] [-p port] [-c répertoire] [[-c " +"répertoire]...]\n" + +#: src/cfgparser.c:319 +#, c-format +msgid "Options:\n" +msgstr "Options :\n" + +#: src/cfgparser.c:320 +#, c-format +msgid " -n, --name=NAME\tSet UPnP Friendly Name (default is '%s')\n" +msgstr " -n, --name=NOM\t\tDéfini le nom UPnP ('%s' par défaut)\n" + +#: src/cfgparser.c:322 +#, c-format +msgid " -i, --interface=IFACE\tUse IFACE Network Interface (default is '%s')\n" +msgstr "" +" -i, --interface=IFACE\tUtiliser l'interface réseau IFACE ('%s' par défaut)\n" + +#: src/cfgparser.c:324 +#, c-format +msgid " -f, --cfg=FILE\t\tConfig file to be used\n" +msgstr " -f, --cfg=FICHIER\tFichier de configuration à utiliser\n" + +#: src/cfgparser.c:325 +#, c-format +msgid " -p, --port=PORT\tForces the HTTP server to run on PORT\n" +msgstr " -p, --port=PORT\tForce le serveur HTTP à utiliser ce PORT\n" + +#: src/cfgparser.c:326 +#, c-format +msgid " -q, --telnet-port=PORT\tForces the TELNET server to run on PORT\n" +msgstr " -q, --telnet-port=PORT\tForce le server TELNET à utiliser ce PORT\n" + +#: src/cfgparser.c:327 +#, c-format +msgid " -c, --content=DIR\tShare the content of DIR directory\n" +msgstr " -c, --content=REP\tPartage le contenu du répertoire REP\n" + +#: src/cfgparser.c:328 +#, c-format +msgid " -w, --no-web\t\tDisable the control web page (enabled by default)\n" +msgstr "" +" -w, --no-web\t\tDésactive la page de contrôle web (activée par défaut)\n" + +#: src/cfgparser.c:329 +#, c-format +msgid " -t, --no-telnet\tDisable the TELNET control (enabled by default)\n" +msgstr "" +" -t, --no-telnet\tDésactiver le contrôle par TELNET (activé par défaut)\n" + +#: src/cfgparser.c:330 +#, c-format +msgid "" +" -o, --override-iconv-err\tIf iconv fails parsing name, still add to media " +"contents (hoping the renderer can handle it)\n" +msgstr "" +" -o, --override-iconv-err\tSi iconv n'arrive pas à traduire le nom du " +"fichier, ajouter tout de même le contenu (en espérant que le lecteur puisse " +"le lire)\n" + +#: src/cfgparser.c:331 +#, c-format +msgid " -v, --verbose\t\tSet verbose display\n" +msgstr " -v, --verbose\t\tMode bavard\n" + +#: src/cfgparser.c:332 +#, c-format +msgid " -x, --xbox\t\tUse XboX 360 compliant profile\n" +msgstr " -x, --xbox\t\tUtiliser le mode de compatibilité XboX 360\n" + +#: src/cfgparser.c:334 +#, c-format +msgid " -d, --dlna\t\tUse DLNA compliant profile (PlayStation3 needs this)\n" +msgstr "" +" -d, --dlna\t\tUtiliser le mode de compatibilité DLNA pour PlayStation3\n" + +#: src/cfgparser.c:336 +#, c-format +msgid " -D, --daemon\t\tRun as a daemon\n" +msgstr " -D, --daemon\t\tDémarrer en mode démon\n" + +#: src/cfgparser.c:337 +#, c-format +msgid " -V, --version\t\tDisplay the version of uShare and exit\n" +msgstr "" +" -V, --version\t\tAffiche la version de uShare et quitte le programme\n" + +#: src/cfgparser.c:338 +#, c-format +msgid " -h, --help\t\tDisplay this help\n" +msgstr " -h, --help\t\tAffiche cette aide\n" + +#: src/presentation.c:108 src/presentation.c:143 +msgid "uShare Information Page" +msgstr "Page d'information de uShare" + +#: src/presentation.c:155 +msgid "uShare UPnP A/V Media Server" +msgstr "uShare - Serveur Multimédia A/V UPnP" + +#: src/presentation.c:156 +msgid "Information Page" +msgstr "Page d'information" + +#: src/presentation.c:163 +msgid "Version" +msgstr "Version" + +#: src/presentation.c:166 +msgid "Device UDN" +msgstr "Périphérique UDN" + +#: src/presentation.c:168 +msgid "Number of shared files and directories" +msgstr "Nombre de fichiers et sous-répertoires partagés" + +#: src/presentation.c:178 +msgid "Share" +msgstr "Partage" + +#: src/presentation.c:184 +msgid "unShare!" +msgstr "Départager !" + +#: src/presentation.c:190 +msgid "Add a new share : " +msgstr "Ajouter un partage : " + +#: src/presentation.c:196 +msgid "Share!" +msgstr "Partager !" + +#: src/presentation.c:207 +msgid "Refresh Shares ..." +msgstr "Rafraîchir les partages ..." diff --git a/po/ushare.pot b/po/ushare.pot new file mode 100644 index 0000000..a5d4389 --- /dev/null +++ b/po/ushare.pot @@ -0,0 +1,293 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR http://ushare.geexbox.org +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: ushare@geexbox.org\n" +"POT-Creation-Date: 2007-11-18 17:38+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/ushare.c:263 +msgid "Stopping UPnP Service ...\n" +msgstr "" + +#: src/ushare.c:316 +msgid "Initializing UPnP subsystem ...\n" +msgstr "" + +#: src/ushare.c:320 +msgid "Cannot initialize UPnP subsystem\n" +msgstr "" + +#: src/ushare.c:325 +msgid "Could not set Max content UPnP\n" +msgstr "" + +#: src/ushare.c:328 +msgid "Starting in XboX 360 compliant profile ...\n" +msgstr "" + +#: src/ushare.c:333 +msgid "Starting in DLNA compliant profile ...\n" +msgstr "" + +#: src/ushare.c:342 +#, c-format +msgid "UPnP MediaServer listening on %s:%d\n" +msgstr "" + +#: src/ushare.c:350 +msgid "Cannot set virtual directory callbacks\n" +msgstr "" + +#: src/ushare.c:358 +msgid "Cannot add virtual directory for web server\n" +msgstr "" + +#: src/ushare.c:368 src/ushare.c:386 +msgid "Cannot register UPnP device\n" +msgstr "" + +#: src/ushare.c:376 +msgid "Cannot unregister UPnP device\n" +msgstr "" + +#: src/ushare.c:391 +msgid "Sending UPnP advertisement for device ...\n" +msgstr "" + +#: src/ushare.c:394 +msgid "Listening for control point connections ...\n" +msgstr "" + +#: src/ushare.c:450 +#, c-format +msgid "Interface %s is down.\n" +msgstr "" + +#: src/ushare.c:451 src/ushare.c:462 +msgid "Recheck uShare's configuration and try again !\n" +msgstr "" + +#: src/ushare.c:461 +#, c-format +msgid "Can't find interface %s.\n" +msgstr "" + +#: src/ushare.c:622 +msgid "Reloading configuration...\n" +msgstr "" + +#: src/ushare.c:683 src/ushare.c:771 +msgid "Error: no content directory to be shared.\n" +msgstr "" + +#: src/ushare.c:691 +#, c-format +msgid "%s (version %s), a lightweight UPnP Media Server.\n" +msgstr "" + +#: src/ushare.c:693 +#, c-format +msgid "Benjamin Zores (C) 2005-2007, for GeeXboX Team.\n" +msgstr "" + +#: src/ushare.c:694 +#, c-format +msgid "See http://ushare.geexbox.org/ for updates.\n" +msgstr "" + +#: src/ushare.c:711 +msgid "" +"Server is shutting down: other clients will be notified soon, Bye bye ...\n" +msgstr "" + +#: src/ushare.c:746 +#, c-format +msgid "Warning: can't parse file \"%s\".\n" +msgstr "" + +#: src/ushare.c:802 +#, c-format +msgid "Error: failed to daemonize program : %s\n" +msgstr "" + +#: src/ushare.c:825 +msgid "Terminates the uShare server" +msgstr "" + +#: src/metadata.c:402 +msgid "Failed to add the RB lookup tree\n" +msgstr "" + +#: src/metadata.c:530 +msgid "Cannot create RB tree for lookups\n" +msgstr "" + +#: src/metadata.c:537 +msgid "Building Metadata List ...\n" +msgstr "" + +#: src/metadata.c:550 +#, c-format +msgid "Looking for files in content directory : %s\n" +msgstr "" + +#: src/metadata.c:574 +#, c-format +msgid "Found %d files and subdirectories.\n" +msgstr "" + +#: src/cfgparser.c:166 +#, c-format +msgid "Warning: port doesn't fit IANA port assignements.\n" +msgstr "" + +#: src/cfgparser.c:168 +#, c-format +msgid "" +"Warning: Only Dynamic or Private Ports can be used (from 49152 through " +"65535)\n" +msgstr "" + +#: src/cfgparser.c:318 +#, c-format +msgid "" +"Usage: ushare [-n name] [-i interface] [-p port] [-c directory] [[-c " +"directory]...]\n" +msgstr "" + +#: src/cfgparser.c:319 +#, c-format +msgid "Options:\n" +msgstr "" + +#: src/cfgparser.c:320 +#, c-format +msgid " -n, --name=NAME\tSet UPnP Friendly Name (default is '%s')\n" +msgstr "" + +#: src/cfgparser.c:322 +#, c-format +msgid " -i, --interface=IFACE\tUse IFACE Network Interface (default is '%s')\n" +msgstr "" + +#: src/cfgparser.c:324 +#, c-format +msgid " -f, --cfg=FILE\t\tConfig file to be used\n" +msgstr "" + +#: src/cfgparser.c:325 +#, c-format +msgid " -p, --port=PORT\tForces the HTTP server to run on PORT\n" +msgstr "" + +#: src/cfgparser.c:326 +#, c-format +msgid " -q, --telnet-port=PORT\tForces the TELNET server to run on PORT\n" +msgstr "" + +#: src/cfgparser.c:327 +#, c-format +msgid " -c, --content=DIR\tShare the content of DIR directory\n" +msgstr "" + +#: src/cfgparser.c:328 +#, c-format +msgid " -w, --no-web\t\tDisable the control web page (enabled by default)\n" +msgstr "" + +#: src/cfgparser.c:329 +#, c-format +msgid " -t, --no-telnet\tDisable the TELNET control (enabled by default)\n" +msgstr "" + +#: src/cfgparser.c:330 +#, c-format +msgid "" +" -o, --override-iconv-err\tIf iconv fails parsing name, still add to media " +"contents (hoping the renderer can handle it)\n" +msgstr "" + +#: src/cfgparser.c:331 +#, c-format +msgid " -v, --verbose\t\tSet verbose display\n" +msgstr "" + +#: src/cfgparser.c:332 +#, c-format +msgid " -x, --xbox\t\tUse XboX 360 compliant profile\n" +msgstr "" + +#: src/cfgparser.c:334 +#, c-format +msgid " -d, --dlna\t\tUse DLNA compliant profile (PlayStation3 needs this)\n" +msgstr "" + +#: src/cfgparser.c:336 +#, c-format +msgid " -D, --daemon\t\tRun as a daemon\n" +msgstr "" + +#: src/cfgparser.c:337 +#, c-format +msgid " -V, --version\t\tDisplay the version of uShare and exit\n" +msgstr "" + +#: src/cfgparser.c:338 +#, c-format +msgid " -h, --help\t\tDisplay this help\n" +msgstr "" + +#: src/presentation.c:108 src/presentation.c:143 +msgid "uShare Information Page" +msgstr "" + +#: src/presentation.c:155 +msgid "uShare UPnP A/V Media Server" +msgstr "" + +#: src/presentation.c:156 +msgid "Information Page" +msgstr "" + +#: src/presentation.c:163 +msgid "Version" +msgstr "" + +#: src/presentation.c:166 +msgid "Device UDN" +msgstr "" + +#: src/presentation.c:168 +msgid "Number of shared files and directories" +msgstr "" + +#: src/presentation.c:178 +msgid "Share" +msgstr "" + +#: src/presentation.c:184 +msgid "unShare!" +msgstr "" + +#: src/presentation.c:190 +msgid "Add a new share : " +msgstr "" + +#: src/presentation.c:196 +msgid "Share!" +msgstr "" + +#: src/presentation.c:207 +msgid "Refresh Shares ..." +msgstr "" diff --git a/scripts/Makefile b/scripts/Makefile new file mode 100644 index 0000000..e457d5d --- /dev/null +++ b/scripts/Makefile @@ -0,0 +1,26 @@ +ifeq (,$(wildcard ../config.mak)) +$(error "config.mak is not present, run configure !") +endif +include ../config.mak + +CONF_FILE = "ushare.conf" +INITD_FILE = "ushare" + +EXTRADIST = $(CONF_FILE) $(INITD_FILE) + +all: + +clean: + +distclean: + +install: + $(INSTALL) -d $(sysconfdir) + $(INSTALL) -m 644 $(CONF_FILE) $(sysconfdir) + $(INSTALL) -d $(sysconfdir)/init.d + $(INSTALL) -m 755 $(INITD_FILE) $(sysconfdir)/init.d + +dist-all: + cp $(EXTRADIST) $(SRCS) Makefile $(DIST) + +.PHONY: clean disctlean install dist-all diff --git a/scripts/ushare b/scripts/ushare new file mode 100755 index 0000000..c06c658 --- /dev/null +++ b/scripts/ushare @@ -0,0 +1,82 @@ +#!/bin/sh -e +# +# uShare init script +# +### BEGIN INIT INFO +# Provides: ushare +# Required-Start: $local_fs $syslog $network +# Should-Start: +# Required-Stop: +# Should-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: uShare +# Description: uShare UPnP (TM) A/V & DLNA Media Server +# You should edit configuration in /etc/ushare.conf file +# See http://ushare.geexbox.org for details +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/bin/ushare +NAME=ushare +DESC="uShare UPnP A/V & DLNA Media Server" +PIDFILE=/var/run/ushare.pid +CONFIGFILE=/etc/ushare.conf + +# abort if no executable exists +[ -x $DAEMON ] || exit 0 + +# Get lsb functions +. /lib/lsb/init-functions +. /etc/default/rcS + +[ -f /etc/default/ushare ] && . /etc/default/ushare + +checkpid() { + [ -e $PIDFILE ] || touch $PIDFILE +} + +check_shares() { + if [ -r "$CONFIGFILE" ]; then + . $CONFIGFILE + [ -n "$USHARE_DIR" ] && return 0 + fi + return 1 +} + +case "$1" in + start) + log_daemon_msg "Starting $DESC: $NAME" + if ! $(check_shares); then + log_warning_msg "No shares avalaible ..." + log_end_msg 0 + else + checkpid + start-stop-daemon --start --quiet --background --oknodo \ + --make-pidfile --pidfile $PIDFILE \ + --exec $DAEMON -- $USHARE_OPTIONS + log_end_msg $? + fi + ;; + stop) + log_daemon_msg "Stopping $DESC: $NAME" + start-stop-daemon --stop --signal 2 --quiet --oknodo --pidfile $PIDFILE + log_end_msg $? + ;; + reload|force-reload) + log_daemon_msg "Reloading $DESC: $NAME" + start-stop-daemon --stop --signal 1 --quiet --oknodo --pidfile $PIDFILE --exec $DAEMON + log_end_msg $? + ;; + restart) + $0 stop + $0 start + ;; + *) + N=/etc/init.d/$NAME + log_success_msg "Usage: $N {start|stop|restart|reload|force-reload}" + exit 1 + ;; +esac + +exit 0 diff --git a/scripts/ushare.conf b/scripts/ushare.conf new file mode 100644 index 0000000..85e0fba --- /dev/null +++ b/scripts/ushare.conf @@ -0,0 +1,45 @@ +# /etc/ushare.conf +# Configuration file for uShare + +# uShare UPnP Friendly Name (default is 'uShare'). +USHARE_NAME= + +# Interface to listen to (default is eth0). +# Ex : USHARE_IFACE=eth1 +USHARE_IFACE= + +# Port to listen to (default is random from IANA Dynamic Ports range) +# Ex : USHARE_PORT=49200 +USHARE_PORT= + +# Port to listen for Telnet connections +# Ex : USHARE_TELNET_PORT=1337 +USHARE_TELNET_PORT= + +# Directories to be shared (space or CSV list). +# Ex: USHARE_DIR=/dir1,/dir2 +USHARE_DIR= + +# Use to override what happens when iconv fails to parse a file name. +# The default uShare behaviour is to not add the entry in the media list +# This option overrides that behaviour and adds the non-iconv'ed string into +# the media list, with the assumption that the renderer will be able to +# handle it. Devices like Noxon 2 have no problem with strings being passed +# as is. (Umlauts for all!) +# +# Options are TRUE/YES/1 for override and anything else for default behaviour +USHARE_OVERRIDE_ICONV_ERR= + +# Enable Web interface (yes/no) +ENABLE_WEB= + +# Enable Telnet control interface (yes/no) +ENABLE_TELNET= + +# Use XboX 360 compatibility mode (yes/no) +ENABLE_XBOX= + +# Use DLNA profile (yes/no) +# This is needed for PlayStation3 to work (among other devices) +ENABLE_DLNA= + diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..bd933d1 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,87 @@ +ifeq (,$(wildcard ../config.mak)) +$(error "../config.mak is not present, run configure !") +endif +include ../config.mak + +PROG = ushare + +EXTRADIST = ushare.1 \ + cds.h \ + cms.h \ + msr.h \ + http.h \ + presentation.h \ + metadata.h \ + mime.h \ + services.h \ + buffer.h \ + util_iconv.h \ + content.h \ + cfgparser.h \ + trace.h \ + redblack.h \ + osdep.h \ + ctrl_telnet.h \ + ushare.h \ + gettext.h \ + minmax.h \ + + +SRCS = \ + cds.c \ + cms.c \ + msr.c \ + http.c \ + presentation.c \ + metadata.c \ + mime.c \ + services.c \ + buffer.c \ + util_iconv.c \ + content.c \ + cfgparser.c \ + trace.c \ + redblack.c \ + osdep.c \ + ctrl_telnet.c \ + ushare.c + +OBJS = $(SRCS:.c=.o) + +.SUFFIXES: .c .o + +all: depend $(PROG) + +.c.o: + $(CC) -c $(CFLAGS) $(OPTFLAGS) -o $@ $< + +$(PROG): $(OBJS) + $(CC) $(OBJS) $(LDFLAGS) $(EXTRALIBS) -o $@ + +clean: + -$(RM) -f *.o $(PROG) + -$(RM) -f .depend + +distclean: + +install: $(PROG) + $(INSTALL) -d $(bindir) + $(INSTALL) $(PROG) $(bindir) + $(STRIP) $(INSTALLSTRIP) $(bindir)/$(PROG) + +depend: + $(CC) -I.. -MM $(CFLAGS) $(SRCS) 1>.depend + +.PHONY: clean distclean install depend + +dist-all: + cp $(EXTRADIST) $(SRCS) Makefile $(DIST) + +.PHONY: dist-all + +# +# include dependency files if they exist +# +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 0000000..5aa2024 --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,106 @@ +/* buffer.c - String buffer manipulation tools. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + * + */ + +#include +#include +#include +#include + +#include "buffer.h" +#include "minmax.h" + +#define BUFFER_DEFAULT_CAPACITY 32768 + +struct buffer_t * +buffer_new (void) +{ + struct buffer_t *buffer = NULL; + + buffer = (struct buffer_t *) malloc (sizeof (struct buffer_t)); + if (!buffer) + return NULL; + + buffer->buf = NULL; + buffer->len = 0; + buffer->capacity = 0; + + return buffer; +} + +void +buffer_append (struct buffer_t *buffer, const char *str) +{ + size_t len; + + if (!buffer || !str) + return; + + if (!buffer->buf) + { + buffer->capacity = BUFFER_DEFAULT_CAPACITY; + buffer->buf = (char *) malloc (buffer->capacity * sizeof (char)); + memset (buffer->buf, '\0', buffer->capacity); + } + + len = buffer->len + strlen (str); + if (len >= buffer->capacity) + { + buffer->capacity = MAX (len + 1, 2 * buffer->capacity); + buffer->buf = realloc (buffer->buf, buffer->capacity); + } + + strcat (buffer->buf, str); + buffer->len += strlen (str); +} + +void +buffer_appendf (struct buffer_t *buffer, const char *format, ...) +{ + char str[BUFFER_DEFAULT_CAPACITY]; + int size; + va_list va; + + if (!buffer || !format) + return; + + va_start (va, format); + size = vsnprintf (str, BUFFER_DEFAULT_CAPACITY, format, va); + if (size >= BUFFER_DEFAULT_CAPACITY) + { + char* dynstr = (char *) malloc (size + 1); + vsnprintf (dynstr, size + 1, format, va); + buffer_append (buffer, dynstr); + free (dynstr); + } + else + buffer_append (buffer, str); + va_end (va); +} + +void +buffer_free (struct buffer_t *buffer) +{ + if (!buffer) + return; + + if (buffer->buf) + free (buffer->buf); + free (buffer); +} diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 0000000..3c37f8f --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,38 @@ +/* buffer.h - String buffer manipulation tools header. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + * + */ + +#ifndef _STRING_BUFFER_H_ +#define _STRING_BUFFER_H_ + +struct buffer_t { + char *buf; + size_t len; + size_t capacity; +}; + +struct buffer_t *buffer_new (void) + __attribute__ ((malloc)); +void buffer_free (struct buffer_t *buffer); + +void buffer_append (struct buffer_t *buffer, const char *str); +void buffer_appendf (struct buffer_t *buffer, const char *format, ...) + __attribute__ ((format (printf , 2, 3))); + +#endif /* _STRING_BUFFER_H_ */ diff --git a/src/cds.c b/src/cds.c new file mode 100644 index 0000000..bc88b3a --- /dev/null +++ b/src/cds.c @@ -0,0 +1,872 @@ +/* + * cds.c : GeeXboX uShare Content Directory Service + * Originally developped for the GeeXboX project. + * Parts of the code are originated from GMediaServer from Oskar Liljeblad. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#include +#include +#include + +#include "ushare.h" +#include "services.h" +#include "ushare.h" +#include "services.h" +#include "metadata.h" +#include "mime.h" +#include "buffer.h" +#include "minmax.h" + +/* Represent the CDS GetSearchCapabilities action. */ +#define SERVICE_CDS_ACTION_SEARCH_CAPS "GetSearchCapabilities" + +/* Represent the CDS GetSortCapabilities action. */ +#define SERVICE_CDS_ACTION_SORT_CAPS "GetSortCapabilities" + +/* Represent the CDS GetSystemUpdateID action. */ +#define SERVICE_CDS_ACTION_UPDATE_ID "GetSystemUpdateID" + +/* Represent the CDS Browse action. */ +#define SERVICE_CDS_ACTION_BROWSE "Browse" + +/* Represent the CDS Search action. */ +#define SERVICE_CDS_ACTION_SEARCH "Search" + +/* Represent the CDS SearchCaps argument. */ +#define SERVICE_CDS_ARG_SEARCH_CAPS "SearchCaps" + +/* Represent the CDS SortCaps argument. */ +#define SERVICE_CDS_ARG_SORT_CAPS "SortCaps" + +/* Represent the CDS UpdateId argument. */ +#define SERVICE_CDS_ARG_UPDATE_ID "Id" + +/* Represent the CDS StartingIndex argument. */ +#define SERVICE_CDS_ARG_START_INDEX "StartingIndex" + +/* Represent the CDS RequestedCount argument. */ +#define SERVICE_CDS_ARG_REQUEST_COUNT "RequestedCount" + +/* Represent the CDS ObjectID argument. */ +#define SERVICE_CDS_ARG_OBJECT_ID "ObjectID" + +/* Represent the CDS Filter argument. */ +#define SERVICE_CDS_ARG_FILTER "Filter" + +/* Represent the CDS BrowseFlag argument. */ +#define SERVICE_CDS_ARG_BROWSE_FLAG "BrowseFlag" + +/* Represent the CDS SortCriteria argument. */ +#define SERVICE_CDS_ARG_SORT_CRIT "SortCriteria" + +/* Represent the CDS SearchCriteria argument. */ +#define SERVICE_CDS_ARG_SEARCH_CRIT "SearchCriteria" + +/* Represent the CDS Root Object ID argument. */ +#define SERVICE_CDS_ROOT_OBJECT_ID "0" + +/* Represent the CDS DIDL Message Metadata Browse flag argument. */ +#define SERVICE_CDS_BROWSE_METADATA "BrowseMetadata" + +/* Represent the CDS DIDL Message DirectChildren Browse flag argument. */ +#define SERVICE_CDS_BROWSE_CHILDREN "BrowseDirectChildren" + +/* Represent the CDS DIDL Message Result argument. */ +#define SERVICE_CDS_DIDL_RESULT "Result" + +/* Represent the CDS DIDL Message NumberReturned argument. */ +#define SERVICE_CDS_DIDL_NUM_RETURNED "NumberReturned" + +/* Represent the CDS DIDL Message TotalMatches argument. */ +#define SERVICE_CDS_DIDL_TOTAL_MATCH "TotalMatches" + +/* Represent the CDS DIDL Message UpdateID argument. */ +#define SERVICE_CDS_DIDL_UPDATE_ID "UpdateID" + +/* DIDL parameters */ +/* Represent the CDS DIDL Message Header Namespace. */ +#define DIDL_NAMESPACE \ + "xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" " \ + "xmlns:dc=\"http://purl.org/dc/elements/1.1/\" " \ + "xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\"" + +/* Represent the CDS DIDL Message Header Tag. */ +#define DIDL_LITE "DIDL-Lite" + +/* Represent the CDS DIDL Message Item value. */ +#define DIDL_ITEM "item" + +/* Represent the CDS DIDL Message Item ID value. */ +#define DIDL_ITEM_ID "id" + +/* Represent the CDS DIDL Message Item Parent ID value. */ +#define DIDL_ITEM_PARENT_ID "parentID" + +/* Represent the CDS DIDL Message Item Restricted value. */ +#define DIDL_ITEM_RESTRICTED "restricted" + +/* Represent the CDS DIDL Message Item UPnP Class value. */ +#define DIDL_ITEM_CLASS "upnp:class" + +/* Represent the CDS DIDL Message Item Title value. */ +#define DIDL_ITEM_TITLE "dc:title" + +/* Represent the CDS DIDL Message Item Resource value. */ +#define DIDL_RES "res" + +/* Represent the CDS DIDL Message Item Protocol Info value. */ +#define DIDL_RES_INFO "protocolInfo" + +/* Represent the CDS DIDL Message Item Resource Size value. */ +#define DIDL_RES_SIZE "size" + +/* Represent the CDS DIDL Message Container value. */ +#define DIDL_CONTAINER "container" + +/* Represent the CDS DIDL Message Container ID value. */ +#define DIDL_CONTAINER_ID "id" + +/* Represent the CDS DIDL Message Container Parent ID value. */ +#define DIDL_CONTAINER_PARENT_ID "parentID" + +/* Represent the CDS DIDL Message Container number of children value. */ +#define DIDL_CONTAINER_CHILDS "childCount" + +/* Represent the CDS DIDL Message Container Restricted value. */ +#define DIDL_CONTAINER_RESTRICTED "restricted" + +/* Represent the CDS DIDL Message Container Searchable value. */ +#define DIDL_CONTAINER_SEARCH "searchable" + +/* Represent the CDS DIDL Message Container UPnP Class value. */ +#define DIDL_CONTAINER_CLASS "upnp:class" + +/* Represent the CDS DIDL Message Container Title value. */ +#define DIDL_CONTAINER_TITLE "dc:title" + +/* Represent the "upnp:class" reserved keyword for Search action */ +#define SEARCH_CLASS_MATCH_KEYWORD "(upnp:class = \"" + +/* Represent the "upnp:class derived from" reserved keyword */ +#define SEARCH_CLASS_DERIVED_KEYWORD "(upnp:class derivedfrom \"" + +/* Represent the "res@protocolInfo contains" reserved keyword */ +#define SEARCH_PROTOCOL_CONTAINS_KEYWORD "(res@protocolInfo contains \"" + +/* Represent the Search default keyword */ +#define SEARCH_OBJECT_KEYWORD "object" + +/* Represent the Search 'AND' connector keyword */ +#define SEARCH_AND ") and (" + +static bool +filter_has_val (const char *filter, const char *val) +{ + char *x = NULL, *token = NULL; + char *m_buffer = NULL, *buffer; + int len = strlen (val); + bool ret = false; + + if (!strcmp (filter, "*")) + return true; + + x = strdup (filter); + if (x) + { + m_buffer = (char*) malloc (strlen (x)); + if (m_buffer) + { + buffer = m_buffer; + token = strtok_r (x, ",", &buffer); + while (token) + { + if (*val == '@') + token = strchr (token, '@'); + if (token && !strncmp (token, val, len)) + { + ret = true; + break; + } + token = strtok_r (NULL, ",", &buffer); + } + free (m_buffer); + } + free (x); + } + return ret; +} + +/* UPnP ContentDirectory Service actions */ +static bool +cds_get_search_capabilities (struct action_event_t *event) +{ + upnp_add_response (event, SERVICE_CDS_ARG_SEARCH_CAPS, ""); + + return event->status; +} + +static bool +cds_get_sort_capabilities (struct action_event_t *event) +{ + upnp_add_response (event, SERVICE_CDS_ARG_SORT_CAPS, ""); + + return event->status; +} + +static bool +cds_get_system_update_id (struct action_event_t *event) +{ + upnp_add_response (event, SERVICE_CDS_ARG_UPDATE_ID, + SERVICE_CDS_ROOT_OBJECT_ID); + + return event->status; +} + +static void +didl_add_header (struct buffer_t *out) +{ + buffer_appendf (out, "<%s %s>", DIDL_LITE, DIDL_NAMESPACE); +} + +static void +didl_add_footer (struct buffer_t *out) +{ + buffer_appendf (out, "", DIDL_LITE); +} + +static void +didl_add_tag (struct buffer_t *out, char *tag, char *value) +{ + if (value) + buffer_appendf (out, "<%s>%s", tag, value, tag); +} + +static void +didl_add_param (struct buffer_t *out, char *param, char *value) +{ + if (value) + buffer_appendf (out, " %s=\"%s\"", param, value); +} + +static void +didl_add_value (struct buffer_t *out, char *param, off_t value) +{ + buffer_appendf (out, " %s=\"%lld\"", param, value); +} + +static void +didl_add_item (struct buffer_t *out, int item_id, + int parent_id, char *restricted, char *class, char *title, + char *protocol_info, off_t size, char *url, char *filter) +{ + buffer_appendf (out, "<%s", DIDL_ITEM); + didl_add_value (out, DIDL_ITEM_ID, item_id); + didl_add_value (out, DIDL_ITEM_PARENT_ID, parent_id); + didl_add_param (out, DIDL_ITEM_RESTRICTED, restricted); + buffer_append (out, ">"); + + didl_add_tag (out, DIDL_ITEM_CLASS, class); + didl_add_tag (out, DIDL_ITEM_TITLE, title); + + if (filter_has_val (filter, DIDL_RES)) + { + buffer_appendf (out, "<%s", DIDL_RES); + // protocolInfo is required : + didl_add_param (out, DIDL_RES_INFO, protocol_info); + if (filter_has_val (filter, "@"DIDL_RES_SIZE)) + didl_add_value (out, DIDL_RES_SIZE, size); + buffer_append (out, ">"); + if (url) + { + extern struct ushare_t *ut; + buffer_appendf (out, "http://%s:%d%s/%s", + UpnpGetServerIpAddress (), ut->port, VIRTUAL_DIR, url); + } + buffer_appendf (out, "", DIDL_RES); + } + buffer_appendf (out, "", DIDL_ITEM); +} + +static void +didl_add_container (struct buffer_t *out, int id, int parent_id, + int child_count, char *restricted, char *searchable, + char *title, char *class) +{ + buffer_appendf (out, "<%s", DIDL_CONTAINER); + + didl_add_value (out, DIDL_CONTAINER_ID, id); + didl_add_value (out, DIDL_CONTAINER_PARENT_ID, parent_id); + if (child_count >= 0) + didl_add_value (out, DIDL_CONTAINER_CHILDS, child_count); + didl_add_param (out, DIDL_CONTAINER_RESTRICTED, restricted); + didl_add_param (out, DIDL_CONTAINER_SEARCH, searchable); + buffer_append (out, ">"); + + didl_add_tag (out, DIDL_CONTAINER_CLASS, class); + didl_add_tag (out, DIDL_CONTAINER_TITLE, title); + + buffer_appendf (out, "", DIDL_CONTAINER); +} + +static int +cds_browse_metadata (struct action_event_t *event, struct buffer_t *out, + int index, int count, struct upnp_entry_t *entry, + char *filter) +{ + int result_count = 0, c = 0; + + if (!entry) + return -1; + + if (entry->child_count == -1) /* item : file */ + { +#ifdef HAVE_DLNA + extern struct ushare_t *ut; +#endif /* HAVE_DLNA */ + + char *protocol = +#ifdef HAVE_DLNA + entry->dlna_profile ? + dlna_write_protocol_info (DLNA_PROTOCOL_INFO_TYPE_HTTP, + DLNA_ORG_PLAY_SPEED_NORMAL, + DLNA_ORG_CONVERSION_NONE, + DLNA_ORG_OPERATION_RANGE, + ut->dlna_flags, entry->dlna_profile) : +#endif /* HAVE_DLNA */ + mime_get_protocol (entry->mime_type); + + didl_add_header (out); +#ifdef HAVE_DLNA + entry->dlna_profile ? + didl_add_item (out, entry->id, entry->parent + ? entry->parent->id : -1, "false", + dlna_profile_upnp_object_item (entry->dlna_profile), + entry->title, + protocol, entry->size, + entry->url, filter) : +#endif /* HAVE_DLNA */ + didl_add_item (out, entry->id, entry->parent + ? entry->parent->id : -1, "false", + entry->mime_type->mime_class, entry->title, + protocol, entry->size, + entry->url, filter); + + didl_add_footer (out); + free (protocol); + + for (c = index; c < MIN (index + count, entry->child_count); c++) + result_count++; + } + else /* container : directory */ + { + didl_add_header (out); + didl_add_container (out, entry->id, entry->parent + ? entry->parent->id : -1, entry->child_count, + "true", "true", entry->title, + entry->mime_type->mime_class); + didl_add_footer (out); + + result_count = 1; + } + + upnp_add_response (event, SERVICE_CDS_DIDL_RESULT, out->buf); + upnp_add_response (event, SERVICE_CDS_DIDL_NUM_RETURNED, "1"); + upnp_add_response (event, SERVICE_CDS_DIDL_TOTAL_MATCH, "1"); + + return result_count; +} + +static int +cds_browse_directchildren (struct action_event_t *event, + struct buffer_t *out, int index, + int count, struct upnp_entry_t *entry, char *filter) +{ + struct upnp_entry_t **childs; + int s, result_count = 0; + char tmp[32]; + + if (entry->child_count == -1) /* item : file */ + return -1; + + didl_add_header (out); + + /* go to the child pointed out by index */ + childs = entry->childs; + for (s = 0; s < index; s++) + if (*childs) + childs++; + + /* UPnP CDS compliance : If starting index = 0 and requested count = 0 + then all children must be returned */ + if (index == 0 && count == 0) + count = entry->child_count; + + for (; *childs; childs++) + { + if (count == 0 || result_count < count) + /* only fetch the requested count number or all entries if count = 0 */ + { + if ((*childs)->child_count >= 0) /* container */ + didl_add_container (out, (*childs)->id, (*childs)->parent ? + (*childs)->parent->id : -1, + (*childs)->child_count, "true", NULL, + (*childs)->title, + (*childs)->mime_type->mime_class); + else /* item */ + { +#ifdef HAVE_DLNA + extern struct ushare_t *ut; +#endif /* HAVE_DLNA */ + + char *protocol = +#ifdef HAVE_DLNA + (*childs)->dlna_profile ? + dlna_write_protocol_info (DLNA_PROTOCOL_INFO_TYPE_HTTP, + DLNA_ORG_PLAY_SPEED_NORMAL, + DLNA_ORG_CONVERSION_NONE, + DLNA_ORG_OPERATION_RANGE, + ut->dlna_flags, (*childs)->dlna_profile) : +#endif /* HAVE_DLNA */ + mime_get_protocol ((*childs)->mime_type); + +#ifdef HAVE_DLNA + (*childs)->dlna_profile ? + didl_add_item (out, (*childs)->id, + (*childs)->parent ? (*childs)->parent->id : -1, + "true", dlna_profile_upnp_object_item ((*childs)->dlna_profile), + (*childs)->title, protocol, + (*childs)->size, (*childs)->url, filter) : +#endif /* HAVE_DLNA */ + didl_add_item (out, (*childs)->id, + (*childs)->parent ? (*childs)->parent->id : -1, + "true", (*childs)->mime_type->mime_class, + (*childs)->title, protocol, + (*childs)->size, (*childs)->url, filter); + + free (protocol); + } + result_count++; + } + } + + didl_add_footer (out); + + upnp_add_response (event, SERVICE_CDS_DIDL_RESULT, out->buf); + sprintf (tmp, "%d", result_count); + upnp_add_response (event, SERVICE_CDS_DIDL_NUM_RETURNED, tmp); + sprintf (tmp, "%d", entry->child_count); + upnp_add_response (event, SERVICE_CDS_DIDL_TOTAL_MATCH, tmp); + + return result_count; +} + +static bool +cds_browse (struct action_event_t *event) +{ + extern struct ushare_t *ut; + struct upnp_entry_t *entry = NULL; + int result_count = 0, index, count, id, sort_criteria; + char *flag = NULL; + char *filter = NULL; + struct buffer_t *out = NULL; + bool metadata; + + if (!event) + return false; + + /* Check for status */ + if (!event->status) + return false; + + /* check if metadatas have been well inited */ + if (!ut->init) + return false; + + /* Retrieve Browse arguments */ + index = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_START_INDEX); + count = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_REQUEST_COUNT); + id = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_OBJECT_ID); + flag = upnp_get_string (event->request, SERVICE_CDS_ARG_BROWSE_FLAG); + filter = upnp_get_string (event->request, SERVICE_CDS_ARG_FILTER); + sort_criteria = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_SORT_CRIT); + + if (!flag || !filter) + return false; + + /* Check arguments validity */ + if (!strcmp (flag, SERVICE_CDS_BROWSE_METADATA)) + { + if (index) + { + free (flag); + return false; + } + metadata = true; + } + else if (!strcmp (flag, SERVICE_CDS_BROWSE_CHILDREN)) + metadata = false; + else + { + free (flag); + return false; + } + free (flag); + + entry = upnp_get_entry (ut, id); + if (!entry && (id < ut->starting_id)) + entry = upnp_get_entry (ut, ut->starting_id); + + if (!entry) + { + free (filter); + return false; + } + + out = buffer_new (); + if (!out) + { + free (filter); + return false; + } + + if (metadata) + result_count = + cds_browse_metadata (event, out, index, count, entry, filter); + else + result_count = + cds_browse_directchildren (event, out, index, count, entry, filter); + free (filter); + + if (result_count < 0) + { + buffer_free (out); + return false; + } + + buffer_free (out); + upnp_add_response (event, SERVICE_CDS_DIDL_UPDATE_ID, + SERVICE_CDS_ROOT_OBJECT_ID); + + return event->status; +} + +static bool +matches_search (char *search_criteria, struct upnp_entry_t *entry) +{ + char keyword[256] = SEARCH_OBJECT_KEYWORD; + bool derived_from = false, protocol_contains = false, result = false; + char *quote_closed = NULL, *and_clause = NULL; +#ifdef HAVE_DLNA + extern struct ushare_t *ut; +#endif /* HAVE_DLNA */ + char *protocol = +#ifdef HAVE_DLNA + entry->dlna_profile ? + dlna_write_protocol_info (DLNA_PROTOCOL_INFO_TYPE_HTTP, + DLNA_ORG_PLAY_SPEED_NORMAL, + DLNA_ORG_CONVERSION_NONE, + DLNA_ORG_OPERATION_RANGE, + ut->dlna_flags, entry->dlna_profile) : +#endif /* HAVE_DLNA */ + mime_get_protocol (entry->mime_type); + + if (!strncmp (search_criteria, SEARCH_CLASS_MATCH_KEYWORD, + strlen (SEARCH_CLASS_MATCH_KEYWORD))) + { + strncpy (keyword, search_criteria + + strlen (SEARCH_CLASS_MATCH_KEYWORD), sizeof (keyword)); + quote_closed = strchr (keyword, '"'); + + if (quote_closed) + *quote_closed = '\0'; + } + else if (!strncmp (search_criteria, SEARCH_CLASS_DERIVED_KEYWORD, + strlen (SEARCH_CLASS_DERIVED_KEYWORD))) + { + derived_from = true; + strncpy (keyword, search_criteria + + strlen (SEARCH_CLASS_DERIVED_KEYWORD), sizeof (keyword)); + quote_closed = strchr (keyword, '"'); + + if (quote_closed) + *quote_closed = '\0'; + } + else if (!strncmp (search_criteria, SEARCH_PROTOCOL_CONTAINS_KEYWORD, + strlen (SEARCH_PROTOCOL_CONTAINS_KEYWORD))) + { + protocol_contains = true; + strncpy (keyword, search_criteria + + strlen (SEARCH_PROTOCOL_CONTAINS_KEYWORD), sizeof (keyword)); + quote_closed = strchr (keyword, '"'); + + if (quote_closed) + *quote_closed = '\0'; + } + + if (derived_from && entry->mime_type + && !strncmp (entry->mime_type->mime_class, keyword, strlen (keyword))) + result = true; + else if (protocol_contains && strstr (protocol, keyword)) + result = true; + else if (entry->mime_type && + !strcmp (entry->mime_type->mime_class, keyword)) + result = true; + free (protocol); + + and_clause = strstr (search_criteria, SEARCH_AND); + if (and_clause) + return (result && + matches_search (and_clause + strlen (SEARCH_AND) -1, entry)); + + return true; +} + +static int +cds_search_directchildren_recursive (struct buffer_t *out, int count, + struct upnp_entry_t *entry, char *filter, + char *search_criteria) +{ + struct upnp_entry_t **childs; + int result_count = 0; + + if (entry->child_count == -1) /* item : file */ + return -1; + + /* go to the first child */ + childs = entry->childs; + + for (; *childs; childs++) + { + if (count == 0 || result_count < count) + /* only fetch the requested count number or all entries if count = 0 */ + { + if ((*childs)->child_count >= 0) /* container */ + { + int new_count; + new_count = cds_search_directchildren_recursive + (out, (count == 0) ? 0 : (count - result_count), + (*childs), filter, search_criteria); + result_count += new_count; + } + else /* item */ + { + if (matches_search (search_criteria, *childs)) + { +#ifdef HAVE_DLNA + extern struct ushare_t *ut; +#endif /* HAVE_DLNA */ + char *protocol = +#ifdef HAVE_DLNA + (*childs)->dlna_profile ? + dlna_write_protocol_info(DLNA_PROTOCOL_INFO_TYPE_HTTP, + DLNA_ORG_PLAY_SPEED_NORMAL, + DLNA_ORG_CONVERSION_NONE, + DLNA_ORG_OPERATION_RANGE, + ut->dlna_flags, (*childs)->dlna_profile): +#endif /* HAVE_DLNA */ + mime_get_protocol ((*childs)->mime_type); + +#ifdef HAVE_DLNA + (*childs)->dlna_profile ? + didl_add_item (out, (*childs)->id, + (*childs)->parent ? (*childs)->parent->id : -1, + "true", dlna_profile_upnp_object_item ((*childs)->dlna_profile), + (*childs)->title, protocol, + (*childs)->size, (*childs)->url, filter) : +#endif /* HAVE_DLNA */ + didl_add_item (out, (*childs)->id, + (*childs)->parent ? (*childs)->parent->id : -1, + "true", (*childs)->mime_type->mime_class, + (*childs)->title, protocol, + (*childs)->size, (*childs)->url, filter); + free (protocol); + result_count++; + } + } + } + } + + return result_count; +} + +static int +cds_search_directchildren (struct action_event_t *event, + struct buffer_t *out, int index, + int count, struct upnp_entry_t *entry, + char *filter, char *search_criteria) +{ + struct upnp_entry_t **childs; + int s, result_count = 0; + char tmp[32]; + + index = 0; + + if (entry->child_count == -1) /* item : file */ + return -1; + + didl_add_header (out); + + /* go to the child pointed out by index */ + childs = entry->childs; + for (s = 0; s < index; s++) + if (*childs) + childs++; + + /* UPnP CDS compliance : If starting index = 0 and requested count = 0 + then all children must be returned */ + if (index == 0 && count == 0) + count = entry->child_count; + + for (; *childs; childs++) + { + if (count == 0 || result_count < count) + /* only fetch the requested count number or all entries if count = 0 */ + { + if ((*childs)->child_count >= 0) /* container */ + { + int new_count; + new_count = cds_search_directchildren_recursive + (out, (count == 0) ? 0 : (count - result_count), + (*childs), filter, search_criteria); + result_count += new_count; + } + else /* item */ + { + if (matches_search (search_criteria, *childs)) + { +#ifdef HAVE_DLNA + extern struct ushare_t *ut; +#endif /* HAVE_DLNA */ + char *protocol = +#ifdef HAVE_DLNA + (*childs)->dlna_profile ? + dlna_write_protocol_info(DLNA_PROTOCOL_INFO_TYPE_HTTP, + DLNA_ORG_PLAY_SPEED_NORMAL, + DLNA_ORG_CONVERSION_NONE, + DLNA_ORG_OPERATION_RANGE, + ut->dlna_flags, (*childs)->dlna_profile): +#endif /* HAVE_DLNA */ + mime_get_protocol ((*childs)->mime_type); + +#ifdef HAVE_DLNA + (*childs)->dlna_profile ? + didl_add_item (out, (*childs)->id, + (*childs)->parent ? (*childs)->parent->id : -1, + "true", dlna_profile_upnp_object_item ((*childs)->dlna_profile), + (*childs)->title, protocol, + (*childs)->size, (*childs)->url, filter) : +#endif /* HAVE_DLNA */ + didl_add_item (out, (*childs)->id, + (*childs)->parent ? (*childs)->parent->id : -1, + "true", (*childs)->mime_type->mime_class, + (*childs)->title, protocol, + (*childs)->size, (*childs)->url, filter); + free (protocol); + result_count++; + } + } + } + } + + didl_add_footer (out); + + upnp_add_response (event, SERVICE_CDS_DIDL_RESULT, out->buf); + + sprintf (tmp, "%d", result_count); + upnp_add_response (event, SERVICE_CDS_DIDL_NUM_RETURNED, tmp); + sprintf (tmp, "%d", result_count); + upnp_add_response (event, SERVICE_CDS_DIDL_TOTAL_MATCH, tmp); + + return result_count; +} + +static bool +cds_search (struct action_event_t *event) +{ + extern struct ushare_t *ut; + struct upnp_entry_t *entry = NULL; + int result_count = 0, index, count, id, sort_criteria; + char *search_criteria = NULL; + char *filter = NULL; + struct buffer_t *out = NULL; + + if (!event) + return false; + + /* Check for status */ + if (!event->status) + return false; + + /* check if metadatas have been well inited */ + if (!ut->init) + return false; + + /* Retrieve Browse arguments */ + index = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_START_INDEX); + count = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_REQUEST_COUNT); + id = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_OBJECT_ID); + + search_criteria = upnp_get_string (event->request, + SERVICE_CDS_ARG_SEARCH_CRIT); + filter = upnp_get_string (event->request, SERVICE_CDS_ARG_FILTER); + sort_criteria = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_SORT_CRIT); + + if (!search_criteria || !filter) + return false; + + entry = upnp_get_entry (ut, id); + + if (!entry && (id < ut->starting_id)) + entry = upnp_get_entry (ut, ut->starting_id); + + if (!entry) + return false; + + out = buffer_new (); + if (!out) + return false; + + result_count = + cds_search_directchildren (event, out, index, count, entry, + filter, search_criteria); + + if (result_count < 0) + { + buffer_free (out); + return false; + } + + buffer_free (out); + upnp_add_response (event, SERVICE_CDS_DIDL_UPDATE_ID, + SERVICE_CDS_ROOT_OBJECT_ID); + + free (search_criteria); + free (filter); + + return event->status; +} + +/* List of UPnP ContentDirectory Service actions */ +struct service_action_t cds_service_actions[] = { + { SERVICE_CDS_ACTION_SEARCH_CAPS, cds_get_search_capabilities }, + { SERVICE_CDS_ACTION_SORT_CAPS, cds_get_sort_capabilities }, + { SERVICE_CDS_ACTION_UPDATE_ID, cds_get_system_update_id }, + { SERVICE_CDS_ACTION_BROWSE, cds_browse }, + { SERVICE_CDS_ACTION_SEARCH, cds_search }, + { NULL, NULL } +}; diff --git a/src/cds.h b/src/cds.h new file mode 100644 index 0000000..c06c625 --- /dev/null +++ b/src/cds.h @@ -0,0 +1,212 @@ +/* + * cds.h : GeeXboX uShare Content Directory Service header. + * Originally developped for the GeeXboX project. + * Parts of the code are originated from GMediaServer from Oskar Liljeblad. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#ifndef CDS_H_ +#define CDS_H_ + +#define CDS_DESCRIPTION \ +"" \ +"" \ +" " \ +" 1" \ +" 0" \ +" " \ +" " \ +" " \ +" Browse" \ +" " \ +" " \ +" ObjectID" \ +" in" \ +" A_ARG_TYPE_ObjectID" \ +" " \ +" " \ +" BrowseFlag" \ +" in" \ +" A_ARG_TYPE_BrowseFlag" \ +" " \ +" " \ +" Filter" \ +" in" \ +" A_ARG_TYPE_Filter" \ +" " \ +" " \ +" StartingIndex" \ +" in" \ +" A_ARG_TYPE_Index" \ +" " \ +" " \ +" RequestedCount" \ +" in" \ +" A_ARG_TYPE_Count" \ +" " \ +" " \ +" SortCriteria" \ +" in" \ +" A_ARG_TYPE_SortCriteria" \ +" " \ +" " \ +" Result" \ +" out" \ +" A_ARG_TYPE_Result" \ +" " \ +" " \ +" NumberReturned" \ +" out" \ +" A_ARG_TYPE_Count" \ +" " \ +" " \ +" TotalMatches" \ +" out" \ +" A_ARG_TYPE_Count" \ +" " \ +" " \ +" UpdateID" \ +" out" \ +" A_ARG_TYPE_UpdateID" \ +" " \ +" " \ +" " \ +" " \ +" DestroyObject" \ +" " \ +" " \ +" ObjectID" \ +" in" \ +" A_ARG_TYPE_ObjectID" \ +" " \ +" " \ +" " \ +" " \ +" GetSystemUpdateID" \ +" " \ +" " \ +" Id" \ +" out" \ +" SystemUpdateID" \ +" " \ +" " \ +" " \ +" " \ +" GetSearchCapabilities" \ +" " \ +" " \ +" SearchCaps" \ +" out" \ +" SearchCapabilities" \ +" " \ +" " \ +" " \ +" " \ +" GetSortCapabilities" \ +" " \ +" " \ +" SortCaps" \ +" out" \ +" SortCapabilities" \ +" " \ +" " \ +" " \ +" " \ +" UpdateObject" \ +" " \ +" " \ +" ObjectID" \ +" in" \ +" A_ARG_TYPE_ObjectID" \ +" " \ +" " \ +" CurrentTagValue" \ +" in" \ +" A_ARG_TYPE_TagValueList" \ +" " \ +" " \ +" NewTagValue" \ +" in" \ +" A_ARG_TYPE_TagValueList" \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" A_ARG_TYPE_BrowseFlag" \ +" string" \ +" " \ +" BrowseMetadata" \ +" BrowseDirectChildren" \ +" " \ +" " \ +" " \ +" SystemUpdateID" \ +" ui4" \ +" " \ +" " \ +" A_ARG_TYPE_Count" \ +" ui4" \ +" " \ +" " \ +" A_ARG_TYPE_SortCriteria" \ +" string" \ +" " \ +" " \ +" SortCapabilities" \ +" string" \ +" " \ +" " \ +" A_ARG_TYPE_Index" \ +" ui4" \ +" " \ +" " \ +" A_ARG_TYPE_ObjectID" \ +" string" \ +" " \ +" " \ +" A_ARG_TYPE_UpdateID" \ +" ui4" \ +" " \ +" " \ +" A_ARG_TYPE_TagValueList" \ +" string" \ +" " \ +" " \ +" A_ARG_TYPE_Result" \ +" string" \ +" " \ +" " \ +" SearchCapabilities" \ +" string" \ +" " \ +" " \ +" A_ARG_TYPE_Filter" \ +" string" \ +" " \ +" " \ +"" + +#define CDS_DESCRIPTION_LEN strlen (CDS_DESCRIPTION) + +#define CDS_LOCATION "/web/cds.xml" + +#define CDS_SERVICE_ID "urn:upnp-org:serviceId:ContentDirectory" +#define CDS_SERVICE_TYPE "urn:schemas-upnp-org:service:ContentDirectory:1" + +#endif /* CDS_H_ */ diff --git a/src/cfgparser.c b/src/cfgparser.c new file mode 100644 index 0000000..b29d6a8 --- /dev/null +++ b/src/cfgparser.c @@ -0,0 +1,450 @@ +/* + * cfgparser.c : GeeXboX uShare config file parser. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Alexis Saettler + * + * 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 Library 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "gettext.h" +#include "cfgparser.h" +#include "ushare.h" +#include "trace.h" +#include "osdep.h" + +#define USHARE_DIR_DELIM "," + +static bool +ignore_line (const char *line) +{ + int i; + size_t len; + + /* commented line */ + if (line[0] == '#' ) + return true; + + len = strlen (line); + + for (i = 0 ; i < (int) len ; i++ ) + if (line[i] != ' ' && line[i] != '\t' && line[i] != '\n') + return false; + + return true; +} + +static char * +strdup_trim (const char *s) +{ + size_t begin, end; + char *r = NULL; + + if (!s) + return NULL; + + end = strlen (s) - 1; + + for (begin = 0 ; begin < end ; begin++) + if (s[begin] != ' ' && s[begin] != '\t' && s[begin] != '"') + break; + + for (; begin < end ; end--) + if (s[end] != ' ' && s[end] != '\t' && s[end] != '"' && s[end] != '\n') + break; + + r = strndup (s + begin, end - begin + 1); + + return r; +} + +static void +ushare_set_name (struct ushare_t *ut, const char *name) +{ + if (!ut || !name) + return; + + if (ut->name) + { + free (ut->name); + ut->name = NULL; + } + + ut->name = strdup_trim (name); +} + +static void +ushare_set_interface (struct ushare_t *ut, const char *iface) +{ + if (!ut || !iface) + return; + + if (ut->interface) + { + free (ut->interface); + ut->interface = NULL; + } + + ut->interface = strdup_trim (iface); +} + +static void +ushare_add_contentdir (struct ushare_t *ut, const char *dir) +{ + if (!ut || !dir) + return; + + ut->contentlist = content_add (ut->contentlist, dir); +} + +static void +ushare_set_cfg_file (struct ushare_t *ut, const char *file) +{ + if (!ut || !file) + return; + + ut->cfg_file = strdup (file); +} + +static void +ushare_set_dir (struct ushare_t *ut, const char *dirlist) +{ + char *x = NULL, *token = NULL; + char *m_buffer = NULL, *buffer; + + if (!ut || !dirlist) + return; + + x = strdup_trim (dirlist); + if (x) + { + m_buffer = (char*) malloc (strlen (x) * sizeof (char)); + if (m_buffer) + { + buffer = m_buffer; + token = strtok_r (x, USHARE_DIR_DELIM, &buffer); + while (token) + { + ushare_add_contentdir (ut, token); + token = strtok_r (NULL, USHARE_DIR_DELIM, &buffer); + } + free (m_buffer); + } + free (x); + } +} + +static void +ushare_set_port (struct ushare_t *ut, const char *port) +{ + if (!ut || !port) + return; + + ut->port = atoi (port); + if (ut->port < 49152) + { + fprintf (stderr, + _("Warning: port doesn't fit IANA port assignements.\n")); + + fprintf (stderr, _("Warning: Only Dynamic or Private Ports can be used " + "(from 49152 through 65535)\n")); + ut->port = 0; + } +} + +static void +ushare_set_telnet_port (struct ushare_t *ut, const char *port) +{ + if (!ut || !port) + return; + + ut->telnet_port = atoi (port); +} + +static void +ushare_use_web (struct ushare_t *ut, const char *val) +{ + if (!ut || !val) + return; + + ut->use_presentation = (!strcmp (val, "yes")) ? true : false; +} + +static void +ushare_use_telnet (struct ushare_t *ut, const char *val) +{ + if (!ut || !val) + return; + + ut->use_telnet = (!strcmp (val, "yes")) ? true : false; +} + +static void +ushare_use_xbox (struct ushare_t *ut, const char *val) +{ + if (!ut || !val) + return; + + ut->xbox360 = (!strcmp (val, "yes")) ? true : false; +} + +static void +ushare_use_dlna (struct ushare_t *ut, const char *val) +{ + if (!ut || !val) + return; + +#ifdef HAVE_DLNA + ut->dlna_enabled = (!strcmp (val, "yes")) ? true : false; +#endif /* HAVE_DLNA */ +} + +static void +ushare_set_override_iconv_err (struct ushare_t *ut, const char *arg) +{ + if (!ut) + return; + + ut->override_iconv_err = false; + + if (!strcasecmp (arg, "yes") + || !strcasecmp (arg, "true") + || !strcmp (arg, "1")) + ut->override_iconv_err = true; +} + +static u_configline_t configline[] = { + { USHARE_NAME, ushare_set_name }, + { USHARE_IFACE, ushare_set_interface }, + { USHARE_PORT, ushare_set_port }, + { USHARE_TELNET_PORT, ushare_set_telnet_port }, + { USHARE_DIR, ushare_set_dir }, + { USHARE_OVERRIDE_ICONV_ERR, ushare_set_override_iconv_err }, + { USHARE_ENABLE_WEB, ushare_use_web }, + { USHARE_ENABLE_TELNET, ushare_use_telnet }, + { USHARE_ENABLE_XBOX, ushare_use_xbox }, + { USHARE_ENABLE_DLNA, ushare_use_dlna }, + { NULL, NULL }, +}; + +static void +parse_config_line (struct ushare_t *ut, const char *line, + u_configline_t *configline) +{ + char *s = NULL; + + s = strchr (line, '='); + if (s && s[1] != '\0') + { + int i; + for (i=0 ; configline[i].name ; i++) + { + if (!strncmp (line, configline[i].name, strlen (configline[i].name))) + { + configline[i].set_var (ut, s + 1); + break; + } + } + } +} + +int +parse_config_file (struct ushare_t *ut) +{ + char filename[PATH_MAX]; + FILE *conffile; + char *line = NULL; + size_t size = 0; + ssize_t read; + + if (!ut) + return -1; + + if (!ut->cfg_file) + snprintf (filename, PATH_MAX, "%s/%s", SYSCONFDIR, USHARE_CONFIG_FILE); + else + snprintf (filename, PATH_MAX, "%s", ut->cfg_file); + + conffile = fopen (filename, "r"); + if (!conffile) + return -1; + + while ((read = getline (&line, &size, conffile)) != -1) + { + if (ignore_line (line)) + continue; + + if (line[read-1] == '\n') + line[read-1] = '\0'; + + while (line[0] == ' ' || line[0] == '\t') + line++; + + parse_config_line (ut, line, configline); + } + + fclose (conffile); + + if (line) + free (line); + + return 0; +} + +inline static void +display_usage (void) +{ + display_headers (); + printf ("\n"); + printf (_("Usage: ushare [-n name] [-i interface] [-p port] [-c directory] [[-c directory]...]\n")); + printf (_("Options:\n")); + printf (_(" -n, --name=NAME\tSet UPnP Friendly Name (default is '%s')\n"), + DEFAULT_USHARE_NAME); + printf (_(" -i, --interface=IFACE\tUse IFACE Network Interface (default is '%s')\n"), + DEFAULT_USHARE_IFACE); + printf (_(" -f, --cfg=FILE\t\tConfig file to be used\n")); + printf (_(" -p, --port=PORT\tForces the HTTP server to run on PORT\n")); + printf (_(" -q, --telnet-port=PORT\tForces the TELNET server to run on PORT\n")); + printf (_(" -c, --content=DIR\tShare the content of DIR directory\n")); + printf (_(" -w, --no-web\t\tDisable the control web page (enabled by default)\n")); + printf (_(" -t, --no-telnet\tDisable the TELNET control (enabled by default)\n")); + printf (_(" -o, --override-iconv-err\tIf iconv fails parsing name, still add to media contents (hoping the renderer can handle it)\n")); + printf (_(" -v, --verbose\t\tSet verbose display\n")); + printf (_(" -x, --xbox\t\tUse XboX 360 compliant profile\n")); +#ifdef HAVE_DLNA + printf (_(" -d, --dlna\t\tUse DLNA compliant profile (PlayStation3 needs this)\n")); +#endif /* HAVE_DLNA */ + printf (_(" -D, --daemon\t\tRun as a daemon\n")); + printf (_(" -V, --version\t\tDisplay the version of uShare and exit\n")); + printf (_(" -h, --help\t\tDisplay this help\n")); +} + +int +parse_command_line (struct ushare_t *ut, int argc, char **argv) +{ + int c, index; + char short_options[] = "VhvDowtxdn:i:p:q:c:f:"; + struct option long_options [] = { + {"version", no_argument, 0, 'V' }, + {"help", no_argument, 0, 'h' }, + {"verbose", no_argument, 0, 'v' }, + {"daemon", no_argument, 0, 'D' }, + {"override-iconv-err", no_argument, 0, 'o' }, + {"name", required_argument, 0, 'n' }, + {"interface", required_argument, 0, 'i' }, + {"port", required_argument, 0, 'p' }, + {"telnet-port", required_argument, 0, 'q' }, + {"content", required_argument, 0, 'c' }, + {"no-web", no_argument, 0, 'w' }, + {"no-telnet", no_argument, 0, 't' }, + {"xbox", no_argument, 0, 'x' }, +#ifdef HAVE_DLNA + {"dlna", no_argument, 0, 'd' }, +#endif /* HAVE_DLNA */ + {"cfg", required_argument, 0, 'f' }, + {0, 0, 0, 0 } + }; + + /* command line argument processing */ + while (true) + { + c = getopt_long (argc, argv, short_options, long_options, &index); + + if (c == EOF) + break; + + switch (c) + { + case 0: + /* opt = long_options[index].name; */ + break; + + case '?': + case 'h': + display_usage (); + return -1; + + case 'V': + display_headers (); + return -1; + + case 'v': + ut->verbose = true; + break; + + case 'D': + ut->daemon = true; + break; + + case 'o': + ut->override_iconv_err = true; + break; + + case 'n': + ushare_set_name (ut, optarg); + break; + + case 'i': + ushare_set_interface (ut, optarg); + break; + + case 'p': + ushare_set_port (ut, optarg); + break; + + case 'q': + ushare_set_telnet_port (ut, optarg); + break; + + case 'c': + ushare_add_contentdir (ut, optarg); + break; + + case 'w': + ut->use_presentation = false; + break; + + case 't': + ut->use_telnet = false; + break; + + case 'x': + ut->xbox360 = true; + break; + +#ifdef HAVE_DLNA + case 'd': + ut->dlna_enabled = true; + break; +#endif /* HAVE_DLNA */ + + case 'f': + ushare_set_cfg_file (ut, optarg); + break; + + default: + break; + } + } + + return 0; +} diff --git a/src/cfgparser.h b/src/cfgparser.h new file mode 100644 index 0000000..4ed6fd8 --- /dev/null +++ b/src/cfgparser.h @@ -0,0 +1,56 @@ +/* + * cfgparser.c : GeeXboX uShare config file parser headers. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Alexis Saettler + * + * 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 Library 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. + */ + +#ifndef _CONFIG_PARSER_H_ +#define _CONFIG_PARSER_H_ + +#include "ushare.h" + +#define USHARE_NAME "USHARE_NAME" +#define USHARE_IFACE "USHARE_IFACE" +#define USHARE_PORT "USHARE_PORT" +#define USHARE_TELNET_PORT "USHARE_TELNET_PORT" +#define USHARE_DIR "USHARE_DIR" +#define USHARE_OVERRIDE_ICONV_ERR "USHARE_OVERRIDE_ICONV_ERR" +#define USHARE_ENABLE_WEB "USHARE_ENABLE_WEB" +#define USHARE_ENABLE_TELNET "USHARE_ENABLE_TELNET" +#define USHARE_ENABLE_XBOX "USHARE_ENABLE_XBOX" +#define USHARE_ENABLE_DLNA "USHARE_ENABLE_DLNA" + +#define USHARE_CONFIG_FILE "ushare.conf" +#define DEFAULT_USHARE_NAME "uShare" + +#if (defined(BSD) || defined(__FreeBSD__)) +#define DEFAULT_USHARE_IFACE "lnc0" +#else /* Linux */ +#define DEFAULT_USHARE_IFACE "eth0" +#endif + +int parse_config_file (struct ushare_t *ut) + __attribute__ ((nonnull)); +int parse_command_line (struct ushare_t *ut, int argc, char **argv) + __attribute__ ((nonnull (1))); + +typedef struct { + char *name; + void (*set_var) (struct ushare_t*, const char*); +} u_configline_t; + +#endif /* _CONFIG_PARSER_H_ */ diff --git a/src/cms.c b/src/cms.c new file mode 100644 index 0000000..69af1b1 --- /dev/null +++ b/src/cms.c @@ -0,0 +1,180 @@ +/* + * cms.c : GeeXboX uShare Connection Management Service. + * Originally developped for the GeeXboX project. + * Parts of the code are originated from GMediaServer from Oskar Liljeblad. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#include +#include +#include + +#include "ushare.h" +#include "services.h" +#include "mime.h" + +/* Represent the CMS GetProtocolInfo action. */ +#define SERVICE_CMS_ACTION_PROT_INFO "GetProtocolInfo" + +/* Represent the CMS GetCurrentConnectionIDs action. */ +#define SERVICE_CMS_ACTION_CON_ID "GetCurrentConnectionIDs" + +/* Represent the CMS GetCurrentConnectionInfo action. */ +#define SERVICE_CMS_ACTION_CON_INFO "GetCurrentConnectionInfo" + +/* Represent the CMS SOURCE argument. */ +#define SERVICE_CMS_ARG_SOURCE "Source" + +/* Represent the CMS SINK argument. */ +#define SERVICE_CMS_ARG_SINK "Sink" + +/* Represent the CMS ConnectionIDs argument. */ +#define SERVICE_CMS_ARG_CONNECTION_IDS "ConnectionIDs" + +/* Represent the CMS ConnectionID argument. */ +#define SERVICE_CMS_ARG_CONNECTION_ID "ConnectionID" + +/* Represent the CMS RcsID argument. */ +#define SERVICE_CMS_ARG_RCS_ID "RcsID" + +/* Represent the CMS AVTransportID argument. */ +#define SERVICE_CMS_ARG_TRANSPORT_ID "AVTransportID" + +/* Represent the CMS ProtocolInfo argument. */ +#define SERVICE_CMS_ARG_PROT_INFO "ProtocolInfo" + +/* Represent the CMS PeerConnectionManager argument. */ +#define SERVICE_CMS_ARG_PEER_CON_MANAGER "PeerConnectionManager" + +/* Represent the CMS PeerConnectionID argument. */ +#define SERVICE_CMS_ARG_PEER_CON_ID "PeerConnectionID" + +/* Represent the CMS Direction argument. */ +#define SERVICE_CMS_ARG_DIRECTION "Direction" + +/* Represent the CMS Status argument. */ +#define SERVICE_CMS_ARG_STATUS "Status" + +/* Represent the CMS default connection ID value. */ +#define SERVICE_CMS_DEFAULT_CON_ID "0" + +/* Represent the CMS unknown connection ID value. */ +#define SERVICE_CMS_UNKNOW_ID "-1" + +/* Represent the CMS Output value. */ +#define SERVICE_CMS_OUTPUT "Output" + +/* Represent the CMS Success Status. */ +#define SERVICE_CMS_STATUS_OK "OK" + +static bool +cms_get_protocol_info (struct action_event_t *event) +{ + extern struct mime_type_t MIME_Type_List[]; + struct mime_type_t *list; + char *respText = NULL, *respPtr; + size_t respLen = 0, len; + + if (!event) + return false; + + // calculating length of response + list = MIME_Type_List; + while (list->extension) + { + char *protocol = mime_get_protocol (list); + respLen += strlen (protocol) + 1; + free (protocol); + list++; + } + + respText = (char*) malloc (respLen * sizeof (char)); + if (!respText) + return event->status; + + list = MIME_Type_List; + respPtr = respText; + while (list->extension) + { + char *protocol = mime_get_protocol (list); + len = strlen (protocol); + strncpy (respPtr, protocol, len); + free (protocol); + respPtr += len; + list++; + if (list->extension) + strcpy (respPtr++, ","); + } + *respPtr = '\0'; + + upnp_add_response (event, SERVICE_CMS_ARG_SOURCE, respText); + upnp_add_response (event, SERVICE_CMS_ARG_SINK, ""); + + free (respText); + return event->status; +} + +static bool +cms_get_current_connection_ids (struct action_event_t *event) +{ + if (!event) + return false; + + upnp_add_response (event, SERVICE_CMS_ARG_CONNECTION_IDS, ""); + + return event->status; +} + +static bool +cms_get_current_connection_info (struct action_event_t *event) +{ + extern struct mime_type_t MIME_Type_List[]; + struct mime_type_t *list = MIME_Type_List; + + if (!event) + return false; + + upnp_add_response (event, SERVICE_CMS_ARG_CONNECTION_ID, + SERVICE_CMS_DEFAULT_CON_ID); + upnp_add_response (event, SERVICE_CMS_ARG_RCS_ID, SERVICE_CMS_UNKNOW_ID); + upnp_add_response (event, SERVICE_CMS_ARG_TRANSPORT_ID, + SERVICE_CMS_UNKNOW_ID); + + while (list->extension) + { + char *protocol = mime_get_protocol (list); + upnp_add_response (event, SERVICE_CMS_ARG_PROT_INFO, protocol); + free (protocol); + list++; + } + + upnp_add_response (event, SERVICE_CMS_ARG_PEER_CON_MANAGER, ""); + upnp_add_response (event, SERVICE_CMS_ARG_PEER_CON_ID, + SERVICE_CMS_UNKNOW_ID); + upnp_add_response (event, SERVICE_CMS_ARG_DIRECTION, SERVICE_CMS_OUTPUT); + upnp_add_response (event, SERVICE_CMS_ARG_STATUS, SERVICE_CMS_STATUS_OK); + + return event->status; +} + +/* List of UPnP ConnectionManager Service actions */ +struct service_action_t cms_service_actions[] = { + { SERVICE_CMS_ACTION_PROT_INFO, cms_get_protocol_info }, + { SERVICE_CMS_ACTION_CON_ID, cms_get_current_connection_ids }, + { SERVICE_CMS_ACTION_CON_INFO, cms_get_current_connection_info }, + { NULL, NULL } +}; diff --git a/src/cms.h b/src/cms.h new file mode 100644 index 0000000..0778432 --- /dev/null +++ b/src/cms.h @@ -0,0 +1,166 @@ +/* + * cms.h : GeeXboX uShare Connection Management Service header. + * Originally developped for the GeeXboX project. + * Parts of the code are originated from GMediaServer from Oskar Liljeblad. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#ifndef CMS_H_ +#define CMS_H_ + +#define CMS_DESCRIPTION \ +"" \ +"" \ +" " \ +" 1" \ +" 0" \ +" " \ +" " \ +" " \ +" GetCurrentConnectionInfo" \ +" " \ +" " \ +" ConnectionID" \ +" in" \ +" A_ARG_TYPE_ConnectionID" \ +" " \ +" " \ +" RcsID" \ +" out" \ +" A_ARG_TYPE_RcsID" \ +" " \ +" " \ +" AVTransportID" \ +" out" \ +" A_ARG_TYPE_AVTransportID" \ +" " \ +" " \ +" ProtocolInfo" \ +" out" \ +" A_ARG_TYPE_ProtocolInfo" \ +" " \ +" " \ +" PeerConnectionManager" \ +" out" \ +" A_ARG_TYPE_ConnectionManager" \ +" " \ +" " \ +" PeerConnectionID" \ +" out" \ +" A_ARG_TYPE_ConnectionID" \ +" " \ +" " \ +" Direction" \ +" out" \ +" A_ARG_TYPE_Direction" \ +" " \ +" " \ +" Status" \ +" out" \ +" A_ARG_TYPE_ConnectionStatus" \ +" " \ +" " \ +" " \ +" " \ +" GetProtocolInfo" \ +" " \ +" " \ +" Source" \ +" out" \ +" SourceProtocolInfo" \ +" " \ +" " \ +" Sink" \ +" out" \ +" SinkProtocolInfo" \ +" " \ +" " \ +" " \ +" " \ +" GetCurrentConnectionIDs" \ +" " \ +" " \ +" ConnectionIDs" \ +" out" \ +" CurrentConnectionIDs" \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" A_ARG_TYPE_ProtocolInfo" \ +" string" \ +" " \ +" " \ +" A_ARG_TYPE_ConnectionStatus" \ +" string" \ +" " \ +" OK" \ +" ContentFormatMismatch" \ +" InsufficientBandwidth" \ +" UnreliableChannel" \ +" Unknown" \ +" " \ +" " \ +" " \ +" A_ARG_TYPE_AVTransportID" \ +" i4" \ +" " \ +" " \ +" A_ARG_TYPE_RcsID" \ +" i4" \ +" " \ +" " \ +" A_ARG_TYPE_ConnectionID" \ +" i4" \ +" " \ +" " \ +" A_ARG_TYPE_ConnectionManager" \ +" string" \ +" " \ +" " \ +" SourceProtocolInfo" \ +" string" \ +" " \ +" " \ +" SinkProtocolInfo" \ +" string" \ +" " \ +" " \ +" A_ARG_TYPE_Direction" \ +" string" \ +" " \ +" Input" \ +" Output" \ +" " \ +" " \ +" " \ +" CurrentConnectionIDs" \ +" string" \ +" " \ +" " \ +"" + +#define CMS_DESCRIPTION_LEN strlen (CMS_DESCRIPTION) + +#define CMS_LOCATION "/web/cms.xml" + +#define CMS_SERVICE_ID "urn:upnp-org:serviceId:ConnectionManager" +#define CMS_SERVICE_TYPE "urn:schemas-upnp-org:service:ConnectionManager:1" + +#endif /* CMS_H_ */ diff --git a/src/content.c b/src/content.c new file mode 100644 index 0000000..3328157 --- /dev/null +++ b/src/content.c @@ -0,0 +1,93 @@ +/* + * content.c : GeeXboX uShare content list + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Alexis Saettler + * + * 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 Library 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. + */ + +#include +#include +#include + +#include "content.h" + +content_list * +content_add(content_list *list, const char *item) +{ + if (!list) + { + list = (content_list*) malloc (sizeof(content_list)); + list->content = NULL; + list->count = 0; + } + if (item) + { + list->count++; + list->content = (char**) realloc (list->content, list->count * sizeof(char*)); + if (!list->content) + { + perror ("error realloc"); + exit (2); + } + list->content[list->count-1] = strdup (item); + } + return list; +} + +/* + * Remove the n'th content (start from 0) + */ +content_list * +content_del(content_list *list, int n) +{ + int i; + + if (!list || n >= list->count) + return NULL; + + if (n >= list->count) + return list; + + if (list->content[n]) + { + free (list->content[n]); + for (i = n ; i < list->count - 1 ; i++) + list->content[i] = list->content[i+1]; + list->count--; + list->content[list->count] = NULL; + } + + return list; +} + +void +content_free(content_list *list) +{ + int i; + if (!list) + return; + + if (list->content) + { + for (i=0 ; i < list->count ; i++) + { + if (list->content[i]) + free (list->content[i]); + } + free (list->content); + } + free (list); +} diff --git a/src/content.h b/src/content.h new file mode 100644 index 0000000..96064cf --- /dev/null +++ b/src/content.h @@ -0,0 +1,36 @@ +/* + * content.h : GeeXboX uShare content list header + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Alexis Saettler + * + * 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 Library 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. + */ + +#ifndef _CONTENT_H_ +#define _CONTENT_H_ + +typedef struct content_list { + char **content; + int count; +} content_list; + +content_list *content_add(content_list *list, const char *item) + __attribute__ ((malloc)); +content_list *content_del(content_list *list, int n) + __attribute__ ((nonnull)); +void content_free(content_list *list) + __attribute__ ((nonnull)); + +#endif diff --git a/src/ctrl_telnet.c b/src/ctrl_telnet.c new file mode 100644 index 0000000..0849d80 --- /dev/null +++ b/src/ctrl_telnet.c @@ -0,0 +1,897 @@ +/* ctrltelnet.c - Telnet controler + * Copyright (C) 2005-2007 Sven Almgren + * + * 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 Library 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. + * + */ + +#define STR(x) _STR(x) +#define _STR(x) __STR(x) +#define __STR(x) #x + +#include "config.h" +#include "ctrl_telnet.h" +#include "minmax.h" +#include "trace.h" + +#include +#include +#include +#include /* For select */ +#include +#include /* For pipe */ +#include +#include +#include +#include +#include + +#if (defined(____DISABLE_MUTEX) || 0) +#define pthread_mutex_lock(x) printf(">>>> Locking " __FILE__ ":" STR(__LINE__) " \t" #x "\n"); +#define pthread_mutex_unlock(x) printf("<<<< Unlocking " __FILE__ ":" STR(__LINE__) " \t" #x "\n"); +#endif + +/** + * @brief Structure holding data between the staring rutine and the thread + */ +typedef struct telnet_thread_data_t +{ + pthread_t thread; + + /* Litening socket */ + int listener; + + /* Socket used to terminate loop: + 0 is reading and 1 is sending, kill by sending to 1 */ + int killer[2]; + + /* Our socket address */ + struct sockaddr_in local_address; + + /* Shared data buffer that can be used by others... */ + char shared_buffer[CTRL_TELNET_SHARED_BUFFER_SIZE]; + + ctrl_telnet_client *clients; +} telnet_thread_data; + +/** + * @brief Struct for registerd commands + */ +typedef struct telnet_function_list_t +{ + /* Function name, or keyword, if you like */ + char *name; + char *description; + ctrl_telnet_command_ptr function; + + struct telnet_function_list_t *next; +} telnet_function_list; + +/* Static yes used to set socketoptions */ +static int yes = 1; +static telnet_thread_data ttd; +static telnet_function_list* functions = NULL; +static pthread_mutex_t functions_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t startstop_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t shared_lock = PTHREAD_MUTEX_INITIALIZER; +static int started = 0; + +/* Threadfunction, core in telnet controler */ +/** + * @brief Thread function + * + * @param data Not used, leave as NULL + */ +static void *ctrl_telnet_thread (void *data); + +/** + * @brief Adds a new client to our list of new ones + * + * @param client to add + */ +static void ctrl_telnet_client_add (ctrl_telnet_client *client); + +/** + * @brief Removes "client" from our list of clients + */ +static void ctrl_telnet_client_remove (ctrl_telnet_client *client); + +/** + * @brief Updates an fd_set to contain the current set of clients + * + * @return max fd found in list + */ +static int ctrl_telnet_fix_fdset (fd_set* readable); + +static void ctrl_telnet_tokenize (char *raw, int *argc, char ***argv); + +static int ctrl_telnet_client_recv (ctrl_telnet_client *client); +static int ctrl_telnet_client_execute (ctrl_telnet_client *client); +static int ctrl_telnet_client_execute_line (ctrl_telnet_client *client, + char *line); +static int ctrl_telnet_client_execute_line_safe (ctrl_telnet_client *client, + char *line); +static void ctrl_telnet_register_internals(); + +/** + * @brief Starts a Telnet bound control interface + * + * @return 0 on success, -1 on error + */ +int +ctrl_telnet_start (int port) +{ + /* Start by making us threadsafe... */ + pthread_mutex_lock (&startstop_lock); + + /* Create listener socket */ + ttd.listener = socket (PF_INET, SOCK_STREAM, 0); + if (ttd.listener == -1) + { + perror ("socket"); + pthread_mutex_unlock (&startstop_lock); + return -1; + } + + /* Clears us from "address already in use" errors */ + if (setsockopt (ttd.listener, SOL_SOCKET, SO_REUSEADDR, + &yes, sizeof (int)) == -1) + perror ("setsockopt"); + + ttd.local_address.sin_family = AF_INET; + ttd.local_address.sin_addr.s_addr = INADDR_ANY; + ttd.local_address.sin_port = htons (port); + memset (&ttd.local_address.sin_zero, '\0', + sizeof (ttd.local_address.sin_zero)); + + if (bind (ttd.listener, (struct sockaddr *) &ttd.local_address, + sizeof (ttd.local_address)) == -1) + { + perror ("bind"); + pthread_mutex_unlock (&startstop_lock); + return -1; + } + + if (listen (ttd.listener, CTRL_TELNET_BACKLOG) == -1) + { + perror ("listen"); + pthread_mutex_unlock (&startstop_lock); + return -1; + } + + print_log (ULOG_NORMAL, "Listening on telnet port %u\n", port); + + /* Create killer pipes */ + if (pipe (ttd.killer)) + { + perror ("Failed to create killer pipe"); + pthread_mutex_unlock (&startstop_lock); + return -1; /* FIXME. Kill all sockets... not critical,, but still */ + } + + if (pthread_create (&ttd.thread, NULL, ctrl_telnet_thread, NULL)) + { + /* FIXME: Killall sockets... */ + perror ("Failed to create thread"); + pthread_mutex_unlock (&startstop_lock); + return -1; + } + + started = 1; + ctrl_telnet_register_internals (); + pthread_mutex_unlock (&startstop_lock); + + return 0; +} + +/** + * @brief Stops all telnet bound control interfaces + */ +void +ctrl_telnet_stop (void) +{ + pthread_mutex_lock (&startstop_lock); + + if (!started) + { + pthread_mutex_unlock (&startstop_lock); + return; + } + + /* yes is int, which is bigger then char, so this should be safe */ + write (ttd.killer[1], &yes, sizeof (char)); + + pthread_mutex_unlock (&startstop_lock); + pthread_join (ttd.thread, NULL); +} + +/** + * @brief Telnet thread function + */ +static void * +ctrl_telnet_thread (void *a __attribute__ ((unused))) +{ + /* fd_set with readable clients */ + fd_set fd_readable; + + /* Pointer to a client object */ + ctrl_telnet_client *client; + + int fd_max; + + while (1) + { + /* Get fds */ + fd_max = ctrl_telnet_fix_fdset (&fd_readable); + + if (select (fd_max + 1, &fd_readable, NULL, NULL, NULL) == -1) + { + perror ("select"); + /* FIXME: Close sockets */ + return NULL; + } + + /* Check killer */ + if (FD_ISSET (ttd.killer[0], &fd_readable)) + { + /* FIXME: TODO: Shut down sockets... */ + + /* Close listener and killer */ + close (ttd.listener); + close (ttd.killer[0]); + close (ttd.killer[1]); + + /* Check which fds that had anyhting to say... */ + client = ttd.clients; + + /* Say goodby to clients */ + while (client) + { + ctrl_telnet_client *current = client; + ctrl_telnet_client_send (current, + "\nServer is going down, Bye bye\n"); + client = client->next; + ctrl_telnet_client_remove (current); + } + + pthread_mutex_lock (&functions_lock); + + while (functions) + { + telnet_function_list *head = functions; + functions = functions->next; + + free (head->name); + if (head->description) + free (head->description); + + free (head); + } + + pthread_mutex_unlock (&functions_lock); + + return NULL; + } + + /* Check for new connection */ + if (FD_ISSET (ttd.listener, &fd_readable)) + { + socklen_t sl_addr; + + /* Create client object */ + client = malloc (sizeof (ctrl_telnet_client)); + + if (!client) + { + perror ("Failed to create new client"); + return NULL; + } + + memset (client, '\0', sizeof (ctrl_telnet_client)); + sl_addr = sizeof (client->remote_address); + + client->socket = accept (ttd.listener, + (struct sockaddr *) &client->remote_address, + &sl_addr); + if (client->socket == -1) + { + perror ("accept"); + free (client); + } + else + { + ctrl_telnet_client_add (client); + ctrl_telnet_client_execute_line_safe (client, "banner"); + ctrl_telnet_client_sendf (client, "For a list of registered commands type \"help\"\n"); + ctrl_telnet_client_send (client, "\n> "); + } + } + + /* Check which fds that had anyhting to say... */ + client = ttd.clients; + + /* Run through all clients and check if there's data avalible + with FD_ISSET(current->socket) */ + while (client) + { + ctrl_telnet_client *current = client; + client = client->next; + + if (FD_ISSET (current->socket, &fd_readable)) + { + if (ctrl_telnet_client_recv (current) <= 0) + { + ctrl_telnet_client_remove (current); + continue; + } + + if (current->ready) + { + ctrl_telnet_client_execute (current); + + if (!current->exiting) + ctrl_telnet_client_send (current, "\n> "); + else + ctrl_telnet_client_remove (current); + } + } + } + } +} + +/** + * @brief Adds a new client to our list of new ones + * + * @note This funtion is only called from a single thread, + * as such it won't need to be threadsafe + * @param client to add + */ +static void +ctrl_telnet_client_add (ctrl_telnet_client *client) +{ + client->next = ttd.clients; + ttd.clients = client; +} + +/** + * @brief Removes "client" from our list of clients + * + * @note This funtion is only called from a single thread, + * as such it won't need to be threadsafe + * @param client to remove + */ +static void +ctrl_telnet_client_remove (ctrl_telnet_client *client) +{ + ctrl_telnet_client *tmp; + + /* Start by dealing with our head */ + if (client == ttd.clients) + ttd.clients = client->next; + else + { + for (tmp = ttd.clients; tmp->next; tmp = tmp->next) + { + if (tmp->next == client) + { + tmp->next = tmp->next->next; + break; + } + } + } + + close (client->socket); + + free (client); +} + +/** + * @brief Clears readable fd_set and adds every client to it, + * returns max fd found + * + * @param readable fd_set to update + * @return Biggest fd + */ +static int +ctrl_telnet_fix_fdset (fd_set *readable) +{ + int maxfd; + ctrl_telnet_client *client; + + maxfd = MAX (ttd.killer[0], ttd.listener); + + FD_ZERO (readable); + FD_SET (ttd.listener, readable); + FD_SET (ttd.killer[0], readable); + + client = ttd.clients; + + while (client) + { + if (client->socket > maxfd) + maxfd = client->socket; + + FD_SET (client->socket, readable); + + client = client->next; + } + + return maxfd; +} + +static int +ctrl_telnet_client_recv (ctrl_telnet_client *client) +{ + int i; + int nbytes; + int buffer_free = CTRL_CLIENT_RECV_BUFFER_SIZE - client->buffer_recv_current - 1; + + nbytes = recv (client->socket, + client->buffer_recv + client->buffer_recv_current, + buffer_free, 0); + if (nbytes <= 0) + { + close (client->socket); + return nbytes; + } + + client->buffer_recv_current += nbytes; + client->buffer_recv[client->buffer_recv_current] = '\0'; + + for (i = 0; i < client->buffer_recv_current; i++) + if (client->buffer_recv[i] == '\n') + client->ready = 1; + + return nbytes; +} + +int +ctrl_telnet_client_send (const ctrl_telnet_client *client, const char *string) +{ + const char* cc = string; + int len = strlen (cc); + int sent = 0; + int senttotal = 0; + + while ((cc - string) < len) + { + /* Use nonblocking just as a precation... + and a failed write won't _really_ kill us */ + sent = send (client->socket, string, len - (cc - string), MSG_DONTWAIT); + + /* This will mark the socket as dead... just to be safe.. + and its only a telnet interface... reconnect and do it again */ + if (sent == -1) + return -1; + + senttotal += sent; + cc += sent; + } + + return senttotal; +} + +int +ctrl_telnet_client_sendf (const ctrl_telnet_client *client, + const char *format, ...) +{ + int retval; + va_list ap; + int len; + + pthread_mutex_lock (&shared_lock); + + va_start (ap, format); + len = vsnprintf (ttd.shared_buffer, + CTRL_TELNET_SHARED_BUFFER_SIZE, format, ap); + va_end (ap); + + /* Check if the message fitted inside the buffer, if not, + either exit or adjust len to be buffersize, I choose exit for now */ + if (len >= CTRL_TELNET_SHARED_BUFFER_SIZE) + { + pthread_mutex_unlock (&shared_lock); + /* FIXME: Return error or send what we've got? */ + return -1; /* Buffer was to small */ + } + + /* TODO: Might be good to have the option to specify str length so + send doesn't have to recompute it... */ + retval = ctrl_telnet_client_send (client, ttd.shared_buffer); + + pthread_mutex_unlock (&shared_lock); + + return retval; +} + +int +ctrl_telnet_client_sendsf (const ctrl_telnet_client *client, + char *buffer, int buffersize, + const char *format, ...) +{ + va_list ap; + int len; + + va_start (ap, format); + len = vsnprintf (buffer, buffersize, format, ap); + va_end (ap); + + /* Check if the message fitted inside the buffer, if not, + either exit or adjust len to be buffersize, I choose exit for now */ + if (len >= buffersize) + return -1; /* Buffer was to small */ + + /* TODO: Might be good to have the option to specify str length + so send doesn't have to recompute it... */ + return ctrl_telnet_client_send (client, buffer); +} + +/* FIXME: Ulgy non optimised version */ +static int +ctrl_telnet_client_execute (ctrl_telnet_client *client) +{ + int i = 0; + + /* Check buffer for complete lines and execute them,,, */ + for (i = 0; i < client->buffer_recv_current; i++) + { + if (client->buffer_recv[i] == '\n' || client->buffer_recv[i] == '\r') + { + /* Replace newline with null (or \r) */ + client->buffer_recv[i] = '\0'; + + /* Send line to execution */ + ctrl_telnet_client_execute_line_safe (client, client->buffer_recv); + + /* Check if next is either newline or CR, strip that too, if needed */ + if ((i + 1 < CTRL_CLIENT_RECV_BUFFER_SIZE) && + (client->buffer_recv[i+1]=='\n' || client->buffer_recv[i+1]=='\r')) + client->buffer_recv[++i] = '\0'; + + /* Remove processed line */ + memmove (client->buffer_recv, client->buffer_recv + i, + client->buffer_recv_current - 1); + client->buffer_recv_current -= (i + 1); + i = -1; + } + } + + return 0; /* No syntax error checking yet */ +} + +static int +ctrl_telnet_client_execute_line_safe (ctrl_telnet_client *client, char *line) +{ + int retval; + + pthread_mutex_lock (&functions_lock); + retval = ctrl_telnet_client_execute_line (client, line); + pthread_mutex_unlock (&functions_lock); + + return retval; +} + +static int +ctrl_telnet_client_execute_line (ctrl_telnet_client *client, char *line) +{ + int argc = 0; + char **argv = NULL; + telnet_function_list *node; + char *line2 = strdup (line); /* To make it safer */ + ctrl_telnet_tokenize (line2, &argc, &argv); + + node = functions; + + if (*argv[0] == '\0') + { + free (argv); + free (line2); + return 0; + } + + while (node) + { + if (!strcmp (node->name, argv[0])) + { + node->function (client, argc, argv); + break; + } + + node = node->next; + } + + if (!node) + ctrl_telnet_client_sendf (client, "%s: Command not found\n", argv[0]); + + free (argv); + free (line2); + + return strlen (line); +} + +void +ctrl_telnet_register (const char *funcname, + ctrl_telnet_command_ptr funcptr, + const char *description) +{ + telnet_function_list *function; + + function = malloc (sizeof (telnet_function_list)); + function->name = strdup (funcname); /* Mayby use strndup...? */ + function->description = description ? strdup (description) : NULL; + function->function = funcptr; + + pthread_mutex_lock (&functions_lock); + function->next = functions; + functions = function; + pthread_mutex_unlock (&functions_lock); +} + +/* Warning: This WILL edit the input string... use strdup or something + if needed, also remember to free() argv as the first array is dynamic */ +/* If *argv != NULL it'll first be free()ed... or realloc, + make sure to clear *argv to null on initialization */ +static void +ctrl_telnet_tokenize (char *raw, int *argc, char ***argv) +{ + int i; + int has_backslash = 0; + int has_quote = 0; + char *pc = raw; + + if (!raw || !argc || !argv) + { + perror ("NULL in " __FILE__ " at line " STR (__LINE__)); + return; + } + + /* (1/3) First run is just to count our arguments... */ + *argc = (raw[0] == '\0') ? 0 : 1; + + pc = raw; + while (*pc) + { + switch (*pc) + { + case '\\': + if (!has_backslash) + has_backslash = 2; /* FULHACK */ + break; + case ' ': + if (!has_backslash && !has_quote) + (*argc)++; + break; + case '"': + if (!has_backslash) + has_quote = !has_quote; + + break; + } + + /* When we get a BS we set it to two, this makes it one, + next run it will still be 1, then one after that is zero... FULHACK */ + if (has_backslash) + has_backslash--; + + pc++; + } + + /* Create argv */ + *argv = malloc (sizeof (char **) * ((*argc) + 1)); + + /* (2/3) Parse throu one more time, this time filling argv (Pass 2 / 3) */ + i = 0; + pc = raw; + has_backslash = 0; + has_quote = 0; + (*argv)[0] = raw; + + while (*pc) + { + switch (*pc) + { + case '\\': + if (!has_backslash) + has_backslash = 2; /* FULHACK */ + break; + case ' ': + if (!has_backslash && !has_quote) + { + *pc = '\0'; + (*argv)[++i] = pc+1; + pc++; + continue; + } + break; + case '"': + if (!has_backslash) + has_quote = !has_quote; + break; + } + + /* When we get a BS we set it to two, this makes it one, + next run it will still be 1, then one after that is zero... FULHACK */ + if (has_backslash) + has_backslash--; + + pc++; + } + + /* Make last element (argc) point to null... */ + (*argv)[++i] = NULL; + + /* (3/3) Parse arguments to remove escapings and such */ + for (i = 0; (*argv)[i]; i++) + { + /* Set up environment */ + pc = (*argv)[i]; + has_backslash = 0; + has_quote = 0; + + /* Remove leading and ending quotes, if existing */ + if (*pc == '"') + { + int len = strlen (pc); + + if (len > 0 && pc[len - 1] == '"') + pc[len - 1] = '\0'; + memmove (pc, pc + 1, len); + } + + /* Remove any special characters */ + while (*pc) + { + switch (*pc) + { + case '\\': + if (!has_backslash) + { + has_backslash = 2; /* FULHACK */ + break; + } + /* Else: fall through */ + case ' ': + case '"': + if (has_backslash) + { + pc--; + memmove (pc, pc + 1, strlen (pc)); /* FIXME: Not cheap */ + } + break; + } + + /* When we get a BS we set it to two, this makes it one, + next run it will still be 1, then one after that is zero... */ + if (has_backslash) + has_backslash--; + + pc++; + } + } +} + +static void +help (ctrl_telnet_client *client, int argc, char **argv) +{ + int hidden = 0; + ctrl_telnet_client_execute_line (client, "banner"); + + if (argc < 2) + { + ctrl_telnet_client_send (client, "\n"); + ctrl_telnet_client_send (client, "Usage: help TOPIC\n"); + ctrl_telnet_client_send (client, "Valid topics are\n"); + ctrl_telnet_client_send + (client, " commands - For a list of registed commands\n"); + ctrl_telnet_client_send + (client, " syntax - For a description of the interface syntax\n"); + return; + } + else + { + if (!strcmp ("commands", argv[1])) + { + telnet_function_list *node; + + node = functions; + ctrl_telnet_client_send + (client, "Registered command (command - description)\n"); + ctrl_telnet_client_send + (client, "=======================================================\n"); + + while (node) + { + /* Make functions without descriptions invisible */ + if (node->description) + ctrl_telnet_client_sendf (client, " %s - %s\n", + node->name, node->description); + else + hidden++; + + node = node->next; + } + + if (hidden) + ctrl_telnet_client_sendf + (client, "There's also %i hidden functions\n", hidden); + } /* commands */ + else if (!strcmp ("syntax", argv[1])) + { + ctrl_telnet_client_send + (client, "Syntax is easy: command parameters\n"); + ctrl_telnet_client_send + (client, " Each new word is a new argument, unless the space is precided\n"); + ctrl_telnet_client_send + (client, " a backslash (\\), or if a set of words are surrounded by quotes\n"); + ctrl_telnet_client_send + (client, " (\"). To get a litteral quote you can escape it as \\\".\n"); + ctrl_telnet_client_send (client, "\n"); + ctrl_telnet_client_send (client, "STUB\n"); + } + else + ctrl_telnet_client_send (client, "Unknown topic\n"); + } +} + +static void +banner (ctrl_telnet_client *client, + int argc __attribute__ ((unused)), + char **argv __attribute__ ((unused))) +{ + ctrl_telnet_client_sendf (client, "%s (%s) (Built %s)\n", + PACKAGE_NAME, VERSION, __DATE__); +} + +static void +echo (ctrl_telnet_client *client, int argc, char **argv) +{ + int i; + + for (i = 1; i < argc; i++) + ctrl_telnet_client_sendf (client, "%s%s", (i > 1 ? " " : ""), argv[i]); + ctrl_telnet_client_send (client, "\n"); +} + +static void +echod (ctrl_telnet_client *client, int argc, char **argv) +{ + int i; + + ctrl_telnet_client_sendf (client, "Argc: %i\n", argc); + + for (i = 0; i < argc; i++) + ctrl_telnet_client_sendf (client, "%i: '%s'\n", i, argv[i]); +} + +static void +ctrl_telnet_exit (ctrl_telnet_client *client, + int argc __attribute__ ((unused)), + char **argv __attribute__ ((unused))) +{ + client->exiting = 1; + ctrl_telnet_client_send (client, "Bye bye\n"); +} + +static void +ctrl_telnet_register_internals (void) +{ + ctrl_telnet_register ("echo", echo, "Echos all arguments"); + ctrl_telnet_register ("echod", echod, "Echos all arguments but with each argument on a new line... DEBUG"); + ctrl_telnet_register ("help", help, "Display help"); + ctrl_telnet_register ("banner", banner, NULL); + ctrl_telnet_register ("exit", ctrl_telnet_exit, "Exits this interface (Or CTRL+D then Enter)"); + /* CTRL+D... But it has to be fallowd by a new line */ + ctrl_telnet_register ("\4", ctrl_telnet_exit, NULL); +} diff --git a/src/ctrl_telnet.h b/src/ctrl_telnet.h new file mode 100644 index 0000000..7185d32 --- /dev/null +++ b/src/ctrl_telnet.h @@ -0,0 +1,75 @@ +/* ctrltelnet.h - Header for the Telnet controler + * Copyright (C) 2005-2007 Sven Almgren + * + * 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 Library 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. + * + */ + +#ifndef _CTRL_TELNET_H_ +#define _CTRL_TELNET_H_ + +#define CTRL_TELNET_PORT 1337 +#define CTRL_TELNET_BACKLOG 10 +#define CTRL_TELNET_SHARED_BUFFER_SIZE 256 +#define CTRL_CLIENT_RECV_BUFFER_SIZE 256 + +#include + +/** + * @brief Structure doubling as both a connected client data holder + * and as a linked list + */ +typedef struct ctrl_telnet_client_t +{ + /* Recv buffer used to read single lines from more then one packet ... + Not garanteed to be NULL terminated */ + char buffer_recv[CTRL_CLIENT_RECV_BUFFER_SIZE]; + + int buffer_recv_current; + int socket; + int ready; /* True if this client has a complete line, ready to be parsed */ + int exiting; + struct sockaddr_in remote_address; + struct ctrl_telnet_client_t* next; +} ctrl_telnet_client; + +typedef void (* ctrl_telnet_command_ptr) (ctrl_telnet_client *, int, char **); + +/** + * @brief Starts a Telnet bound control interface + * + * @return 0 on success, -1 on error + */ +int ctrl_telnet_start (int port); + +/** + * @brief Stops all telnet bound control interfaces + */ +void ctrl_telnet_stop (void); + +/* FIXME: You can register a function name multiple times, + but the last one added is the one getting called... not a problem a.t.m. */ +void ctrl_telnet_register (const char *funcname, + ctrl_telnet_command_ptr funcptr, + const char* description); + +int ctrl_telnet_client_send (const ctrl_telnet_client *, const char* string); +int ctrl_telnet_client_sendf (const ctrl_telnet_client *client, + const char* format, ...); +int ctrl_telnet_client_sendsf (const ctrl_telnet_client *client, + char* buffer, int buffersize, + const char* format, ...); + +#endif /* _CTRL_TELNET_H_ */ diff --git a/src/gettext.h b/src/gettext.h new file mode 100644 index 0000000..88603da --- /dev/null +++ b/src/gettext.h @@ -0,0 +1,81 @@ +/* Convenience header for conditional use of GNU . + Copyright (C) 1995-1998, 2000-2002, 2004 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published + by the Free Software Foundation; either version 2, 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library 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. */ + +#ifndef _LIBGETTEXT_H +#define _LIBGETTEXT_H 1 + +#define _(string) gettext (string) + +/* NLS can be disabled through the configure --disable-nls option. */ +#ifdef CONFIG_NLS + +/* Get declarations of GNU message catalog functions. */ +# include + +#else + +/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which + chokes if dcgettext is defined as a macro. So include it now, to make + later inclusions of a NOP. We don't include + as well because people using "gettext.h" will not include , + and also including would fail on SunOS 4, whereas + is OK. */ +#if defined(__sun) +# include +#endif + +/* Many header files from the libstdc++ coming with g++ 3.3 or newer include + , which chokes if dcgettext is defined as a macro. So include + it now, to make later inclusions of a NOP. */ +#if defined(__cplusplus) && defined(__GNUG__) && (__GNUC__ >= 3) +# include +# if (__GLIBC__ >= 2) || _GLIBCXX_HAVE_LIBINTL_H +# include +# endif +#endif + +/* Disabled NLS. + The casts to 'const char *' serve the purpose of producing warnings + for invalid uses of the value returned from these functions. + On pre-ANSI systems without 'const', the config.h file is supposed to + contain "#define const". */ +# define gettext(Msgid) ((const char *) (Msgid)) +# define dgettext(Domainname, Msgid) ((const char *) (Msgid)) +# define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid)) +# define ngettext(Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dngettext(Domainname, Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define textdomain(Domainname) ((const char *) (Domainname)) +# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname)) +# define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset)) + +#endif + +/* A pseudo function call that serves as a marker for the automated + extraction of messages, but does not call gettext(). The run-time + translation is done at a different place in the code. + The argument, String, should be a literal string. Concatenated strings + and other string expressions won't work. + The macro's expansion is not parenthesized, so that it is suitable as + initializer for static 'char[]' or 'const char[]' variables. */ +#define gettext_noop(String) String + +#endif /* _LIBGETTEXT_H */ diff --git a/src/http.c b/src/http.c new file mode 100644 index 0000000..815ac1b --- /dev/null +++ b/src/http.c @@ -0,0 +1,414 @@ +/* + * http.c : GeeXboX uShare Web Server handler. + * Originally developped for the GeeXboX project. + * Parts of the code are originated from GMediaServer from Oskar Liljeblad. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "services.h" +#include "cds.h" +#include "cms.h" +#include "msr.h" +#include "metadata.h" +#include "http.h" +#include "minmax.h" +#include "trace.h" +#include "presentation.h" +#include "osdep.h" +#include "mime.h" + +#define PROTOCOL_TYPE_PRE_SZ 11 /* for the str length of "http-get:*:" */ +#define PROTOCOL_TYPE_SUFF_SZ 2 /* for the str length of ":*" */ + +struct web_file_t { + char *fullpath; + off_t pos; + enum { + FILE_LOCAL, + FILE_MEMORY + } type; + union { + struct { + int fd; + struct upnp_entry_t *entry; + } local; + struct { + char *contents; + off_t len; + } memory; + } detail; +}; + + +static inline void +set_info_file (struct File_Info *info, const size_t length, + const char *content_type) +{ + info->file_length = length; + info->last_modified = 0; + info->is_directory = 0; + info->is_readable = 1; + info->content_type = ixmlCloneDOMString (content_type); +} + +static int +http_get_info (const char *filename, struct File_Info *info) +{ + extern struct ushare_t *ut; + struct upnp_entry_t *entry = NULL; + struct stat st; + int upnp_id = 0; + char *content_type = NULL; + char *protocol = NULL; + + if (!filename || !info) + return -1; + + log_verbose ("http_get_info, filename : %s\n", filename); + + if (!strcmp (filename, CDS_LOCATION)) + { + set_info_file (info, CDS_DESCRIPTION_LEN, SERVICE_CONTENT_TYPE); + return 0; + } + + if (!strcmp (filename, CMS_LOCATION)) + { + set_info_file (info, CMS_DESCRIPTION_LEN, SERVICE_CONTENT_TYPE); + return 0; + } + + if (!strcmp (filename, MSR_LOCATION)) + { + set_info_file (info, MSR_DESCRIPTION_LEN, SERVICE_CONTENT_TYPE); + return 0; + } + + if (ut->use_presentation && !strcmp (filename, USHARE_PRESENTATION_PAGE)) + { + if (build_presentation_page (ut) < 0) + return -1; + + set_info_file (info, ut->presentation->len, PRESENTATION_PAGE_CONTENT_TYPE); + return 0; + } + + if (ut->use_presentation && !strncmp (filename, USHARE_CGI, strlen (USHARE_CGI))) + { + if (process_cgi (ut, (char *) (filename + strlen (USHARE_CGI) + 1)) < 0) + return -1; + + set_info_file (info, ut->presentation->len, PRESENTATION_PAGE_CONTENT_TYPE); + return 0; + } + + upnp_id = atoi (strrchr (filename, '/') + 1); + entry = upnp_get_entry (ut, upnp_id); + if (!entry) + return -1; + + if (!entry->fullpath) + return -1; + + if (stat (entry->fullpath, &st) < 0) + return -1; + + if (access (entry->fullpath, R_OK) < 0) + { + if (errno != EACCES) + return -1; + info->is_readable = 0; + } + else + info->is_readable = 1; + + /* file exist and can be read */ + info->file_length = st.st_size; + info->last_modified = st.st_mtime; + info->is_directory = S_ISDIR (st.st_mode); + + protocol = +#ifdef HAVE_DLNA + entry->dlna_profile ? + dlna_write_protocol_info (DLNA_PROTOCOL_INFO_TYPE_HTTP, + DLNA_ORG_PLAY_SPEED_NORMAL, + DLNA_ORG_CONVERSION_NONE, + DLNA_ORG_OPERATION_RANGE, + ut->dlna_flags, entry->dlna_profile) : +#endif /* HAVE_DLNA */ + mime_get_protocol (entry->mime_type); + + content_type = + strndup ((protocol + PROTOCOL_TYPE_PRE_SZ), + strlen (protocol + PROTOCOL_TYPE_PRE_SZ) + - PROTOCOL_TYPE_SUFF_SZ); + free (protocol); + + if (content_type) + { + info->content_type = ixmlCloneDOMString (content_type); + free (content_type); + } + else + info->content_type = ixmlCloneDOMString (""); + + return 0; +} + +static UpnpWebFileHandle +get_file_memory (const char *fullpath, const char *description, + const size_t length) +{ + struct web_file_t *file; + + file = malloc (sizeof (struct web_file_t)); + file->fullpath = strdup (fullpath); + file->pos = 0; + file->type = FILE_MEMORY; + file->detail.memory.contents = strdup (description); + file->detail.memory.len = length; + + return ((UpnpWebFileHandle) file); +} + +static UpnpWebFileHandle +http_open (const char *filename, enum UpnpOpenFileMode mode) +{ + extern struct ushare_t *ut; + struct upnp_entry_t *entry = NULL; + struct web_file_t *file; + int fd, upnp_id = 0; + + if (!filename) + return NULL; + + log_verbose ("http_open, filename : %s\n", filename); + + if (mode != UPNP_READ) + return NULL; + + if (!strcmp (filename, CDS_LOCATION)) + return get_file_memory (CDS_LOCATION, CDS_DESCRIPTION, CDS_DESCRIPTION_LEN); + + if (!strcmp (filename, CMS_LOCATION)) + return get_file_memory (CMS_LOCATION, CMS_DESCRIPTION, CMS_DESCRIPTION_LEN); + + if (!strcmp (filename, MSR_LOCATION)) + return get_file_memory (MSR_LOCATION, MSR_DESCRIPTION, MSR_DESCRIPTION_LEN); + + if (ut->use_presentation && ( !strcmp (filename, USHARE_PRESENTATION_PAGE) + || !strncmp (filename, USHARE_CGI, strlen (USHARE_CGI)))) + return get_file_memory (USHARE_PRESENTATION_PAGE, ut->presentation->buf, + ut->presentation->len); + + upnp_id = atoi (strrchr (filename, '/') + 1); + entry = upnp_get_entry (ut, upnp_id); + if (!entry) + return NULL; + + if (!entry->fullpath) + return NULL; + + log_verbose ("Fullpath : %s\n", entry->fullpath); + + fd = open (entry->fullpath, O_RDONLY | O_NONBLOCK | O_SYNC | O_NDELAY); + if (fd < 0) + return NULL; + + file = malloc (sizeof (struct web_file_t)); + file->fullpath = strdup (entry->fullpath); + file->pos = 0; + file->type = FILE_LOCAL; + file->detail.local.entry = entry; + file->detail.local.fd = fd; + + return ((UpnpWebFileHandle) file); +} + +static int +http_read (UpnpWebFileHandle fh, char *buf, size_t buflen) +{ + struct web_file_t *file = (struct web_file_t *) fh; + ssize_t len = -1; + + log_verbose ("http_read\n"); + + if (!file) + return -1; + + switch (file->type) + { + case FILE_LOCAL: + log_verbose ("Read local file.\n"); + len = read (file->detail.local.fd, buf, buflen); + break; + case FILE_MEMORY: + log_verbose ("Read file from memory.\n"); + len = (size_t) MIN (buflen, file->detail.memory.len - file->pos); + memcpy (buf, file->detail.memory.contents + file->pos, (size_t) len); + break; + default: + log_verbose ("Unknown file type.\n"); + break; + } + + if (len >= 0) + file->pos += len; + + log_verbose ("Read %zd bytes.\n", len); + + return len; +} + +static int +http_write (UpnpWebFileHandle fh __attribute__((unused)), + char *buf __attribute__((unused)), + size_t buflen __attribute__((unused))) +{ + log_verbose ("http write\n"); + + return 0; +} + +static int +http_seek (UpnpWebFileHandle fh, off_t offset, int origin) +{ + struct web_file_t *file = (struct web_file_t *) fh; + off_t newpos = -1; + + log_verbose ("http_seek\n"); + + if (!file) + return -1; + + switch (origin) + { + case SEEK_SET: + log_verbose ("Attempting to seek to %lld (was at %lld) in %s\n", + offset, file->pos, file->fullpath); + newpos = offset; + break; + case SEEK_CUR: + log_verbose ("Attempting to seek by %lld from %lld in %s\n", + offset, file->pos, file->fullpath); + newpos = file->pos + offset; + break; + case SEEK_END: + log_verbose ("Attempting to seek by %lld from end (was at %lld) in %s\n", + offset, file->pos, file->fullpath); + + if (file->type == FILE_LOCAL) + { + struct stat sb; + if (stat (file->fullpath, &sb) < 0) + { + log_verbose ("%s: cannot stat: %s\n", + file->fullpath, strerror (errno)); + return -1; + } + newpos = sb.st_size + offset; + } + else if (file->type == FILE_MEMORY) + newpos = file->detail.memory.len + offset; + break; + } + + switch (file->type) + { + case FILE_LOCAL: + /* Just make sure we cannot seek before start of file. */ + if (newpos < 0) + { + log_verbose ("%s: cannot seek: %s\n", file->fullpath, strerror (EINVAL)); + return -1; + } + + /* Don't seek with origin as specified above, as file may have + changed in size since our last stat. */ + if (lseek (file->detail.local.fd, newpos, SEEK_SET) == -1) + { + log_verbose ("%s: cannot seek: %s\n", file->fullpath, strerror (errno)); + return -1; + } + break; + case FILE_MEMORY: + if (newpos < 0 || newpos > file->detail.memory.len) + { + log_verbose ("%s: cannot seek: %s\n", file->fullpath, strerror (EINVAL)); + return -1; + } + break; + } + + file->pos = newpos; + + return 0; +} + +static int +http_close (UpnpWebFileHandle fh) +{ + struct web_file_t *file = (struct web_file_t *) fh; + + log_verbose ("http_close\n"); + + if (!file) + return -1; + + switch (file->type) + { + case FILE_LOCAL: + close (file->detail.local.fd); + break; + case FILE_MEMORY: + /* no close operation */ + if (file->detail.memory.contents) + free (file->detail.memory.contents); + break; + default: + log_verbose ("Unknown file type.\n"); + break; + } + + if (file->fullpath) + free (file->fullpath); + free (file); + + return 0; +} + +struct UpnpVirtualDirCallbacks virtual_dir_callbacks = + { + http_get_info, + http_open, + http_read, + http_write, + http_seek, + http_close + }; diff --git a/src/http.h b/src/http.h new file mode 100644 index 0000000..0e329a4 --- /dev/null +++ b/src/http.h @@ -0,0 +1,30 @@ +/* + * http.h : GeeXboX uShare Web Server handler header. + * Originally developped for the GeeXboX project. + * Parts of the code are originated from GMediaServer from Oskar Liljeblad. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#ifndef _HTTP_H_ +#define _HTTP_H_ + +#include +#include + +struct UpnpVirtualDirCallbacks virtual_dir_callbacks; + +#endif /* _HTTP_H_ */ diff --git a/src/metadata.c b/src/metadata.c new file mode 100644 index 0000000..f6b086c --- /dev/null +++ b/src/metadata.c @@ -0,0 +1,595 @@ +/* + * metadata.c : GeeXboX uShare CDS Metadata DB. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "mime.h" +#include "metadata.h" +#include "util_iconv.h" +#include "content.h" +#include "gettext.h" +#include "trace.h" + +#define TITLE_UNKNOWN "unknown" + +#define MAX_URL_SIZE 32 + +struct upnp_entry_lookup_t { + int id; + struct upnp_entry_t *entry_ptr; +}; + +static char * +getExtension (const char *filename) +{ + char *str = NULL; + + str = strrchr (filename, '.'); + if (str) + str++; + + return str; +} + +static struct mime_type_t * +getMimeType (const char *extension) +{ + extern struct mime_type_t MIME_Type_List[]; + struct mime_type_t *list; + + if (!extension) + return NULL; + + list = MIME_Type_List; + while (list->extension) + { + if (!strcasecmp (list->extension, extension)) + return list; + list++; + } + + return NULL; +} + +static bool +is_valid_extension (const char *extension) +{ + if (!extension) + return false; + + if (getMimeType (extension)) + return true; + + return false; +} + +static int +get_list_length (void *list) +{ + void **l = list; + int n = 0; + + while (*(l++)) + n++; + + return n; +} + +static xml_convert_t xml_convert[] = { + {'"' , """}, + {'&' , "&"}, + {'\'', "'"}, + {'<' , "<"}, + {'>' , ">"}, + {'\n', " "}, + {'\r', " "}, + {'\t', " "}, + {0, NULL}, +}; + +static char * +get_xmlconvert (int c) +{ + int j; + for (j = 0; xml_convert[j].xml; j++) + { + if (c == xml_convert[j].charac) + return xml_convert[j].xml; + } + return NULL; +} + +static char * +convert_xml (const char *title) +{ + char *newtitle, *s, *t, *xml; + int nbconvert = 0; + + /* calculate extra size needed */ + for (t = (char*) title; *t; t++) + { + xml = get_xmlconvert (*t); + if (xml) + nbconvert += strlen (xml) - 1; + } + if (!nbconvert) + return NULL; + + newtitle = s = (char*) malloc (strlen (title) + nbconvert + 1); + + for (t = (char*) title; *t; t++) + { + xml = get_xmlconvert (*t); + if (xml) + { + strcpy (s, xml); + s += strlen (xml); + } + else + *s++ = *t; + } + *s = '\0'; + + return newtitle; +} + +static struct mime_type_t Container_MIME_Type = + { NULL, "object.container.storageFolder", NULL}; + +static struct upnp_entry_t * +upnp_entry_new (struct ushare_t *ut, const char *name, const char *fullpath, + struct upnp_entry_t *parent, off_t size, int dir) +{ + struct upnp_entry_t *entry = NULL; + char *title = NULL, *x = NULL; + char url_tmp[MAX_URL_SIZE] = { '\0' }; + char *title_or_name = NULL; + + if (!name) + return NULL; + + entry = (struct upnp_entry_t *) malloc (sizeof (struct upnp_entry_t)); + +#ifdef HAVE_DLNA + entry->dlna_profile = NULL; + entry->url = NULL; + if (ut->dlna_enabled && fullpath && !dir) + { + dlna_profile_t *p = dlna_guess_media_profile (ut->dlna, fullpath); + if (!p) + { + free (entry); + return NULL; + } + entry->dlna_profile = p; + } +#endif /* HAVE_DLNA */ + + if (ut->xbox360) + { + if (ut->root_entry) + entry->id = ut->starting_id + ut->nr_entries++; + else + entry->id = 0; /* Creating the root node so don't use the usual IDs */ + } + else + entry->id = ut->starting_id + ut->nr_entries++; + + entry->fullpath = fullpath ? strdup (fullpath) : NULL; + entry->parent = parent; + entry->child_count = dir ? 0 : -1; + entry->title = NULL; + + entry->childs = (struct upnp_entry_t **) + malloc (sizeof (struct upnp_entry_t *)); + *(entry->childs) = NULL; + + if (!dir) /* item */ + { +#ifdef HAVE_DLNA + if (ut->dlna_enabled) + entry->mime_type = NULL; + else + { +#endif /* HAVE_DLNA */ + struct mime_type_t *mime = getMimeType (getExtension (name)); + if (!mime) + { + --ut->nr_entries; + upnp_entry_free (ut, entry); + log_error ("Invalid Mime type for %s, entry ignored", name); + return NULL; + } + entry->mime_type = mime; +#ifdef HAVE_DLNA + } +#endif /* HAVE_DLNA */ + + if (snprintf (url_tmp, MAX_URL_SIZE, "%d.%s", + entry->id, getExtension (name)) >= MAX_URL_SIZE) + log_error ("URL string too long for id %d, truncated!!", entry->id); + + /* Only malloc() what we really need */ + entry->url = strdup (url_tmp); + } + else /* container */ + { + entry->mime_type = &Container_MIME_Type; + entry->url = NULL; + } + + /* Try Iconv'ing the name but if it fails the end device + may still be able to handle it */ + title = iconv_convert (name); + if (title) + title_or_name = title; + else + { + if (ut->override_iconv_err) + { + title_or_name = strdup (name); + log_error ("Entry invalid name id=%d [%s]\n", entry->id, name); + } + else + { + upnp_entry_free (ut, entry); + log_error ("Freeing entry invalid name id=%d [%s]\n", entry->id, name); + return NULL; + } + } + + if (!dir) + { + x = strrchr (title_or_name, '.'); + if (x) /* avoid displaying file extension */ + *x = '\0'; + } + x = convert_xml (title_or_name); + if (x) + { + free (title_or_name); + title_or_name = x; + } + entry->title = title_or_name; + + if (!strcmp (title_or_name, "")) /* DIDL dc:title can't be empty */ + { + free (title_or_name); + entry->title = strdup (TITLE_UNKNOWN); + } + + entry->size = size; + entry->fd = -1; + + if (entry->id && entry->url) + log_verbose ("Entry->URL (%d): %s\n", entry->id, entry->url); + + return entry; +} + +/* Seperate recursive free() function in order to avoid freeing off + * the parents child list within the freeing of the first child, as + * the only entry which is not part of a childs list is the root entry + */ +static void +_upnp_entry_free (struct upnp_entry_t *entry) +{ + struct upnp_entry_t **childs; + + if (!entry) + return; + + if (entry->fullpath) + free (entry->fullpath); + if (entry->title) + free (entry->title); + if (entry->url) + free (entry->url); +#ifdef HAVE_DLNA + if (entry->dlna_profile) + entry->dlna_profile = NULL; +#endif /* HAVE_DLNA */ + + for (childs = entry->childs; *childs; childs++) + _upnp_entry_free (*childs); + free (entry->childs); +} + +void +upnp_entry_free (struct ushare_t *ut, struct upnp_entry_t *entry) +{ + if (!ut || !entry) + return; + + /* Free all entries (i.e. children) */ + if (entry == ut->root_entry) + { + struct upnp_entry_t *entry_found = NULL; + struct upnp_entry_lookup_t *lk = NULL; + RBLIST *rblist; + int i = 0; + + rblist = rbopenlist (ut->rb); + lk = (struct upnp_entry_lookup_t *) rbreadlist (rblist); + + while (lk) + { + entry_found = lk->entry_ptr; + if (entry_found) + { + if (entry_found->fullpath) + free (entry_found->fullpath); + if (entry_found->title) + free (entry_found->title); + if (entry_found->url) + free (entry_found->url); + + free (entry_found); + i++; + } + + free (lk); /* delete the lookup */ + lk = (struct upnp_entry_lookup_t *) rbreadlist (rblist); + } + + rbcloselist (rblist); + rbdestroy (ut->rb); + ut->rb = NULL; + + log_verbose ("Freed [%d] entries\n", i); + } + else + _upnp_entry_free (entry); + + free (entry); +} + +static void +upnp_entry_add_child (struct ushare_t *ut, + struct upnp_entry_t *entry, struct upnp_entry_t *child) +{ + struct upnp_entry_lookup_t *entry_lookup_ptr = NULL; + struct upnp_entry_t **childs; + int n; + + if (!entry || !child) + return; + + for (childs = entry->childs; *childs; childs++) + if (*childs == child) + return; + + n = get_list_length ((void *) entry->childs) + 1; + entry->childs = (struct upnp_entry_t **) + realloc (entry->childs, (n + 1) * sizeof (*(entry->childs))); + entry->childs[n] = NULL; + entry->childs[n - 1] = child; + entry->child_count++; + + entry_lookup_ptr = (struct upnp_entry_lookup_t *) + malloc (sizeof (struct upnp_entry_lookup_t)); + entry_lookup_ptr->id = child->id; + entry_lookup_ptr->entry_ptr = child; + + if (rbsearch ((void *) entry_lookup_ptr, ut->rb) == NULL) + log_info (_("Failed to add the RB lookup tree\n")); +} + +struct upnp_entry_t * +upnp_get_entry (struct ushare_t *ut, int id) +{ + struct upnp_entry_lookup_t *res, entry_lookup; + + log_verbose ("Looking for entry id %d\n", id); + if (id == 0) /* We do not store the root (id 0) as it is not a child */ + return ut->root_entry; + + entry_lookup.id = id; + res = (struct upnp_entry_lookup_t *) + rbfind ((void *) &entry_lookup, ut->rb); + + if (res) + { + log_verbose ("Found at %p\n", + ((struct upnp_entry_lookup_t *) res)->entry_ptr); + return ((struct upnp_entry_lookup_t *) res)->entry_ptr; + } + + log_verbose ("Not Found\n"); + + return NULL; +} + +static void +metadata_add_file (struct ushare_t *ut, struct upnp_entry_t *entry, + const char *file, const char *name, struct stat *st_ptr) +{ + if (!entry || !file || !name) + return; + +#ifdef HAVE_DLNA + if (ut->dlna_enabled || is_valid_extension (getExtension (file))) +#else + if (is_valid_extension (getExtension (file))) +#endif + { + struct upnp_entry_t *child = NULL; + + child = upnp_entry_new (ut, name, file, entry, st_ptr->st_size, false); + if (child) + upnp_entry_add_child (ut, entry, child); + } +} + +static void +metadata_add_container (struct ushare_t *ut, + struct upnp_entry_t *entry, const char *container) +{ + struct dirent **namelist; + int n,i; + + if (!entry || !container) + return; + + n = scandir (container, &namelist, 0, alphasort); + if (n < 0) + { + perror ("scandir"); + return; + } + + for (i = 0; i < n; i++) + { + struct stat st; + char *fullpath = NULL; + + if (namelist[i]->d_name[0] == '.') + { + free (namelist[i]); + continue; + } + + fullpath = (char *) + malloc (strlen (container) + strlen (namelist[i]->d_name) + 2); + sprintf (fullpath, "%s/%s", container, namelist[i]->d_name); + + log_verbose ("%s\n", fullpath); + + if (stat (fullpath, &st) < 0) + { + free (namelist[i]); + free (fullpath); + continue; + } + + if (S_ISDIR (st.st_mode)) + { + struct upnp_entry_t *child = NULL; + + child = upnp_entry_new (ut, namelist[i]->d_name, + fullpath, entry, 0, true); + if (child) + { + metadata_add_container (ut, child, fullpath); + upnp_entry_add_child (ut, entry, child); + } + } + else + metadata_add_file (ut, entry, fullpath, namelist[i]->d_name, &st); + + free (namelist[i]); + free (fullpath); + } + free (namelist); +} + +void +free_metadata_list (struct ushare_t *ut) +{ + ut->init = 0; + if (ut->root_entry) + upnp_entry_free (ut, ut->root_entry); + ut->root_entry = NULL; + ut->nr_entries = 0; + + if (ut->rb) + { + rbdestroy (ut->rb); + ut->rb = NULL; + } + + ut->rb = rbinit (rb_compare, NULL); + if (!ut->rb) + log_error (_("Cannot create RB tree for lookups\n")); +} + +void +build_metadata_list (struct ushare_t *ut) +{ + int i; + log_info (_("Building Metadata List ...\n")); + + /* build root entry */ + if (!ut->root_entry) + ut->root_entry = upnp_entry_new (ut, "root", NULL, NULL, -1, true); + + /* add files from content directory */ + for (i=0 ; i < ut->contentlist->count ; i++) + { + struct upnp_entry_t *entry = NULL; + char *title = NULL; + int size = 0; + + log_info (_("Looking for files in content directory : %s\n"), + ut->contentlist->content[i]); + + size = strlen (ut->contentlist->content[i]); + if (ut->contentlist->content[i][size - 1] == '/') + ut->contentlist->content[i][size - 1] = '\0'; + title = strrchr (ut->contentlist->content[i], '/'); + if (title) + title++; + else + { + /* directly use content directory name if no '/' before basename */ + title = ut->contentlist->content[i]; + } + + entry = upnp_entry_new (ut, title, ut->contentlist->content[i], + ut->root_entry, -1, true); + + if (!entry) + continue; + upnp_entry_add_child (ut, ut->root_entry, entry); + metadata_add_container (ut, entry, ut->contentlist->content[i]); + } + + log_info (_("Found %d files and subdirectories.\n"), ut->nr_entries); + ut->init = 1; +} + +int +rb_compare (const void *pa, const void *pb, + const void *config __attribute__ ((unused))) +{ + struct upnp_entry_lookup_t *a, *b; + + a = (struct upnp_entry_lookup_t *) pa; + b = (struct upnp_entry_lookup_t *) pb; + + if (a->id < b->id) + return -1; + + if (a->id > b->id) + return 1; + + return 0; +} + diff --git a/src/metadata.h b/src/metadata.h new file mode 100644 index 0000000..fd59425 --- /dev/null +++ b/src/metadata.h @@ -0,0 +1,60 @@ +/* + * metadata.h : GeeXboX uShare CDS Metadata DB header. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#ifndef _METADATA_H_ +#define _METADATA_H_ + +#include +#include +#include +#include + +#include "ushare.h" +#include "http.h" +#include "content.h" + +struct upnp_entry_t { + int id; + char *fullpath; +#ifdef HAVE_DLNA + dlna_profile_t *dlna_profile; +#endif /* HAVE_DLNA */ + struct upnp_entry_t *parent; + int child_count; + struct upnp_entry_t **childs; + struct mime_type_t *mime_type; + char *title; + char *url; + off_t size; + int fd; +}; + +typedef struct xml_convert_s { + char charac; + char *xml; +} xml_convert_t; + +void free_metadata_list (struct ushare_t *ut); +void build_metadata_list (struct ushare_t *ut); +struct upnp_entry_t *upnp_get_entry (struct ushare_t *ut, int id); +void upnp_entry_free (struct ushare_t *ut, struct upnp_entry_t *entry); +int rb_compare (const void *pa, const void *pb, const void *config); + +#endif /* _METADATA_H_ */ diff --git a/src/mime.c b/src/mime.c new file mode 100644 index 0000000..66c48ab --- /dev/null +++ b/src/mime.c @@ -0,0 +1,150 @@ +/* + * mime.c : GeeXboX uShare media file MIME-type association. + * Originally developped for the GeeXboX project. + * Ref : http://freedesktop.org/wiki/Standards_2fshared_2dmime_2dinfo_2dspec + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#include +#include + +#include "mime.h" +#include "ushare.h" + +#define UPNP_VIDEO "object.item.videoItem" +#define UPNP_AUDIO "object.item.audioItem.musicTrack" +#define UPNP_PHOTO "object.item.imageItem.photo" +#define UPNP_PLAYLIST "object.item.playlistItem" +#define UPNP_TEXT "object.item.textItem" + +const struct mime_type_t MIME_Type_List[] = { + /* Video files */ + { "asf", UPNP_VIDEO, "http-get:*:video/x-ms-asf:"}, + { "avc", UPNP_VIDEO, "http-get:*:video/avi:"}, + { "avi", UPNP_VIDEO, "http-get:*:video/avi:"}, + { "dv", UPNP_VIDEO, "http-get:*:video/x-dv:"}, + { "divx", UPNP_VIDEO, "http-get:*:video/avi:"}, + { "wmv", UPNP_VIDEO, "http-get:*:video/x-ms-wmv:"}, + { "mjpg", UPNP_VIDEO, "http-get:*:video/x-motion-jpeg:"}, + { "mjpeg", UPNP_VIDEO, "http-get:*:video/x-motion-jpeg:"}, + { "mpeg", UPNP_VIDEO, "http-get:*:video/mpeg:"}, + { "mpg", UPNP_VIDEO, "http-get:*:video/mpeg:"}, + { "mpe", UPNP_VIDEO, "http-get:*:video/mpeg:"}, + { "mp2p", UPNP_VIDEO, "http-get:*:video/mp2p:"}, + { "vob", UPNP_VIDEO, "http-get:*:video/mp2p:"}, + { "mp2t", UPNP_VIDEO, "http-get:*:video/mp2t:"}, + { "m1v", UPNP_VIDEO, "http-get:*:video/mpeg:"}, + { "m2v", UPNP_VIDEO, "http-get:*:video/mpeg2:"}, + { "mpg2", UPNP_VIDEO, "http-get:*:video/mpeg2:"}, + { "mpeg2", UPNP_VIDEO, "http-get:*:video/mpeg2:"}, + { "m4v", UPNP_VIDEO, "http-get:*:video/mp4:"}, + { "m4p", UPNP_VIDEO, "http-get:*:video/mp4:"}, + { "mp4ps", UPNP_VIDEO, "http-get:*:video/x-nerodigital-ps:"}, + { "ts", UPNP_VIDEO, "http-get:*:video/mpeg2:"}, + { "ogm", UPNP_VIDEO, "http-get:*:video/mpeg:"}, + { "mkv", UPNP_VIDEO, "http-get:*:video/mpeg:"}, + { "rmvb", UPNP_VIDEO, "http-get:*:video/mpeg:"}, + { "mov", UPNP_VIDEO, "http-get:*:video/quicktime:"}, + { "hdmov", UPNP_VIDEO, "http-get:*:video/quicktime:"}, + { "qt", UPNP_VIDEO, "http-get:*:video/quicktime:"}, + { "bin", UPNP_VIDEO, "http-get:*:video/mpeg2:"}, + { "iso", UPNP_VIDEO, "http-get:*:video/mpeg2:"}, + + /* Audio files */ + { "3gp", UPNP_AUDIO, "http-get:*:audio/3gpp:"}, + { "aac", UPNP_AUDIO, "http-get:*:audio/x-aac:"}, + { "ac3", UPNP_AUDIO, "http-get:*:audio/x-ac3:"}, + { "aif", UPNP_AUDIO, "http-get:*:audio/aiff:"}, + { "aiff", UPNP_AUDIO, "http-get:*:audio/aiff:"}, + { "at3p", UPNP_AUDIO, "http-get:*:audio/x-atrac3:"}, + { "au", UPNP_AUDIO, "http-get:*:audio/basic:"}, + { "snd", UPNP_AUDIO, "http-get:*:audio/basic:"}, + { "dts", UPNP_AUDIO, "http-get:*:audio/x-dts:"}, + { "rmi", UPNP_AUDIO, "http-get:*:audio/midi:"}, + { "mid", UPNP_AUDIO, "http-get:*:audio/midi:"}, + { "mp1", UPNP_AUDIO, "http-get:*:audio/mp1:"}, + { "mp2", UPNP_AUDIO, "http-get:*:audio/mp2:"}, + { "mp3", UPNP_AUDIO, "http-get:*:audio/mpeg:"}, + { "mp4", UPNP_AUDIO, "http-get:*:audio/mp4:"}, + { "m4a", UPNP_AUDIO, "http-get:*:audio/mp4:"}, + { "ogg", UPNP_AUDIO, "http-get:*:audio/x-ogg:"}, + { "wav", UPNP_AUDIO, "http-get:*:audio/wav:"}, + { "pcm", UPNP_AUDIO, "http-get:*:audio/l16:"}, + { "lpcm", UPNP_AUDIO, "http-get:*:audio/l16:"}, + { "l16", UPNP_AUDIO, "http-get:*:audio/l16:"}, + { "wma", UPNP_AUDIO, "http-get:*:audio/x-ms-wma:"}, + { "mka", UPNP_AUDIO, "http-get:*:audio/mpeg:"}, + { "ra", UPNP_AUDIO, "http-get:*:audio/x-pn-realaudio:"}, + { "rm", UPNP_AUDIO, "http-get:*:audio/x-pn-realaudio:"}, + { "ram", UPNP_AUDIO, "http-get:*:audio/x-pn-realaudio:"}, + { "flac", UPNP_AUDIO, "http-get:*:audio/x-flac:"}, + + /* Images files */ + { "bmp", UPNP_PHOTO, "http-get:*:image/bmp:"}, + { "ico", UPNP_PHOTO, "http-get:*:image/x-icon:"}, + { "gif", UPNP_PHOTO, "http-get:*:image/gif:"}, + { "jpeg", UPNP_PHOTO, "http-get:*:image/jpeg:"}, + { "jpg", UPNP_PHOTO, "http-get:*:image/jpeg:"}, + { "jpe", UPNP_PHOTO, "http-get:*:image/jpeg:"}, + { "pcd", UPNP_PHOTO, "http-get:*:image/x-ms-bmp:"}, + { "png", UPNP_PHOTO, "http-get:*:image/png:"}, + { "pnm", UPNP_PHOTO, "http-get:*:image/x-portable-anymap:"}, + { "ppm", UPNP_PHOTO, "http-get:*:image/x-portable-pixmap:"}, + { "qti", UPNP_PHOTO, "http-get:*:image/x-quicktime:"}, + { "qtf", UPNP_PHOTO, "http-get:*:image/x-quicktime:"}, + { "qtif", UPNP_PHOTO, "http-get:*:image/x-quicktime:"}, + { "tif", UPNP_PHOTO, "http-get:*:image/tiff:"}, + { "tiff", UPNP_PHOTO, "http-get:*:image/tiff:"}, + + /* Playlist files */ + { "pls", UPNP_PLAYLIST, "http-get:*:audio/x-scpls:"}, + { "m3u", UPNP_PLAYLIST, "http-get:*:audio/mpegurl:"}, + { "asx", UPNP_PLAYLIST, "http-get:*:video/x-ms-asf:"}, + + /* Subtitle Text files */ + { "srt", UPNP_TEXT, "http-get:*:text/srt:"}, /* SubRip */ + { "ssa", UPNP_TEXT, "http-get:*:text/ssa:"}, /* SubStation Alpha */ + { "stl", UPNP_TEXT, "http-get:*:text/srt:"}, /* Spruce */ + { "psb", UPNP_TEXT, "http-get:*:text/psb:"}, /* PowerDivX */ + { "pjs", UPNP_TEXT, "http-get:*:text/pjs:"}, /* Phoenix Japanim */ + { "sub", UPNP_TEXT, "http-get:*:text/sub:"}, /* MicroDVD */ + { "idx", UPNP_TEXT, "http-get:*:text/idx:"}, /* VOBsub */ + { "dks", UPNP_TEXT, "http-get:*:text/dks:"}, /* DKS */ + { "scr", UPNP_TEXT, "http-get:*:text/scr:"}, /* MACsub */ + { "tts", UPNP_TEXT, "http-get:*:text/tts:"}, /* TurboTitler */ + { "vsf", UPNP_TEXT, "http-get:*:text/vsf:"}, /* ViPlay */ + { "zeg", UPNP_TEXT, "http-get:*:text/zeg:"}, /* ZeroG */ + { "mpl", UPNP_TEXT, "http-get:*:text/mpl:"}, /* MPL */ + + /* Miscellaneous text files */ + { "bup", UPNP_TEXT, "http-get:*:text/bup:"}, /* DVD backup */ + { "ifo", UPNP_TEXT, "http-get:*:text/ifo:"}, /* DVD information */ + + { NULL, NULL, NULL} +}; + +char *mime_get_protocol (struct mime_type_t *mime) +{ + char protocol[512]; + + if (!mime) + return NULL; + + sprintf (protocol, mime->mime_protocol); + strcat (protocol, "*"); + return strdup (protocol); +} diff --git a/src/mime.h b/src/mime.h new file mode 100644 index 0000000..fa6fece --- /dev/null +++ b/src/mime.h @@ -0,0 +1,32 @@ +/* + * mime.h : GeeXboX uShare media file MIME-type association header. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#ifndef _MIME_H_ +#define _MIME_H_ + +struct mime_type_t { + char *extension; + char *mime_class; + char *mime_protocol; +}; + +char *mime_get_protocol (struct mime_type_t *mime); + +#endif /* _MIME_H */ diff --git a/src/minmax.h b/src/minmax.h new file mode 100644 index 0000000..fadff57 --- /dev/null +++ b/src/minmax.h @@ -0,0 +1,32 @@ +/* MIN, MAX macros. + * Copyright (C) 1995, 1998, 2001, 2003, 2005 Free Software Foundation, Inc. + * + * 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, 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. + */ + +#ifndef _MINMAX_H_ +#define _MINMAX_H_ + +#include + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#endif /* _MINMAX_H_ */ diff --git a/src/msr.c b/src/msr.c new file mode 100644 index 0000000..e406c32 --- /dev/null +++ b/src/msr.c @@ -0,0 +1,92 @@ +/* + * msr.c : GeeXboX uShare Microsoft Registrar Service. + * Originally developped for the GeeXboX project. + * Copyright (C) 2006 Benjamin Zores + * + * 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 Library 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. + */ + +#include +#include + +#include "ushare.h" +#include "services.h" + +/* Represent the MSR IsAuthorized action. */ +#define SERVICE_MSR_ACTION_IS_AUTHORIZED "IsAuthorized" + +/* Represent the MSR RegisterDevice action. */ +#define SERVICE_MSR_ACTION_REGISTER_DEVICE "RegisterDevice" + +/* Represent the MSR IsValidated action. */ +#define SERVICE_MSR_ACTION_IS_VALIDATED "IsValidated" + +/* Represent the MSR DeviceID argument. */ +#define SERVICE_MSR_ARG_DEVICE_ID "DeviceID" + +/* Represent the MSR Result argument. */ +#define SERVICE_MSR_ARG_RESULT "Result" + +/* Represent the MSR RegistrationReqMsg argument. */ +#define SERVICE_MSR_ARG_REGISTRATION_REQUEST_MSG "RegistrationReqMsg" + +/* Represent the MSR RegistrationRespMsg argument. */ +#define SERVICE_MSR_ARG_REGISTRATION_RESPONSE_MSG "RegistrationRespMsg" + +/* Represent the MSR Registered/Activated ID value. */ +#define SERVICE_MSR_STATUS_OK "1" + +static bool +msr_is_authorized (struct action_event_t *event) +{ + if (!event) + return false; + + /* send a fake authorization to these stupid MS players ;-) */ + upnp_add_response (event, SERVICE_MSR_ARG_RESULT, SERVICE_MSR_STATUS_OK); + + return event->status; +} + +static bool +msr_register_device (struct action_event_t *event) +{ + if (!event) + return false; + + /* dummy action */ + + return event->status; +} + +static bool +msr_is_validated (struct action_event_t *event) +{ + if (!event) + return false; + + /* send a fake validation to these stupid MS players ;-) */ + upnp_add_response (event, SERVICE_MSR_ARG_RESULT, SERVICE_MSR_STATUS_OK); + + return event->status; +} + +/* List of UPnP Microsoft Registrar Service actions */ +struct service_action_t msr_service_actions[] = { + { SERVICE_MSR_ACTION_IS_AUTHORIZED, msr_is_authorized }, + { SERVICE_MSR_ACTION_REGISTER_DEVICE, msr_register_device }, + { SERVICE_MSR_ACTION_IS_VALIDATED, msr_is_validated }, + { NULL, NULL } +}; diff --git a/src/msr.h b/src/msr.h new file mode 100644 index 0000000..e01e7e2 --- /dev/null +++ b/src/msr.h @@ -0,0 +1,121 @@ +/* + * msr.h : GeeXboX uShare Microsoft Registrar Service header. + * Originally developped for the GeeXboX project. + * Copyright (C) 2006 Benjamin Zores + * + * 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 Library 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. + */ + +#ifndef MSR_H_ +#define MSR_H_ + +#define MSR_DESCRIPTION \ +"" \ +"" \ +"" \ +" 1" \ +" 0" \ +"" \ +"" \ +" " \ +" IsAuthorized" \ +" " \ +" " \ +" DeviceID" \ +" in" \ +" A_ARG_TYPE_DeviceID" \ +" " \ +" " \ +" Result" \ +" out" \ +" A_ARG_TYPE_Result" \ +" " \ +" " \ +" " \ +" " \ +" RegisterDevice" \ +" " \ +" " \ +" RegistrationReqMsg" \ +" in" \ +" A_ARG_TYPE_RegistrationReqMsg" \ +" " \ +" " \ +" RegistrationRespMsg" \ +" out" \ +" A_ARG_TYPE_RegistrationRespMsg" \ +" " \ +" " \ +" " \ +" " \ +" IsValidated" \ +" " \ +" " \ +" DeviceID" \ +" in" \ +" A_ARG_TYPE_DeviceID" \ +" " \ +" " \ +" Result" \ +" out" \ +" A_ARG_TYPE_Result" \ +" " \ +" " \ +" " \ +"" \ +"" \ +" " \ +" A_ARG_TYPE_DeviceID" \ +" string" \ +" " \ +" " \ +" A_ARG_TYPE_Result" \ +" int" \ +" " \ +" " \ +" A_ARG_TYPE_RegistrationReqMsg" \ +" bin.base64" \ +" " \ +" " \ +" A_ARG_TYPE_RegistrationRespMsg" \ +" bin.base64" \ +" " \ +" " \ +" AuthorizationGrantedUpdateID" \ +" ui4" \ +" " \ +" " \ +" AuthorizationDeniedUpdateID" \ +" ui4" \ +" " \ +" " \ +" ValidationSucceededUpdateID" \ +" ui4" \ +" " \ +" " \ +" ValidationRevokedUpdateID" \ +" ui4" \ +" " \ +"" \ +"" + +#define MSR_DESCRIPTION_LEN strlen (MSR_DESCRIPTION) + +#define MSR_LOCATION "/web/msr.xml" + +#define MSR_SERVICE_ID "urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar" +#define MSR_SERVICE_TYPE "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1" + +#endif /* MSR_H_ */ diff --git a/src/osdep.c b/src/osdep.c new file mode 100644 index 0000000..67f4f4c --- /dev/null +++ b/src/osdep.c @@ -0,0 +1,91 @@ +/* + * osdep.c : GeeXboX uShare OS independant helpers. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#if (defined(__unix__) || defined(unix)) && !defined(USG) +#include +#endif + +#include +#include +#include +#include + +#include "osdep.h" + +#if (defined(BSD) || defined(__FreeBSD__) || defined(__APPLE__)) +char * +strndup (const char *s, size_t n) +{ + size_t len; + char *sdup = NULL; + + if (!s) + return NULL; + + len = strlen (s); + len = n < len ? n : len; + sdup = (char *) malloc (len + 1); + + if (sdup) + { + memcpy (sdup, s, len); + sdup[len] = '\0'; + } + + return sdup; +} + +ssize_t +getline (char **lineptr, size_t *n, FILE *stream) +{ + static char line[256]; + char *ptr; + ssize_t len; + + if (!lineptr || !n) + return -1; + + if (ferror (stream)) + return -1; + + if (feof (stream)) + return -1; + + fgets (line, 256, stream); + ptr = strchr (line, '\n'); + + if (ptr) + *ptr = '\0'; + + len = strlen (line); + if ((len + 1) < 256) + { + ptr = realloc (*lineptr, 256); + if (!ptr) + return -1; + + *lineptr = ptr; + *n = 256; + } + strcpy (*lineptr, line); + + return len; +} +#endif /* (defined(BSD) || defined(__FreeBSD__) || defined(__APPLE__)) */ diff --git a/src/osdep.h b/src/osdep.h new file mode 100644 index 0000000..7b5dc76 --- /dev/null +++ b/src/osdep.h @@ -0,0 +1,30 @@ +/* + * osdep.h : GeeXboX uShare OS independant helpers headers. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#ifndef _OS_DEP_H_ +#define _OS_DEP_H_ + +#if (defined(BSD) || defined(__FreeBSD__)) +#include +char *strndup (const char *s, size_t n); +ssize_t getline (char **lineptr, size_t *n, FILE *stream); +#endif + +#endif /* _OS_DEP_H_ */ diff --git a/src/presentation.c b/src/presentation.c new file mode 100644 index 0000000..ce9d14f --- /dev/null +++ b/src/presentation.c @@ -0,0 +1,215 @@ +/* + * presentation.c : GeeXboX uShare UPnP Presentation Page. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#include + +#if HAVE_LANGINFO_CODESET +# include +#endif + +#include "config.h" +#include "metadata.h" +#include "content.h" +#include "buffer.h" +#include "presentation.h" +#include "gettext.h" +#include "util_iconv.h" + +#define CGI_ACTION "action=" +#define CGI_ACTION_ADD "add" +#define CGI_ACTION_DEL "del" +#define CGI_ACTION_REFRESH "refresh" +#define CGI_PATH "path" +#define CGI_SHARE "share" + +int +process_cgi (struct ushare_t *ut, char *cgiargs) +{ + char *action = NULL; + int refresh = 0; + + if (!ut || !cgiargs) + return -1; + + if (strncmp (cgiargs, CGI_ACTION, strlen (CGI_ACTION))) + return -1; + + action = cgiargs + strlen (CGI_ACTION); + + if (!strncmp (action, CGI_ACTION_ADD, strlen (CGI_ACTION_ADD))) + { + char *path = NULL; + path = action + strlen (CGI_ACTION_ADD) + 1; + + if (path && !strncmp (path, CGI_PATH"=", strlen (CGI_PATH) + 1)) + { + ut->contentlist = content_add (ut->contentlist, + path + strlen (CGI_PATH) + 1); + refresh = 1; + } + } + else if (!strncmp (action, CGI_ACTION_DEL, strlen (CGI_ACTION_DEL))) + { + char *shares,*share; + char *m_buffer = NULL, *buffer; + int num, shift=0; + + shares = strdup (action + strlen (CGI_ACTION_DEL) + 1); + m_buffer = (char*) malloc (strlen (shares) * sizeof (char)); + if (m_buffer) + { + buffer = m_buffer; + for (share = strtok_r (shares, "&", &buffer) ; share ; + share = strtok_r (NULL, "&", &buffer)) + { + if (sscanf (share, CGI_SHARE"[%d]=on", &num) < 0) + continue; + ut->contentlist = content_del (ut->contentlist, num - shift++); + } + free (m_buffer); + } + + refresh = 1; + free (shares); + } + else if (!strncmp (action, CGI_ACTION_REFRESH, strlen (CGI_ACTION_REFRESH))) + refresh = 1; + + if (refresh && ut->contentlist) + { + free_metadata_list (ut); + build_metadata_list (ut); + } + + if (ut->presentation) + buffer_free (ut->presentation); + ut->presentation = buffer_new (); + + buffer_append (ut->presentation, ""); + buffer_append (ut->presentation, ""); + buffer_appendf (ut->presentation, "%s", + _("uShare Information Page")); + buffer_append (ut->presentation, + ""); + buffer_append (ut->presentation, + ""); + buffer_append (ut->presentation, + ""); + buffer_append (ut->presentation, ""); + buffer_append (ut->presentation, ""); + + return 0; +} + +int +build_presentation_page (struct ushare_t *ut) +{ + int i; + char *mycodeset = NULL; + + if (!ut) + return -1; + + if (ut->presentation) + buffer_free (ut->presentation); + ut->presentation = buffer_new (); + +#if HAVE_LANGINFO_CODESET + mycodeset = nl_langinfo (CODESET); +#endif + if (!mycodeset) + mycodeset = UTF8; + + buffer_append (ut->presentation, ""); + buffer_append (ut->presentation, ""); + buffer_appendf (ut->presentation, "%s", + _("uShare Information Page")); + buffer_appendf (ut->presentation, + "", + mycodeset); + buffer_append (ut->presentation, + ""); + buffer_append (ut->presentation, + ""); + buffer_append (ut->presentation, ""); + buffer_append (ut->presentation, ""); + buffer_append (ut->presentation, "

"); + buffer_appendf (ut->presentation, "%s
", + _("uShare UPnP A/V Media Server")); + buffer_append (ut->presentation, _("Information Page")); + buffer_append (ut->presentation, "

"); + buffer_append (ut->presentation, "
"); + + buffer_append (ut->presentation, "
"); + buffer_append (ut->presentation, ""); + buffer_appendf (ut->presentation, "%s : %s
", + _("Version"), VERSION); + buffer_append (ut->presentation, ""); + buffer_appendf (ut->presentation, "%s : %s
", + _("Device UDN"), ut->udn); + buffer_appendf (ut->presentation, "%s : %d
", + _("Number of shared files and directories"), ut->nr_entries); + buffer_append (ut->presentation, "

"); + + buffer_appendf (ut->presentation, + "
", USHARE_CGI); + buffer_appendf (ut->presentation, + "", + CGI_ACTION_DEL); + for (i = 0 ; i < ut->contentlist->count ; i++) + { + buffer_appendf (ut->presentation, "%s #%d :", _("Share"), i + 1); + buffer_appendf (ut->presentation, + "", i); + buffer_appendf (ut->presentation, "%s
", ut->contentlist->content[i]); + } + buffer_appendf (ut->presentation, + "", _("unShare!")); + buffer_append (ut->presentation, "
"); + buffer_append (ut->presentation, "
"); + + buffer_appendf (ut->presentation, + "
", USHARE_CGI); + buffer_append (ut->presentation, _("Add a new share : ")); + buffer_appendf (ut->presentation, + "", + CGI_ACTION_ADD); + buffer_append (ut->presentation, ""); + buffer_appendf (ut->presentation, + "", _("Share!")); + buffer_append (ut->presentation, "
"); + + buffer_append (ut->presentation, "
"); + + buffer_appendf (ut->presentation, + "
", USHARE_CGI); + buffer_appendf (ut->presentation, + "", + CGI_ACTION_REFRESH); + buffer_appendf (ut->presentation, "", + _("Refresh Shares ...")); + buffer_append (ut->presentation, "
"); + buffer_append (ut->presentation, ""); + + buffer_append (ut->presentation, ""); + buffer_append (ut->presentation, ""); + + return 0; +} diff --git a/src/presentation.h b/src/presentation.h new file mode 100644 index 0000000..2653e18 --- /dev/null +++ b/src/presentation.h @@ -0,0 +1,31 @@ +/* + * presentation.h : GeeXboX uShare UPnP Presentation Page headers. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#ifndef _PRESENTATION_H_ +#define _PRESENTATION_H_ + +#define USHARE_PRESENTATION_PAGE "/web/ushare.html" +#define PRESENTATION_PAGE_CONTENT_TYPE "text/html" +#define USHARE_CGI "/web/ushare.cgi" + +int process_cgi (struct ushare_t *ut, char *cgiargs); +int build_presentation_page (struct ushare_t *ut); + +#endif /* _PRESENTATION_H_ */ diff --git a/src/redblack.c b/src/redblack.c new file mode 100644 index 0000000..036e5c9 --- /dev/null +++ b/src/redblack.c @@ -0,0 +1,1140 @@ +static char rcsid[]="$Id: redblack.c,v 1.9 2003/10/24 01:31:21 damo Exp $"; + +/* + Redblack balanced tree algorithm + Copyright (C) Damian Ivereigh 2000 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. See the file COPYING for details. + + 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 Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Implement the red/black tree structure. It is designed to emulate +** the standard tsearch() stuff. i.e. the calling conventions are +** exactly the same +*/ + +#include +#include +#include +#include "redblack.h" + +#define assert(expr) + +/* Uncomment this if you would rather use a raw sbrk to get memory +** (however the memory is never released again (only re-used). Can't +** see any point in using this these days. +*/ +/* #define USE_SBRK */ + +enum nodecolour { BLACK, RED }; + +struct RB_ENTRY(node) +{ + struct RB_ENTRY(node) *left; /* Left down */ + struct RB_ENTRY(node) *right; /* Right down */ + struct RB_ENTRY(node) *up; /* Up */ + enum nodecolour colour; /* Node colour */ +#ifdef RB_INLINE + RB_ENTRY(data_t) key; /* User's key (and data) */ +#define RB_GET(x,y) &x->y +#define RB_SET(x,y,v) x->y = *(v) +#else + const RB_ENTRY(data_t) *key; /* Pointer to user's key (and data) */ +#define RB_GET(x,y) x->y +#define RB_SET(x,y,v) x->y = v +#endif /* RB_INLINE */ +}; + +/* Dummy (sentinel) node, so that we can make X->left->up = X +** We then use this instead of NULL to mean the top or bottom +** end of the rb tree. It is a black node. +** +** Initialization of the last field in this initializer is left implicit +** because it could be of any type. We count on the compiler to zero it. +*/ +struct RB_ENTRY(node) RB_ENTRY(_null)={&RB_ENTRY(_null), &RB_ENTRY(_null), &RB_ENTRY(_null), BLACK, &RB_ENTRY(_null)}; +#define RBNULL (&RB_ENTRY(_null)) + +#if defined(USE_SBRK) + +static struct RB_ENTRY(node) *RB_ENTRY(_alloc)(); +static void RB_ENTRY(_free)(struct RB_ENTRY(node) *); + +#else + +static struct RB_ENTRY(node) *RB_ENTRY(_alloc)() {return (struct RB_ENTRY(node) *) malloc(sizeof(struct RB_ENTRY(node)));} +static void RB_ENTRY(_free)(struct RB_ENTRY(node) *x) {free(x);} + +#endif + +/* These functions are always needed */ +static void RB_ENTRY(_left_rotate)(struct RB_ENTRY(node) **, struct RB_ENTRY(node) *); +static void RB_ENTRY(_right_rotate)(struct RB_ENTRY(node) **, struct RB_ENTRY(node) *); +static struct RB_ENTRY(node) *RB_ENTRY(_successor)(const struct RB_ENTRY(node) *); +static struct RB_ENTRY(node) *RB_ENTRY(_predecessor)(const struct RB_ENTRY(node) *); +static struct RB_ENTRY(node) *RB_ENTRY(_traverse)(int, const RB_ENTRY(data_t) * , struct RB_ENTRY(tree) *); + +/* These functions may not be needed */ +#ifndef no_lookup +static struct RB_ENTRY(node) *RB_ENTRY(_lookup)(int, const RB_ENTRY(data_t) * , struct RB_ENTRY(tree) *); +#endif + +#ifndef no_destroy +static void RB_ENTRY(_destroy)(struct RB_ENTRY(node) *); +#endif + +#ifndef no_delete +static void RB_ENTRY(_delete)(struct RB_ENTRY(node) **, struct RB_ENTRY(node) *); +static void RB_ENTRY(_delete_fix)(struct RB_ENTRY(node) **, struct RB_ENTRY(node) *); +#endif + +#ifndef no_walk +static void RB_ENTRY(_walk)(const struct RB_ENTRY(node) *, void (*)(const RB_ENTRY(data_t) *, const VISIT, const int, void *), void *, int); +#endif + +#ifndef no_readlist +static RBLIST *RB_ENTRY(_openlist)(const struct RB_ENTRY(node) *); +static const RB_ENTRY(data_t) * RB_ENTRY(_readlist)(RBLIST *); +static void RB_ENTRY(_closelist)(RBLIST *); +#endif + +/* +** OK here we go, the balanced tree stuff. The algorithm is the +** fairly standard red/black taken from "Introduction to Algorithms" +** by Cormen, Leiserson & Rivest. Maybe one of these days I will +** fully understand all this stuff. +** +** Basically a red/black balanced tree has the following properties:- +** 1) Every node is either red or black (colour is RED or BLACK) +** 2) A leaf (RBNULL pointer) is considered black +** 3) If a node is red then its children are black +** 4) Every path from a node to a leaf contains the same no +** of black nodes +** +** 3) & 4) above guarantee that the longest path (alternating +** red and black nodes) is only twice as long as the shortest +** path (all black nodes). Thus the tree remains fairly balanced. +*/ + +/* + * Initialise a tree. Identifies the comparison routine and any config + * data that must be sent to it when called. + * Returns a pointer to the top of the tree. + */ +#ifndef RB_CUSTOMIZE +RB_STATIC struct RB_ENTRY(tree) * +rbinit(int (*cmp)(const void *, const void *, const void *), const void *config) +#else +RB_STATIC struct RB_ENTRY(tree) *RB_ENTRY(init)(void) +#endif /* RB_CUSTOMIZE */ +{ + struct RB_ENTRY(tree) *retval; + char c; + + c=rcsid[0]; /* This does nothing but shutup the -Wall */ + + if ((retval=(struct RB_ENTRY(tree) *) malloc(sizeof(struct RB_ENTRY(tree))))==NULL) + return(NULL); + +#ifndef RB_CUSTOMIZE + retval->rb_cmp=cmp; + retval->rb_config=config; +#endif /* RB_CUSTOMIZE */ + retval->rb_root=RBNULL; + + return(retval); +} + +#ifndef no_destroy +RB_STATIC void +RB_ENTRY(destroy)(struct RB_ENTRY(tree) *rbinfo) +{ + if (rbinfo==NULL) + return; + + if (rbinfo->rb_root!=RBNULL) + RB_ENTRY(_destroy)(rbinfo->rb_root); + + free(rbinfo); +} +#endif /* no_destroy */ + +#ifndef no_search +RB_STATIC const RB_ENTRY(data_t) * +RB_ENTRY(search)(const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) +{ + struct RB_ENTRY(node) *x; + + if (rbinfo==NULL) + return(NULL); + + x=RB_ENTRY(_traverse)(1, key, rbinfo); + + return((x==RBNULL) ? NULL : RB_GET(x, key)); +} +#endif /* no_search */ + +#ifndef no_find +RB_STATIC const RB_ENTRY(data_t) * +RB_ENTRY(find)(const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) +{ + struct RB_ENTRY(node) *x; + + if (rbinfo==NULL) + return(NULL); + + /* If we have a NULL root (empty tree) then just return */ + if (rbinfo->rb_root==RBNULL) + return(NULL); + + x=RB_ENTRY(_traverse)(0, key, rbinfo); + + return((x==RBNULL) ? NULL : RB_GET(x, key)); +} +#endif /* no_find */ + +#ifndef no_delete +RB_STATIC const RB_ENTRY(data_t) * +RB_ENTRY(delete)(const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) +{ + struct RB_ENTRY(node) *x; + const RB_ENTRY(data_t) * y; + + if (rbinfo==NULL) + return(NULL); + + x=RB_ENTRY(_traverse)(0, key, rbinfo); + + if (x==RBNULL) + { + return(NULL); + } + else + { + y=RB_GET(x, key); + RB_ENTRY(_delete)(&rbinfo->rb_root, x); + + return(y); + } +} +#endif /* no_delete */ + +#ifndef no_walk +RB_STATIC void +RB_ENTRY(walk)(const struct RB_ENTRY(tree) *rbinfo, void (*action)(const RB_ENTRY(data_t) *, const VISIT, const int, void *), void *arg) +{ + if (rbinfo==NULL) + return; + + RB_ENTRY(_walk)(rbinfo->rb_root, action, arg, 0); +} +#endif /* no_walk */ + +#ifndef no_readlist +RB_STATIC RBLIST * +RB_ENTRY(openlist)(const struct RB_ENTRY(tree) *rbinfo) +{ + if (rbinfo==NULL) + return(NULL); + + return(RB_ENTRY(_openlist)(rbinfo->rb_root)); +} + +RB_STATIC const RB_ENTRY(data_t) * +RB_ENTRY(readlist)(RBLIST *rblistp) +{ + if (rblistp==NULL) + return(NULL); + + return(RB_ENTRY(_readlist)(rblistp)); +} + +RB_STATIC void +RB_ENTRY(closelist)(RBLIST *rblistp) +{ + if (rblistp==NULL) + return; + + RB_ENTRY(_closelist)(rblistp); +} +#endif /* no_readlist */ + +#ifndef no_lookup +RB_STATIC const RB_ENTRY(data_t) * +RB_ENTRY(lookup)(int mode, const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) +{ + struct RB_ENTRY(node) *x; + + /* If we have a NULL root (empty tree) then just return NULL */ + if (rbinfo==NULL || rbinfo->rb_root==NULL) + return(NULL); + + x=RB_ENTRY(_lookup)(mode, key, rbinfo); + + return((x==RBNULL) ? NULL : RB_GET(x, key)); +} +#endif /* no_lookup */ + +/* --------------------------------------------------------------------- */ + +/* Search for and if not found and insert is true, will add a new +** node in. Returns a pointer to the new node, or the node found +*/ +static struct RB_ENTRY(node) * +RB_ENTRY(_traverse)(int insert, const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) +{ + struct RB_ENTRY(node) *x,*y,*z; + int cmp; + int found=0; + int cmpmods(); + + y=RBNULL; /* points to the parent of x */ + x=rbinfo->rb_root; + + /* walk x down the tree */ + while(x!=RBNULL && found==0) + { + y=x; + /* printf("key=%s, RB_GET(x, key)=%s\n", key, RB_GET(x, key)); */ +#ifndef RB_CUSTOMIZE + cmp=RB_CMP(key, RB_GET(x, key), rbinfo->rb_config); +#else + cmp=RB_CMP(key, RB_GET(x, key)); +#endif /* RB_CUSTOMIZE */ + + if (cmp<0) + x=x->left; + else if (cmp>0) + x=x->right; + else + found=1; + } + + if (found || !insert) + return(x); + + if ((z=RB_ENTRY(_alloc)())==NULL) + { + /* Whoops, no memory */ + return(RBNULL); + } + + RB_SET(z, key, key); + z->up=y; + if (y==RBNULL) + { + rbinfo->rb_root=z; + } + else + { +#ifndef RB_CUSTOMIZE + cmp=RB_CMP(RB_GET(z, key), RB_GET(y, key), rbinfo->rb_config); +#else + cmp=RB_CMP(RB_GET(z, key), RB_GET(y, key)); +#endif /* RB_CUSTOMIZE */ + if (cmp<0) + y->left=z; + else + y->right=z; + } + + z->left=RBNULL; + z->right=RBNULL; + + /* colour this new node red */ + z->colour=RED; + + /* Having added a red node, we must now walk back up the tree balancing + ** it, by a series of rotations and changing of colours + */ + x=z; + + /* While we are not at the top and our parent node is red + ** N.B. Since the root node is garanteed black, then we + ** are also going to stop if we are the child of the root + */ + + while(x != rbinfo->rb_root && (x->up->colour == RED)) + { + /* if our parent is on the left side of our grandparent */ + if (x->up == x->up->up->left) + { + /* get the right side of our grandparent (uncle?) */ + y=x->up->up->right; + if (y->colour == RED) + { + /* make our parent black */ + x->up->colour = BLACK; + /* make our uncle black */ + y->colour = BLACK; + /* make our grandparent red */ + x->up->up->colour = RED; + + /* now consider our grandparent */ + x=x->up->up; + } + else + { + /* if we are on the right side of our parent */ + if (x == x->up->right) + { + /* Move up to our parent */ + x=x->up; + RB_ENTRY(_left_rotate)(&rbinfo->rb_root, x); + } + + /* make our parent black */ + x->up->colour = BLACK; + /* make our grandparent red */ + x->up->up->colour = RED; + /* right rotate our grandparent */ + RB_ENTRY(_right_rotate)(&rbinfo->rb_root, x->up->up); + } + } + else + { + /* everything here is the same as above, but + ** exchanging left for right + */ + + y=x->up->up->left; + if (y->colour == RED) + { + x->up->colour = BLACK; + y->colour = BLACK; + x->up->up->colour = RED; + + x=x->up->up; + } + else + { + if (x == x->up->left) + { + x=x->up; + RB_ENTRY(_right_rotate)(&rbinfo->rb_root, x); + } + + x->up->colour = BLACK; + x->up->up->colour = RED; + RB_ENTRY(_left_rotate)(&rbinfo->rb_root, x->up->up); + } + } + } + + /* Set the root node black */ + (rbinfo->rb_root)->colour = BLACK; + + return(z); +} + +#ifndef no_lookup +/* Search for a key according to mode (see redblack.h) +*/ +static struct RB_ENTRY(node) * +RB_ENTRY(_lookup)(int mode, const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) +{ + struct RB_ENTRY(node) *x,*y; + int cmp=0; + int found=0; + + y=RBNULL; /* points to the parent of x */ + x=rbinfo->rb_root; + + if (mode==RB_LUFIRST) + { + /* Keep going left until we hit a NULL */ + while(x!=RBNULL) + { + y=x; + x=x->left; + } + + return(y); + } + else if (mode==RB_LULAST) + { + /* Keep going right until we hit a NULL */ + while(x!=RBNULL) + { + y=x; + x=x->right; + } + + return(y); + } + + /* walk x down the tree */ + while(x!=RBNULL && found==0) + { + y=x; + /* printf("key=%s, RB_GET(x, key)=%s\n", key, RB_GET(x, key)); */ +#ifndef RB_CUSTOMIZE + cmp=RB_CMP(key, RB_GET(x, key), rbinfo->rb_config); +#else + cmp=RB_CMP(key, RB_GET(x, key)); +#endif /* RB_CUSTOMIZE */ + + + if (cmp<0) + x=x->left; + else if (cmp>0) + x=x->right; + else + found=1; + } + + if (found && (mode==RB_LUEQUAL || mode==RB_LUGTEQ || mode==RB_LULTEQ)) + return(x); + + if (!found && (mode==RB_LUEQUAL || mode==RB_LUNEXT || mode==RB_LUPREV)) + return(RBNULL); + + if (mode==RB_LUGTEQ || (!found && mode==RB_LUGREAT)) + { + if (cmp>0) + return(RB_ENTRY(_successor)(y)); + else + return(y); + } + + if (mode==RB_LULTEQ || (!found && mode==RB_LULESS)) + { + if (cmp<0) + return(RB_ENTRY(_predecessor)(y)); + else + return(y); + } + + if (mode==RB_LUNEXT || (found && mode==RB_LUGREAT)) + return(RB_ENTRY(_successor)(x)); + + if (mode==RB_LUPREV || (found && mode==RB_LULESS)) + return(RB_ENTRY(_predecessor)(x)); + + /* Shouldn't get here */ + return(RBNULL); +} +#endif /* no_lookup */ + +#ifndef no_destroy +/* + * Destroy all the elements blow us in the tree + * only useful as part of a complete tree destroy. + */ +static void +RB_ENTRY(_destroy)(struct RB_ENTRY(node) *x) +{ + if (x!=RBNULL) + { + if (x->left!=RBNULL) + RB_ENTRY(_destroy)(x->left); + if (x->right!=RBNULL) + RB_ENTRY(_destroy)(x->right); + RB_ENTRY(_free)(x); + } +} +#endif /* no_destroy */ + +/* +** Rotate our tree thus:- +** +** X rb_left_rotate(X)---> Y +** / \ / \ +** A Y <---rb_right_rotate(Y) X C +** / \ / \ +** B C A B +** +** N.B. This does not change the ordering. +** +** We assume that neither X or Y is NULL +*/ + +static void +RB_ENTRY(_left_rotate)(struct RB_ENTRY(node) **rootp, struct RB_ENTRY(node) *x) +{ + struct RB_ENTRY(node) *y; + + assert(x!=RBNULL); + assert(x->right!=RBNULL); + + y=x->right; /* set Y */ + + /* Turn Y's left subtree into X's right subtree (move B)*/ + x->right = y->left; + + /* If B is not null, set it's parent to be X */ + if (y->left != RBNULL) + y->left->up = x; + + /* Set Y's parent to be what X's parent was */ + y->up = x->up; + + /* if X was the root */ + if (x->up == RBNULL) + { + *rootp=y; + } + else + { + /* Set X's parent's left or right pointer to be Y */ + if (x == x->up->left) + { + x->up->left=y; + } + else + { + x->up->right=y; + } + } + + /* Put X on Y's left */ + y->left=x; + + /* Set X's parent to be Y */ + x->up = y; +} + +static void +RB_ENTRY(_right_rotate)(struct RB_ENTRY(node) **rootp, struct RB_ENTRY(node) *y) +{ + struct RB_ENTRY(node) *x; + + assert(y!=RBNULL); + assert(y->left!=RBNULL); + + x=y->left; /* set X */ + + /* Turn X's right subtree into Y's left subtree (move B) */ + y->left = x->right; + + /* If B is not null, set it's parent to be Y */ + if (x->right != RBNULL) + x->right->up = y; + + /* Set X's parent to be what Y's parent was */ + x->up = y->up; + + /* if Y was the root */ + if (y->up == RBNULL) + { + *rootp=x; + } + else + { + /* Set Y's parent's left or right pointer to be X */ + if (y == y->up->left) + { + y->up->left=x; + } + else + { + y->up->right=x; + } + } + + /* Put Y on X's right */ + x->right=y; + + /* Set Y's parent to be X */ + y->up = x; +} + +/* Return a pointer to the smallest key greater than x +*/ +static struct RB_ENTRY(node) * +RB_ENTRY(_successor)(const struct RB_ENTRY(node) *x) +{ + struct RB_ENTRY(node) *y; + + if (x->right!=RBNULL) + { + /* If right is not NULL then go right one and + ** then keep going left until we find a node with + ** no left pointer. + */ + for (y=x->right; y->left!=RBNULL; y=y->left); + } + else + { + /* Go up the tree until we get to a node that is on the + ** left of its parent (or the root) and then return the + ** parent. + */ + y=x->up; + while(y!=RBNULL && x==y->right) + { + x=y; + y=y->up; + } + } + return(y); +} + +/* Return a pointer to the largest key smaller than x +*/ +static struct RB_ENTRY(node) * +RB_ENTRY(_predecessor)(const struct RB_ENTRY(node) *x) +{ + struct RB_ENTRY(node) *y; + + if (x->left!=RBNULL) + { + /* If left is not NULL then go left one and + ** then keep going right until we find a node with + ** no right pointer. + */ + for (y=x->left; y->right!=RBNULL; y=y->right); + } + else + { + /* Go up the tree until we get to a node that is on the + ** right of its parent (or the root) and then return the + ** parent. + */ + y=x->up; + while(y!=RBNULL && x==y->left) + { + x=y; + y=y->up; + } + } + return(y); +} + +#ifndef no_delete +/* Delete the node z, and free up the space +*/ +static void +RB_ENTRY(_delete)(struct RB_ENTRY(node) **rootp, struct RB_ENTRY(node) *z) +{ + struct RB_ENTRY(node) *x, *y; + + if (z->left == RBNULL || z->right == RBNULL) + y=z; + else + y=RB_ENTRY(_successor)(z); + + if (y->left != RBNULL) + x=y->left; + else + x=y->right; + + x->up = y->up; + + if (y->up == RBNULL) + { + *rootp=x; + } + else + { + if (y==y->up->left) + y->up->left = x; + else + y->up->right = x; + } + + if (y!=z) + { + RB_SET(z, key, RB_GET(y, key)); + } + + if (y->colour == BLACK) + RB_ENTRY(_delete_fix)(rootp, x); + + RB_ENTRY(_free)(y); +} + +/* Restore the reb-black properties after a delete */ +static void +RB_ENTRY(_delete_fix)(struct RB_ENTRY(node) **rootp, struct RB_ENTRY(node) *x) +{ + struct RB_ENTRY(node) *w; + + while (x!=*rootp && x->colour==BLACK) + { + if (x==x->up->left) + { + w=x->up->right; + if (w->colour==RED) + { + w->colour=BLACK; + x->up->colour=RED; + rb_left_rotate(rootp, x->up); + w=x->up->right; + } + + if (w->left->colour==BLACK && w->right->colour==BLACK) + { + w->colour=RED; + x=x->up; + } + else + { + if (w->right->colour == BLACK) + { + w->left->colour=BLACK; + w->colour=RED; + RB_ENTRY(_right_rotate)(rootp, w); + w=x->up->right; + } + + + w->colour=x->up->colour; + x->up->colour = BLACK; + w->right->colour = BLACK; + RB_ENTRY(_left_rotate)(rootp, x->up); + x=*rootp; + } + } + else + { + w=x->up->left; + if (w->colour==RED) + { + w->colour=BLACK; + x->up->colour=RED; + RB_ENTRY(_right_rotate)(rootp, x->up); + w=x->up->left; + } + + if (w->right->colour==BLACK && w->left->colour==BLACK) + { + w->colour=RED; + x=x->up; + } + else + { + if (w->left->colour == BLACK) + { + w->right->colour=BLACK; + w->colour=RED; + RB_ENTRY(_left_rotate)(rootp, w); + w=x->up->left; + } + + w->colour=x->up->colour; + x->up->colour = BLACK; + w->left->colour = BLACK; + RB_ENTRY(_right_rotate)(rootp, x->up); + x=*rootp; + } + } + } + + x->colour=BLACK; +} +#endif /* no_delete */ + +#ifndef no_walk +static void +RB_ENTRY(_walk)(const struct RB_ENTRY(node) *x, void (*action)(const RB_ENTRY(data_t) *, const VISIT, const int, void *), void *arg, int level) +{ + if (x==RBNULL) + return; + + if (x->left==RBNULL && x->right==RBNULL) + { + /* leaf */ + (*action)(RB_GET(x, key), leaf, level, arg); + } + else + { + (*action)(RB_GET(x, key), preorder, level, arg); + + RB_ENTRY(_walk)(x->left, action, arg, level+1); + + (*action)(RB_GET(x, key), postorder, level, arg); + + RB_ENTRY(_walk)(x->right, action, arg, level+1); + + (*action)(RB_GET(x, key), endorder, level, arg); + } +} +#endif /* no_walk */ + +#ifndef no_readlist +static RBLIST * +RB_ENTRY(_openlist)(const struct RB_ENTRY(node) *rootp) +{ + RBLIST *rblistp; + + rblistp=(RBLIST *) malloc(sizeof(RBLIST)); + if (!rblistp) + return(NULL); + + rblistp->rootp=rootp; + rblistp->nextp=rootp; + + if (rootp!=RBNULL) + { + while(rblistp->nextp->left!=RBNULL) + { + rblistp->nextp=rblistp->nextp->left; + } + } + + return(rblistp); +} + +static const RB_ENTRY(data_t) * +RB_ENTRY(_readlist)(RBLIST *rblistp) +{ + const RB_ENTRY(data_t) *key=NULL; + + if (rblistp!=NULL && rblistp->nextp!=RBNULL) + { + key=RB_GET(rblistp->nextp, key); + rblistp->nextp=RB_ENTRY(_successor)(rblistp->nextp); + } + + return(key); +} + +static void +rb_closelist(RBLIST *rblistp) +{ + if (rblistp) + free(rblistp); +} +#endif /* no_readlist */ + +#if defined(RB_USE_SBRK) +/* Allocate space for our nodes, allowing us to get space from +** sbrk in larger chucks. +*/ +static struct RB_ENTRY(node) *rbfreep=NULL; + +#define RB_ENTRY(NODE)ALLOC_CHUNK_SIZE 1000 +static struct RB_ENTRY(node) * +RB_ENTRY(_alloc)() +{ + struct RB_ENTRY(node) *x; + int i; + + if (rbfreep==NULL) + { + /* must grab some more space */ + rbfreep=(struct RB_ENTRY(node) *) sbrk(sizeof(struct RB_ENTRY(node)) * RB_ENTRY(NODE)ALLOC_CHUNK_SIZE); + + if (rbfreep==(struct RB_ENTRY(node) *) -1) + { + return(NULL); + } + + /* tie them together in a linked list (use the up pointer) */ + for (i=0, x=rbfreep; iup = (x+1); + } + x->up=NULL; + } + + x=rbfreep; + rbfreep = rbfreep->up; +#ifdef RB_ALLOC + RB_ALLOC(ACCESS(x, key)); +#endif /* RB_ALLOC */ + return(x); +} + +/* free (dealloc) an RB_ENTRY(node) structure - add it onto the front of the list +** N.B. RB_ENTRY(node) need not have been allocated through rb_alloc() +*/ +static void +RB_ENTRY(_free)(struct RB_ENTRY(node) *x) +{ +#ifdef RB_FREE + RB_FREE(ACCESS(x, key)); +#endif /* RB_FREE */ + x->up=rbfreep; + rbfreep=x; +} + +#endif + +#if 0 +int +RB_ENTRY(_check)(struct RB_ENTRY(node) *rootp) +{ + if (rootp==NULL || rootp==RBNULL) + return(0); + + if (rootp->up!=RBNULL) + { + fprintf(stderr, "Root up pointer not RBNULL"); + dumptree(rootp, 0); + return(1); + } + + if (RB_ENTRY(_check)1(rootp)) + { + RB_ENTRY(dumptree)(rootp, 0); + return(1); + } + + if (RB_ENTRY(count_black)(rootp)==-1) + { + RB_ENTRY(dumptree)(rootp, 0); + return(-1); + } + + return(0); +} + +int +RB_ENTRY(_check1)(struct RB_ENTRY(node) *x) +{ + if (x->left==NULL || x->right==NULL) + { + fprintf(stderr, "Left or right is NULL"); + return(1); + } + + if (x->colour==RED) + { + if (x->left->colour!=BLACK && x->right->colour!=BLACK) + { + fprintf(stderr, "Children of red node not both black, x=%ld", x); + return(1); + } + } + + if (x->left != RBNULL) + { + if (x->left->up != x) + { + fprintf(stderr, "x->left->up != x, x=%ld", x); + return(1); + } + + if (rb_check1(x->left)) + return(1); + } + + if (x->right != RBNULL) + { + if (x->right->up != x) + { + fprintf(stderr, "x->right->up != x, x=%ld", x); + return(1); + } + + if (rb_check1(x->right)) + return(1); + } + return(0); +} + +RB_ENTRY(count_black)(struct RB_ENTRY(node) *x) +{ + int nleft, nright; + + if (x==RBNULL) + return(1); + + nleft=RB_ENTRY(count_black)(x->left); + nright=RB_ENTRY(count_black)(x->right); + + if (nleft==-1 || nright==-1) + return(-1); + + if (nleft != nright) + { + fprintf(stderr, "Black count not equal on left & right, x=%ld", x); + return(-1); + } + + if (x->colour == BLACK) + { + nleft++; + } + + return(nleft); +} + +RB_ENTRY(dumptree)(struct RB_ENTRY(node) *x, int n) +{ + char *prkey(); + + if (x!=NULL && x!=RBNULL) + { + n++; + fprintf(stderr, "Tree: %*s %ld: left=%ld, right=%ld, colour=%s, key=%s", + n, + "", + x, + x->left, + x->right, + (x->colour==BLACK) ? "BLACK" : "RED", + prkey(RB_GET(x, key))); + + RB_ENTRY(dumptree)(x->left, n); + RB_ENTRY(dumptree)(x->right, n); + } +} +#endif + +/* + * $Log: redblack.c,v $ + * Revision 1.9 2003/10/24 01:31:21 damo + * Patches from Eric Raymond: %prefix is implemented.  Various other small + * changes avoid stepping on global namespaces and improve the documentation. + * + * Revision 1.8 2002/08/26 05:33:47 damo + * Some minor fixes:- + * Stopped ./configure warning about stuff being in the wrong order + * Fixed compiler warning about const (not sure about this) + * Changed directory of redblack.c in documentation + * + * Revision 1.7 2002/08/26 03:11:40 damo + * Fixed up a bunch of compiler warnings when compiling example4 + * + * Tidies up the Makefile.am & Specfile. + * + * Renamed redblack to rbgen + * + * Revision 1.6 2002/08/26 01:03:35 damo + * Patch from Eric Raymond to change the way the library is used:- + * + * Eric's idea is to convert libredblack into a piece of in-line code + * generated by another program. This should be faster, smaller and easier + * to use. + * + * This is the first check-in of his code before I start futzing with it! + * + * Revision 1.5 2002/01/30 07:54:53 damo + * Fixed up the libtool versioning stuff (finally) + * Fixed bug 500600 (not detecting a NULL return from malloc) + * Fixed bug 509485 (no longer needs search.h) + * Cleaned up debugging section + * Allow multiple inclusions of redblack.h + * Thanks to Matthias Andree for reporting (and fixing) these + * + * Revision 1.4 2000/06/06 14:43:43 damo + * Added all the rbwalk & rbopenlist stuff. Fixed up malloc instead of sbrk. + * Added two new examples + * + * Revision 1.3 2000/05/24 06:45:27 damo + * Converted everything over to using const + * Added a new example1.c file to demonstrate the worst case scenario + * Minor fixups of the spec file + * + * Revision 1.2 2000/05/24 06:17:10 damo + * Fixed up the License (now the LGPL) + * + * Revision 1.1 2000/05/24 04:15:53 damo + * Initial import of files. Versions are now all over the place. Oh well + * + */ + diff --git a/src/redblack.h b/src/redblack.h new file mode 100644 index 0000000..a4f8bcf --- /dev/null +++ b/src/redblack.h @@ -0,0 +1,188 @@ +/* + * RCS $Id: redblack.h,v 1.9 2003/10/24 01:31:21 damo Exp $ + */ + +/* + Redblack balanced tree algorithm + Copyright (C) Damian Ivereigh 2000 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. See the file COPYING for details. + + 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 Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Header file for redblack.c, should be included by any code that +** uses redblack.c since it defines the functions +*/ + +/* Stop multiple includes */ +#ifndef _REDBLACK_H + +#ifndef RB_CUSTOMIZE +/* + * Without customization, the data member in the tree nodes is a void + * pointer, and you need to pass in a comparison function to be + * applied at runtime. With customization, you specify the data type + * as the macro RB_ENTRY(data_t) (has to be a macro because compilers + * gag on typdef void) and the name of the compare function as the + * value of the macro RB_CMP. Because the comparison function is + * compiled in, RB_CMP only needs to take two arguments. If your + * content type is not a pointer, define INLINE to get direct access. + */ +#define rbdata_t void +#define RB_CMP(s, t, e) (*rbinfo->rb_cmp)(s, t, e) +#undef RB_INLINE +#define RB_ENTRY(name) rb##name +#endif /* RB_CUSTOMIZE */ + +#ifndef RB_STATIC +#define RB_STATIC +#endif + +/* Modes for rblookup */ +#define RB_NONE -1 /* None of those below */ +#define RB_LUEQUAL 0 /* Only exact match */ +#define RB_LUGTEQ 1 /* Exact match or greater */ +#define RB_LULTEQ 2 /* Exact match or less */ +#define RB_LULESS 3 /* Less than key (not equal to) */ +#define RB_LUGREAT 4 /* Greater than key (not equal to) */ +#define RB_LUNEXT 5 /* Next key after current */ +#define RB_LUPREV 6 /* Prev key before current */ +#define RB_LUFIRST 7 /* First key in index */ +#define RB_LULAST 8 /* Last key in index */ + +/* For rbwalk - pinched from search.h */ +typedef enum +{ + preorder, + postorder, + endorder, + leaf +} +VISIT; + +struct RB_ENTRY(lists) { +const struct RB_ENTRY(node) *rootp; +const struct RB_ENTRY(node) *nextp; +}; + +#define RBLIST struct RB_ENTRY(lists) + +struct RB_ENTRY(tree) { +#ifndef RB_CUSTOMIZE + /* comparison routine */ +int (*rb_cmp)(const void *, const void *, const void *); + /* config data to be passed to rb_cmp */ +const void *rb_config; + /* root of tree */ +#endif /* RB_CUSTOMIZE */ +struct RB_ENTRY(node) *rb_root; +}; + +#ifndef RB_CUSTOMIZE +RB_STATIC struct RB_ENTRY(tree) *rbinit(int (*)(const void *, const void *, const void *), + const void *); +#else +RB_STATIC struct RB_ENTRY(tree) *RB_ENTRY(init)(void); +#endif /* RB_CUSTOMIZE */ + +#ifndef no_delete +RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(delete)(const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); +#endif + +#ifndef no_find +RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(find)(const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); +#endif + +#ifndef no_lookup +RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(lookup)(int, const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); +#endif + +#ifndef no_search +RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(search)(const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); +#endif + +#ifndef no_destroy +RB_STATIC void RB_ENTRY(destroy)(struct RB_ENTRY(tree) *); +#endif + +#ifndef no_walk +RB_STATIC void RB_ENTRY(walk)(const struct RB_ENTRY(tree) *, + void (*)(const RB_ENTRY(data_t) *, const VISIT, const int, void *), + void *); +#endif + +#ifndef no_readlist +RB_STATIC RBLIST *RB_ENTRY(openlist)(const struct RB_ENTRY(tree) *); +RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(readlist)(RBLIST *); +RB_STATIC void RB_ENTRY(closelist)(RBLIST *); +#endif + +/* Some useful macros */ +#define rbmin(rbinfo) RB_ENTRY(lookup)(RB_LUFIRST, NULL, (rbinfo)) +#define rbmax(rbinfo) RB_ENTRY(lookup)(RB_LULAST, NULL, (rbinfo)) + +#define _REDBLACK_H +#endif /* _REDBLACK_H */ + +/* + * + * $Log: redblack.h,v $ + * Revision 1.9 2003/10/24 01:31:21 damo + * Patches from Eric Raymond: %prefix is implemented.  Various other small + * changes avoid stepping on global namespaces and improve the documentation. + * + * Revision 1.8 2003/10/23 04:18:47 damo + * Fixed up the rbgen stuff ready for the 1.3 release + * + * Revision 1.7 2002/08/26 03:11:40 damo + * Fixed up a bunch of compiler warnings when compiling example4 + * + * Tidies up the Makefile.am & Specfile. + * + * Renamed redblack to rbgen + * + * Revision 1.6 2002/08/26 01:03:35 damo + * Patch from Eric Raymond to change the way the library is used:- + * + * Eric's idea is to convert libredblack into a piece of in-line code + * generated by another program. This should be faster, smaller and easier + * to use. + * + * This is the first check-in of his code before I start futzing with it! + * + * Revision 1.5 2002/01/30 07:54:53 damo + * Fixed up the libtool versioning stuff (finally) + * Fixed bug 500600 (not detecting a NULL return from malloc) + * Fixed bug 509485 (no longer needs search.h) + * Cleaned up debugging section + * Allow multiple inclusions of redblack.h + * Thanks to Matthias Andree for reporting (and fixing) these + * + * Revision 1.4 2000/06/06 14:43:43 damo + * Added all the rbwalk & rbopenlist stuff. Fixed up malloc instead of sbrk. + * Added two new examples + * + * Revision 1.3 2000/05/24 06:45:27 damo + * Converted everything over to using const + * Added a new example1.c file to demonstrate the worst case scenario + * Minor fixups of the spec file + * + * Revision 1.2 2000/05/24 06:17:10 damo + * Fixed up the License (now the LGPL) + * + * Revision 1.1 2000/05/24 04:15:53 damo + * Initial import of files. Versions are now all over the place. Oh well + * + */ + diff --git a/src/services.c b/src/services.c new file mode 100644 index 0000000..9907312 --- /dev/null +++ b/src/services.c @@ -0,0 +1,177 @@ +/* + * services.c : GeeXboX uShare UPnP services handler. + * Originally developped for the GeeXboX project. + * Parts of the code are originated from GMediaServer from Oskar Liljeblad. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#include +#include +#include + +#include "ushare.h" +#include "services.h" +#include "cms.h" +#include "cds.h" +#include "msr.h" +#include "trace.h" + +/* Represent the ObjectID argument. */ +#define ARG_OBJECT_ID "ObjectID" + +/* Represent the ContainerID argument. */ +#define ARG_CONTAINER_ID "ContainerID" + +extern struct service_action_t cds_service_actions[]; +extern struct service_action_t cms_service_actions[]; +extern struct service_action_t msr_service_actions[]; + +static struct service_t services[] = { + { + CDS_SERVICE_ID, + CDS_SERVICE_TYPE, + cds_service_actions + }, + { + CMS_SERVICE_ID, + CMS_SERVICE_TYPE, + cms_service_actions + }, + { + MSR_SERVICE_ID, + MSR_SERVICE_TYPE, + msr_service_actions + }, + { NULL, NULL, NULL } +}; + +bool +find_service_action (struct Upnp_Action_Request *request, + struct service_t **service, + struct service_action_t **action) +{ + int c, d; + + *service = NULL; + *action = NULL; + + if (!request || !request->ActionName) + return false; + + for (c = 0; services[c].id != NULL; c++) + if (!strcmp (services[c].id, request->ServiceID)) + { + *service = &services[c]; + for (d = 0; services[c].actions[d].name; d++) + { + if (!strcmp (services[c].actions[d].name, request->ActionName)) + { + *action = &services[c].actions[d]; + return true; + } + } + return false; + } + + return false; +} + +bool +upnp_add_response (struct action_event_t *event, char *key, const char *value) +{ + char *val; + int res; + + if (!event || !event->status || !key || !value) + return false; + + val = strdup (value); + if (!val) + return false; + + res = UpnpAddToActionResponse (&event->request->ActionResult, + event->request->ActionName, + event->service->type, key, val); + + if (res != UPNP_E_SUCCESS) + { + free (val); + return false; + } + + free (val); + return true; +} + +char * +upnp_get_string (struct Upnp_Action_Request *request, const char *key) +{ + IXML_Node *node = NULL; + + if (!request || !request->ActionRequest || !key) + return NULL; + + node = (IXML_Node *) request->ActionRequest; + if (!node) + { + log_verbose ("Invalid action request document\n"); + return NULL; + } + + node = ixmlNode_getFirstChild (node); + if (!node) + { + log_verbose ("Invalid action request document\n"); + return NULL; + } + + node = ixmlNode_getFirstChild (node); + for (; node; node = ixmlNode_getNextSibling (node)) + if (!strcmp (ixmlNode_getNodeName (node), key)) + { + node = ixmlNode_getFirstChild (node); + if (!node) + return strdup (""); + return strdup (ixmlNode_getNodeValue (node)); + } + + log_verbose ("Missing action request argument (%s)\n", key); + + return NULL; +} + +int +upnp_get_ui4 (struct Upnp_Action_Request *request, const char *key) +{ + char *value; + int val; + + if (!request || !key) + return 0; + + value = upnp_get_string (request, key); + if (!value && !strcmp (key, ARG_OBJECT_ID)) + value = upnp_get_string (request, ARG_CONTAINER_ID); + + if (!value) + return 0; + + val = atoi (value); + free (value); + + return val; +} diff --git a/src/services.h b/src/services.h new file mode 100644 index 0000000..89c072e --- /dev/null +++ b/src/services.h @@ -0,0 +1,53 @@ +/* + * services.h : GeeXboX uShare UPnP services handler header. + * Originally developped for the GeeXboX project. + * Parts of the code are originated from GMediaServer from Oskar Liljeblad. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#ifndef _SERVICES_H_ +#define _SERVICES_H_ + +#include +#include +#include "ushare.h" + +struct service_action_t { + char *name; + bool (*function) (struct action_event_t *); +}; + +struct service_t { + char *id; + char *type; + struct service_action_t *actions; +}; + +#define SERVICE_CONTENT_TYPE "text/xml" + +bool find_service_action (struct Upnp_Action_Request *request, + struct service_t **service, + struct service_action_t **action); + +bool upnp_add_response (struct action_event_t *event, + char *key, const char *value); + +char * upnp_get_string (struct Upnp_Action_Request *request, const char *key); + +int upnp_get_ui4 (struct Upnp_Action_Request *request, const char *key); + +#endif /* _SERVICES_H_ */ diff --git a/src/trace.c b/src/trace.c new file mode 100644 index 0000000..09dbb86 --- /dev/null +++ b/src/trace.c @@ -0,0 +1,64 @@ +/* + * trace.c : GeeXboX uShare log facility. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Alexis Saettler + * + * 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 Library 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. + */ + +#include +#include +#include +#include + +#include "config.h" +#include "trace.h" +#include "ushare.h" + +extern struct ushare_t *ut; + +void +print_log (log_level level, const char *format, ...) +{ + va_list va; + bool is_daemon = ut ? ut->daemon : false; + bool is_verbose = ut ? ut->verbose : false; + + if (!format) + return; + + if (!is_verbose && level >= ULOG_VERBOSE) + return; + + va_start (va, format); + if (is_daemon) + { + int flags = LOG_DAEMON; + flags |= level == ULOG_ERROR ? LOG_ERR : LOG_NOTICE; + vsyslog (flags, format, va); + } + else + { + FILE *output = level == ULOG_ERROR ? stderr : stdout; + vfprintf (output, format, va); + } + va_end (va); +} + +inline void +start_log (void) +{ + openlog (PACKAGE_NAME, LOG_PID, LOG_DAEMON); +} diff --git a/src/trace.h b/src/trace.h new file mode 100644 index 0000000..7701507 --- /dev/null +++ b/src/trace.h @@ -0,0 +1,55 @@ +/* + * trace.h : GeeXboX uShare log facility headers. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Alexis Saettler + * + * 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 Library 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. + */ + +#ifndef _TRACE_H_ +#define _TRACE_H_ + +typedef enum { + ULOG_NORMAL = 1, + ULOG_ERROR = 2, + ULOG_VERBOSE = 3, +} log_level; + +void print_log (log_level level, const char *format, ...) + __attribute__ ((format (printf, 2, 3))); +inline void start_log (void); + +/* log_info + * Normal print, to replace printf + */ +#define log_info(s, str...) { \ + print_log (ULOG_NORMAL, (s), ##str); \ + } + +/* log_error + * Error messages, output to stderr + */ +#define log_error(s, str...) { \ + print_log (ULOG_ERROR, (s), ##str); \ + } + +/* log_verbose + * Output only in verbose mode + */ +#define log_verbose(s, str...) { \ + print_log (ULOG_VERBOSE, (s), ##str); \ + } + +#endif /* _TRACE_H_ */ diff --git a/src/ushare.1 b/src/ushare.1 new file mode 100644 index 0000000..eccc491 --- /dev/null +++ b/src/ushare.1 @@ -0,0 +1,121 @@ +.\" -*- nroff -*- +.\" ushare.1 - Manual page for uShare. +.\" +.\" Copyright (C) 2005-2007 Benjamin Zores +.\" +.\" 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 Library 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. +.\" +.TH USHARE 1 "July 05, 2007" +.SH NAME +uShare \(hy a lightweight UPnP A/V and DLNA Media Server +.SH SYNOPSIS +.B ushare +[\f-\-v\fR] [\f-\-n name\fR] [\f-\-i interface\fR] [\f-\-p port\fR] [\f-\-w\fR] [\f-\-c directory\fR] [[\f-\-c directory\fR]...] +.SH DESCRIPTION +\fBuShare\fP is a free UPnP / DLNA multimedia files Media Server. +It implements the server component that provides UPnP media devices with +information on available multimedia files. uShare uses the built-in http +server of libupnp to stream the files to clients. + +.SH OPTIONS +This program follow the usual GNU command line syntax. +.TP +\fB\-\-name (\-n)\fR \fINAME\fR +Set UPnP Friendly Name (display name of the media server). +Default is 'uShare'. +.TP +\fB\-\-interface (\-i)\fR \fIINTERFACE\fR +Set Network interface to listen on. +Default is 'uShare'. +.TP +\fB\-\-cfg (\-f)\fR \fIFILE\fR +Config file to be used. +.TP +\fB\-\-port (\-p)\fR \fIPORT\fR +Set Network port to listen on. +Default is random above 49152. +.TP +\fB\-\-telnet_port (\-q)\fR \fIPORT\fR +Set Network port to listen on for telnet control connections. +Default is 1337. +.TP +\fB\-\-content (\-c)\fR \fIDIRECTORY\fR +Set directory to scan for multimedia files. +.TP +\fB\-\-no\-web (\-w)\fR +Disable the control web page (enabled by default). +.TP +\fB\-\-no\-telnet (\-t)\fR +Disable the telnet control (enabled by default). +.TP +\fB\-\-override-iconv-err (\-o)\fR +If iconv fails parsing name, still add to media contents +(hoping the renderer can handle it). +.TP +\fB\-\-verbose (\-v)\fR +Set verbose display. +.TP +\fB\-\-xbox (\-x)\fR +Use XboX 360 compliant profile. +.TP +\fB\-\-dlna (\-d)\fR +Use DLNA compliant profile (required for PlayStation3). +.TP +\fB\-\-daemon (\-D)\fR +Run as a daemon. +.TP +\fB\-\-version (\-V)\fR +Display uShare version number. +.TP +\fB\-\-help (\-h)\fR +Display help message. +.SH "REMOTE CONTROL" +You can also perform remote control of uShare UPnP Media Server through its +web interface. This let you define new content locations at runtime or +update the currently shared one in case the filesystem has changed. +To disable this feature, use the \-\-no\-web option (see bellow). +.TP +Just go to : +.TP +.B http://ip_address:port/web/ushare.html +.SH "FILE TYPES" +The following file formats (extensions) are supported : +.TP +.B Video +asf, avi, dv, divx, wmv, mjpg, mjpeg, mpeg, mpg, mpe, mp2p, vob, mp2t, m1v, m2v, m4v, m4p, mp4ps, ts, ogm, mkv, rmvb, mov, qt +.TP +.B Audio +aac, ac3, aif, aiff, at3p, au, snd, dts, rmi, mp1, mp2, mp3, mp4, mpa, ogg, wav, pcm, lpcm, l16, wma, mka, ra, rm, ram +.TP +.B Images +bmp, ico, gif, jpeg, jpg, jpe, pcd, png, pnm, ppm, qti, qtf, qtif, tif, tiff +.TP +.B Playlist +pls, m3u, asx +.TP +.B Subtitles +dks, idx, mpl, pjs, psb, scr, srt, ssa, stl, sub, tts, vsf, zeg +.TP +.B Miscellaneous files +bup, ifo +.SH "REPORTING BUGS" +Report bugs to <\fIushare@geexbox.org\fP>. +.SH AUTHOR +uShare was written by Benjamin Zores . +.SH COPYRIGHT +Copyright \(co 2005-2007 Benjamin Zores + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/src/ushare.c b/src/ushare.c new file mode 100644 index 0000000..717e862 --- /dev/null +++ b/src/ushare.c @@ -0,0 +1,884 @@ +/* + * ushare.c : GeeXboX uShare UPnP Media Server. + * Originally developped for the GeeXboX project. + * Parts of the code are originated from GMediaServer from Oskar Liljeblad. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if (defined(BSD) || defined(__FreeBSD__) || defined(__APPLE__)) +#include +#include +#include +#endif + +#if (defined(__APPLE__)) +#include +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_IFADDRS_H +#include +#endif + +#if (defined(__unix__) || defined(unix)) && !defined(USG) +#include +#endif + +#include +#include + +#if (defined(HAVE_SETLOCALE) && defined(CONFIG_NLS)) +# include +#endif + +#include "config.h" +#include "ushare.h" +#include "services.h" +#include "http.h" +#include "metadata.h" +#include "util_iconv.h" +#include "content.h" +#include "cfgparser.h" +#include "gettext.h" +#include "trace.h" +#include "buffer.h" +#include "ctrl_telnet.h" + +struct ushare_t *ut = NULL; + +static struct ushare_t * ushare_new (void) + __attribute__ ((malloc)); + +static struct ushare_t * +ushare_new (void) +{ + struct ushare_t *ut = (struct ushare_t *) malloc (sizeof (struct ushare_t)); + if (!ut) + return NULL; + + ut->name = strdup (DEFAULT_USHARE_NAME); + ut->interface = strdup (DEFAULT_USHARE_IFACE); + ut->model_name = strdup (DEFAULT_USHARE_NAME); + ut->contentlist = NULL; + ut->rb = rbinit (rb_compare, NULL); + ut->root_entry = NULL; + ut->nr_entries = 0; + ut->starting_id = STARTING_ENTRY_ID_DEFAULT; + ut->init = 0; + ut->dev = 0; + ut->udn = NULL; + ut->ip = NULL; + ut->port = 0; /* Randomly attributed by libupnp */ + ut->telnet_port = CTRL_TELNET_PORT; + ut->presentation = NULL; + ut->use_presentation = true; + ut->use_telnet = true; +#ifdef HAVE_DLNA + ut->dlna_enabled = false; + ut->dlna = NULL; + ut->dlna_flags = DLNA_ORG_FLAG_STREAMING_TRANSFER_MODE | + DLNA_ORG_FLAG_BACKGROUND_TRANSFERT_MODE | + DLNA_ORG_FLAG_CONNECTION_STALL | + DLNA_ORG_FLAG_DLNA_V15; +#endif /* HAVE_DLNA */ + ut->xbox360 = false; + ut->verbose = false; + ut->daemon = false; + ut->override_iconv_err = false; + ut->cfg_file = NULL; + + pthread_mutex_init (&ut->termination_mutex, NULL); + pthread_cond_init (&ut->termination_cond, NULL); + + return ut; +} + +static void +ushare_free (struct ushare_t *ut) +{ + if (!ut) + return; + + if (ut->name) + free (ut->name); + if (ut->interface) + free (ut->interface); + if (ut->model_name) + free (ut->model_name); + if (ut->contentlist) + content_free (ut->contentlist); + if (ut->rb) + rbdestroy (ut->rb); + if (ut->root_entry) + upnp_entry_free (ut, ut->root_entry); + if (ut->udn) + free (ut->udn); + if (ut->ip) + free (ut->ip); + if (ut->presentation) + buffer_free (ut->presentation); +#ifdef HAVE_DLNA + if (ut->dlna_enabled) + { + if (ut->dlna) + dlna_uninit (ut->dlna); + ut->dlna = NULL; + } +#endif /* HAVE_DLNA */ + if (ut->cfg_file) + free (ut->cfg_file); + + pthread_cond_destroy (&ut->termination_cond); + pthread_mutex_destroy (&ut->termination_mutex); + + free (ut); +} + +static void +ushare_signal_exit (void) +{ + pthread_mutex_lock (&ut->termination_mutex); + pthread_cond_signal (&ut->termination_cond); + pthread_mutex_unlock (&ut->termination_mutex); +} + +static void +handle_action_request (struct Upnp_Action_Request *request) +{ + struct service_t *service; + struct service_action_t *action; + char val[256]; + uint32_t ip; + + if (!request || !ut) + return; + + if (request->ErrCode != UPNP_E_SUCCESS) + return; + + if (strcmp (request->DevUDN + 5, ut->udn)) + return; + + ip = request->CtrlPtIPAddr.s_addr; + ip = ntohl (ip); + sprintf (val, "%d.%d.%d.%d", + (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF); + + if (ut->verbose) + { + DOMString str = ixmlPrintDocument (request->ActionRequest); + log_verbose ("***************************************************\n"); + log_verbose ("** New Action Request **\n"); + log_verbose ("***************************************************\n"); + log_verbose ("ServiceID: %s\n", request->ServiceID); + log_verbose ("ActionName: %s\n", request->ActionName); + log_verbose ("CtrlPtIP: %s\n", val); + log_verbose ("Action Request:\n%s\n", str); + ixmlFreeDOMString (str); + } + + if (find_service_action (request, &service, &action)) + { + struct action_event_t event; + + event.request = request; + event.status = true; + event.service = service; + + if (action->function (&event) && event.status) + request->ErrCode = UPNP_E_SUCCESS; + + if (ut->verbose) + { + DOMString str = ixmlPrintDocument (request->ActionResult); + log_verbose ("Action Result:\n%s", str); + log_verbose ("***************************************************\n"); + log_verbose ("\n"); + ixmlFreeDOMString (str); + } + + return; + } + + if (service) /* Invalid Action name */ + strcpy (request->ErrStr, "Unknown Service Action"); + else /* Invalid Service name */ + strcpy (request->ErrStr, "Unknown Service ID"); + + request->ActionResult = NULL; + request->ErrCode = UPNP_SOAP_E_INVALID_ACTION; +} + +static int +device_callback_event_handler (Upnp_EventType type, void *event, + void *cookie __attribute__((unused))) +{ + switch (type) + { + case UPNP_CONTROL_ACTION_REQUEST: + handle_action_request ((struct Upnp_Action_Request *) event); + break; + case UPNP_CONTROL_ACTION_COMPLETE: + case UPNP_EVENT_SUBSCRIPTION_REQUEST: + case UPNP_CONTROL_GET_VAR_REQUEST: + break; + default: + break; + } + + return 0; +} + +static int +finish_upnp (struct ushare_t *ut) +{ + if (!ut) + return -1; + + log_info (_("Stopping UPnP Service ...\n")); + UpnpUnRegisterRootDevice (ut->dev); + UpnpFinish (); + + return UPNP_E_SUCCESS; +} + +static int +init_upnp (struct ushare_t *ut) +{ + char *description = NULL; + int res; + size_t len; + + if (!ut || !ut->name || !ut->udn || !ut->ip) + return -1; + +#ifdef HAVE_DLNA + if (ut->dlna_enabled) + { + len = 0; + description = + dlna_dms_description_get (ut->name, + "GeeXboX Team", + "http://ushare.geexbox.org/", + "uShare : DLNA Media Server", + ut->model_name, + "001", + "http://ushare.geexbox.org/", + "USHARE-01", + ut->udn, + "/web/ushare.html", + "/web/cms.xml", + "/web/cms_control", + "/web/cms_event", + "/web/cds.xml", + "/web/cds_control", + "/web/cds_event"); + if (!description) + return -1; + } + else + { +#endif /* HAVE_DLNA */ + len = strlen (UPNP_DESCRIPTION) + strlen (ut->name) + + strlen (ut->model_name) + strlen (ut->udn) + 1; + description = (char *) malloc (len * sizeof (char)); + memset (description, 0, len); + sprintf (description, UPNP_DESCRIPTION, ut->name, ut->model_name, ut->udn); +#ifdef HAVE_DLNA + } +#endif /* HAVE_DLNA */ + + log_info (_("Initializing UPnP subsystem ...\n")); + res = UpnpInit (ut->ip, ut->port); + if (res != UPNP_E_SUCCESS) + { + log_error (_("Cannot initialize UPnP subsystem\n")); + return -1; + } + + if (UpnpSetMaxContentLength (UPNP_MAX_CONTENT_LENGTH) != UPNP_E_SUCCESS) + log_info (_("Could not set Max content UPnP\n")); + + if (ut->xbox360) + log_info (_("Starting in XboX 360 compliant profile ...\n")); + +#ifdef HAVE_DLNA + if (ut->dlna_enabled) + { + log_info (_("Starting in DLNA compliant profile ...\n")); + ut->dlna = dlna_init (); + dlna_set_verbosity (ut->dlna, ut->verbose ? 1 : 0); + dlna_set_extension_check (ut->dlna, 1); + dlna_register_all_media_profiles (ut->dlna); + } +#endif /* HAVE_DLNA */ + + ut->port = UpnpGetServerPort(); + log_info (_("UPnP MediaServer listening on %s:%d\n"), + UpnpGetServerIpAddress (), ut->port); + + UpnpEnableWebserver (TRUE); + + res = UpnpSetVirtualDirCallbacks (&virtual_dir_callbacks); + if (res != UPNP_E_SUCCESS) + { + log_error (_("Cannot set virtual directory callbacks\n")); + free (description); + return -1; + } + + res = UpnpAddVirtualDir (VIRTUAL_DIR); + if (res != UPNP_E_SUCCESS) + { + log_error (_("Cannot add virtual directory for web server\n")); + free (description); + return -1; + } + + res = UpnpRegisterRootDevice2 (UPNPREG_BUF_DESC, description, 0, 1, + device_callback_event_handler, + NULL, &(ut->dev)); + if (res != UPNP_E_SUCCESS) + { + log_error (_("Cannot register UPnP device\n")); + free (description); + return -1; + } + + res = UpnpUnRegisterRootDevice (ut->dev); + if (res != UPNP_E_SUCCESS) + { + log_error (_("Cannot unregister UPnP device\n")); + free (description); + return -1; + } + + res = UpnpRegisterRootDevice2 (UPNPREG_BUF_DESC, description, 0, 1, + device_callback_event_handler, + NULL, &(ut->dev)); + if (res != UPNP_E_SUCCESS) + { + log_error (_("Cannot register UPnP device\n")); + free (description); + return -1; + } + + log_info (_("Sending UPnP advertisement for device ...\n")); + UpnpSendAdvertisement (ut->dev, 1800); + + log_info (_("Listening for control point connections ...\n")); + + if (description) + free (description); + + return 0; +} + +static bool +has_iface (char *interface) +{ +#ifdef HAVE_IFADDRS_H + struct ifaddrs *itflist, *itf; + + if (!interface) + return false; + + if (getifaddrs (&itflist) < 0) + { + perror ("getifaddrs"); + return false; + } + + itf = itflist; + while (itf) + { + if ((itf->ifa_flags & IFF_UP) + && !strncmp (itf->ifa_name, interface, IFNAMSIZ)) + { + log_error (_("Interface %s is down.\n"), interface); + log_error (_("Recheck uShare's configuration and try again !\n")); + freeifaddrs (itflist); + return true; + } + itf = itf->ifa_next; + } + + freeifaddrs (itf); +#else + int sock, i, n; + struct ifconf ifc; + struct ifreq ifr; + char buff[8192]; + + if (!interface) + return false; + + /* determine UDN according to MAC address */ + sock = socket (AF_INET, SOCK_STREAM, 0); + if (sock < 0) + { + perror ("socket"); + return false; + } + + /* get list of available interfaces */ + ifc.ifc_len = sizeof (buff); + ifc.ifc_buf = buff; + + if (ioctl (sock, SIOCGIFCONF, &ifc) < 0) + { + perror ("ioctl"); + close (sock); + return false; + } + + n = ifc.ifc_len / sizeof (struct ifreq); + for (i = n - 1 ; i >= 0 ; i--) + { + ifr = ifc.ifc_req[i]; + + if (strncmp (ifr.ifr_name, interface, IFNAMSIZ)) + continue; + + if (ioctl (sock, SIOCGIFFLAGS, &ifr) < 0) + { + perror ("ioctl"); + close (sock); + return false; + } + + if (!(ifr.ifr_flags & IFF_UP)) + { + /* interface is down */ + log_error (_("Interface %s is down.\n"), interface); + log_error (_("Recheck uShare's configuration and try again !\n")); + close (sock); + return false; + } + + /* found right interface */ + close (sock); + return true; + } + close (sock); +#endif + + log_error (_("Can't find interface %s.\n"),interface); + log_error (_("Recheck uShare's configuration and try again !\n")); + + return false; +} + +static char * +create_udn (char *interface) +{ + int sock = -1; + char *buf; + unsigned char *ptr; + +#if (defined(BSD) || defined(__FreeBSD__) || defined(__APPLE__)) + int mib[6]; + size_t len; + struct if_msghdr *ifm; + struct sockaddr_dl *sdl; +#else /* Linux */ + struct ifreq ifr; +#endif + + if (!interface) + return NULL; + +#if (defined(BSD) || defined(__FreeBSD__) || defined(__APPLE__)) + mib[0] = CTL_NET; + mib[1] = AF_ROUTE; + mib[2] = 0; + mib[3] = AF_LINK; + mib[4] = NET_RT_IFLIST; + + mib[5] = if_nametoindex (interface); + if (mib[5] == 0) + { + perror ("if_nametoindex"); + return NULL; + } + + if (sysctl (mib, 6, NULL, &len, NULL, 0) < 0) + { + perror ("sysctl"); + return NULL; + } + + buf = malloc (len); + if (sysctl (mib, 6, buf, &len, NULL, 0) < 0) + { + perror ("sysctl"); + return NULL; + } + + ifm = (struct if_msghdr *) buf; + sdl = (struct sockaddr_dl*) (ifm + 1); + ptr = (unsigned char *) LLADDR (sdl); +#else /* Linux */ + /* determine UDN according to MAC address */ + sock = socket (AF_INET, SOCK_STREAM, 0); + if (sock < 0) + { + perror ("socket"); + return NULL; + } + + strcpy (ifr.ifr_name, interface); + strcpy (ifr.ifr_hwaddr.sa_data, ""); + + if (ioctl (sock, SIOCGIFHWADDR, &ifr) < 0) + { + perror ("ioctl"); + return NULL; + } + + buf = (char *) malloc (64 * sizeof (char)); + memset (buf, 0, 64); + ptr = (unsigned char *) ifr.ifr_hwaddr.sa_data; +#endif /* (defined(BSD) || defined(__FreeBSD__)) */ + + snprintf (buf, 64, "%s-%02x%02x%02x%02x%02x%02x", DEFAULT_UUID, + (ptr[0] & 0377), (ptr[1] & 0377), (ptr[2] & 0377), + (ptr[3] & 0377), (ptr[4] & 0377), (ptr[5] & 0377)); + + if (sock) + close (sock); + + return buf; +} + +static char * +get_iface_address (char *interface) +{ + int sock; + uint32_t ip; + struct ifreq ifr; + char *val; + + if (!interface) + return NULL; + + /* determine UDN according to MAC address */ + sock = socket (AF_INET, SOCK_STREAM, 0); + if (sock < 0) + { + perror ("socket"); + return NULL; + } + + strcpy (ifr.ifr_name, interface); + ifr.ifr_addr.sa_family = AF_INET; + + if (ioctl (sock, SIOCGIFADDR, &ifr) < 0) + { + perror ("ioctl"); + close (sock); + return NULL; + } + + val = (char *) malloc (16 * sizeof (char)); + ip = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr.s_addr; + ip = ntohl (ip); + sprintf (val, "%d.%d.%d.%d", + (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF); + + close (sock); + + return val; +} + +static int +restart_upnp (struct ushare_t *ut) +{ + finish_upnp (ut); + + if (ut->udn) + free (ut->udn); + ut->udn = create_udn (ut->interface); + if (!ut->udn) + return -1; + + if (ut->ip) + free (ut->ip); + ut->ip = get_iface_address (ut->interface); + if (!ut->ip) + return -1; + + return (init_upnp (ut)); +} + +static void +UPnPBreak (int s __attribute__ ((unused))) +{ + ushare_signal_exit (); +} + +static void +reload_config (int s __attribute__ ((unused))) +{ + struct ushare_t *ut2; + bool reload = false; + + log_info (_("Reloading configuration...\n")); + + ut2 = ushare_new (); + if (!ut || !ut2) + return; + + if (parse_config_file (ut2) < 0) + return; + + if (ut->name && strcmp (ut->name, ut2->name)) + { + free (ut->name); + ut->name = ut2->name; + ut2->name = NULL; + reload = true; + } + + if (ut->interface && strcmp (ut->interface, ut2->interface)) + { + if (!has_iface (ut2->interface)) + { + ushare_free (ut2); + raise (SIGINT); + } + else + { + free (ut->interface); + ut->interface = ut2->interface; + ut2->interface = NULL; + reload = true; + } + } + + if (ut->port != ut2->port) + { + ut->port = ut2->port; + reload = true; + } + + if (reload) + { + if (restart_upnp (ut) < 0) + { + ushare_free (ut2); + raise (SIGINT); + } + } + + if (ut->contentlist) + content_free (ut->contentlist); + ut->contentlist = ut2->contentlist; + ut2->contentlist = NULL; + ushare_free (ut2); + + if (ut->contentlist) + { + free_metadata_list (ut); + build_metadata_list (ut); + } + else + { + log_error (_("Error: no content directory to be shared.\n")); + raise (SIGINT); + } +} + +inline void +display_headers (void) +{ + printf (_("%s (version %s), a lightweight UPnP A/V and DLNA Media Server.\n"), + PACKAGE_NAME, VERSION); + printf (_("Benjamin Zores (C) 2005-2007, for GeeXboX Team.\n")); + printf (_("See http://ushare.geexbox.org/ for updates.\n")); +} + +inline static void +setup_i18n(void) +{ +#ifdef CONFIG_NLS +#ifdef HAVE_SETLOCALE + setlocale (LC_ALL, ""); +#endif +#if (!defined(BSD) && !defined(__FreeBSD__)) + bindtextdomain (PACKAGE, LOCALEDIR); +#endif + textdomain (PACKAGE); +#endif +} + +#define SHUTDOWN_MSG _("Server is shutting down: other clients will be notified soon, Bye bye ...\n") + +static void +ushare_kill (ctrl_telnet_client *client, + int argc __attribute__((unused)), + char **argv __attribute__((unused))) +{ + if (ut->use_telnet) + { + ctrl_telnet_client_send (client, SHUTDOWN_MSG); + client->exiting = true; + } + ushare_signal_exit (); +} + +int +main (int argc, char **argv) +{ + ut = ushare_new (); + if (!ut) + return EXIT_FAILURE; + + setup_i18n (); + setup_iconv (); + + /* Parse args before cfg file, as we may override the default file */ + if (parse_command_line (ut, argc, argv) < 0) + { + ushare_free (ut); + return EXIT_SUCCESS; + } + + if (parse_config_file (ut) < 0) + { + /* fprintf here, because syslog not yet ready */ + fprintf (stderr, _("Warning: can't parse file \"%s\".\n"), + ut->cfg_file ? ut->cfg_file : SYSCONFDIR "/" USHARE_CONFIG_FILE); + } + + if (ut->xbox360) + { + char *name; + + name = malloc (strlen (XBOX_MODEL_NAME) + strlen (ut->model_name) + 4); + sprintf (name, "%s (%s)", XBOX_MODEL_NAME, ut->model_name); + free (ut->model_name); + ut->model_name = strdup (name); + free (name); + + ut->starting_id = STARTING_ENTRY_ID_XBOX360; + } + + if (ut->daemon) + { + /* starting syslog feature as soon as possible */ + start_log (); + } + + if (!ut->contentlist) + { + log_error (_("Error: no content directory to be shared.\n")); + ushare_free (ut); + return EXIT_FAILURE; + } + + if (!has_iface (ut->interface)) + { + ushare_free (ut); + return EXIT_FAILURE; + } + + ut->udn = create_udn (ut->interface); + if (!ut->udn) + { + ushare_free (ut); + return EXIT_FAILURE; + } + + ut->ip = get_iface_address (ut->interface); + if (!ut->ip) + { + ushare_free (ut); + return EXIT_FAILURE; + } + + if (ut->daemon) + { + int err; + err = daemon (0, 0); + if (err == -1) + { + log_error (_("Error: failed to daemonize program : %s\n"), + strerror (err)); + ushare_free (ut); + return EXIT_FAILURE; + } + } + else + { + display_headers (); + } + + signal (SIGINT, UPnPBreak); + signal (SIGHUP, reload_config); + + if (ut->use_telnet) + { + if (ctrl_telnet_start (ut->telnet_port) < 0) + { + ushare_free (ut); + return EXIT_FAILURE; + } + + ctrl_telnet_register ("kill", ushare_kill, + _("Terminates the uShare server")); + } + + if (init_upnp (ut) < 0) + { + finish_upnp (ut); + ushare_free (ut); + return EXIT_FAILURE; + } + + build_metadata_list (ut); + + /* Let main sleep until it's time to die... */ + pthread_mutex_lock (&ut->termination_mutex); + pthread_cond_wait (&ut->termination_cond, &ut->termination_mutex); + pthread_mutex_unlock (&ut->termination_mutex); + + if (ut->use_telnet) + ctrl_telnet_stop (); + finish_upnp (ut); + free_metadata_list (ut); + ushare_free (ut); + finish_iconv (); + + /* it should never be executed */ + return EXIT_SUCCESS; +} diff --git a/src/ushare.h b/src/ushare.h new file mode 100644 index 0000000..6256a27 --- /dev/null +++ b/src/ushare.h @@ -0,0 +1,132 @@ +/* + * ushare.h : GeeXboX uShare UPnP Media Server header. + * Originally developped for the GeeXboX project. + * Parts of the code are originated from GMediaServer from Oskar Liljeblad. + * Copyright (C) 2005-2007 Benjamin Zores + * + * 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 Library 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. + */ + +#ifndef _USHARE_H_ +#define _USHARE_H_ + +#include +#include +#include +#include + +#ifdef HAVE_DLNA +#include +#endif /* HAVE_DLNA */ + +#include "content.h" +#include "buffer.h" +#include "redblack.h" + +#define VIRTUAL_DIR "/web" +#define XBOX_MODEL_NAME "Windows Media Connect Compatible" +#define DEFAULT_UUID "898f9738-d930-4db4-a3cf" + +#define UPNP_MAX_CONTENT_LENGTH 4096 + +#define STARTING_ENTRY_ID_DEFAULT 0 +#define STARTING_ENTRY_ID_XBOX360 100000 + +#define UPNP_DESCRIPTION \ +"" \ +"" \ +" " \ +" 1" \ +" 0" \ +" " \ +" " \ +" urn:schemas-upnp-org:device:MediaServer:1" \ +" %s: 1" \ +" GeeXboX Team" \ +" http://ushare.geexbox.org/" \ +" GeeXboX uShare : UPnP Media Server" \ +" %s" \ +" 001" \ +" http://ushare.geexbox.org/" \ +" GEEXBOX-USHARE-01" \ +" uuid:%s" \ +" " \ +" " \ +" urn:schemas-upnp-org:service:ConnectionManager:1" \ +" urn:upnp-org:serviceId:ConnectionManager" \ +" /web/cms.xml" \ +" /web/cms_control" \ +" /web/cms_event" \ +" " \ +" " \ +" urn:schemas-upnp-org:service:ContentDirectory:1" \ +" urn:upnp-org:serviceId:ContentDirectory" \ +" /web/cds.xml" \ +" /web/cds_control" \ +" /web/cds_event" \ +" " \ +" " \ +" urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1\n" \ +" urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar\n" \ +" /web/msr.xml" \ +" /web/msr_control" \ +" /web/msr_event" \ +" \n" \ +" " \ +" /web/ushare.html" \ +" " \ +"" + +struct ushare_t { + char *name; + char *interface; + char *model_name; + content_list *contentlist; + struct rbtree *rb; + struct upnp_entry_t *root_entry; + int nr_entries; + int starting_id; + int init; + UpnpDevice_Handle dev; + char *udn; + char *ip; + unsigned short port; + unsigned short telnet_port; + struct buffer_t *presentation; + bool use_presentation; + bool use_telnet; +#ifdef HAVE_DLNA + bool dlna_enabled; + dlna_t *dlna; + dlna_org_flags_t dlna_flags; +#endif /* HAVE_DLNA */ + bool xbox360; + bool verbose; + bool daemon; + bool override_iconv_err; + char *cfg_file; + pthread_mutex_t termination_mutex; + pthread_cond_t termination_cond; +}; + +struct action_event_t { + struct Upnp_Action_Request *request; + bool status; + struct service_t *service; +}; + +inline void display_headers (void); + +#endif /* _USHARE_H_ */ diff --git a/src/util_iconv.c b/src/util_iconv.c new file mode 100644 index 0000000..0b1e289 --- /dev/null +++ b/src/util_iconv.c @@ -0,0 +1,170 @@ +/* + * util_iconv.c : GeeXboX uShare iconv string encoding utlities. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Alexis Saettler + * + * 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 Library 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. + */ + +#include +#include +#include +#include + +#include "util_iconv.h" + +#if HAVE_ICONV +#include +static iconv_t cd = 0; +#endif + +#if HAVE_LANGINFO_CODESET +#include +#endif + +void +setup_iconv (void) +{ +#if HAVE_ICONV && HAVE_LANGINFO_CODESET + char *mycodeset = NULL; + + mycodeset = nl_langinfo (CODESET); + if (!mycodeset) + return; + + /** + * Setup conversion descriptor if user's console is non-UTF-8. Otherwise + * we can just leave cd as NULL + */ + if (strcmp (mycodeset, UTF8)) + { + cd = iconv_open (UTF8, mycodeset); + if (cd == (iconv_t) (-1)) + { + perror ("iconv_open"); + cd = 0; + } + } +#endif +} + +void +finish_iconv (void) +{ +#if HAVE_ICONV + if (!cd) + return; + if (iconv_close (cd) < 0) + perror ("iconv_close"); + cd = 0; +#endif +} + +/** + * iconv_convert : convert a string, using the current codeset + * return: a malloc'd string with the converted result + */ +char * +iconv_convert (const char *input) +{ +#if HAVE_ICONV + size_t inputsize = strlen (input) + 1; + size_t dummy = 0; + size_t length = 0; + char *result; + char *inptr, *outptr; + size_t insize, outsize; + + /* conversion not necessary. save our time. */ + if (!cd) + return strdup (input); + + /* Determine the length we need. */ + iconv (cd, NULL, NULL, NULL, &dummy); + { + static char tmpbuf[BUFSIZ]; + inptr = (char*) input; + insize = inputsize; + while (insize > 0) + { + outptr = tmpbuf; + outsize = BUFSIZ; + if (iconv (cd, &inptr, &insize, &outptr, &outsize) == (size_t) (-1)) + { + /** + * if error is EINVAL or EILSEQ, conversion must be stoped, + * but if it is E2BIG (not enough space in buffer), we just loop again + */ + if( errno != E2BIG) + { + perror ("error iconv"); + return NULL; + } + } + length += outptr - tmpbuf; + } + + outptr = tmpbuf; + outsize = BUFSIZ; + if (iconv (cd, NULL, NULL, &outptr, &outsize) == (size_t) (-1)) + { + perror ("error iconv"); + return NULL; + } + length += outptr - tmpbuf; + } + + /* length determined, allocate result space */ + if ((result = (char*) malloc (length * sizeof (char))) == NULL) + { + perror ("error malloc"); + return NULL; + } + + /* Do the conversion for real. */ + iconv (cd, NULL, NULL, NULL, &dummy); + { + inptr = (char*) input; + insize = inputsize; + outptr = result; + outsize = length; + while (insize > 0) + { + if (iconv (cd, &inptr, &insize, &outptr, &outsize) == (size_t) (-1)) + { + if (errno != E2BIG) + { + perror ("error iconv"); + free (result); + return NULL; + } + } + } + if (iconv (cd, NULL, NULL, &outptr, &outsize) == (size_t) (-1)) + { + perror ("error iconv"); + free (result); + return NULL; + } + + if (outsize != 0) + abort (); + } + + return result; +#else + return strdup (input); +#endif +} diff --git a/src/util_iconv.h b/src/util_iconv.h new file mode 100644 index 0000000..3c88739 --- /dev/null +++ b/src/util_iconv.h @@ -0,0 +1,31 @@ +/* + * util_iconv.h : GeeXboX uShare iconv string encoding utilities headers. + * Originally developped for the GeeXboX project. + * Copyright (C) 2005-2007 Alexis Saettler + * + * 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 Library 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. + */ + +#ifndef _UTIL_ICONV_H_ +#define _UTIL_ICONV_H_ + +void setup_iconv (void); +void finish_iconv (void); +char *iconv_convert (const char *inbuf) + __attribute__ ((malloc, nonnull, format_arg (1))); + +#define UTF8 "UTF-8" + +#endif