pax_global_header00006660000000000000000000000064130566031230014511gustar00rootroot0000000000000052 comment=776950d5d96ac9dbf5c5c47bde8ac06f50a3cf46 dleyna-server-0.6.0/000077500000000000000000000000001305660312300142745ustar00rootroot00000000000000dleyna-server-0.6.0/.gitignore000066400000000000000000000010001305660312300162530ustar00rootroot00000000000000*.o *.lo *~ *.la *.pc *.tgz *.gz *.pyc .dirstamp Makefile Makefile.in aclocal.m4 autom4te.cache/ compile config.guess config.h config.h.in config.log config.status config.sub configure depcomp install-sh libtool ltmain.sh missing .deps/ .libs/ stamp-h1 INSTALL m4/libtool.m4 m4/ltoptions.m4 m4/ltsugar.m4 m4/ltversion.m4 m4/lt~obsolete.m4 server/com.intel.dleyna-server.service libdleyna/server/dleyna-server-service.conf libdleyna/server/dleyna-server-1.0.pc server/dleyna-server-service test/dbus/dms-info dleyna-server-0.6.0/AUTHORS000066400000000000000000000003331305660312300153430ustar00rootroot00000000000000Mark Ryan (mark.d.ryan@intel.com) Ludovic Ferrandis (ludovic.ferrandis@intel.com) Sébastien Bianti (sebastien.bianti@intel.com) Regis Merlino (regis.merlino@intel.com) Christophe Guiraud (christophe.guiraud@intel.com) dleyna-server-0.6.0/COPYING000066400000000000000000000636421305660312300153420ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 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. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, 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 and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, 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 library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete 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 distribute a copy of this License along with the Library. 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 Library or any portion of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, 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 Library, 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 Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you 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. If distribution of 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 satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be 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. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library 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. 9. 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 Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library 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 with this License. 11. 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 Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library 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 Library. 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. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library 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. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library 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 Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, 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 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. 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 LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. 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 library 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. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; 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. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! dleyna-server-0.6.0/ChangeLog000077500000000000000000000126131305660312300160540ustar00rootroot00000000000000version 0.6.0 - [Props] Fix ChildCount property type - Added new core.c and core.h to Makefile.am. - Add Artist and AlbumArtURL to MediaContainer2 - ifaddrs.h for Andriod compile - [m4] Don't use bash arrays in m4 macros - [m4] Don't use bash "let" builtin - [m4] Use AS_VAR_APPEND macro instead of "+=" Merge pull request #149 - Fix possible use-after-free on exit Merge pull request #151 - fix for issue #156 Invalid log line https://github.com/01org/dleyna-server/issues/156 Merge pull request #157 - fix for issues #154 and #155 Dereference of undefined pointer value https://github.com/01org/dleyna-server/issues/154 Result of operation is garbage or undefined https://github.com/01org/dleyna-server/issues/155 Merge pull request #158 - Include libgupnp/gupnp-context-manager.h Merge pull request #159 - Changed the Copyright to 2017 version 0.5.0 - [Build] Fix out-of-source-tree builds https://github.com/01org/dleyna-server/issues/143 - Fix various configure & build issues [Autoconf] Sub Makefile: Remove ACLOCAL_AMFLAGS https://github.com/01org/dleyna-collabora-android/issues/127 [Autoconf] Add --no-undefined to compiler option - [upnp] Use "port" setting when creating GUPnPContextManager https://github.com/01org/dleyna-collabora-android/issues/140 - [Deprecated API] Use new API instead of deprecated https://github.com/01org/dleyna-server/issues/104 - [Device] Fix wake-on ip address used for broadcast - [Device] Sleeping devices cache cleanup - [Device] Improve sleeping context lookup - [Device] Move the network interface info storage - [Device] Improve Network Interface info and device context matching - [Device] Retrieve sleeping state via GetInterfaceInfo action - [Device] Add sleeping device memory cache - [Device] Add wake packet sending support - [Device] Add Energy Management features - [Configuration] Remove libdleyna-server .pc file https://github.com/01org/dleyna-server/issues/113 - [Documentation] Fix URL to MediaServer2Spec - [test] Save changed ServiceResetToken in configuration - [test] Survive a missing ServiceResetToken in dsc - [Tests] Fix a typo in the Upload Sync Controller's error management version 0.4.0 - Logs: provide correct component version - Don't return empty properties in GetAll - Refactor the filtering for GetAll - Add a new MediaDevice BrowseObjects API https://github.com/01org/dleyna-server/issues/114 - Support for a new "Error" property, used in BrowseObjects - Deprecate LastChange in favor of Changed signal https://github.com/01org/dleyna-server/issues/109 - Fix a memory leak https://github.com/01org/dleyna-collabora-android/issues/58 - Support of the ChildCount property https://github.com/01org/dleyna-server/issues/122 - New whitelist APIs - Add a 'never quit' property to the Manager object version 0.2.1 - Various build optimizations - Fix a crash case when the server stops - Added the new TypeEx property and fixed Type - Some documentation fixes - Code cleanup - Add a sample upload sync controller app - Add network filtering support version 0.1.0 - Prepare for first stable branch version 0.0.2 - Add a new method GetMetaData to the org.gnome.UPnP.MediaObject2 interface to allow the retrieval of the Meta data information of an object in DIDL-Lite XML format - Handle wild cards for SortCaps & SearchCaps properties - Add a new property and a new function to media container objects: Resource and GetCompatibleResource() - Fixed bug: CreateContainer does not specify the correct DLNAManaged flags https://github.com/01org/dleyna-server/issues/24 - Add MediaItem2 resource properties to container objects: URLs, Size, DLNAProfile & MimeType - Fixed bug: ObjectUpdateID and ContainerUpdateID are reported on all objects and containers https://github.com/01org/dleyna-server/issues/27 - Fixed bug: dleyna-server can fail to discover a device if a network connection is lost during device construction https://github.com/01org/dleyna-server/issues/14 - Fixed bug: We don't display media with invalid metadata https://github.com/01org/dleyna-renderer/issues/30 - Fixed bug: MediaConnect DMS crashes dLeyna https://github.com/01org/dleyna-server/issues/49 - Two new methods have been added to the UPNP class, server_from_name and server_from_udn. These methods can be used to construct Device objects from UDNs or friendly names - Add new org.gnome.MediaContainer2 properties: DLNAConversion, DLNAFlags & DLNAOperation - Add a Rescan method to Manager interface - Add autogen.sh script to call `autoreconf -i` - Remove CreatePlayListInAnyContainer() from com.intel.dLeynaServer.MediaDevice - Remove CreatePlayList() from org.gnome.MediaContainer2 - Add CreateReference method to org.gnome.MediaContainer2 - Add support for object.container.playlistContainer and object.container.storageFolder object classes - Fixed bug: The dLeyna-server CreateClasses property is incorrect https://github.com/01org/dleyna-server/issues/94 - Fixed bug: Incorrect Parent Path reported for XBMC https://github.com/01org/dleyna-server/issues/32 - Add a GetIcon() method to com.intel.dLeynaServer.MediaDevice version 0.0.1 - Initial version of dleyna-server. - Enable support of deleyna-server as git submodules. dleyna-server-0.6.0/Makefile.am000066400000000000000000000005551305660312300163350ustar00rootroot00000000000000SUBDIRS = libdleyna/server if BUILD_SERVER SUBDIRS += server test/dbus endif ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} MAINTAINERCLEANFILES = Makefile.in \ aclocal.m4 \ configure \ config.h.in \ config.h.in~ \ build-aux/depcomp \ build-aux/compile \ build-aux/missing \ build-aux/install-sh maintainer-clean-local: rm -rf build-aux dleyna-server-0.6.0/NEWS000066400000000000000000000000001305660312300147610ustar00rootroot00000000000000dleyna-server-0.6.0/README000066400000000000000000000051471305660312300151630ustar00rootroot00000000000000Introduction: ------------- TODO Compilation ------------ TODO Working with the source code repository --------------------------------------- dleyna-server can be downloaded, compiled and installed as follows: Clone repository # git clone git://github.com/01org/dleyna-server.git # cd dleyna-server Configure and build # ./autogen.sh # make Final installation # sudo make install These instructions are suitable for users who simply want to install and run dleyna-server. However, developers wishing to contribute to the project should follow a separate "Configure and build" step. Configure and build # ./bootstrap-configure # make The script "bootstrap-configure" cleans the repository, calls autreconf and then invokes configure with proper settings for development. These settings include the enabling of maintainer mode and debugging. Developers can remove autogenerated files with the following command # make maintainer-clean Configure Options: ------------------ --enable-werror This option is disabled by default. To enable use --enable-werror. When enabled, all warnings are treated as errors during compilation. Should be enabled during development to ensure that errors do not creep into the code base. This option is enabled by bootstrap-configure. --enable-debug This option is disabled by default. To enable use --enable-debug. When enabled, the make files produce debug builds. This option is enabled by bootstrap-configure. --enable-optimization This option is enabled by default. To disable use --disable-optimization. When enabled it turns on compiler optimizations. Disable = -O0, enable = -O2. --enable-never-quit This option is disabled by default. To enable use --enable-never-quit. When enabled, dleyna-server-service doesn't quit when the last client disconnects. --with-log-type See logging.txt for more information about logging. --with-log-level See logging.txt for more information about logging. --with-connector-name Set the IPC mechanism to be used. --enable-lib-only This option is disabled by default. To enable use --enable-lib-only. When enabled, only the libdleyna-server library is built. --with-ua-prefix This option allows a prefix to be added to the SOUP session user agent. For example, --with-ua-prefix=MyPrefix can be used to change a default user agent string from "dLeyna/0.0.1 GUPnP/0.19.4 DLNADOC/1.50" to "MyPrefix dLeyna/0.0.1 GUPnP/0.19.4 DLNADOC/1.50". --with-dbus-service-dir By default, the dbus service files are installed in $(datadir)/dbus-1/services. This option allows to choose another installation directory. dleyna-server-0.6.0/autogen.sh000077500000000000000000000007471305660312300163050ustar00rootroot00000000000000#!/bin/sh # Run this to generate all the initial makefiles, etc. # Derived from https://git.gnome.org/browse/glib/tree/autogen.sh test -n "$srcdir" || srcdir=`dirname "$0"` test -n "$srcdir" || srcdir=. olddir=`pwd` cd "$srcdir" AUTORECONF=`which autoreconf` if test -z $AUTORECONF; then echo "*** No autoreconf found, please install it ***" exit 1 fi autoreconf --force --install --verbose || exit $? cd "$olddir" test -n "$NOCONFIGURE" || "$srcdir/configure" "$@" dleyna-server-0.6.0/bootstrap-configure000077500000000000000000000003141305660312300202140ustar00rootroot00000000000000#!/bin/sh if [ -f config.status ]; then make maintainer-clean fi ./autogen.sh --enable-maintainer-mode \ --enable-silent-rules \ --disable-optimization \ --enable-debug \ --with-log-level=8 $* dleyna-server-0.6.0/configure.ac000066400000000000000000000165741305660312300165770ustar00rootroot00000000000000AC_PREREQ([2.66]) AC_INIT([dleyna-server], [0.6.0], [https://github.com/01org/dleyna-server/issues/new], , [https://01.org/dleyna/]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([libdleyna/server/server.c]) AC_PREFIX_DEFAULT(/usr/local) AM_INIT_AUTOMAKE([subdir-objects]) AM_MAINTAINER_MODE AM_SILENT_RULES([yes]) DLEYNA_SERVER_COMPILER_FLAGS # Checks for languages. AC_LANG_C # Checks for programs. AC_PROG_CC AM_PROG_CC_C_O AC_PROG_MKDIR_P # Initialize libtool # Disable generation of static libraries LT_PREREQ([2.2.6]) LT_INIT([dlopen disable-static]) LT_LANG([C]) # Checks for libraries. PKG_PROG_PKG_CONFIG(0.16) PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.28]) PKG_CHECK_MODULES([GIO], [gio-2.0 >= 2.28]) PKG_CHECK_MODULES([GSSDP], [gssdp-1.0 >= 0.13.2]) PKG_CHECK_MODULES([GUPNP], [gupnp-1.0 >= 0.20.3]) PKG_CHECK_MODULES([GUPNPAV], [gupnp-av-1.0 >= 0.11.5]) PKG_CHECK_MODULES([GUPNPDLNA], [gupnp-dlna-2.0 >= 0.9.4]) PKG_CHECK_MODULES([SOUP], [libsoup-2.4 >= 2.28.2]) PKG_CHECK_MODULES([LIBXML], [libxml-2.0]) # Checks for header files. AC_CHECK_HEADERS([stdlib.h string.h syslog.h]) # Checks for typedefs, structures, and compiler characteristics. AC_TYPE_UINT8_T AC_HEADER_STDBOOL AC_TYPE_SIZE_T # Checks for library functions. AC_FUNC_MALLOC AC_FUNC_REALLOC AC_CHECK_FUNCS([memset strchr strrchr strstr]) # Define Log Level values LOG_LEVEL_0=0x00 LOG_LEVEL_1=0x01 LOG_LEVEL_2=0x02 LOG_LEVEL_3=0x04 LOG_LEVEL_4=0x08 LOG_LEVEL_5=0x10 LOG_LEVEL_6=0x20 LOG_LEVEL_7=0x13 LOG_LEVEL_8=0x3F AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_DISABLED], [${LOG_LEVEL_0}], [Log level flag for disabled messages]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_ERROR], [${LOG_LEVEL_1}], [Log level flag for errors]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_CRITICAL], [${LOG_LEVEL_2}], [Log level flag for critical messages]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_WARNING], [${LOG_LEVEL_3}], [Log level flag for warnings]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_MESSAGE], [${LOG_LEVEL_4}], [Log level flag for messages]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_INFO], [${LOG_LEVEL_5}], [Log level flag for informational messages]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_DEBUG], [${LOG_LEVEL_6}], [Log level flag for debug messages]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_DEFAULT], [${LOG_LEVEL_7}], [Log level flag to display default level messages]) AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL_ALL], [${LOG_LEVEL_8}], [Log level flag for all messages]) AC_ARG_ENABLE(master-build,, [], [master_build=no]) AS_IF([test "x$master_build" = "xno"], [PKG_CHECK_MODULES([DLEYNA_CORE], [dleyna-core-1.0 >= 0.6.0])], [this_abs_top_srcdir=`cd "$srcdir" && pwd`; DLEYNA_CORE_CFLAGS="-I$this_abs_top_srcdir/../dleyna-core"; DLEYNA_CORE_LIBS="-L$this_abs_top_srcdir/../dleyna-core/.libs -ldleyna-core-1.0" ]) AC_ARG_ENABLE(debug, AS_HELP_STRING( [--enable-debug], [enable compiling with debugging information]), [], [enable_debug=no]) AS_CASE("${enable_debug}", [yes], [CFLAGS="$CFLAGS -g"; AC_DEFINE_UNQUOTED([DLEYNA_DEBUG_ENABLED],[1], [Compiling with debugging information enabled]) ], [no], [], [AC_MSG_ERROR([bad value ${enable_debug} for --enable-debug])]) AC_ARG_ENABLE(werror, AS_HELP_STRING( [--enable-werror], [warnings are treated as errors]), [], [enable_werror=no]) AS_CASE("${enable_werror}", [yes], [CFLAGS="$CFLAGS -Werror"], [no], [], [AC_MSG_ERROR([bad value ${enable_werror} for --enable-werror])]) AC_ARG_ENABLE(optimization, AS_HELP_STRING( [--disable-optimization], [disable code optimization through compiler]), [], [enable_optimization=yes]) AS_CASE("${enable_optimization}", [yes], [disable_optimization=no], [no], [CFLAGS="$CFLAGS -O0"; disable_optimization=yes], [AC_MSG_ERROR([bad value ${enable_optimization} for --enable-werror])]) AC_ARG_ENABLE(never-quit, AS_HELP_STRING( [--enable-never-quit], [Service doesn't quit when last client disconnects]), [], [enable_never_quit=no]) AS_CASE("${enable_never_quit}", [yes], [never_quit=true], [no], [never_quit=false], [AC_MSG_ERROR([bad value ${enable_never_quit} for --enable-never-quit])]) AC_ARG_WITH(connector-name, AS_HELP_STRING( [--with-connector-name], [IPC connector name]), [], [with_connector_name=dbus]) AC_ARG_WITH(log-level, AS_HELP_STRING( [--with-log-level], [enable logging information (0,1..6,7,8)\ 0=disabled \ 7=default (=1,2,5) \ 8=all (=1,2,3,4,5,6) \ 1,..,6=a comma separated list of log level\ ]), [], [with_log_level=7]) DLEYNA_LOG_LEVEL_CHECK([${with_log_level}]) AC_ARG_WITH(log-type, AS_HELP_STRING( [--with-log-type], [Select log output technology \ 0=syslog 1=GLib \ ]), [], [with_log_type=0]) DLEYNA_LOG_TYPE_CHECK([${with_log_type}]) AC_ARG_WITH(ua-prefix, AS_HELP_STRING( [--with-ua-prefix], [Specify a user agent prefix]), [with_ua_prefix = "$withval"; AC_DEFINE_UNQUOTED([UA_PREFIX], "$with_ua_prefix", [User Agent prefix])], []) AC_ARG_WITH(dbus_service_dir, AS_HELP_STRING([--with-dbus-service-dir=PATH],[choose directory for dbus service files, [default=PREFIX/share/dbus-1/services]]), with_dbus_service_dir="$withval", with_dbus_service_dir=$datadir/dbus-1/services) DBUS_SERVICE_DIR=$with_dbus_service_dir AC_SUBST(DBUS_SERVICE_DIR) AC_ARG_ENABLE(lib-only, AS_HELP_STRING( [--enable-lib-only], [compile only the libdleyna-server library]), [], [enable_lib_only=no]) AM_CONDITIONAL([BUILD_SERVER], [test "x$enable_lib_only" = "xno"]) AC_DEFINE([DLEYNA_SERVER_OBJECT], "/com/intel/dLeynaServer", [Name of object exposed by dleyna-server]) AC_DEFINE([DLEYNA_SERVER_PATH], "/com/intel/dLeynaServer/server", [Path of server objects]) DLEYNA_SERVER_NAME=com.intel.dleyna-server AC_SUBST(DLEYNA_SERVER_NAME) AC_DEFINE([DLEYNA_SERVER_NAME], "com.intel.dleyna-server", [d-Bus Name of dleyna-server]) DLEYNA_SERVER_INTERFACE_MANAGER=com.intel.dLeynaServer.Manager AC_SUBST(DLEYNA_SERVER_INTERFACE_MANAGER) AC_DEFINE([DLEYNA_SERVER_INTERFACE_MANAGER], "com.intel.dLeynaServer.Manager", [d-Bus Name of dleyna-server main interface]) DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE=com.intel.dLeynaServer.MediaDevice AC_SUBST(DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE) AC_DEFINE([DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE], "com.intel.dLeynaServer.MediaDevice", [d-Bus Name of dleyna-server device interface]) AC_SUBST([never_quit]) AC_SUBST([with_connector_name]) AC_SUBST([with_log_level]) AC_SUBST([with_log_type]) AC_CONFIG_FILES([Makefile \ libdleyna/server/Makefile \ libdleyna/server/dleyna-server-service.conf \ server/dleyna-server-service-1.0.pc \ server/Makefile \ test/dbus/Makefile ]) AC_OUTPUT AS_ECHO(["------------------------------------------------- ${PACKAGE_NAME} Version ${PACKAGE_VERSION} Prefix : '${prefix}' Compiler : '${CC}' CFLAGS : '${CFLAGS}' -Package features: - enable-werror : ${enable_werror} - enable-debug : ${enable_debug} - enable-never-quit : ${enable_never_quit} - with-connector-name : ${with_connector_name} - disable-optimization : ${disable_optimization} - with-log-level : ${with_log_level} - with-log-type : ${with_log_type} - with-ua-prefix : ${with_ua_prefix} - enable-lib-only : ${enable_lib_only} - with-dbus-service-dir : ${with_dbus_service_dir} --------------------------------------------------"]) dleyna-server-0.6.0/doc/000077500000000000000000000000001305660312300150415ustar00rootroot00000000000000dleyna-server-0.6.0/doc/TODO.txt000066400000000000000000000002751305660312300163530ustar00rootroot00000000000000TODO: ----- You can check the list of features we have planned or are currently working on in github: dleyna-server-0.6.0/doc/coding-style.txt000066400000000000000000000001741305660312300202050ustar00rootroot00000000000000dLeyna-server uses the dLeyna-core coding guidelines: https://github.com/01org/dleyna-core/blob/master/doc/coding-style.txtdleyna-server-0.6.0/doc/logging.txt000066400000000000000000000001701305660312300172260ustar00rootroot00000000000000dLeyna-server uses the dLeyna-core logging guidelines: https://github.com/01org/dleyna-core/blob/master/doc/logging.txtdleyna-server-0.6.0/doc/server/000077500000000000000000000000001305660312300163475ustar00rootroot00000000000000dleyna-server-0.6.0/doc/server/dbus/000077500000000000000000000000001305660312300173045ustar00rootroot00000000000000dleyna-server-0.6.0/doc/server/dbus/API.txt000066400000000000000000001551061305660312300204660ustar00rootroot00000000000000API --- Dleyna-server-service is a middleware component. It is designed to be launched by d-Bus activation when needed. It automatically shuts down when the last of its clients quits or releases its connection. Dleyna-server-service currently connects to the d-Bus session bus, although this may change in the future. It exposes four different types of objects: 1. A manager object. There is only ever a single instance of this object. It can be used to retrieve a list of the DMSs on the local area network. It is also used to perform certain server independent tasks. 2. Server objects. One separate object is exposed for each DMS available on the LAN. These objects expose interfaces that allow clients to retrieve information about the servers and to browse and search their contents. 3. Container objects. Container objects represent separate folders within the DMS. They allow clients to browse and search the contents of the folder to which they correspond. 4. Item objects. Represent individual pieces of media content published by a DMS. These objects can be used to retrieve information about media objects, such as their name, their authors, and most importantly, their URLs. The remainder of this document will describe the four d-Bus objects listed above and the interfaces they support. The Manager Object: ------------------- There is only ever a single instance of this object. The manager object exposes two d-Bus interfaces: 1 - com.intel.dLeynaServer.Manager. 2 - org.freedesktop.DBus.Properties. com.intel.dLeynaServer.Manager ------------------------------ Methods: ---------- The interface com.intel.dLeynaServer.Manager contains 6 methods. Descriptions of each of these methods along with their d-Bus signatures are given below. GetServers() -> ao GetServers takes no parameters and returns an array of d-Bus object paths. Each of these paths reference a d-Bus object that represents a single DMS. GetVersion() -> s Returns the version number of dleyna-server-service Release() Indicates to dleyna-server-service that a client is no longer interested in its services. Internally, dleyna-server-service maintains a reference count. This reference count is increased when a new client connects. It is decreased when a client quits. When the reference count reaches 0, dleyna-server-service exits. A call to Release also decreases the reference count. Clients should call this method if they intend to keep running but they have no immediate plans to invoke any of dleyna-server-service's methods. This allows dleyna-server-service to quit, freeing up system resources. SetProtocolInfo(s ProtocolInfo) -> void Informs dleyna-server-service of the mime types and network protocols supported by the client. The explanation behind this function is a little complex. DMSs often support a feature call transcoding. This allows them to publish each media item they manage in a number of different formats. This is useful as not all devices support the same set of media formats and codecs. For example, a DMS might publish two separate URLS for a video file. The first URL might point to the file in its original format, e.g., to an ogg vorbis file. The second URL however, could point to a MP4 encoded version of the same file. The second URL might be preferable if viewing the file on a mobile device. In short, SetProtocolInfo allows clients to specify the formats that they would like to receive media content in. This function should be called by all MediaPlayers. Doing so, will ensure that dleyna-server-service only returns compatible URLs via the org.gnome.MediaItem2 interface. For more information see the section on MediaServer2Spec below. The Protocol info field is a comma separated list of protocol info values. Each protocol info value consists of 4 fields separated by colons. Unfortunately, the format is too complex to describe in this document. The reader is referred to the UPnP Connection Manager Service Template document (1) and the DLNA Guidelines (2) where it is described extensively. However, an example protocol info value is presented below, to give the reader an idea of what such a string might look like. "http-get:*:audio/mp4:DLNA.ORG_PN=AMR_WBplus, http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED" The protocol info value above indicates that the client supports the retrieval, via HTTP, and the playback of audio MP4 and JPEG files. PreferLocalAddresses(b Prefer) -> void Indicates whether local addresses should be prioritized or not. DMSs tend to make their services available on multiple interfaces. If a DMP and a DMS are running on the same device, the DMP will have the option of communicating with the DMS over the loopback or the LAN. By default dleyna-server-service will return loopback addresses to clients running on the same device. This is not very helpful for DMCs who need non-local addresses to pass to renderers running on other devices. DMCs should therefore call this function with the value FALSE before requesting any URLs from any servers. Rescan() -> void Forces a rescan for DMSs on the local area network. This is useful to detect DMSs which have shut down without sending BYE messages or to discover new DMSs which for some reason were not detected when either they, or the device on which dLeyna-server runs, was started or joined the network. New in version 0.0.2. Properties: --------- The com.intel.dLeynaServer.Manager interface exposes information via a number of d-Bus properties. These properties are described below: |------------------------------------------------------------------------------| | Name | Type |m/o*| Description | |------------------------------------------------------------------------------| | NeverQuit | b | m | True if the service always stay in | | | | | memory running. False if the service | | | | | quit when the last client disconnects. | |------------------------------------------------------------------------------| | WhiteListEntries | as | m | The list of entries that compose the | | | | | white list used to filter the networks. | | | | | An Entry could be an interface name | | | | | (eth0), an ip address (127.0.0.1) or | | | | | a SSID (MyWiFi) | |------------------------------------------------------------------------------| | WhiteListEnabled | b | m | True if the Network Filtering is active.| |------------------------------------------------------------------------------| A org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted when these properties change. These properties can be changed using the Set() method of org.freedesktop.DBus.Properties interface. Signals: --------- The com.intel.dLeynaServer.Manager interface also exposes two signals. FoundServer(o) Is generated whenever a new DMS is detected on the local area network. The signal contains the path of the newly discovered server. LostServer(o) Is generated whenever a DMS is shutdown. The signal contains the path of the server which has just been shutdown. The Server Objects: ------------------ Dleyna-server-service exposes a separate d-Bus object for each DMS it detects on the LAN. These objects serve two purposes. 1. They allow the client to retrieve information about the DMS, such as its name, the URL of its icon, its manufacturer, etc. 2. They represent the root container of the DMS allowing clients to search and browse the DMSs contents. Each server object exposes three separate interfaces: com.intel.dLeynaServer.MediaDevice, org.gnome.MediaObject2 and org.gnome.UPnP.MediaContainer2. com.intel.dLeynaServer.MediaDevice: --------------------------- The com.intel.dLeynaServer.MediaDevice interface exposes information about the DMS via a number of d-Bus properties. These properties are described below: |------------------------------------------------------------------------------| | Name | Type |m/o*| Description | |------------------------------------------------------------------------------| | DeviceType | s | m | The UPnP type of the device, e.g., | | | | | urn:schemas-upnp-org:device:\ | | | | | MediaServer:1 | |------------------------------------------------------------------------------| | UDN | s | m | The Unique Device Name of the server. | |------------------------------------------------------------------------------| | RootUDN | s | o | The Unique Device Name of the root | | | | | device, if the server is a sub-device. | |------------------------------------------------------------------------------| | FriendlyName | s | m | The friendly name of the media server. | -------------------------------------------------------------------------------| | IconURL | s | o | A URL pointing to an icon that | | | | | graphically identifies the server | |------------------------------------------------------------------------------| | Manufacturer | s | m | A string identifying the manufacturer | | | | | of the server | |------------------------------------------------------------------------------| | ManufacturerUrl | s | o | A URL pointing to the manufacturer's | | | | | web site. | |------------------------------------------------------------------------------| | ModelDescription | s | o | A description of the server. | |------------------------------------------------------------------------------| | ModelName | s | m | The model name of the server. | |------------------------------------------------------------------------------| | ModelNumber | s | o | The server's version number | |------------------------------------------------------------------------------| | SerialNumber | s | o | The server's serial number | |------------------------------------------------------------------------------| | PresentationURL | s | o | The presentation URL of the server, | | | | | i.e., a link to it's HTML management | | | | | interface. | |------------------------------------------------------------------------------| | DLNACaps | a{sv} | o | Represents the device capabilities as | | | | | announced in the device description | | | | | file via the dlna:X_DLNACAP element. (1)| |------------------------------------------------------------------------------| | SearchCaps | as | m | List of property names that can be used | | | | | in search queries. | | | | | A wildcard (“*”) indicates that the | | | | | device supports search queries using | | | | | any property name(s). | | | | | Empty if not supported by the device. | |------------------------------------------------------------------------------| | SortCaps | as | m | List of property names that can be used | | | | | to sort Search() or Browse() action | | | | | results. | | | | | A wildcard (“*”) indicates that the | | | | | device supports sorting using all | | | | | property names. | | | | | Empty if not supported by the device. | |------------------------------------------------------------------------------| | SortExtCaps | as | o | List of sort modifiers that can be used | | | | | to sort Search() or Browse() results. | | | | | Empty if not supported by the device. | |------------------------------------------------------------------------------| | FeatureList | a(ssao) | o | Each element in the FeatureList array | | | | | represents a feature supported by the | | | | | DMS. Each feature contains three pieces | | | | | of information, a name, a version number| | | | | and an array of object paths that can | | | | | clients can use to take advantage of the| | | | | feature. There are three standardized | | | | | feature names, BOOKMARK, EPG and TUNER. | |------------------------------------------------------------------------------| | ServiceResetToken | s | o | A string containing the current | | | | | service reset token of the server. | |------------------------------------------------------------------------------| | SystemUpdateID | u | m | An integer value that is incremenented | | | | | every time changes are made to the DMS. | |------------------------------------------------------------------------------| | Sleeping | b | o | An boolean value which represents the | | | | | server sleeping state. | |------------------------------------------------------------------------------| (* where m/o indicates whether the property is optional or mandatory ) (1) A value of -1 for the srs-rt-retention-period capability denotes an infinite retention period. All of the above properties are static with the sole exception of SystemUpdateID. A org.freedesktop.DBus.Properties.PropertiesChanged signal is emitted when this property changes. Methods: --------- The com.intel.dLeynaServer.MediaDevice interface currently exposes 8 methods: UploadToAnyContainer(s DisplayName, s FilePath) -> (u UploadId, o ObjectPath) UploadToAnyContainer allows a client to upload a local file to a DMS. The DMS chooses the best place to store the file based on the file's type. DisplayName is the friendly name the DMS should associate with the file and FilePath is a full path to the file. The successful completion of this method indicates that the upload has begun, but has not necessarily finished. There are two return values. The UploadId, which can be used to monitor the progress of the upload and to cancel it and ObjectPath, which contains the path of the newly created object. CreateContainerInAnyContainer(s DisplayName, s TypeEx, as ChildTypes) -> o ObjectPath Creates a new container. The DMS chooses the best place to create this new container. DisplayName is the name of the new container, TypeEx is the extended type and ChildTypes is an array of extended types that can be stored in the new folder, e.g., ['video','container']. A special value of ['*'] indicates that no restriction is placed on the types of objects that can be stored in the container. The path of the newly created object is returned. GetUploadStatus(u UploadId) -> (s UploadStatus, t Length, t Total) GetUploadStatus returns the current status of an upload previously started by a call to Upload or UploadToAnyContainer. Clients should pass in the UploadId they received as a return value from either of these two functions. This method returns three pieces of information. i. UploadStatus, indicating the status of the upload. Four separate values are possible. "IN_PROGRESS", "CANCELLED", "ERROR", "COMPLETED" ii. Length, indicating the number of bytes that have been transferred so far. iii. Total, indicating the total size of the upload. Clients can call GetUploadStatus to retrieve the status of an upload up to 30 seconds after the specified upload has finished. The only exception to this rule is if the DMS to which the file is being uploaded shuts down or disappears from the UPnP network. If this happens all active uploads to this DMS will be cancelled and the Device object representing this DMS will disappear. As the Device object is no longer available, clients cannot call GetUploadStatus to determine the status of their uploads. Clients that initiate uploads should listen for the LostServer signal. They should assume that any uploads to a DMS that were in progress when this signal is received for that DMS, have been cancelled. GetUploadIDs() -> (au UploadIDs) GetUploadIDs returns an array containing the IDs of all the outstanding upload tasks. An empty array will be returned if no uploads are in progress on the current device. As with GetUploadStatus, the IDs of upload tasks will be returned by GetUploadIDs for 30 seconds after the uploads have finished. CancelUpload(u UploadId) -> void Cancels the upload identified by UploadId. This function has no effect if the upload has already completed, i.e., its status is set to COMPLETED. Cancel() -> void Cancels all requests a client has outstanding on that server. GetIcon(s RequestedMimeType, s Resolution) -> (ay Bytes, s MimeType) Returns the device icon bytes and mime type according to the RequestedMimeType and Resolution parameters. Both RequestedMimeType and Resolution parameters are currently reserved for future use and should be set as an empty string. BrowseObjects(as ObjectPath, as Filter) -> aa{sv} This method returns properties of all media objects specified by the first parameter. The list of properties to return is specified by the second parameter. The first argument is an array of object paths. The second argument is an array of property name. This is the list of properties that would be returned for each object. The format and the behaviour of this array is identical to the filter argument passed to the Search and List functions. The result is an array of property name+value dictionaries, one for each object specified in the first argument. In case of even a single 'Invalid path' in ObjectPath parameter, the function will failed and return an error. In case of 'Invalid ID', the function will succeed, and an 'Error' property will be set for this object. We guarantee that the number of elements of the result array is the same and in the same order as the ObjectPath array. The purposes of this method are: 1. It allows applications to easily retrieve information about unrelated objects in one call. 2. It should be used in preference to GetAll, if an application wishes to retrieve a subset of an object's properties. Browse is likely to be more efficient than GetAll in such cases. Wake() -> void Sends a magic packet to the server to wake it up if it is in sleeping state. Signals: --------- The com.intel.dLeynaServer.MediaDevice interface also exposes three signals. Changed (aa{sv} ChangedObjects) The Changed signal is generated by servers to provide precise information of changes that have taken place to their content. Changed is useful for synchronisation controllers. It allows them to dynamically update their local cache of the server's content. This signal contains an array of zero or more dictionary elements, each of which represents a change to a specific object. The dictionary is composed of the following keys: |------------------------------------------------------------------------------| | Key | Type | m/o | Description | |------------------------------------------------------------------------------| | ChangeType | u | m | Contains the Type of the notification: | | | | | 1 (ADD) an object has been added | | | | | 2 (MOD) an object has been modified | | | | | 3 (DEL) an object has been deleted | | | | | 4 (DONE) an update to a sub-tree has | | | | | completed | | | | | 5 (CONTAINER) a container has been updated | |------------------------------------------------------------------------------| | Path | o | m | Contains the Object Path property of the | | | | | object that was changed. | |------------------------------------------------------------------------------| | UpdateID | u | m | ContainerUpdateID or SystemUpdateID when the | | | | | object was changed. | |------------------------------------------------------------------------------| | SubTreeUpdate | b | o | True if the object was added as part of a | | | | | subtree update. | |------------------------------------------------------------------------------| | Parent | o | o | Contains the Object Path property of the | | | | | parent container of the changed object. | |------------------------------------------------------------------------------| | TypeEx | s | o | Contains the value of the TypeEx property | | | | | (converted upnp:class property) of the | | | | | object that was changed. | |------------------------------------------------------------------------------| | Type | s | o | Contains the value of the Type property | | | | | (converted upnp:class property) of the object | | | | | that was changed. | |------------------------------------------------------------------------------| ContainerUpdateIDs(a(ou) ContainerPathsIDs) Is generated when the contents of one or more folders maintained by the server have changed. This signal contains an array of paths/ContainerUpdateID of the server containers that have changed. UploadUpdate(u UploadId, s UploadStatus, Length t, Total t) Is generated when an upload completes, fails or is cancelled. The first parameter is the ID of the upload. The second contains one of three values, "CANCELLED", "COMPLETED", "ERROR", indicating whether the upload was cancelled, completed successfully or failed, respectively. The third parameter indicates the total amount of bytes that were uploaded and the fourth, the size of the file being uploaded. Here is some example code in python that enumerates all the media servers present on the network and prints their names and the paths of the d-Bus objects that represent them, to the screen. import dbus bus = dbus.SessionBus() manager = dbus.Interface(bus.get_object('com.intel.dleyna-server', '/com/intel/dLeynaServer'), 'com.intel.dLeynaServer.Manager') for path in manager.GetServers(): server = dbus.Interface(bus.get_object( 'com.intel.dleyna-server', path), 'org.freedesktop.DBus.Properties') folderName = server.Get("", "DisplayName") print '{0:<30}{1:<30}'.format(folderName , path) Running this code on a LAN with 3 DMSs produces the following output: My Media /com/intel/dLeynaServer/server/3 Root /com/intel/dLeynaServer/server/1 Test Streams /com/intel/dLeynaServer/server/4 org.gnome.MediaObject2 ---------------------- The org.gnome.MediaObject2 interface exposes some basic properties of the server's root directory. This interface is described MediaServer2Spec specification (3). Dleyna-server-service enhances this interface by adding new properties and methods and by defining additional values for the Type property. Additional Values for the Type Property: ---------------------------------------- The MediaServer2Spec Type property can be set to one of only seven possible values: 'container', 'video', 'video.movie', 'audio', 'music', 'image' or 'image.photo'. This causes a number of problems for dLeyna-server. First, there is only one value for container, so it is not possible to indicate if the container is an album or a playlist, for instance. Secondly, there is no way to represent an item that is not an audio, video or image item, such as a generic item (UPnP class of object.item) or a playlist item. dLeyna-server solves these two different problems in two ways: - It provides a new property called TypeEx, which provides extended type information. TypeEx is a superset of Type, providing specific values of object types that are not supported by Type. For example, for an album, Type will be container but TypeEx will be set to container.album.musicAlbum. - It provides a new catch all value for Type, item.unclassified. item.unclassified means that the object is an item but it is not an audio, video, or image item, e.g.,a generic item or a playlist item. The actual type of the item is stored in TypeEx. item.unclassified may be specified as a value for the Type property in searches. In such cases it is treated as the UPnP type object.item. Adding an additional value to MediaServer2Spec's Type property breaks our compliance with this spec a little, but it is necessary to achieve UPnP certification. Additional Properties: ---------------------- |---------------------------------------------------------------------------| | Name | Type | m/o* | Description | |---------------------------------------------------------------------------| | DLNAManaged | a{sb} | o | A dictionary of boolean values | | | | | indicating the values of the various | | | | | DLNA managed attributes. There are 5 | | | | | keys: Upload, CreateContainer, Delete, | | | | | UploadDelete, ChangeMeta | |---------------------------------------------------------------------------| | Restricted | b | m | Indicates whether the object is | | | | | modifiable. | |---------------------------------------------------------------------------| | Creator | s | o | Indicates an entity that owns the | | | | | content or is primarily responsible for | | | | | creating the content. | |---------------------------------------------------------------------------| | ObjectUpdateID | u | o | Represents a last-modified timestamp | | | | | for the object relative to the | | | | | SystemUpdateID state variable. | |---------------------------------------------------------------------------| | TypeEx | s | m | The extended Type of the object. | | | | | TypeEx permits a superset of the values | | | | | supported by the Type property. When | | | | | the Type of an object is accurately | | | | | described by Type, i.e., the object | | | | | is a basic container or is an image, | | | | | audio, or video file, TypeEx = Type. | | | | | For objects where this is not the case, | | | | | e.g., an album, the TypeEx value is | | | | | formed by removing the | | | | | "object." prefix from the UPnP | | | | | class. For example, the TypeEx value | | | | | for the UPnP class, | | | | | object.container.playlistContainer is | | | | | container.playlistContainer. | | | | | | | | | | New in version 0.2.0. | |---------------------------------------------------------------------------| Additional Methods: ------------------ Delete() -> void Delete will simply call the UPnP DestroyObject method on the relevant server side object. Note that calling Delete on the root object of a server will delete all the contents on the server but not the device object itself. Update(a{sv} ToAddUpdate, as ToDelete) -> void Updates the meta data of an existing DMS object. It is based on the MediaServerAv UpdateObject command. ToAddUpdate is a dictionary of property name value pairs that are to be updated, or added if they do not already exist. ToDelete is an array of properties to be deleted. The same property cannot appear in both ToAddUpdate and ToDelete. Only the following properties can be updated: 'Date', 'DisplayName', 'Artists', 'Album', 'TypeEx', 'TrackNumber'. GetMetaData() -> s Returns the object meta data information in DIDL-Lite XML format. This is useful when writing Digital Media Controllers. The controllers can pass this information to the DMR which can use it to determine the server side streaming options for the object. New in v0.0.2. org.gnome.MediaContainer2 ---------------------- The org.gnome.MediaContainer2 interface exposes some additional properties of the root directory. It also provides methods that can be used by clients to perform a recursive search on the root directory and to retrieve a list of its children. The org.gnome.MediaContainer2 interface is documented in the MediaServer2Spec (3). However, dleyna-server-service's implementation of org.gnome.MediaContainer2 differs slightly from the specification. These differences can be grouped into two categories: unsupported properties and new methods. Unsupported Properties: ----------------------- The properties org.gnome.UPnP.MediaContainer2.ItemCount and org.gnome.UPnP.MediaContainer2.ContainerCount are not implemented as there is no way to efficiently implement these properties in dleyna-server-service. In addition, org.gnome.UPnP.MediaContainer2.Icon is not supported either as its implementation is really complicated, requiring the creation of virtual objects. Furthermore, it cannot be properly implemented in dleyna-server-service as the UPnP servers do not provide us with enough information to populate the mandatory properties for these virtual objects. Nevertheless, clients can retrieve a URL that points to a server's icon via the com.intel.dLeynaServer.MediaDevice property IconURL. Please note that none of these unsupported properties are mandatory, so their absence does not affect dleyna-server-service's compatibility with MediaServer2Spec. Additional properties unsupported by org.gnome.MediaContainer2 -------------------------------------------------------------- |---------------------------------------------------------------------------| | Name | Type | m/o* | Description | |---------------------------------------------------------------------------| | CreateClasses |a(sb) | o | The CreateClasses property is an | | | | | array of tuples (sb) that lists | | | | | the extended types of objects that| | | | | that can be created in a | | | | | container. A boolean value is | | | | | associated with each type. This | | | | | boolean indicates whether objects | | | | | of types derived from the | | | | | specified extended type can also | | | | | be created in this container. | | | | | For example, | | | | | ("container.album.","true") | | | | | would indicate that objects of | | | | | type container.album, | | | | | container.album.music and | | | | | container.album.photo can be | | | | | created in the container. | |---------------------------------------------------------------------------| | ContainerUpdateID | u | o | Contains the value of the | | | | | SystemUpdateID state variable | | | | | generated by the most recent | | | | | container modification for that | | | | | container | |---------------------------------------------------------------------------| | TotalDeletedChildCount | u | o | Contains the total number of | | | | | child objects that have been | | | | | deleted from a container object | | | | | since the last initialization | |---------------------------------------------------------------------------| | Resources |aa{sv}| o | Returns the set of resources | | | | | associated with a container. | | | | | New in v0.0.2 | |---------------------------------------------------------------------------| | DLNAConversion | a{sb}| o | | | DLNAFlags | a{sb}| o | | | DLNAOperation | a{sb}| o | See "Container properties" | | DLNAProfile | s | o | paragraph below. New in v0.0.2 | | MIMEType | s | o | | | Size | x | o | | | UpdateCount | u | o | | | URLs | as | o | | |---------------------------------------------------------------------------| Container properties: --------------------- Each container may expose one or more resources via the Resources property. Each resource typically represents a playlist, containing references (often URLs) to the container's media items. Multiple resources may be specified for a single container as the DMS may support different playlist formats, e.g., M3U, DIDL-S, etc. The properties of one of these resources may be duplicated directly in the Container object itself. The algorithm for selecting this resource is given in the section "Transcoding and org.gnome.UPnP.MediaItem2". In MediaItem2 section also, you will find the definition for these properties in Resources/Additional properties. New Methods: ------------ Eight new methods have been added. These methods are: ListChildrenEx(u Offset, u Max, as Filter, s SortBy) -> aa{sv} ListItemsEx(u Offset, u Max, as Filter, s SortBy) -> aa{sv} ListContainersEx(u Offset, u Max, as Filter, s SortBy) -> aa{sv} SearchObjectsEx(s Query, u Offset, u Max, as Filter, s SortBy) -> (aa{sv}u) Upload(s DisplayName, s FilePath) -> (u UploadId, o ObjectPath) CreateContainer(s DisplayName, s TypeEx, as ChildTypes) -> o ObjectPath CreateReference(o ObjectPath) -> o ObjectPath GetCompatibleResources(s protocol_info, as filter) -> a{sv} The first four methods are identical in function and behaviour to existing the MediaServer2Spec functions ListChildren, ListItems, ListContainers, and SearchObjects, with the exception that they take one extra parameter, and in the case of SearchObjectsEx, return an extra result. This extra parameter allows clients to specify a sort order for the returned items. It is a string that specifies a comma separated list of PropertyNames, identifying the order in which returned items should be sorted. Each property name can be preceded with a '-' or a '+' to indicate descending or ascending order respectively. An example, is "+Date,-DisplayName", which will sort the return items first by date in ascending order and then by name in descending order. White space is not permitted in this string. The return signature of SearchObjectsEx is (aa{sv}u). Note the extra integer return value after the dictionary of objects. This integer contains the total number of items matching the specified search as opposed to the total number of objects contained in the returned dictionary of objects. These two values may differ if the client has used the Offset and Max parameters to request a portion of the total results returned, because for example its screen is only capable of displaying 20 objects at any one time. Knowing the total number of objects that match a search is useful for applications. It allows them to inform the user as to the total number of matches and to correctly compute the scrollbars of the list displaying the found items. A small Python function is given below to demonstrate how these new methods may be used. This function accepts one parameter, a path to a d-Bus container object, and it prints out the names of all the children of that object in descending order. def list_children(server_path): bus = dbus.SessionBus() container = dbus.Interface(bus.get_object( 'com.intel.dleyna-server', path), 'org.gnome.UPnP.MediaContainer2') objects = container.ListChildrenEx(0, 0, ['DisplayName'], "-DisplayName") for item in objects: for key, value in item.iteritems(): print '{0:<30}{1:<30}'.format(key, value) The output of this function might look something like this: DisplayName Videos DisplayName Pictures DisplayName Music DisplayName Files & Folders when the server_path parameter contains the path of a server object. The fifth new method, Upload, allows clients to upload a local file to the org.gnome.UPnP.MediaContainer2 object upon which it was executed. It is identical in function and behavior to com.intel.dLeynaServer.MediaDevice.UploadToAnyContainer with the sole exception that Upload allows the client to specify the server-side container into which the file is to be uploaded whereas UploadToAnyContainer leaves it up to the server to determine the most suitable location for the file. The CreateContainer method creates a new child container. DisplayName is the name of the new container, TypeEx is the extended type and ChildTypes is an array of extended types that can be stored in the new folder, e.g., ['video','container']. A special value of ['*'] indicates that no restriction is placed on the types of objects that can be stored in the container. The path of the newly created object is returned. The CreateReference method creates a reference in the container to the object identified by the ObjectPath parameter. This method returns the d-Bus path of the newly created reference. CreateReference is useful when creating server side playlists. CreateContainer can be used to create a new playlist, and CreateReference can be used to add items to that playlist. The GetCompatibleResources method: see description in MediaItem2. Recommended Usage: ------------------ All of the list and search functions supported by dleyna-server-service's implementation of org.gnome.UPnP.MediaContainer2 contain three parameters that should be used to improve the efficiency and responsiveness of applications built using dleyna-server-service. The first two parameters of interest are offset and max. These parameters allow the client application to retrieve the contents of a directory, or the results of a search, in chunks. This is vital if the client's user interface is limited to displaying a fixed number, let's say 30, items on the screen at any one time. Suppose the client performs a search which has 2000 results. If it passes the Search or SearchEx method an offset and a max of 0, all of the results will be returned to the client at once, even though it is only capable of display 30 items at a time. This will increase the memory requirements of the client and reduce its responsiveness as it must wait until all 2000 items have been received before it can update the UI. Also, if the user locates the item he is looking for in the first page of items, a lot of network and IPC traffic will have been generated in vain. For these reasons, it is better to retrieve items in chunks, as needed. The amount of network and IPC traffic can be reduced further by prudent use of the filter parameter. This parameter is an array of strings, each element of which corresponds to a property name. If the client invokes a function specifying a filter parameter that is set to a single element array containing the string '*', dleyna-server-service will include all the properties of all the requested objects in the result. However, often the client only needs to know a subset of these properties. A UI that displays results of a search might only want to display the names and perhaps the dates of the items that match the search. Once the user has identified the item he is looking for, the remaining properties for that item, and only that item, can be retrieved. As an example, consider the list_children function above. It requests that only the DisplayName of each of the containers' children be returned. Replacing ['DisplayName'] with ['*'] will generate a lot more output. Container Objects: ------------------ Container objects represent folders in a DMS. In order to manipulate a container object one first needs to discover its path. This can be done by calling one of the List or Search methods implemented by the server object or another container object. Note that a server object is also a container object so a container object can be constructed by using a server's d-Bus object path. Container objects support two interfaces: org.gnome.MediaObject2 and org.gnome.MediaContainer2. Both of these interfaces have been described in detail above and will not be discussed further in this section. An example of how container objects can be used is given in the following function. def tree(server_path, level=0): bus = dbus.SessionBus() container = dbus.Interface(bus.get_object( 'com.intel.dleyna-server', server_path), 'org.gnome.UPnP.MediaContainer2') objects = container.ListChildren(0, 0, ["DisplayName", "Path", "Type"]) for props in objects: print " " * (level * 4) + ( props["DisplayName"] + " : (" + props["Path"]+ ")") if props["Type"] == "container": tree(props["Path"], level + 1) When given the path of a container, and this could be a server object which acts as a root container, it recursively prints the contents of that container in depth first order. For example, to dump the entire contents of the My Media server introduced above, one would simply need to invoke tree("/com/intel/dLeynaServer/server/3"). Item Objects: ------------- Item objects represent a unique piece of media that can be downloaded or streamed. Each item object implements the org.gnome.UPnP.MediaObject2 interface, which is described above, and the org.gnome.UPnP.MediaItem2 interface that is documented in the MediaServer2Spec (3). Although the org.gnome.UPnP.MediaItem2 is documented elsewhere, there are some peculiarities of its implementation in dleyna-server-service that merit further explanation. These are described in the sections below. Transcoding and org.gnome.UPnP.MediaItem2 ----------------------------------------- As mentioned above, DMSs often provide different representations of the same media file. Separate sets of meta data, such as a mime type, a URL, a size, a ColorDepth, etc., are associated with each representation. Unfortunately, it is difficult to represent this in org.gnome.UPnP.MediaItem2. Although, org.gnome.UPnP.MediaItem2, allows multiple URLs to be specified, it only permits a single set of meta data to be associated with each item. For example, the MIMEType property is a single string and not an array. If multiple URLs were associated with the same item, one would not know to which URL the MIMEType property pertained. For this reason, dleyna-server-service only ever returns one element in the URLs property. By default, the value of this URL and all of the meta data properties that can change with a representation (MIMEType, DLNAProfile, Size, Duration, Bitrate, SampleRate, BitsPerSample, Width, Height, ColorDepth) refer to the first representation returned by the DMS for this item. However, the client can change this behaviour by calling com.intel.dLeynaServer.Manager.SetProtocolInfo. If this is done, these properties correspond to the first representation returned by the server that is compatible with the specified protocol info. If no representations are compatible, these properties will not be present in the item. Resources ---------- Dleyna-server-service offers a second solution to the above problem that may be useful in certain programming environments. It implements a non standard property called "Resources" which is an array of dictionaries. Each dictionary represents a separate representation of the item and contains all the properties, including the MIMEType and the URL that pertain to that representation. This can be convenient if you wish to display the media item in a web browser. You can simply create a video or an audio tag with one source for each element in the resources array and then let the browser work out which one suits it best. The specification for the Resources property is given in the table below: |---------------------------------------------------------------------------| | Name | Type | m/o* | Description | |---------------------------------------------------------------------------| | Resources |aa{sv}| m | Returns the set of resources associated | | | | | with an item | |---------------------------------------------------------------------------| The names of the properties included in this array are identical to the names of the org.gnome.UPnP.MediaItem2 properties to which they correspond, e.g., MIMEType, Size. However, four additional properties are included. Please note that if you want the resource properties to be included in the results of a call to one of the List or the Search functions, and you are explicitly filtering properties, you must include "Resources" in the filter array. The filter parameter also applies to the contents of the returned resources. So if you specify a filter of ["MIMEType", "URL", "Resources"] the dictionaries in the resources array will contain only these properties. dLeyna-server defines four additional resource properties. These have been added to improve our DLNA support. |---------------------------------------------------------------------------| | Name | Type | m/o* | Description | |---------------------------------------------------------------------------| | DLNAConversion | a{sb} | o | Indicates whether the content of the | | | | | Resource is in original source format | | | | | or if the content is transcoded. | | | | | There is 1 key: Transcoded. | | | | | See Reference (4): DLNA.ORG_CI. | | | | | | | | | | New in v0.0.2 | |---------------------------------------------------------------------------| | DLNAFlags | a{sb} | o | A dictionary of boolean values | | | | | indicating the values of the various | | | | | operations supported by a resource. | | | | | There are 12 keys: | | | | | SenderPaced, TimeBased, ByteBased, | | | | | PlayContainer, S0Increase, SNIncrease | | | | | RTSPPause, StreamingTM, InteractiveTM | | | | | BackgroundTM, ConnectionStall, DLNA_V15 | | | | | See Reference (5): DLNA.ORG_FLAGS. | | | | | | | | | | New in v0.0.2 | |---------------------------------------------------------------------------| | DLNAOperation | a{sb} | o | A dictionary of boolean values | | | | | indicating the values of the various | | | | | DLNA Operation attributes. There are 2 | | | | | keys: RangeSeek and TimeSeek. | | | | | See Reference (6): DLNA.ORG_OP. | | | | | | | | | | New in v0.0.2 | |---------------------------------------------------------------------------| | UpdateCount | u | o | Contains the number of times the | | | | | implementation detects that a change | | | | | was made to the resource. | | | | | | | | | | New in v0.0.2 | |---------------------------------------------------------------------------| GetCompatibleResource ------------------------ A third option is provided to make the lives of DMC authors easier. In a DMC, the best resource is defined not by the local device but by the capabilities of the renderer chosen by the user to play the given item. Once the user selects a file to play and a renderer upon which to play it, the DMC needs to retrieve the renderer's Sink ProtocolInfo. It can then pass this data directly to a new function that dleyna-server-service adds to the org.gnome.UPnP.MediaItem2 interface, called GetCompatibleResource. GetCompatibleResource returns a dictionary of properties that corresponds to the item representation that best matches a specified protocol info. The signature of GetCompatibleResources is given below: GetCompatibleResources(s protocol_info, as filter) -> a{sv} The first argument is a comma separated list of protocol info values, as described above. The second argument is an array of properties to be included in the returned dictionary. The format and the behaviour of this array is identical to the filter argument passed to the Search and List functions. Additional Properties --------------------- Dleyna-server-service defines some additional properties for org.gnome.UPnP.MediaItem2. These properties are described in the table below: |---------------------------------------------------------------------------| | Name | Type | m/o* | Description | |---------------------------------------------------------------------------| | AlbumArtURL | s | o | Contains the URL of the album art | | | | | associated with an item | |---------------------------------------------------------------------------| | RefPath | o | o | The presence of this property indicates | | | | | that the item is a reference item that | | | | | references another item located | | | | | elsewhere in the DMS's directory | | | | | structure. The value of this property | | | | | is the path of the referenced item. | |---------------------------------------------------------------------------| | Artists | o | m | An array of all the artists who | | | | | contributed towards the creation of the | | | | | item. The array will be empty if no | | | | | artists are associated with the item. | |---------------------------------------------------------------------------| | DLNAConversion | a{sb} | o | These 4 properties are copied from the | |-------------------------------- currently selected resource into the | | DLNAFlags | a{sb} | o | org.gnome.UPnP.MediaItem2 object. | |-------------------------------- | | DLNAOperation | a{sb} | o | | |-------------------------------- | | UpdateCount | u | o | | ----------------------------------------------------------------------------- Dleyna-server-service does not implement the org.gnome.UPnP.MediaItem2.AlbumArt property for the same reasons that it does not implement the org.gnome.UPnP.MediaContainer2.Icon property. However, it does provide an alternative method of retrieving the album art associated with media content. It does this through a new property called AlbumArtURL described in the table above that exposes the URL of the album art directly. Unimplemented Properties ----------------------- Dleyna-server-service does not support the following MediaServer2Spec properties: PixelWidth PixelHeight Thumbnail AlbumArt However, as these properties are optional, dleyna-server-service's failure to support them does not affect its compatibility with MediaServer2Spec. References: ----------- 1) ConnectionManager:2 Service Template (http://www.upnp.org/) 2) DLNA Guidelines December 2011, Part 1 Architectures and Protocols (http://www.dlna.org/) 3) MediaServer2Spec (https://wiki.gnome.org/Projects/Rygel/MediaServer2Spec) 4) See 2) 7.4.1.3.22 MM ci-param (Conversion Indicator Flag) 5) See 2) 7.4.1.3.23 MM flags-param (Flags Parameter) 6) See 2) 7.4.1.3.19 MM op-param (Operations Parameter for HTTP) dleyna-server-0.6.0/libdleyna/000077500000000000000000000000001305660312300162375ustar00rootroot00000000000000dleyna-server-0.6.0/libdleyna/server/000077500000000000000000000000001305660312300175455ustar00rootroot00000000000000dleyna-server-0.6.0/libdleyna/server/Makefile.am000066400000000000000000000031441305660312300216030ustar00rootroot00000000000000DLEYNA_SERVER_VERSION = 1:3:0 AM_CFLAGS = $(GLIB_CFLAGS) \ $(GIO_CFLAGS) \ $(DLEYNA_CORE_CFLAGS) \ $(GSSDP_CFLAGS) \ $(GUPNP_CFLAGS) \ $(GUPNPAV_CFLAGS) \ $(GUPNPDLNA_CFLAGS) \ $(SOUP_CFLAGS) \ $(LIBXML_CFLAGS) \ -include config.h pkglib_LTLIBRARIES = libdleyna-server-1.0.la libdleyna_serverincdir = $(includedir)/dleyna-1.0/libdleyna/server libdleyna_serverinc_HEADERS = control-point-server.h libdleyna_server_1_0_la_LDFLAGS = -version-info $(DLEYNA_SERVER_VERSION) \ -no-undefined libdleyna_server_1_0_la_SOURCES = $(libdleyna_serverinc_HEADERS) \ server.c \ async.c \ device.c \ manager.c \ path.c \ props.c \ search.c \ sort.c \ task.c \ upnp.c \ xml-util.c libdleyna_server_1_0_la_LIBADD = $(GLIB_LIBS) \ $(GIO_LIBS) \ $(DLEYNA_CORE_LIBS) \ $(GSSDP_LIBS) \ $(GUPNP_LIBS) \ $(GUPNPAV_LIBS) \ $(GUPNPDLNA_LIBS) \ $(SOUP_LIBS) \ $(LIBXML_LIBS) MAINTAINERCLEANFILES = Makefile.in \ aclocal.m4 \ configure \ config.h.in \ config.h.in~ \ build-aux/depcomp \ build-aux/compile \ build-aux/missing \ build-aux/install-sh sysconf_DATA = dleyna-server-service.conf EXTRA_DIST = $(sysconf_DATA) \ async.h \ client.h \ device.h \ interface.h \ manager.h \ path.h \ props.h \ search.h \ server.h \ sort.h \ task.h \ upnp.h \ xml-util.h CLEANFILES = dleyna-server-service.conf DISTCLEANFILES = dleyna-server-service.conf maintainer-clean-local: rm -rf build-aux dleyna-server-0.6.0/libdleyna/server/async.c000077500000000000000000000054211305660312300210330ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #include #include #include "async.h" #include "server.h" void dls_async_task_delete(dls_async_task_t *cb_data) { switch (cb_data->task.type) { case DLS_TASK_GET_CHILDREN: case DLS_TASK_SEARCH: if (cb_data->ut.bas.vbs) g_ptr_array_unref(cb_data->ut.bas.vbs); break; case DLS_TASK_MANAGER_GET_ALL_PROPS: case DLS_TASK_GET_ALL_PROPS: case DLS_TASK_GET_RESOURCE: if (cb_data->ut.get_all.vb) g_variant_builder_unref(cb_data->ut.get_all.vb); break; case DLS_TASK_BROWSE_OBJECTS: if (cb_data->ut.browse_objects.avb) g_variant_builder_unref(cb_data->ut.browse_objects.avb); g_free(cb_data->ut.browse_objects.objects_id); g_free(cb_data->ut.browse_objects.upnp_filter); break; case DLS_TASK_UPLOAD_TO_ANY: case DLS_TASK_UPLOAD: g_free(cb_data->ut.upload.mime_type); break; case DLS_TASK_UPDATE_OBJECT: g_free(cb_data->ut.update.current_tag_value); g_free(cb_data->ut.update.new_tag_value); break; default: break; } if (cb_data->cancellable) g_object_unref(cb_data->cancellable); } gboolean dls_async_task_complete(gpointer user_data) { dls_async_task_t *cb_data = user_data; DLEYNA_LOG_DEBUG("Enter. Error %p", (void *)cb_data->error); DLEYNA_LOG_DEBUG_NL(); if (cb_data->proxy != NULL) g_object_remove_weak_pointer((G_OBJECT(cb_data->proxy)), (gpointer *)&cb_data->proxy); cb_data->cb(&cb_data->task, cb_data->error); return FALSE; } void dls_async_task_cancelled_cb(GCancellable *cancellable, gpointer user_data) { dls_async_task_t *cb_data = user_data; if (cb_data->proxy != NULL) gupnp_service_proxy_cancel_action(cb_data->proxy, cb_data->action); if (!cb_data->error) cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_CANCELLED, "Operation cancelled."); (void) g_idle_add(dls_async_task_complete, cb_data); } void dls_async_task_cancel(dls_async_task_t *cb_data) { if (cb_data->cancellable) g_cancellable_cancel(cb_data->cancellable); } dleyna-server-0.6.0/libdleyna/server/async.h000077500000000000000000000062351305660312300210440ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #ifndef DLS_ASYNC_H__ #define DLS_ASYNC_H__ #include #include #include #include "server.h" #include "task.h" #include "upnp.h" typedef struct dls_async_task_t_ dls_async_task_t; typedef guint64 dls_upnp_prop_mask; typedef void (*dls_async_cb_t)(dls_async_task_t *cb_data); typedef struct dls_async_bas_t_ dls_async_bas_t; struct dls_async_bas_t_ { dls_upnp_prop_mask filter_mask; GPtrArray *vbs; const gchar *protocol_info; gboolean need_child_count; guint retrieved; guint max_count; dls_async_cb_t get_children_cb; }; typedef struct dls_async_get_prop_t_ dls_async_get_prop_t; struct dls_async_get_prop_t_ { GCallback prop_func; const gchar *protocol_info; }; typedef struct dls_async_get_all_t_ dls_async_get_all_t; struct dls_async_get_all_t_ { GCallback prop_func; GVariantBuilder *vb; dls_upnp_prop_mask filter_mask; const gchar *protocol_info; gboolean need_child_count; gboolean device_object; GUPnPServiceProxy *proxy; }; typedef struct dls_async_upload_t_ dls_async_upload_t; struct dls_async_upload_t_ { const gchar *object_class; gchar *mime_type; }; typedef struct dls_async_update_t_ dls_async_update_t; struct dls_async_update_t_ { gchar *current_tag_value; gchar *new_tag_value; GHashTable *map; }; typedef struct dls_async_browse_objects_t_ dls_async_browse_objects_t; struct dls_async_browse_objects_t_ { dls_async_get_all_t get_all; /* pseudo inheritance - MUST be first */ GVariantBuilder *avb; gchar *upnp_filter; const dleyna_task_queue_key_t *queue_id; const gchar **objects_id; guint object_count; guint index; }; struct dls_async_task_t_ { dls_task_t task; /* pseudo inheritance - MUST be first field */ dls_upnp_task_complete_t cb; GError *error; GUPnPServiceProxyAction *action; GUPnPServiceProxy *proxy; GCancellable *cancellable; gulong cancel_id; union { dls_async_bas_t bas; dls_async_get_prop_t get_prop; dls_async_get_all_t get_all; dls_async_upload_t upload; dls_async_update_t update; dls_async_browse_objects_t browse_objects; } ut; }; void dls_async_task_delete(dls_async_task_t *cb_data); gboolean dls_async_task_complete(gpointer user_data); void dls_async_task_cancelled_cb(GCancellable *cancellable, gpointer user_data); void dls_async_task_cancel(dls_async_task_t *cb_data); #endif /* DLS_ASYNC_H__ */ dleyna-server-0.6.0/libdleyna/server/client.h000077500000000000000000000020251305660312300211760ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Regis Merlino * */ #ifndef DLS_CLIENT_H__ #define DLS_CLIENT_H__ #include typedef struct dls_client_t_ dls_client_t; struct dls_client_t_ { gchar *protocol_info; gboolean prefer_local_addresses; }; #endif /* DLS_CLIENT_H__ */ dleyna-server-0.6.0/libdleyna/server/control-point-server.h000066400000000000000000000020471305660312300240340ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Regis Merlino * */ #ifndef DLEYNA_CONTROL_POINT_SERVER_H__ #define DLEYNA_CONTROL_POINT_SERVER_H__ #include const dleyna_control_point_t *dleyna_control_point_get_server(void); #endif /* DLEYNA_CONTROL_POINT_SERVER_H__ */ dleyna-server-0.6.0/libdleyna/server/device.c000066400000000000000000004664661305660312300211760ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #include #include #include #include #include #include #ifdef __ANDROID__ #include "ifaddrs.h" #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "device.h" #include "interface.h" #include "path.h" #include "server.h" #include "xml-util.h" #define DLS_SYSTEM_UPDATE_VAR "SystemUpdateID" #define DLS_CONTAINER_UPDATE_VAR "ContainerUpdateIDs" #define DLS_LAST_CHANGE_VAR "LastChange" #define DLS_NETWORK_INTERFACE_INFO_VAR "NetworkInterfaceInfo" #define DLS_DMS_DEVICE_TYPE "urn:schemas-upnp-org:device:MediaServer:" #define DLS_CONTENT_DIRECTORY_SERVICE_TYPE \ "urn:schemas-upnp-org:service:ContentDirectory" #define DLS_ENERGY_MANAGEMENT_SERVICE_TYPE \ "urn:schemas-upnp-org:service:EnergyManagement:1" #define DLS_UPLOAD_STATUS_IN_PROGRESS "IN_PROGRESS" #define DLS_UPLOAD_STATUS_CANCELLED "CANCELLED" #define DLS_UPLOAD_STATUS_ERROR "ERROR" #define DLS_UPLOAD_STATUS_COMPLETED "COMPLETED" #define DLS_DEFAULT_WAKE_PORT 9 #define DLS_DEFAULT_WAKE_ON_DELAY 30 typedef gboolean(*dls_device_count_cb_t)(dls_async_task_t *cb_data, gint count); typedef struct dls_device_count_data_t_ dls_device_count_data_t; struct dls_device_count_data_t_ { dls_device_count_cb_t cb; dls_async_task_t *cb_data; }; typedef struct dls_device_object_builder_t_ dls_device_object_builder_t; struct dls_device_object_builder_t_ { GVariantBuilder *vb; gchar *id; gboolean needs_child_count; }; typedef struct dls_device_upload_t_ dls_device_upload_t; struct dls_device_upload_t_ { SoupSession *soup_session; SoupMessage *msg; GMappedFile *mapped_file; gchar *body; gsize body_length; const gchar *status; guint64 bytes_uploaded; guint64 bytes_to_upload; }; typedef struct dls_device_upload_job_t_ dls_device_upload_job_t; struct dls_device_upload_job_t_ { gint upload_id; dls_device_t *device; guint remove_idle; }; typedef struct dls_device_download_t_ dls_device_download_t; struct dls_device_download_t_ { SoupSession *session; SoupMessage *msg; dls_async_task_t *task; }; typedef struct dls_tcp_wake_t_ dls_tcp_wake_t; struct dls_tcp_wake_t_ { GOutputStream *output_stream; GSocketConnection *socket_connection; guint8 *buffer; gssize to_send; gssize sent; guint max_wake_on_delay; dls_async_task_t *task; }; /* Private structure used in chain task */ typedef struct prv_new_device_ct_t_ prv_new_device_ct_t; struct prv_new_device_ct_t_ { dls_device_t *dev; dleyna_connector_id_t connection; const dleyna_connector_dispatch_cb_t *vtable; GHashTable *property_map; }; enum prv_changed_event_type_t_ { PRV_CHANGED_EVENT_ADD = 1, PRV_CHANGED_EVENT_MOD, PRV_CHANGED_EVENT_DEL, PRV_CHANGED_EVENT_DONE, PRV_CHANGED_EVENT_CONTAINER }; typedef enum prv_changed_event_type_t_ prv_changed_event_type_t; static void prv_get_child_count(dls_async_task_t *cb_data, dls_device_count_cb_t cb, const gchar *id); static void prv_retrieve_child_count_for_list(dls_async_task_t *cb_data); static void prv_container_update_cb(GUPnPServiceProxy *proxy, const char *variable, GValue *value, gpointer user_data); static void prv_system_update_cb(GUPnPServiceProxy *proxy, const char *variable, GValue *value, gpointer user_data); static void prv_last_change_cb(GUPnPServiceProxy *proxy, const char *variable, GValue *value, gpointer user_data); static void prv_network_interface_info_cb(GUPnPServiceProxy *proxy, const char *variable, GValue *value, gpointer user_data); static void prv_upload_delete(gpointer up); static void prv_upload_job_delete(gpointer up); static void prv_get_sr_token_for_props(GUPnPServiceProxy *proxy, const dls_device_t *device, dls_async_task_t *cb_data); static void prv_browse_objects_end_action_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data); static GUPnPServiceProxyAction *prv_browse_objects_begin_action_cb( dleyna_service_task_t *task, GUPnPServiceProxy *proxy, gboolean *failed); static void prv_get_sleeping_for_props(GUPnPServiceProxy *proxy, const dls_device_t *device, dls_async_task_t *cb_data); static void prv_free_network_if_info(dls_network_if_info_t *info); static void prv_object_builder_delete(void *dob) { dls_device_object_builder_t *builder = dob; if (builder) { if (builder->vb) g_variant_builder_unref(builder->vb); g_free(builder->id); g_free(builder); } } static void prv_count_data_new(dls_async_task_t *cb_data, dls_device_count_cb_t cb, dls_device_count_data_t **count_data) { dls_device_count_data_t *cd; cd = g_new(dls_device_count_data_t, 1); cd->cb = cb; cd->cb_data = cb_data; *count_data = cd; } static void prv_context_unsubscribe(dls_device_context_t *ctx) { if (ctx->cds.timeout_id) { (void) g_source_remove(ctx->cds.timeout_id); ctx->cds.timeout_id = 0; } if (ctx->ems.timeout_id) { (void) g_source_remove(ctx->ems.timeout_id); ctx->ems.timeout_id = 0; } if (ctx->cds.subscribed) { gupnp_service_proxy_remove_notify(ctx->cds.proxy, DLS_SYSTEM_UPDATE_VAR, prv_system_update_cb, ctx->device); gupnp_service_proxy_remove_notify(ctx->cds.proxy, DLS_CONTAINER_UPDATE_VAR, prv_container_update_cb, ctx->device); gupnp_service_proxy_remove_notify(ctx->cds.proxy, DLS_LAST_CHANGE_VAR, prv_last_change_cb, ctx->device); gupnp_service_proxy_set_subscribed(ctx->cds.proxy, FALSE); ctx->cds.subscribed = FALSE; } if (ctx->ems.subscribed) { gupnp_service_proxy_remove_notify( ctx->ems.proxy, DLS_NETWORK_INTERFACE_INFO_VAR, prv_network_interface_info_cb, ctx->device); gupnp_service_proxy_set_subscribed(ctx->ems.proxy, FALSE); ctx->ems.subscribed = FALSE; } } void dls_device_delete_context(dls_device_context_t *ctx) { if (ctx) { prv_context_unsubscribe(ctx); if (ctx->device_info) g_object_unref(ctx->device_info); if (ctx->device_proxy) g_object_unref(ctx->device_proxy); if (ctx->cds.proxy) g_object_unref(ctx->cds.proxy); if (ctx->ems.proxy) g_object_unref(ctx->ems.proxy); g_free(ctx->ip_address); g_free(ctx); } } static GUPnPServiceInfo *prv_lookup_em_service(GUPnPDeviceInfo *device_info) { GList *child_devices; GList *next; GUPnPDeviceInfo *child_info = NULL; GUPnPServiceInfo *service_info = NULL; child_devices = gupnp_device_info_list_devices(device_info); next = child_devices; while (next != NULL) { child_info = (GUPnPDeviceInfo *)next->data; service_info = gupnp_device_info_get_service(child_info, DLS_ENERGY_MANAGEMENT_SERVICE_TYPE); if (service_info != NULL) break; service_info = prv_lookup_em_service(child_info); if (service_info != NULL) break; next = g_list_next(next); } g_list_free_full(child_devices, g_object_unref); return service_info; } static void prv_context_new(const gchar *ip_address, GUPnPDeviceProxy *proxy, GUPnPDeviceInfo *device_info, dls_device_t *device, dls_device_context_t **context) { dls_device_context_t *ctx = g_new(dls_device_context_t, 1); ctx->ip_address = g_strdup(ip_address); ctx->device_proxy = proxy; ctx->device_info = device_info; ctx->device = device; ctx->cds.subscribed = FALSE; ctx->cds.timeout_id = 0; ctx->ems.subscribed = FALSE; ctx->ems.timeout_id = 0; g_object_ref(proxy); g_object_ref(device_info); ctx->cds.proxy = (GUPnPServiceProxy *) gupnp_device_info_get_service( device_info, DLS_CONTENT_DIRECTORY_SERVICE_TYPE); ctx->ems.proxy = (GUPnPServiceProxy *) gupnp_device_info_get_service( (GUPnPDeviceInfo *)proxy, DLS_ENERGY_MANAGEMENT_SERVICE_TYPE); if (ctx->ems.proxy == NULL) ctx->ems.proxy = (GUPnPServiceProxy *) prv_lookup_em_service( (GUPnPDeviceInfo *)proxy); *context = ctx; } void dls_device_delete(void *device) { dls_device_t *dev = device; if (dev) { DLEYNA_LOG_DEBUG("Deleting device"); dev->shutting_down = TRUE; g_hash_table_unref(dev->upload_jobs); g_hash_table_unref(dev->uploads); if (dev->timeout_id) (void) g_source_remove(dev->timeout_id); if (dev->id) (void) dls_server_get_connector()->unpublish_subtree( dev->connection, dev->id); prv_free_network_if_info(dev->network_if_info); g_ptr_array_unref(dev->contexts); dls_device_delete_context(dev->sleeping_context); if (dev->wake_on_timeout_id) (void) g_source_remove(dev->wake_on_timeout_id); g_free(dev->path); g_variant_unref(dev->search_caps); g_variant_unref(dev->sort_caps); g_variant_unref(dev->sort_ext_caps); g_variant_unref(dev->feature_list); g_free(dev->icon.mime_type); g_free(dev->icon.bytes); g_free(dev); } } void dls_device_unsubscribe(void *device) { unsigned int i; dls_device_t *dev = device; dls_device_context_t *context; if (dev) { for (i = 0; i < dev->contexts->len; ++i) { context = g_ptr_array_index(dev->contexts, i); prv_context_unsubscribe(context); } } } static void prv_last_change_decode(GUPnPCDSLastChangeEntry *entry, GVariantBuilder *array, const char *root_path) { GUPnPCDSLastChangeEvent event; const char *object_id; const char *parent_id; const char *mclass; const char *media_class; const char *media_class_ex; char *parent_path; char *path = NULL; gboolean sub_update; guint32 update_id; GVariantBuilder *dict; gboolean mod; dict = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); object_id = gupnp_cds_last_change_entry_get_object_id(entry); if (!object_id) goto on_error; sub_update = gupnp_cds_last_change_entry_is_subtree_update(entry); update_id = gupnp_cds_last_change_entry_get_update_id(entry); path = dls_path_from_id(root_path, object_id); event = gupnp_cds_last_change_entry_get_event(entry); switch (event) { case GUPNP_CDS_LAST_CHANGE_EVENT_OBJECT_ADDED: parent_id = gupnp_cds_last_change_entry_get_parent_id(entry); if (!parent_id) goto on_error; mclass = gupnp_cds_last_change_entry_get_class(entry); if (!mclass) goto on_error; media_class = dls_props_upnp_class_to_media_spec(mclass); if (!media_class) goto on_error; media_class_ex = dls_props_upnp_class_to_media_spec_ex(mclass); parent_path = dls_path_from_id(root_path, parent_id); g_variant_builder_add( dict, "{sv}", DLS_INTERFACE_PROP_CHANGE_TYPE, g_variant_new_uint32(PRV_CHANGED_EVENT_ADD)); g_variant_builder_add( dict, "{sv}", DLS_INTERFACE_PROP_PATH, g_variant_new_string(path)); g_variant_builder_add( dict, "{sv}", DLS_INTERFACE_PROP_UPDATE_ID, g_variant_new_uint32(update_id)); g_variant_builder_add( dict, "{sv}", DLS_INTERFACE_PROP_SUBTREE_UPDATE, g_variant_new_boolean(sub_update)); g_variant_builder_add( dict, "{sv}", DLS_INTERFACE_PROP_PARENT, g_variant_new_string(parent_path)); g_variant_builder_add( dict, "{sv}", DLS_INTERFACE_PROP_TYPE, g_variant_new_string(media_class)); g_variant_builder_add( dict, "{sv}", DLS_INTERFACE_PROP_TYPE_EX, g_variant_new_string(media_class_ex)); g_free(parent_path); break; case GUPNP_CDS_LAST_CHANGE_EVENT_OBJECT_REMOVED: case GUPNP_CDS_LAST_CHANGE_EVENT_OBJECT_MODIFIED: mod = (event == GUPNP_CDS_LAST_CHANGE_EVENT_OBJECT_MODIFIED); g_variant_builder_add( dict, "{sv}", DLS_INTERFACE_PROP_CHANGE_TYPE, g_variant_new_uint32(mod ? PRV_CHANGED_EVENT_MOD : PRV_CHANGED_EVENT_DEL)); g_variant_builder_add( dict, "{sv}", DLS_INTERFACE_PROP_PATH, g_variant_new_string(path)); g_variant_builder_add( dict, "{sv}", DLS_INTERFACE_PROP_UPDATE_ID, g_variant_new_uint32(update_id)); g_variant_builder_add( dict, "{sv}", DLS_INTERFACE_PROP_SUBTREE_UPDATE, g_variant_new_boolean(sub_update)); break; case GUPNP_CDS_LAST_CHANGE_EVENT_ST_DONE: g_variant_builder_add( dict, "{sv}", DLS_INTERFACE_PROP_CHANGE_TYPE, g_variant_new_uint32(PRV_CHANGED_EVENT_DONE)); g_variant_builder_add( dict, "{sv}", DLS_INTERFACE_PROP_PATH, g_variant_new_string(path)); g_variant_builder_add( dict, "{sv}", DLS_INTERFACE_PROP_UPDATE_ID, g_variant_new_uint32(update_id)); break; case GUPNP_CDS_LAST_CHANGE_EVENT_INVALID: default: goto on_error; break; } g_variant_builder_add(array, "@a{sv}", g_variant_builder_end(dict)); on_error: g_variant_builder_unref(dict); g_free(path); } static void prv_last_change_cb(GUPnPServiceProxy *proxy, const char *variable, GValue *value, gpointer user_data) { const gchar *last_change; GVariantBuilder array; GVariant *val; dls_device_t *device = user_data; GUPnPCDSLastChangeParser *parser; GList *list; GList *next; GError *error = NULL; last_change = g_value_get_string(value); DLEYNA_LOG_DEBUG_NL(); DLEYNA_LOG_DEBUG("LastChange XML: %s", last_change); DLEYNA_LOG_DEBUG_NL(); parser = gupnp_cds_last_change_parser_new(); list = gupnp_cds_last_change_parser_parse(parser, last_change, &error); if (error != NULL) { DLEYNA_LOG_WARNING( "gupnp_cds_last_change_parser_parse parsing failed: %s", error->message); goto on_error; } g_variant_builder_init(&array, G_VARIANT_TYPE("aa{sv}")); next = list; while (next) { prv_last_change_decode(next->data, &array, device->path); gupnp_cds_last_change_entry_unref(next->data); next = g_list_next(next); } val = g_variant_new("(@aa{sv})", g_variant_builder_end(&array)); (void) dls_server_get_connector()->notify(device->connection, device->path, DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE, DLS_INTERFACE_CHANGED_EVENT, val, NULL); on_error: g_list_free(list); g_object_unref(parser); if (error != NULL) g_error_free(error); } static void prv_free_network_if_info(dls_network_if_info_t *info) { if (info != NULL) { g_free(info->mac_address); g_free(info->device_uuid); g_free(info->network_if_mode); g_free(info->wake_on_pattern); g_free(info->wake_transport); g_list_free_full(info->ip_addresses, g_free); g_free(info); } } static dls_network_if_info_t *prv_get_network_if_info(xmlNode *device_if_node, const gchar *udn) { dls_network_if_info_t *info = NULL; GList *ipv4_addresses; GList *ipv6_addresses; gchar *wake_on_delay = NULL; info = g_new0(dls_network_if_info_t, 1); ipv4_addresses = xml_util_get_child_string_list_content_by_name( device_if_node, "NetworkInterface", "AssociatedIpAddresses", "Ipv4", NULL); ipv6_addresses = xml_util_get_child_string_list_content_by_name( device_if_node, "NetworkInterface", "AssociatedIpAddresses", "Ipv6", NULL); info->ip_addresses = g_list_concat(ipv4_addresses, ipv6_addresses); info->device_uuid = xml_util_get_child_string_content_by_name( device_if_node, "DeviceUUID", NULL); info->mac_address = xml_util_get_child_string_content_by_name( device_if_node, "NetworkInterface", "MacAddress", NULL); info->network_if_mode = xml_util_get_child_string_content_by_name( device_if_node, "NetworkInterface", "NetworkInterfaceMode", NULL); info->wake_on_pattern = xml_util_get_child_string_content_by_name( device_if_node, "NetworkInterface", "WakeOnPattern", NULL); info->wake_transport = xml_util_get_child_string_content_by_name( device_if_node, "NetworkInterface", "WakeSupportedTransport", NULL); wake_on_delay = xml_util_get_child_string_content_by_name( device_if_node, "NetworkInterface", "MaxWakeOnDelay", NULL); if (wake_on_delay == NULL) { info->max_wake_on_delay = DLS_DEFAULT_WAKE_ON_DELAY; } else { info->max_wake_on_delay = atoi(wake_on_delay); g_free(wake_on_delay); } if ((info->device_uuid == NULL || strlen(info->device_uuid) > 70) || (info->mac_address == NULL || strlen(info->mac_address) != 17) || (info->network_if_mode == NULL) || (info->ip_addresses == NULL) || (info->wake_on_pattern == NULL)) goto on_error; if (strcmp(info->device_uuid, udn)) goto on_error; return info; on_error: prv_free_network_if_info(info); return NULL; } static GList *prv_network_if_info_decode(const gchar *info, const gchar *udn) { xmlDoc *doc; xmlNode *node; GList *info_list = NULL; dls_network_if_info_t *if_info; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG_NL(); DLEYNA_LOG_DEBUG("NetworkInterfaceInfo XML: %s", info); DLEYNA_LOG_DEBUG_NL(); doc = xmlParseMemory(info, strlen(info) + 1); if (doc == NULL) { DLEYNA_LOG_WARNING("XML: invalid document"); goto on_exit; } node = xmlDocGetRootElement(doc); if (node == NULL) { DLEYNA_LOG_WARNING("XML: empty document"); goto on_exit; } if (node->name == NULL) { DLEYNA_LOG_WARNING("XML: empty document name"); goto on_exit; } if (strcmp((char *)node->name, "NetworkInterfaceInfo")) { DLEYNA_LOG_WARNING("XML: invalid document name"); goto on_exit; } for (node = node->children; node; node = node->next) { if (node->name != NULL && !strcmp((char *)node->name, "DeviceInterface")) { if_info = prv_get_network_if_info(node, udn); if (if_info != NULL) info_list = g_list_prepend(info_list, if_info); } } on_exit: DLEYNA_LOG_DEBUG("Exit"); if (doc != NULL) xmlFreeDoc(doc); return info_list; } static gboolean prv_get_device_sleeping_state(dls_device_t *device, const gchar *network_if_info_xml, gboolean *sleeping) { dls_network_if_info_t *info; GList *info_list; GList *next; unsigned int i; dls_device_context_t *ctx; gboolean found = FALSE; const gchar *udn; GList *next_ip; guint ip_idx; gchar *ip_address; DLEYNA_LOG_DEBUG("Enter"); if (device->contexts->len == 0) goto on_exit; ctx = g_ptr_array_index(device->contexts, 0); udn = gupnp_device_info_get_udn((GUPnPDeviceInfo *)ctx->device_proxy); info_list = prv_network_if_info_decode(network_if_info_xml, udn); if (info_list == NULL) { DLEYNA_LOG_DEBUG("no UDN match found."); goto on_exit; } DLEYNA_LOG_DEBUG("Device UUID = %s", udn); next = info_list; while (next != NULL) { info = (dls_network_if_info_t *)next->data; for (i = 0; i < device->contexts->len; ++i) { ctx = g_ptr_array_index(device->contexts, i); DLEYNA_LOG_DEBUG("Context[%u] - IP address = %s", i, ctx->ip_address); next_ip = info->ip_addresses; ip_idx = 0; while (next_ip != NULL) { ip_address = (gchar *)next_ip->data; DLEYNA_LOG_DEBUG( "Network Interface Info - IP address #%u = %s", ip_idx, ip_address); if (!strcmp(ctx->ip_address, ip_address)) { found = TRUE; DLEYNA_LOG_DEBUG("IP+UDN match found"); break; } next_ip = g_list_next(next_ip); ip_idx++; } if (found) break; } next = g_list_next(next); if (found) break; } if (!found) { DLEYNA_LOG_DEBUG("IP+UDN match not found, use UDN match only"); ctx = g_ptr_array_index(device->contexts, 0); info = (dls_network_if_info_t *)info_list->data; ip_idx = 0; found = TRUE; } info->ip_address_position = ip_idx; DLEYNA_LOG_DEBUG("Matching Network Interface Info:"); DLEYNA_LOG_DEBUG("- Mode = %s", info->network_if_mode); DLEYNA_LOG_DEBUG("- Wake-on IP address = %s", (gchar *)g_list_nth_data(info->ip_addresses, ip_idx)); if (!strcmp(info->network_if_mode, "IP-up")) *sleeping = FALSE; else *sleeping = TRUE; prv_free_network_if_info(device->network_if_info); device->network_if_info = info; info_list = g_list_remove(info_list, info); g_list_free_full(info_list, (GDestroyNotify)prv_free_network_if_info); on_exit: DLEYNA_LOG_DEBUG("Exit"); return found; } static void prv_network_interface_info_cb(GUPnPServiceProxy *proxy, const char *variable, GValue *value, gpointer user_data) { dls_device_t *device = user_data; GVariantBuilder *array; GVariant *val; gboolean sleeping; if (prv_get_device_sleeping_state(device, g_value_get_string(value), &sleeping)) { device->sleeping = sleeping; array = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(array, "{sv}", DLS_INTERFACE_PROP_SLEEPING, g_variant_new_boolean(sleeping)); val = g_variant_new("(s@a{sv}as)", DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE, g_variant_builder_end(array), NULL); (void) dls_server_get_connector()->notify(device->connection, device->path, DLS_INTERFACE_PROPERTIES, DLS_INTERFACE_PROPERTIES_CHANGED, val, NULL); g_variant_builder_unref(array); } } static void prv_build_container_update_array(const gchar *root_path, const gchar *value, GVariantBuilder *builder) { gchar **str_array; int pos = 0; gchar *path; guint id; str_array = g_strsplit(value, ",", 0); DLEYNA_LOG_DEBUG_NL(); while (str_array[pos] && str_array[pos + 1]) { path = dls_path_from_id(root_path, str_array[pos++]); id = atoi(str_array[pos++]); g_variant_builder_add(builder, "(ou)", path, id); DLEYNA_LOG_DEBUG("@Id [%s] - Path [%s] - id[%d]", str_array[pos-2], path, id); g_free(path); } DLEYNA_LOG_DEBUG_NL(); g_strfreev(str_array); } static void prv_build_container_update_changed_array(const gchar *root_path, const gchar *value, GVariantBuilder *builder) { gchar **str_array; int pos = 0; gchar *path; guint id; GVariantBuilder dict; str_array = g_strsplit(value, ",", 0); while (str_array[pos] && str_array[pos + 1]) { g_variant_builder_init(&dict, G_VARIANT_TYPE("a{sv}")); path = dls_path_from_id(root_path, str_array[pos++]); id = atoi(str_array[pos++]); g_variant_builder_add( &dict, "{sv}", DLS_INTERFACE_PROP_CHANGE_TYPE, g_variant_new_uint32(PRV_CHANGED_EVENT_CONTAINER)); g_variant_builder_add(&dict, "{sv}", DLS_INTERFACE_PROP_PATH, g_variant_new_string(path)); g_variant_builder_add(&dict, "{sv}", DLS_INTERFACE_PROP_UPDATE_ID, g_variant_new_uint32(id)); g_variant_builder_add(builder, "@a{sv}", g_variant_builder_end(&dict)); g_free(path); } g_strfreev(str_array); } static void prv_container_update_cb(GUPnPServiceProxy *proxy, const char *variable, GValue *value, gpointer user_data) { dls_device_t *device = user_data; GVariantBuilder array; g_variant_builder_init(&array, G_VARIANT_TYPE("a(ou)")); prv_build_container_update_array(device->path, g_value_get_string(value), &array); (void) dls_server_get_connector()->notify( device->connection, device->path, DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE, DLS_INTERFACE_ESV_CONTAINER_UPDATE_IDS, g_variant_new("(@a(ou))", g_variant_builder_end(&array)), NULL); if (!device->has_last_change) { g_variant_builder_init(&array, G_VARIANT_TYPE("aa{sv}")); prv_build_container_update_changed_array( device->path, g_value_get_string(value), &array); (void) dls_server_get_connector()->notify( device->connection, device->path, DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE, DLS_INTERFACE_CHANGED_EVENT, g_variant_new("(@aa{sv})", g_variant_builder_end( &array)), NULL); } } static void prv_system_update_cb(GUPnPServiceProxy *proxy, const char *variable, GValue *value, gpointer user_data) { GVariantBuilder *array; GVariant *val; dls_device_t *device = user_data; guint suid = g_value_get_uint(value); DLEYNA_LOG_DEBUG("System Update %u", suid); device->system_update_id = suid; array = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(array, "{sv}", DLS_INTERFACE_PROP_ESV_SYSTEM_UPDATE_ID, g_variant_new_uint32(suid)); val = g_variant_new("(s@a{sv}as)", DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE, g_variant_builder_end(array), NULL); (void) dls_server_get_connector()->notify(device->connection, device->path, DLS_INTERFACE_PROPERTIES, DLS_INTERFACE_PROPERTIES_CHANGED, val, NULL); g_variant_builder_unref(array); } static gboolean prv_re_enable_cd_subscription(gpointer user_data) { dls_device_context_t *context = user_data; context->cds.timeout_id = 0; return FALSE; } static void prv_cd_subscription_lost_cb(GUPnPServiceProxy *proxy, const GError *reason, gpointer user_data) { dls_device_context_t *context = user_data; if (!context->cds.timeout_id) { gupnp_service_proxy_set_subscribed(context->cds.proxy, TRUE); context->cds.timeout_id = g_timeout_add_seconds( 10, prv_re_enable_cd_subscription, context); } else { g_source_remove(context->cds.timeout_id); gupnp_service_proxy_remove_notify(context->cds.proxy, DLS_SYSTEM_UPDATE_VAR, prv_system_update_cb, context->device); gupnp_service_proxy_remove_notify(context->cds.proxy, DLS_CONTAINER_UPDATE_VAR, prv_container_update_cb, context->device); gupnp_service_proxy_remove_notify(context->cds.proxy, DLS_LAST_CHANGE_VAR, prv_last_change_cb, context->device); context->cds.timeout_id = 0; context->cds.subscribed = FALSE; } } static gboolean prv_re_enable_em_subscription(gpointer user_data) { dls_device_context_t *context = user_data; context->cds.timeout_id = 0; return FALSE; } static void prv_em_subscription_lost_cb(GUPnPServiceProxy *proxy, const GError *reason, gpointer user_data) { dls_device_context_t *context = user_data; if (!context->ems.timeout_id) { gupnp_service_proxy_set_subscribed(context->ems.proxy, TRUE); context->ems.timeout_id = g_timeout_add_seconds( 10, prv_re_enable_em_subscription, context); } else { g_source_remove(context->ems.timeout_id); gupnp_service_proxy_remove_notify( context->ems.proxy, DLS_NETWORK_INTERFACE_INFO_VAR, prv_network_interface_info_cb, context->device); context->ems.timeout_id = 0; context->ems.subscribed = FALSE; } } void dls_device_subscribe_to_service_changes(dls_device_t *device) { dls_device_context_t *context; context = dls_device_get_context(device, NULL); DLEYNA_LOG_DEBUG("Subscribe for events on context: %s", context->ip_address); if (context->cds.proxy) { gupnp_service_proxy_add_notify(context->cds.proxy, DLS_SYSTEM_UPDATE_VAR, G_TYPE_UINT, prv_system_update_cb, device); gupnp_service_proxy_add_notify(context->cds.proxy, DLS_CONTAINER_UPDATE_VAR, G_TYPE_STRING, prv_container_update_cb, device); gupnp_service_proxy_add_notify(context->cds.proxy, DLS_LAST_CHANGE_VAR, G_TYPE_STRING, prv_last_change_cb, device); context->cds.subscribed = TRUE; gupnp_service_proxy_set_subscribed(context->cds.proxy, TRUE); g_signal_connect(context->cds.proxy, "subscription-lost", G_CALLBACK(prv_cd_subscription_lost_cb), context); } if (context->ems.proxy) { gupnp_service_proxy_add_notify(context->ems.proxy, DLS_NETWORK_INTERFACE_INFO_VAR, G_TYPE_STRING, prv_network_interface_info_cb, device); context->ems.subscribed = TRUE; gupnp_service_proxy_set_subscribed(context->ems.proxy, TRUE); g_signal_connect(context->ems.proxy, "subscription-lost", G_CALLBACK(prv_em_subscription_lost_cb), context); } } static void prv_feature_list_add_feature(gchar *root_path, GUPnPFeature *feature, GVariantBuilder *vb) { GVariantBuilder vbo; GVariant *var_obj; const char *name; const char *version; const char *obj_id; gchar **obj; gchar **saved; gchar *path; name = gupnp_feature_get_name(feature); version = gupnp_feature_get_version(feature); obj_id = gupnp_feature_get_object_ids(feature); g_variant_builder_init(&vbo, G_VARIANT_TYPE("ao")); if (obj_id != NULL && *obj_id) { obj = g_strsplit(obj_id, ",", 0); saved = obj; while (obj && *obj) { path = dls_path_from_id(root_path, *obj); g_variant_builder_add(&vbo, "o", path); g_free(path); obj++; } g_strfreev(saved); } var_obj = g_variant_builder_end(&vbo); g_variant_builder_add(vb, "(ss@ao)", name, version, var_obj); } static void prv_get_feature_list_analyze(dls_device_t *device, gchar *result) { GUPnPFeatureListParser *parser; GUPnPFeature *feature; GList *list; GList *item; GError *error = NULL; GVariantBuilder vb; #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG char *str; #endif parser = gupnp_feature_list_parser_new(); list = gupnp_feature_list_parser_parse_text(parser, result, &error); if (error != NULL) { DLEYNA_LOG_WARNING("GetFeatureList parsing failed: %s", error->message); goto on_exit; } g_variant_builder_init(&vb, G_VARIANT_TYPE("a(ssao)")); item = list; while (item != NULL) { feature = (GUPnPFeature *)item->data; prv_feature_list_add_feature(device->path, feature, &vb); g_object_unref(feature); item = g_list_next(item); } device->feature_list = g_variant_ref_sink(g_variant_builder_end(&vb)); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG str = g_variant_print(device->feature_list, FALSE); DLEYNA_LOG_DEBUG("%s = %s", DLS_INTERFACE_PROP_SV_FEATURE_LIST, str); g_free(str); #endif on_exit: g_list_free(list); g_object_unref(parser); if (error != NULL) g_error_free(error); } static void prv_get_feature_list_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { gchar *result = NULL; gboolean end; GError *error = NULL; prv_new_device_ct_t *priv_t = (prv_new_device_ct_t *)user_data; priv_t->dev->construct_step++; end = gupnp_service_proxy_end_action(proxy, action, &error, "FeatureList", G_TYPE_STRING, &result, NULL); if (!end || (result == NULL)) { DLEYNA_LOG_WARNING("GetFeatureList operation failed: %s", ((error != NULL) ? error->message : "Invalid result")); goto on_error; } DLEYNA_LOG_DEBUG("GetFeatureList result: %s", result); prv_get_feature_list_analyze(priv_t->dev, result); on_error: if (error != NULL) g_error_free(error); g_free(result); } static GUPnPServiceProxyAction *prv_get_feature_list( dleyna_service_task_t *task, GUPnPServiceProxy *proxy, gboolean *failed) { *failed = FALSE; return gupnp_service_proxy_begin_action( proxy, "GetFeatureList", dleyna_service_task_begin_action_cb, task, NULL); } static void prv_get_sort_ext_capabilities_analyze(dls_device_t *device, gchar *result) { gchar **caps; gchar **saved; #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG gchar *props; #endif GVariantBuilder sort_ext_caps_vb; g_variant_builder_init(&sort_ext_caps_vb, G_VARIANT_TYPE("as")); caps = g_strsplit(result, ",", 0); saved = caps; while (caps && *caps) { g_variant_builder_add(&sort_ext_caps_vb, "s", *caps); caps++; } g_strfreev(saved); device->sort_ext_caps = g_variant_ref_sink(g_variant_builder_end( &sort_ext_caps_vb)); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG props = g_variant_print(device->sort_ext_caps, FALSE); DLEYNA_LOG_DEBUG("%s = %s", DLS_INTERFACE_PROP_SV_SORT_EXT_CAPABILITIES, props); g_free(props); #endif } static void prv_get_sort_ext_capabilities_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { gchar *result = NULL; gboolean end; GError *error = NULL; prv_new_device_ct_t *priv_t = (prv_new_device_ct_t *)user_data; priv_t->dev->construct_step++; end = gupnp_service_proxy_end_action(proxy, action, &error, "SortExtensionCaps", G_TYPE_STRING, &result, NULL); if (!end || (result == NULL)) { DLEYNA_LOG_WARNING( "GetSortExtensionCapabilities operation failed: %s", ((error != NULL) ? error->message : "Invalid result")); goto on_error; } DLEYNA_LOG_DEBUG("GetSortExtensionCapabilities result: %s", result); prv_get_sort_ext_capabilities_analyze(priv_t->dev, result); on_error: if (error) g_error_free(error); g_free(result); } static GUPnPServiceProxyAction *prv_get_sort_ext_capabilities( dleyna_service_task_t *task, GUPnPServiceProxy *proxy, gboolean *failed) { *failed = FALSE; return gupnp_service_proxy_begin_action( proxy, "GetSortExtensionCapabilities", dleyna_service_task_begin_action_cb, task, NULL); } static void prv_get_capabilities_analyze(GHashTable *property_map, gchar *result, GVariant **variant) { gchar **caps; gchar **saved; gchar *prop_name; GVariantBuilder caps_vb; g_variant_builder_init(&caps_vb, G_VARIANT_TYPE("as")); if (!strcmp(result, "*")) { g_variant_builder_add(&caps_vb, "s", "*"); } else { caps = g_strsplit(result, ",", 0); saved = caps; while (caps && *caps) { prop_name = g_hash_table_lookup(property_map, *caps); if (prop_name) { g_variant_builder_add(&caps_vb, "s", prop_name); /* TODO: Okay this is not very nice. A better way to fix this would be to change the p_map to be many : many. */ if (!strcmp(*caps, "upnp:class")) g_variant_builder_add( &caps_vb, "s", DLS_INTERFACE_PROP_TYPE_EX); } caps++; } g_strfreev(saved); } *variant = g_variant_ref_sink(g_variant_builder_end(&caps_vb)); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG prop_name = g_variant_print(*variant, FALSE); DLEYNA_LOG_DEBUG("%s = %s", " Variant", prop_name); g_free(prop_name); #endif } static void prv_get_sort_capabilities_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { gchar *result = NULL; gboolean end; GError *error = NULL; prv_new_device_ct_t *priv_t = (prv_new_device_ct_t *)user_data; priv_t->dev->construct_step++; end = gupnp_service_proxy_end_action(proxy, action, &error, "SortCaps", G_TYPE_STRING, &result, NULL); if (!end || (result == NULL)) { DLEYNA_LOG_WARNING("GetSortCapabilities operation failed: %s", ((error != NULL) ? error->message : "Invalid result")); goto on_error; } DLEYNA_LOG_DEBUG("GetSortCapabilities result: %s", result); prv_get_capabilities_analyze(priv_t->property_map, result, &priv_t->dev->sort_caps); on_error: if (error) g_error_free(error); g_free(result); } static GUPnPServiceProxyAction *prv_get_sort_capabilities( dleyna_service_task_t *task, GUPnPServiceProxy *proxy, gboolean *failed) { *failed = FALSE; return gupnp_service_proxy_begin_action( proxy, "GetSortCapabilities", dleyna_service_task_begin_action_cb, task, NULL); } static void prv_get_search_capabilities_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { gchar *result = NULL; gboolean end; GError *error = NULL; prv_new_device_ct_t *priv_t = (prv_new_device_ct_t *)user_data; priv_t->dev->construct_step++; end = gupnp_service_proxy_end_action(proxy, action, &error, "SearchCaps", G_TYPE_STRING, &result, NULL); if (!end || (result == NULL)) { DLEYNA_LOG_WARNING("GetSearchCapabilities operation failed: %s", ((error != NULL) ? error->message : "Invalid result")); goto on_error; } DLEYNA_LOG_DEBUG("GetSearchCapabilities result: %s", result); prv_get_capabilities_analyze(priv_t->property_map, result, &priv_t->dev->search_caps); if (g_hash_table_lookup(priv_t->property_map, "upnp:objectUpdateID")) priv_t->dev->has_last_change = TRUE; on_error: if (error) g_error_free(error); g_free(result); } static GUPnPServiceProxyAction *prv_get_search_capabilities( dleyna_service_task_t *task, GUPnPServiceProxy *proxy, gboolean *failed) { *failed = FALSE; return gupnp_service_proxy_begin_action( proxy, "GetSearchCapabilities", dleyna_service_task_begin_action_cb, task, NULL); } static GUPnPServiceProxyAction *prv_subscribe(dleyna_service_task_t *task, GUPnPServiceProxy *proxy, gboolean *failed) { dls_device_t *device; device = (dls_device_t *)dleyna_service_task_get_user_data(task); device->construct_step++; dls_device_subscribe_to_service_changes(device); *failed = FALSE; return NULL; } static gboolean prv_subtree_interface_filter(const gchar *object_path, const gchar *node, const gchar *interface) { gboolean root_object = FALSE; const gchar *slash; gboolean retval = TRUE; /* All objects in the hierarchy support the same interface. Strictly speaking this is not correct as it will allow ListChildren to be executed on a mediaitem object. However, returning the correct interface here would be too inefficient. We would need to either cache the type of all objects encountered so far or issue a UPnP request here to determine the objects type. Best to let the client call ListChildren on a item. This will lead to an error when we execute the UPnP command and we can return an error then. We do know however that the root objects are containers. Therefore we can remove the MediaItem2 interface from the root containers. We also know that only the root objects suport the MediaDevice interface. */ if (dls_path_get_non_root_id(object_path, &slash)) root_object = !slash; if (root_object && !strcmp(interface, DLS_INTERFACE_MEDIA_ITEM)) retval = FALSE; else if (!root_object && !strcmp(interface, DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE)) retval = FALSE; return retval; } static GUPnPServiceProxyAction *prv_declare(dleyna_service_task_t *task, GUPnPServiceProxy *proxy, gboolean *failed) { guint id; dls_device_t *device; prv_new_device_ct_t *priv_t; priv_t = (prv_new_device_ct_t *)dleyna_service_task_get_user_data(task); device = priv_t->dev; device->construct_step++; id = dls_server_get_connector()->publish_subtree(priv_t->connection, device->path, priv_t->vtable, DLS_INTERFACE_INFO_MAX, prv_subtree_interface_filter); if (id) { device->id = id; device->uploads = g_hash_table_new_full( g_int_hash, g_int_equal, g_free, prv_upload_delete); device->upload_jobs = g_hash_table_new_full( g_int_hash, g_int_equal, g_free, prv_upload_job_delete); } else { DLEYNA_LOG_WARNING("dleyna_connector_publish_subtree FAILED"); } *failed = (!id); return NULL; } void dls_device_construct( dls_device_t *dev, dls_device_context_t *context, dleyna_connector_id_t connection, const dleyna_connector_dispatch_cb_t *dispatch_table, GHashTable *property_map, const dleyna_task_queue_key_t *queue_id) { prv_new_device_ct_t *priv_t; GUPnPServiceProxy *s_proxy; DLEYNA_LOG_DEBUG("Current step: %d", dev->construct_step); priv_t = g_new0(prv_new_device_ct_t, 1); priv_t->dev = dev; priv_t->connection = connection; priv_t->vtable = dispatch_table; priv_t->property_map = property_map; s_proxy = context->cds.proxy; if (dev->construct_step < 1) dleyna_service_task_add(queue_id, prv_get_search_capabilities, s_proxy, prv_get_search_capabilities_cb, NULL, priv_t); if (dev->construct_step < 2) dleyna_service_task_add(queue_id, prv_get_sort_capabilities, s_proxy, prv_get_sort_capabilities_cb, NULL, priv_t); if (dev->construct_step < 3) dleyna_service_task_add(queue_id, prv_get_sort_ext_capabilities, s_proxy, prv_get_sort_ext_capabilities_cb, NULL, priv_t); if (dev->construct_step < 4) dleyna_service_task_add(queue_id, prv_get_feature_list, s_proxy, prv_get_feature_list_cb, NULL, priv_t); /* The following task should always be completed */ dleyna_service_task_add(queue_id, prv_subscribe, s_proxy, NULL, NULL, dev); if (dev->construct_step < 6) dleyna_service_task_add(queue_id, prv_declare, s_proxy, NULL, g_free, priv_t); dleyna_task_queue_start(queue_id); } dls_device_t *dls_device_new( dleyna_connector_id_t connection, GUPnPDeviceProxy *proxy, GUPnPDeviceInfo *device_info, const gchar *ip_address, const dleyna_connector_dispatch_cb_t *dispatch_table, GHashTable *property_map, const char *udn, const dleyna_task_queue_key_t *queue_id) { dls_device_t *dev; gchar *new_path; gchar *uuid; dls_device_context_t *context; DLEYNA_LOG_DEBUG("New Device on %s", ip_address); uuid = dleyna_core_prv_convert_udn_to_path(udn); new_path = g_strdup_printf("%s/%s", DLEYNA_SERVER_PATH, uuid); g_free(uuid); DLEYNA_LOG_DEBUG("Server Path %s", new_path); dev = g_new0(dls_device_t, 1); dev->connection = connection; dev->contexts = g_ptr_array_new_with_free_func((GDestroyNotify) dls_device_delete_context); dev->path = new_path; context = dls_device_append_new_context(dev, ip_address, proxy, device_info); dls_device_construct(dev, context, connection, dispatch_table, property_map, queue_id); return dev; } dls_device_context_t *dls_device_append_new_context(dls_device_t *device, const gchar *ip_address, GUPnPDeviceProxy *proxy, GUPnPDeviceInfo *device_info) { dls_device_context_t *context; prv_context_new(ip_address, proxy, device_info, device, &context); g_ptr_array_add(device->contexts, context); return context; } dls_device_t *dls_device_from_path(const gchar *path, GHashTable *device_list) { GHashTableIter iter; gpointer value; dls_device_t *device; dls_device_t *retval = NULL; g_hash_table_iter_init(&iter, device_list); while (g_hash_table_iter_next(&iter, NULL, &value)) { device = value; if (!strcmp(device->path, path)) { retval = device; break; } } return retval; } dls_device_context_t *dls_device_get_context(const dls_device_t *device, dls_client_t *client) { dls_device_context_t *context; unsigned int i; const char ip4_local_prefix[] = "127.0.0."; gboolean prefer_local; gboolean is_local; prefer_local = (client && client->prefer_local_addresses); for (i = 0; i < device->contexts->len; ++i) { context = g_ptr_array_index(device->contexts, i); is_local = (!strncmp(context->ip_address, ip4_local_prefix, sizeof(ip4_local_prefix) - 1) || !strcmp(context->ip_address, "::1") || !strcmp(context->ip_address, "0:0:0:0:0:0:0:1")); if (prefer_local == is_local) break; } if (i == device->contexts->len) context = g_ptr_array_index(device->contexts, 0); return context; } static void prv_found_child(GUPnPDIDLLiteParser *parser, GUPnPDIDLLiteObject *object, gpointer user_data) { dls_async_task_t *cb_data = user_data; dls_task_t *task = &cb_data->task; dls_task_get_children_t *task_data = &task->ut.get_children; dls_async_bas_t *cb_task_data = &cb_data->ut.bas; dls_device_object_builder_t *builder; gboolean have_child_count; DLEYNA_LOG_DEBUG("Enter"); builder = g_new0(dls_device_object_builder_t, 1); if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) { if (!task_data->containers) goto on_error; } else { if (!task_data->items) goto on_error; } builder->vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); if (!dls_props_add_object(builder->vb, object, task->target.root_path, task->target.path, cb_task_data->filter_mask)) goto on_error; if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) { dls_props_add_container(builder->vb, (GUPnPDIDLLiteContainer *)object, cb_task_data->filter_mask, cb_task_data->protocol_info, &have_child_count); if (!have_child_count && (cb_task_data->filter_mask & DLS_UPNP_MASK_PROP_CHILD_COUNT)) { builder->needs_child_count = TRUE; builder->id = g_strdup( gupnp_didl_lite_object_get_id(object)); cb_task_data->need_child_count = TRUE; } } else { dls_props_add_item(builder->vb, object, task->target.root_path, cb_task_data->filter_mask, cb_task_data->protocol_info); } g_ptr_array_add(cb_task_data->vbs, builder); DLEYNA_LOG_DEBUG("Exit with SUCCESS"); return; on_error: prv_object_builder_delete(builder); DLEYNA_LOG_DEBUG("Exit with FAIL"); } static GVariant *prv_children_result_to_variant(dls_async_task_t *cb_data) { guint i; dls_device_object_builder_t *builder; dls_async_bas_t *cb_task_data = &cb_data->ut.bas; GVariantBuilder vb; g_variant_builder_init(&vb, G_VARIANT_TYPE("aa{sv}")); for (i = 0; i < cb_task_data->vbs->len; ++i) { builder = g_ptr_array_index(cb_task_data->vbs, i); g_variant_builder_add(&vb, "@a{sv}", g_variant_builder_end(builder->vb)); } return g_variant_builder_end(&vb); } static void prv_get_search_ex_result(dls_async_task_t *cb_data) { GVariant *out_params[2]; dls_async_bas_t *cb_task_data = &cb_data->ut.bas; out_params[0] = prv_children_result_to_variant(cb_data); out_params[1] = g_variant_new_uint32(cb_task_data->max_count); cb_data->task.result = g_variant_ref_sink( g_variant_new_tuple(out_params, 2)); } static void prv_get_children_result(dls_async_task_t *cb_data) { GVariant *retval = prv_children_result_to_variant(cb_data); cb_data->task.result = g_variant_ref_sink(retval); } static gboolean prv_child_count_for_list_cb(dls_async_task_t *cb_data, gint count) { dls_async_bas_t *cb_task_data = &cb_data->ut.bas; dls_device_object_builder_t *builder; builder = g_ptr_array_index(cb_task_data->vbs, cb_task_data->retrieved); dls_props_add_child_count(builder->vb, count); cb_task_data->retrieved++; prv_retrieve_child_count_for_list(cb_data); return cb_task_data->retrieved >= cb_task_data->vbs->len; } static void prv_retrieve_child_count_for_list(dls_async_task_t *cb_data) { dls_async_bas_t *cb_task_data = &cb_data->ut.bas; dls_device_object_builder_t *builder = NULL; guint i; for (i = cb_task_data->retrieved; i < cb_task_data->vbs->len; ++i) { builder = g_ptr_array_index(cb_task_data->vbs, i); if (builder->needs_child_count) break; } cb_task_data->retrieved = i; if (i < cb_task_data->vbs->len && builder) prv_get_child_count(cb_data, prv_child_count_for_list_cb, builder->id); else cb_task_data->get_children_cb(cb_data); } static void prv_get_children_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { gchar *result = NULL; const gchar *message; gboolean end; GUPnPDIDLLiteParser *parser = NULL; GError *error = NULL; dls_async_task_t *cb_data = user_data; dls_async_bas_t *cb_task_data = &cb_data->ut.bas; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &error, "Result", G_TYPE_STRING, &result, NULL); if (!end || (result == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("Browse operation failed: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Browse operation failed: %s", message); goto on_error; } DLEYNA_LOG_DEBUG("GetChildren result: %s", result); parser = gupnp_didl_lite_parser_new(); g_signal_connect(parser, "object-available" , G_CALLBACK(prv_found_child), cb_data); cb_task_data->vbs = g_ptr_array_new_with_free_func( prv_object_builder_delete); if (!gupnp_didl_lite_parser_parse_didl(parser, result, &error) && error->code != GUPNP_XML_ERROR_EMPTY_NODE) { DLEYNA_LOG_WARNING("Unable to parse results of browse: %s", error->message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Unable to parse results of browse: %s", error->message); goto on_error; } if (cb_task_data->need_child_count) { DLEYNA_LOG_DEBUG("Need to retrieve ChildCounts"); cb_task_data->get_children_cb = prv_get_children_result; prv_retrieve_child_count_for_list(cb_data); goto no_complete; } else { prv_get_children_result(cb_data); } on_error: (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); no_complete: if (error) g_error_free(error); if (parser) g_object_unref(parser); g_free(result); DLEYNA_LOG_DEBUG("Exit"); } void dls_device_get_children(dls_client_t *client, dls_task_t *task, const gchar *upnp_filter, const gchar *sort_by) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_device_context_t *context; DLEYNA_LOG_DEBUG("Enter"); context = dls_device_get_context(task->target.device, client); cb_data->proxy = context->cds.proxy; g_object_add_weak_pointer((G_OBJECT(context->cds.proxy)), (gpointer *)&cb_data->proxy); cb_data->action = gupnp_service_proxy_begin_action(cb_data->proxy, "Browse", prv_get_children_cb, cb_data, "ObjectID", G_TYPE_STRING, task->target.id, "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren", "Filter", G_TYPE_STRING, upnp_filter, "StartingIndex", G_TYPE_INT, task->ut.get_children.start, "RequestedCount", G_TYPE_INT, task->ut.get_children.count, "SortCriteria", G_TYPE_STRING, sort_by, NULL); cb_data->cancel_id = g_cancellable_connect( cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); DLEYNA_LOG_DEBUG("Exit"); } static void prv_get_item(GUPnPDIDLLiteParser *parser, GUPnPDIDLLiteObject *object, gpointer user_data) { dls_async_task_t *cb_data = user_data; dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; if (!GUPNP_IS_DIDL_LITE_CONTAINER(object)) dls_props_add_item(cb_task_data->vb, object, cb_data->task.target.root_path, cb_task_data->filter_mask, cb_task_data->protocol_info); else cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_INTERFACE, "Interface not supported on container."); } static void prv_get_container(GUPnPDIDLLiteParser *parser, GUPnPDIDLLiteObject *object, gpointer user_data) { dls_async_task_t *cb_data = user_data; dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; gboolean have_child_count; if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) { dls_props_add_container(cb_task_data->vb, (GUPnPDIDLLiteContainer *)object, cb_task_data->filter_mask, cb_task_data->protocol_info, &have_child_count); if (!have_child_count) cb_task_data->need_child_count = TRUE; } else { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_INTERFACE, "Interface not supported on item."); } } static void prv_get_object(GUPnPDIDLLiteParser *parser, GUPnPDIDLLiteObject *object, gpointer user_data) { dls_async_task_t *cb_data = user_data; dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; const char *object_id; const char *parent_id; const char *parent_path; gchar *path = NULL; object_id = gupnp_didl_lite_object_get_id(object); if (!object_id) goto on_error; parent_id = gupnp_didl_lite_object_get_parent_id(object); if (!parent_id) goto on_error; if (!strcmp(object_id, "0") || !strcmp(parent_id, "-1")) { parent_path = cb_data->task.target.root_path; } else { path = dls_path_from_id(cb_data->task.target.root_path, parent_id); parent_path = path; } if (!dls_props_add_object(cb_task_data->vb, object, cb_data->task.target.root_path, parent_path, cb_task_data->filter_mask)) goto on_error; g_free(path); return; on_error: cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_RESULT, "Unable to retrieve mandatory object properties"); g_free(path); } static void prv_get_all(GUPnPDIDLLiteParser *parser, GUPnPDIDLLiteObject *object, gpointer user_data) { dls_async_task_t *cb_data = user_data; dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; gboolean have_child_count; prv_get_object(parser, object, user_data); if (!cb_data->error) { if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) { dls_props_add_container( cb_task_data->vb, (GUPnPDIDLLiteContainer *) object, cb_task_data->filter_mask, cb_task_data->protocol_info, &have_child_count); if (!have_child_count) cb_task_data->need_child_count = TRUE; } else { dls_props_add_item(cb_task_data->vb, object, cb_data->task.target.root_path, cb_task_data->filter_mask, cb_task_data->protocol_info); } } } static gboolean prv_cds_subscribed(const dls_device_t *device) { dls_device_context_t *context; unsigned int i; gboolean subscribed = FALSE; for (i = 0; i < device->contexts->len; ++i) { context = g_ptr_array_index(device->contexts, i); if (context->cds.subscribed) { subscribed = TRUE; break; } } return subscribed; } static void prv_system_update_id_for_prop_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { GError *error = NULL; const gchar *message; gboolean end; guint id = G_MAXUINT32; dls_async_task_t *cb_data = user_data; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(proxy, action, &error, "Id", G_TYPE_UINT, &id, NULL); if (!end || (id == G_MAXUINT32)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("Unable to retrieve SystemUpdateID: %s", message); cb_data->error = g_error_new( DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Unable to retrieve SystemUpdateID: %s", message); goto on_complete; } cb_data->task.result = g_variant_ref_sink(g_variant_new_uint32(id)); on_complete: (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); if (error) g_error_free(error); DLEYNA_LOG_DEBUG("Exit"); } static void prv_get_system_update_id_for_prop(GUPnPServiceProxy *proxy, const dls_device_t *device, dls_async_task_t *cb_data) { guint suid; DLEYNA_LOG_DEBUG("Enter"); if (prv_cds_subscribed(device)) { suid = device->system_update_id; cb_data->task.result = g_variant_ref_sink( g_variant_new_uint32(suid)); (void) g_idle_add(dls_async_task_complete, cb_data); goto on_complete; } cb_data->action = gupnp_service_proxy_begin_action( proxy, "GetSystemUpdateID", prv_system_update_id_for_prop_cb, cb_data, NULL); cb_data->proxy = proxy; g_object_add_weak_pointer((G_OBJECT(proxy)), (gpointer *)&cb_data->proxy); cb_data->cancel_id = g_cancellable_connect( cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); on_complete: DLEYNA_LOG_DEBUG("Exit"); } static void prv_system_update_id_for_props_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { GError *error = NULL; const gchar *message; gboolean end; guint id = G_MAXUINT32; dls_async_task_t *cb_data = user_data; dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(proxy, action, &error, "Id", G_TYPE_UINT, &id, NULL); if (!end || (id == G_MAXUINT32)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("Unable to retrieve SystemUpdateID: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Unable to retrieve SystemUpdateID: %s", message); goto on_complete; } g_variant_builder_add(cb_task_data->vb, "{sv}", DLS_SYSTEM_UPDATE_VAR, g_variant_new_uint32(id)); cb_data->task.result = g_variant_ref_sink(g_variant_builder_end( cb_task_data->vb)); on_complete: if (!cb_data->error) prv_get_sr_token_for_props(proxy, cb_data->task.target.device, cb_data); else { (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); } if (error) g_error_free(error); DLEYNA_LOG_DEBUG("Exit"); } static void prv_get_system_update_id_for_props(GUPnPServiceProxy *proxy, const dls_device_t *device, dls_async_task_t *cb_data) { dls_async_get_all_t *cb_task_data; guint suid; DLEYNA_LOG_DEBUG("Enter"); if (prv_cds_subscribed(device)) { suid = device->system_update_id; cb_task_data = &cb_data->ut.get_all; g_variant_builder_add(cb_task_data->vb, "{sv}", DLS_SYSTEM_UPDATE_VAR, g_variant_new_uint32(suid)); prv_get_sr_token_for_props(proxy, device, cb_data); goto on_complete; } cb_data->action = gupnp_service_proxy_begin_action( proxy, "GetSystemUpdateID", prv_system_update_id_for_props_cb, cb_data, NULL); if (cb_data->proxy != NULL) g_object_remove_weak_pointer((G_OBJECT(cb_data->proxy)), (gpointer *)&cb_data->proxy); cb_data->proxy = proxy; g_object_add_weak_pointer((G_OBJECT(proxy)), (gpointer *)&cb_data->proxy); if (!cb_data->cancel_id) cb_data->cancel_id = g_cancellable_connect( cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); on_complete: DLEYNA_LOG_DEBUG("Exit"); } static gboolean prv_ems_subscribed(const dls_device_t *device) { dls_device_context_t *context; unsigned int i; gboolean subscribed = FALSE; for (i = 0; i < device->contexts->len; ++i) { context = g_ptr_array_index(device->contexts, i); if (context->ems.subscribed) { subscribed = TRUE; break; } } return subscribed; } static void prv_sleeping_for_props_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { GError *error = NULL; const gchar *message; gchar *info = NULL; gboolean end; dls_async_task_t *cb_data = user_data; dls_async_get_all_t *cb_task_data; gboolean sleeping; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(proxy, action, &error, "NetworkInterfaceInfo", G_TYPE_STRING, &info, NULL); if (!end || (info == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("NetworkInterfaceInfo retrieval failed: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "GetInterfaceInfo failed: %s", message); goto on_complete; } if (prv_get_device_sleeping_state(cb_data->task.target.device, info, &sleeping)) { cb_task_data = &cb_data->ut.get_all; g_variant_builder_add(cb_task_data->vb, "{sv}", DLS_INTERFACE_PROP_SLEEPING, g_variant_new_boolean(sleeping)); cb_data->task.result = g_variant_ref_sink(g_variant_builder_end( cb_task_data->vb)); } g_free(info); on_complete: (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); if (error) g_error_free(error); DLEYNA_LOG_DEBUG("Exit"); } static void prv_get_sleeping_for_props(GUPnPServiceProxy *proxy, const dls_device_t *device, dls_async_task_t *cb_data) { dls_async_get_all_t *cb_task_data; gboolean sleeping; DLEYNA_LOG_DEBUG("Enter"); cb_task_data = &cb_data->ut.get_all; if (proxy == NULL) goto on_complete; if (prv_ems_subscribed(device)) { sleeping = device->sleeping; g_variant_builder_add(cb_task_data->vb, "{sv}", DLS_INTERFACE_PROP_SLEEPING, g_variant_new_boolean(sleeping)); goto on_complete; } cb_data->action = gupnp_service_proxy_begin_action( proxy, "GetInterfaceInfo", prv_sleeping_for_props_cb, cb_data, NULL); if (cb_data->proxy != NULL) g_object_remove_weak_pointer((G_OBJECT(cb_data->proxy)), (gpointer *)&cb_data->proxy); cb_data->proxy = proxy; g_object_add_weak_pointer((G_OBJECT(proxy)), (gpointer *)&cb_data->proxy); if (!cb_data->cancel_id) cb_data->cancel_id = g_cancellable_connect( cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); return; on_complete: cb_data->task.result = g_variant_ref_sink(g_variant_builder_end( cb_task_data->vb)); (void) g_idle_add(dls_async_task_complete, cb_data); DLEYNA_LOG_DEBUG("Exit"); } static int prv_get_media_server_version(const dls_device_t *device) { dls_device_context_t *context; const char *device_type; const char *version; context = dls_device_get_context(device, NULL); device_type = gupnp_device_info_get_device_type((GUPnPDeviceInfo *) context->device_proxy); if (!device_type || !g_str_has_prefix(device_type, DLS_DMS_DEVICE_TYPE)) goto on_error; version = device_type + sizeof(DLS_DMS_DEVICE_TYPE) - 1; return atoi(version); on_error: return -1; } static void prv_service_reset_for_prop_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { GError *error = NULL; const gchar *message; gchar *token = NULL; gboolean end; dls_async_task_t *cb_data = user_data; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(proxy, action, &error, "ResetToken", G_TYPE_STRING, &token, NULL); if (!end || (token == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("Unable to retrieve ServiceResetToken: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "GetServiceResetToken failed: %s", message); goto on_complete; } cb_data->task.result = g_variant_ref_sink(g_variant_new_string(token)); DLEYNA_LOG_DEBUG("Service Reset %s", token); g_free(token); on_complete: (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); if (error) g_error_free(error); DLEYNA_LOG_DEBUG("Exit"); } static void prv_get_sr_token_for_prop(GUPnPServiceProxy *proxy, const dls_device_t *device, dls_async_task_t *cb_data) { DLEYNA_LOG_DEBUG("Enter"); if (prv_get_media_server_version(device) < 3) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_PROPERTY, "Unknown property"); (void) g_idle_add(dls_async_task_complete, cb_data); goto on_error; } cb_data->action = gupnp_service_proxy_begin_action( proxy, "GetServiceResetToken", prv_service_reset_for_prop_cb, cb_data, NULL); cb_data->proxy = proxy; g_object_add_weak_pointer((G_OBJECT(proxy)), (gpointer *)&cb_data->proxy); cb_data->cancel_id = g_cancellable_connect( cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); on_error: DLEYNA_LOG_DEBUG("Exit"); } static void prv_service_reset_for_props_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { GError *error = NULL; const gchar *message; gchar *token = NULL; gboolean end; dls_async_task_t *cb_data = user_data; dls_async_get_all_t *cb_task_data; DLEYNA_LOG_DEBUG("Enter"); cb_task_data = &cb_data->ut.get_all; end = gupnp_service_proxy_end_action(proxy, action, &error, "ResetToken", G_TYPE_STRING, &token, NULL); if (!end || (token == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("Unable to retrieve ServiceResetToken: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "GetServiceResetToken failed: %s", message); goto on_complete; } g_variant_builder_add(cb_task_data->vb, "{sv}", DLS_INTERFACE_PROP_SV_SERVICE_RESET_TOKEN, g_variant_new_string(token)); DLEYNA_LOG_DEBUG("Service Reset %s", token); g_free(token); on_complete: if ((!cb_data->error) && (cb_task_data->proxy)) prv_get_sleeping_for_props(cb_task_data->proxy, cb_data->task.target.device, cb_data); else { cb_data->task.result = g_variant_ref_sink(g_variant_builder_end( cb_task_data->vb)); (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); } if (error) g_error_free(error); DLEYNA_LOG_DEBUG("Exit"); } static void prv_get_sr_token_for_props(GUPnPServiceProxy *proxy, const dls_device_t *device, dls_async_task_t *cb_data) { dls_async_get_all_t *cb_task_data; DLEYNA_LOG_DEBUG("Enter"); if (prv_get_media_server_version(device) < 3) { cb_task_data = &cb_data->ut.get_all; prv_get_sleeping_for_props(cb_task_data->proxy, device, cb_data); goto on_exit; /* No error here, just skip the property */ } cb_data->action = gupnp_service_proxy_begin_action( proxy, "GetServiceResetToken", prv_service_reset_for_props_cb, cb_data, NULL); if (cb_data->proxy != NULL) g_object_remove_weak_pointer((G_OBJECT(cb_data->proxy)), (gpointer *)&cb_data->proxy); cb_data->proxy = proxy; g_object_add_weak_pointer((G_OBJECT(proxy)), (gpointer *)&cb_data->proxy); if (!cb_data->cancel_id) cb_data->cancel_id = g_cancellable_connect( cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); on_exit: DLEYNA_LOG_DEBUG("Exit"); return; } static void prv_sleeping_for_prop_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { GError *error = NULL; const gchar *message; gchar *info = NULL; gboolean end; dls_async_task_t *cb_data = user_data; gboolean sleeping; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(proxy, action, &error, "NetworkInterfaceInfo", G_TYPE_STRING, &info, NULL); if (!end || (info == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("NetworkInterfaceInfo retrieval failed: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "GetNetworkInterfaceInfo failed: %s", message); goto on_complete; } if (prv_get_device_sleeping_state(cb_data->task.target.device, info, &sleeping)) { cb_data->task.result = g_variant_ref_sink( g_variant_new_boolean(sleeping)); } g_free(info); on_complete: (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); if (error) g_error_free(error); DLEYNA_LOG_DEBUG("Exit"); } static void prv_get_sleeping_for_prop(GUPnPServiceProxy *proxy, const dls_device_t *device, dls_async_task_t *cb_data) { gboolean sleeping; DLEYNA_LOG_DEBUG("Enter"); if (proxy == NULL) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_PROPERTY, "Unknown property"); (void) g_idle_add(dls_async_task_complete, cb_data); goto on_complete; } else if ((device->contexts->len == 0) || prv_ems_subscribed(device)) { sleeping = device->sleeping; cb_data->task.result = g_variant_ref_sink( g_variant_new_boolean(sleeping)); (void) g_idle_add(dls_async_task_complete, cb_data); goto on_complete; } cb_data->action = gupnp_service_proxy_begin_action( proxy, "GetInterfaceInfo", prv_sleeping_for_prop_cb, cb_data, NULL); cb_data->proxy = proxy; g_object_add_weak_pointer((G_OBJECT(proxy)), (gpointer *)&cb_data->proxy); cb_data->cancel_id = g_cancellable_connect( cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); on_complete: DLEYNA_LOG_DEBUG("Exit"); } static gboolean prv_get_all_child_count_cb(dls_async_task_t *cb_data, gint count) { dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; dls_props_add_child_count(cb_task_data->vb, count); if (cb_task_data->device_object) prv_get_system_update_id_for_props(cb_data->proxy, cb_data->task.target.device, cb_data); else cb_data->task.result = g_variant_ref_sink(g_variant_builder_end( cb_task_data->vb)); return !cb_task_data->device_object; } static void prv_get_all_ms2spec_props_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { GError *error = NULL; const gchar *message; gchar *result = NULL; gboolean end; GUPnPDIDLLiteParser *parser = NULL; dls_async_task_t *cb_data = user_data; dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &error, "Result", G_TYPE_STRING, &result, NULL); if (!end || (result == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("Browse operation failed: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Browse operation failed: %s", message); goto on_error; } DLEYNA_LOG_DEBUG("GetMS2SpecProps result: %s", result); parser = gupnp_didl_lite_parser_new(); g_signal_connect(parser, "object-available" , cb_task_data->prop_func, cb_data); if (!gupnp_didl_lite_parser_parse_didl(parser, result, &error)) { if (error->code == GUPNP_XML_ERROR_EMPTY_NODE) { DLEYNA_LOG_WARNING("Property not defined for object"); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_PROPERTY, "Property not defined for object"); } else { DLEYNA_LOG_WARNING( "Unable to parse results of browse: %s", error->message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Unable to parse results of browse: %s", error->message); } goto on_error; } if (cb_data->error) goto on_error; if (cb_task_data->need_child_count) { DLEYNA_LOG_DEBUG("Need Child Count"); prv_get_child_count(cb_data, prv_get_all_child_count_cb, cb_data->task.target.id); goto no_complete; } else if (cb_data->task.type == DLS_TASK_GET_ALL_PROPS && cb_task_data->device_object) { prv_get_system_update_id_for_props(proxy, cb_data->task.target.device, cb_data); goto no_complete; } else { cb_data->task.result = g_variant_ref_sink(g_variant_builder_end( cb_task_data->vb)); } on_error: (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); no_complete: if (error) g_error_free(error); if (parser) g_object_unref(parser); g_free(result); DLEYNA_LOG_DEBUG("Exit"); } static void prv_get_all_ms2spec_props(dls_device_context_t *context, dls_async_task_t *cb_data) { dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; dls_task_t *task = &cb_data->task; dls_task_get_props_t *task_data = &task->ut.get_props; DLEYNA_LOG_DEBUG("Enter called"); if (!strcmp(DLS_INTERFACE_MEDIA_CONTAINER, task_data->interface_name)) { cb_task_data->prop_func = G_CALLBACK(prv_get_container); } else if (!strcmp(DLS_INTERFACE_MEDIA_ITEM, task_data->interface_name)) { cb_task_data->prop_func = G_CALLBACK(prv_get_item); } else if (!strcmp(DLS_INTERFACE_MEDIA_OBJECT, task_data->interface_name)) { cb_task_data->prop_func = G_CALLBACK(prv_get_object); } else if (!strcmp("", task_data->interface_name)) { cb_task_data->prop_func = G_CALLBACK(prv_get_all); } else { DLEYNA_LOG_WARNING("Interface is unknown."); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_INTERFACE, "Interface is unknown."); goto on_error; } cb_data->action = gupnp_service_proxy_begin_action( context->cds.proxy, "Browse", prv_get_all_ms2spec_props_cb, cb_data, "ObjectID", G_TYPE_STRING, task->target.id, "BrowseFlag", G_TYPE_STRING, "BrowseMetadata", "Filter", G_TYPE_STRING, "*", "StartingIndex", G_TYPE_INT, 0, "RequestedCount", G_TYPE_INT, 0, "SortCriteria", G_TYPE_STRING, "", NULL); cb_data->proxy = context->cds.proxy; g_object_add_weak_pointer((G_OBJECT(context->cds.proxy)), (gpointer *)&cb_data->proxy); cb_data->cancel_id = g_cancellable_connect( cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); DLEYNA_LOG_DEBUG("Exit with SUCCESS"); return; on_error: (void) g_idle_add(dls_async_task_complete, cb_data); DLEYNA_LOG_DEBUG("Exit with FAIL"); return; } void dls_device_get_all_props(dls_client_t *client, dls_task_t *task, gboolean root_object) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_async_get_all_t *cb_task_data; dls_task_get_props_t *task_data = &task->ut.get_props; dls_device_context_t *context; DLEYNA_LOG_DEBUG("Enter"); context = dls_device_get_context(task->target.device, client); cb_task_data = &cb_data->ut.get_all; cb_task_data->vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); cb_task_data->device_object = root_object; cb_task_data->proxy = context->ems.proxy; if (!strcmp(task_data->interface_name, DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE)) { if (root_object) { dls_props_add_device((GUPnPDeviceInfo *) context->device_proxy, context->device_info, context->ems.proxy, task->target.device, cb_task_data->vb); prv_get_system_update_id_for_props( context->cds.proxy, task->target.device, cb_data); } else { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_INTERFACE, "Interface is only valid on root objects."); (void) g_idle_add(dls_async_task_complete, cb_data); } } else if (strcmp(task_data->interface_name, "")) { cb_task_data->device_object = FALSE; prv_get_all_ms2spec_props(context, cb_data); } else { if (root_object) dls_props_add_device((GUPnPDeviceInfo *) context->device_proxy, context->device_info, context->ems.proxy, task->target.device, cb_task_data->vb); prv_get_all_ms2spec_props(context, cb_data); } DLEYNA_LOG_DEBUG("Exit"); } static void prv_get_object_property(GUPnPDIDLLiteParser *parser, GUPnPDIDLLiteObject *object, gpointer user_data) { dls_async_task_t *cb_data = user_data; dls_task_t *task = &cb_data->task; dls_task_get_prop_t *task_data = &task->ut.get_prop; if (cb_data->task.result) goto on_error; cb_data->task.result = dls_props_get_object_prop(task_data->prop_name, task->target.root_path, object); on_error: return; } static void prv_get_item_property(GUPnPDIDLLiteParser *parser, GUPnPDIDLLiteObject *object, gpointer user_data) { dls_async_task_t *cb_data = user_data; dls_task_t *task = &cb_data->task; dls_task_get_prop_t *task_data = &task->ut.get_prop; dls_async_get_prop_t *cb_task_data = &cb_data->ut.get_prop; if (cb_data->task.result) goto on_error; cb_data->task.result = dls_props_get_item_prop( task_data->prop_name, task->target.root_path, object, cb_task_data->protocol_info); on_error: return; } static void prv_get_container_property(GUPnPDIDLLiteParser *parser, GUPnPDIDLLiteObject *object, gpointer user_data) { dls_async_task_t *cb_data = user_data; dls_task_t *task = &cb_data->task; dls_task_get_prop_t *task_data = &task->ut.get_prop; dls_async_get_prop_t *cb_task_data = &cb_data->ut.get_prop; if (cb_data->task.result) goto on_error; cb_data->task.result = dls_props_get_container_prop( task_data->prop_name, object, cb_task_data->protocol_info); on_error: return; } static void prv_get_all_property(GUPnPDIDLLiteParser *parser, GUPnPDIDLLiteObject *object, gpointer user_data) { dls_async_task_t *cb_data = user_data; prv_get_object_property(parser, object, user_data); if (cb_data->task.result) goto on_error; if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) prv_get_container_property(parser, object, user_data); else prv_get_item_property(parser, object, user_data); on_error: return; } static gboolean prv_get_child_count_cb(dls_async_task_t *cb_data, gint count) { DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Count %d", count); cb_data->task.result = g_variant_ref_sink( g_variant_new_uint32((guint) count)); DLEYNA_LOG_DEBUG("Exit"); return TRUE; } static void prv_count_children_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { dls_device_count_data_t *count_data = user_data; dls_async_task_t *cb_data = count_data->cb_data; GError *error = NULL; const gchar *message; guint count = G_MAXUINT32; gboolean complete = FALSE; gboolean end; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &error, "TotalMatches", G_TYPE_UINT, &count, NULL); if (!end || (count == G_MAXUINT32)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("Browse operation failed: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Browse operation failed: %s", message); goto on_error; } complete = count_data->cb(cb_data, count); on_error: g_free(user_data); if (cb_data->error || complete) { (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); } if (error) g_error_free(error); DLEYNA_LOG_DEBUG("Exit"); } static void prv_get_child_count(dls_async_task_t *cb_data, dls_device_count_cb_t cb, const gchar *id) { dls_device_count_data_t *count_data; DLEYNA_LOG_DEBUG("Enter"); prv_count_data_new(cb_data, cb, &count_data); cb_data->action = gupnp_service_proxy_begin_action(cb_data->proxy, "Browse", prv_count_children_cb, count_data, "ObjectID", G_TYPE_STRING, id, "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren", "Filter", G_TYPE_STRING, "", "StartingIndex", G_TYPE_INT, 0, "RequestedCount", G_TYPE_INT, 1, "SortCriteria", G_TYPE_STRING, "", NULL); DLEYNA_LOG_DEBUG("Exit with SUCCESS"); } static void prv_get_ms2spec_prop_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { GError *error = NULL; const gchar *message; gchar *result = NULL; gboolean end; GUPnPDIDLLiteParser *parser = NULL; dls_async_task_t *cb_data = user_data; dls_async_get_prop_t *cb_task_data = &cb_data->ut.get_prop; dls_task_get_prop_t *task_data = &cb_data->task.ut.get_prop; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &error, "Result", G_TYPE_STRING, &result, NULL); if (!end || (result == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("Browse operation failed: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Browse operation failed: %s", message); goto on_error; } DLEYNA_LOG_DEBUG("GetMS2SpecProp result: %s", result); parser = gupnp_didl_lite_parser_new(); g_signal_connect(parser, "object-available" , cb_task_data->prop_func, cb_data); if (!gupnp_didl_lite_parser_parse_didl(parser, result, &error)) { if (error->code == GUPNP_XML_ERROR_EMPTY_NODE) { DLEYNA_LOG_WARNING("Property not defined for object"); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_PROPERTY, "Property not defined for object"); } else { DLEYNA_LOG_WARNING( "Unable to parse results of browse: %s", error->message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Unable to parse results of browse: %s", error->message); } goto on_error; } if (!cb_data->task.result) { DLEYNA_LOG_WARNING("Property not defined for object"); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_PROPERTY, "Property not defined for object"); } on_error: if (cb_data->error && !strcmp(task_data->prop_name, DLS_INTERFACE_PROP_CHILD_COUNT)) { DLEYNA_LOG_DEBUG("ChildCount not supported by server"); g_error_free(cb_data->error); cb_data->error = NULL; prv_get_child_count(cb_data, prv_get_child_count_cb, cb_data->task.target.id); } else { (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); } if (error) g_error_free(error); if (parser) g_object_unref(parser); g_free(result); DLEYNA_LOG_DEBUG("Exit"); } static void prv_get_ms2spec_prop(dls_device_context_t *context, dls_prop_map_t *prop_map, dls_task_get_prop_t *task_data, dls_async_task_t *cb_data) { dls_async_get_prop_t *cb_task_data; const gchar *filter; DLEYNA_LOG_DEBUG("Enter"); cb_task_data = &cb_data->ut.get_prop; if (!prop_map) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_PROPERTY, "Unknown property"); goto on_error; } filter = prop_map->filter ? prop_map->upnp_prop_name : ""; if (!strcmp(DLS_INTERFACE_MEDIA_CONTAINER, task_data->interface_name)) { cb_task_data->prop_func = G_CALLBACK(prv_get_container_property); } else if (!strcmp(DLS_INTERFACE_MEDIA_ITEM, task_data->interface_name)) { cb_task_data->prop_func = G_CALLBACK(prv_get_item_property); } else if (!strcmp(DLS_INTERFACE_MEDIA_OBJECT, task_data->interface_name)) { cb_task_data->prop_func = G_CALLBACK(prv_get_object_property); } else if (!strcmp("", task_data->interface_name)) { cb_task_data->prop_func = G_CALLBACK(prv_get_all_property); } else { DLEYNA_LOG_WARNING("Interface is unknown.%s", task_data->interface_name); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_INTERFACE, "Interface is unknown."); goto on_error; } cb_data->proxy = context->cds.proxy; g_object_add_weak_pointer((G_OBJECT(context->cds.proxy)), (gpointer *)&cb_data->proxy); cb_data->action = gupnp_service_proxy_begin_action( cb_data->proxy, "Browse", prv_get_ms2spec_prop_cb, cb_data, "ObjectID", G_TYPE_STRING, cb_data->task.target.id, "BrowseFlag", G_TYPE_STRING, "BrowseMetadata", "Filter", G_TYPE_STRING, filter, "StartingIndex", G_TYPE_INT, 0, "RequestedCount", G_TYPE_INT, 0, "SortCriteria", G_TYPE_STRING, "", NULL); cb_data->cancel_id = g_cancellable_connect( cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); DLEYNA_LOG_DEBUG("Exit with SUCCESS"); return; on_error: (void) g_idle_add(dls_async_task_complete, cb_data); DLEYNA_LOG_DEBUG("Exit with FAIL"); return; } void dls_device_get_prop(dls_client_t *client, dls_task_t *task, dls_prop_map_t *prop_map, gboolean root_object) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_task_get_prop_t *task_data = &task->ut.get_prop; dls_device_context_t *context; gboolean complete = FALSE; DLEYNA_LOG_DEBUG("Enter"); if (task->target.device->contexts->len != 0) context = dls_device_get_context(task->target.device, client); else context = task->target.device->sleeping_context; if (!strcmp(task_data->interface_name, DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE)) { if (root_object) { if (!strcmp( task_data->prop_name, DLS_INTERFACE_PROP_ESV_SYSTEM_UPDATE_ID)) { prv_get_system_update_id_for_prop( context->cds.proxy, task->target.device, cb_data); } else if (!strcmp( task_data->prop_name, DLS_INTERFACE_PROP_SV_SERVICE_RESET_TOKEN)) { prv_get_sr_token_for_prop( context->cds.proxy, task->target.device, cb_data); } else if (!strcmp( task_data->prop_name, DLS_INTERFACE_PROP_SLEEPING)) { prv_get_sleeping_for_prop( context->ems.proxy, task->target.device, cb_data); } else { cb_data->task.result = dls_props_get_device_prop( (GUPnPDeviceInfo *) context->device_proxy, context->device_info, task->target.device, task_data->prop_name); if (!cb_data->task.result) cb_data->error = g_error_new( DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_PROPERTY, "Unknown property"); (void) g_idle_add(dls_async_task_complete, cb_data); } } else { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_INTERFACE, "Interface is unknown."); (void) g_idle_add(dls_async_task_complete, cb_data); } } else if (strcmp(task_data->interface_name, "")) { prv_get_ms2spec_prop(context, prop_map, &task->ut.get_prop, cb_data); } else { if (root_object) { if (!strcmp( task_data->prop_name, DLS_INTERFACE_PROP_ESV_SYSTEM_UPDATE_ID)) { prv_get_system_update_id_for_prop( context->cds.proxy, task->target.device, cb_data); complete = TRUE; } else if (!strcmp( task_data->prop_name, DLS_INTERFACE_PROP_SV_SERVICE_RESET_TOKEN)) { prv_get_sr_token_for_prop( context->cds.proxy, task->target.device, cb_data); complete = TRUE; } else if (!strcmp( task_data->prop_name, DLS_INTERFACE_PROP_SLEEPING)) { prv_get_sleeping_for_prop( context->ems.proxy, task->target.device, cb_data); complete = TRUE; } else { cb_data->task.result = dls_props_get_device_prop( (GUPnPDeviceInfo *) context->device_proxy, context->device_info, task->target.device, task_data->prop_name); if (cb_data->task.result) { (void) g_idle_add( dls_async_task_complete, cb_data); complete = TRUE; } } } if (!complete) prv_get_ms2spec_prop(context, prop_map, &task->ut.get_prop, cb_data); } DLEYNA_LOG_DEBUG("Exit"); } static void prv_found_target(GUPnPDIDLLiteParser *parser, GUPnPDIDLLiteObject *object, gpointer user_data) { dls_async_task_t *cb_data = user_data; dls_async_bas_t *cb_task_data = &cb_data->ut.bas; const char *object_id; const char *parent_id; const char *parent_path; gchar *path = NULL; gboolean have_child_count; dls_device_object_builder_t *builder; DLEYNA_LOG_DEBUG("Enter"); builder = g_new0(dls_device_object_builder_t, 1); object_id = gupnp_didl_lite_object_get_id(object); if (!object_id) goto on_error; parent_id = gupnp_didl_lite_object_get_parent_id(object); if (!parent_id) goto on_error; if (!strcmp(object_id, "0") || !strcmp(parent_id, "-1")) { parent_path = cb_data->task.target.root_path; } else { path = dls_path_from_id(cb_data->task.target.root_path, parent_id); parent_path = path; } builder->vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); if (!dls_props_add_object(builder->vb, object, cb_data->task.target.root_path, parent_path, cb_task_data->filter_mask)) goto on_error; if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) { dls_props_add_container(builder->vb, (GUPnPDIDLLiteContainer *)object, cb_task_data->filter_mask, cb_task_data->protocol_info, &have_child_count); if (!have_child_count && (cb_task_data->filter_mask & DLS_UPNP_MASK_PROP_CHILD_COUNT)) { builder->needs_child_count = TRUE; builder->id = g_strdup( gupnp_didl_lite_object_get_id(object)); cb_task_data->need_child_count = TRUE; } } else { dls_props_add_item(builder->vb, object, cb_data->task.target.root_path, cb_task_data->filter_mask, cb_task_data->protocol_info); } g_ptr_array_add(cb_task_data->vbs, builder); g_free(path); DLEYNA_LOG_DEBUG("Exit with SUCCESS"); return; on_error: g_free(path); prv_object_builder_delete(builder); DLEYNA_LOG_DEBUG("Exit with FAIL"); } static void prv_search_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { gchar *result = NULL; const gchar *message; gboolean end; guint count = G_MAXUINT32; GUPnPDIDLLiteParser *parser = NULL; GError *error = NULL; dls_async_task_t *cb_data = user_data; dls_async_bas_t *cb_task_data = &cb_data->ut.bas; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &error, "Result", G_TYPE_STRING, &result, "TotalMatches", G_TYPE_UINT, &count, NULL); if (!end || (result == NULL) || (count == G_MAXUINT32)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("Search operation failed %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Search operation failed: %s", message); goto on_error; } cb_task_data->max_count = count; parser = gupnp_didl_lite_parser_new(); cb_task_data->vbs = g_ptr_array_new_with_free_func( prv_object_builder_delete); g_signal_connect(parser, "object-available" , G_CALLBACK(prv_found_target), cb_data); DLEYNA_LOG_DEBUG("Server Search result: %s", result); if (!gupnp_didl_lite_parser_parse_didl(parser, result, &error) && error->code != GUPNP_XML_ERROR_EMPTY_NODE) { DLEYNA_LOG_WARNING("Unable to parse results of search: %s", error->message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Unable to parse results of search: %s", error->message); goto on_error; } if (cb_task_data->need_child_count) { DLEYNA_LOG_DEBUG("Need to retrieve child count"); if (cb_data->task.multiple_retvals) cb_task_data->get_children_cb = prv_get_search_ex_result; else cb_task_data->get_children_cb = prv_get_children_result; prv_retrieve_child_count_for_list(cb_data); goto no_complete; } else { if (cb_data->task.multiple_retvals) prv_get_search_ex_result(cb_data); else prv_get_children_result(cb_data); } on_error: (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); no_complete: if (parser) g_object_unref(parser); g_free(result); if (error) g_error_free(error); DLEYNA_LOG_DEBUG("Exit"); } void dls_device_search(dls_client_t *client, dls_task_t *task, const gchar *upnp_filter, const gchar *upnp_query, const gchar *sort_by) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_device_context_t *context; DLEYNA_LOG_DEBUG("Enter"); context = dls_device_get_context(task->target.device, client); cb_data->proxy = context->cds.proxy; g_object_add_weak_pointer((G_OBJECT(context->cds.proxy)), (gpointer *)&cb_data->proxy); cb_data->action = gupnp_service_proxy_begin_action( cb_data->proxy, "Search", prv_search_cb, cb_data, "ContainerID", G_TYPE_STRING, task->target.id, "SearchCriteria", G_TYPE_STRING, upnp_query, "Filter", G_TYPE_STRING, upnp_filter, "StartingIndex", G_TYPE_INT, task->ut.search.start, "RequestedCount", G_TYPE_INT, task->ut.search.count, "SortCriteria", G_TYPE_STRING, sort_by, NULL); cb_data->cancel_id = g_cancellable_connect( cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); DLEYNA_LOG_DEBUG("Exit"); } static void prv_get_resource(GUPnPDIDLLiteParser *parser, GUPnPDIDLLiteObject *object, gpointer user_data) { dls_async_task_t *cb_data = user_data; dls_task_t *task = &cb_data->task; dls_task_get_resource_t *task_data = &task->ut.resource; dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; DLEYNA_LOG_DEBUG("Enter"); dls_props_add_resource(cb_task_data->vb, object, cb_task_data->filter_mask, task_data->protocol_info); } static void prv_browse_objects_add_error_result(dls_async_browse_objects_t *bo, const gchar *path, GError *error) { GVariantBuilder evb; GVariant *gv_result; DLEYNA_LOG_WARNING("%s: %s", path, error->message); g_variant_builder_init(&evb, G_VARIANT_TYPE("a{sv}")); if (bo->get_all.filter_mask & DLS_UPNP_MASK_PROP_PATH) g_variant_builder_add( &evb, "{sv}", DLS_INTERFACE_PROP_PATH, g_variant_new_object_path(path)); gv_result = dls_props_get_error_prop(error); g_variant_builder_add(&evb, "{sv}", DLS_INTERFACE_PROP_ERROR, gv_result); gv_result = g_variant_builder_end(&evb); g_variant_builder_add(bo->avb, "@a{sv}", gv_result); } static void prv_get_child_count_end_action_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { GError *error = NULL; const gchar *message; guint count = G_MAXUINT32; gboolean end; dls_async_browse_objects_t *cb_task_data; dls_async_task_t *cb_data = user_data; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(proxy, action, &error, "TotalMatches", G_TYPE_UINT, &count, NULL); cb_task_data = &((dls_async_task_t *)user_data)->ut.browse_objects; if (!end || (count == G_MAXUINT32)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("Browse operation failed: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Browse operation failed: %s", message); goto on_complete; } g_variant_builder_add(cb_task_data->get_all.vb, "{sv}", DLS_INTERFACE_PROP_CHILD_COUNT, g_variant_new_uint32(count)); g_variant_builder_add(cb_task_data->avb, "@a{sv}", g_variant_builder_end(cb_task_data->get_all.vb)); DLEYNA_LOG_DEBUG("Child Count: %u", count); on_complete: if (cb_data->error != NULL) { message = cb_task_data->objects_id[cb_task_data->index - 1]; prv_browse_objects_add_error_result(cb_task_data, message, cb_data->error); g_error_free(cb_data->error); cb_data->error = NULL; } if (cb_task_data->get_all.vb != NULL) { g_variant_builder_unref(cb_task_data->get_all.vb); cb_task_data->get_all.vb = NULL; } if (cb_task_data->index < cb_task_data->object_count) dleyna_service_task_add(cb_task_data->queue_id, prv_browse_objects_begin_action_cb, proxy, prv_browse_objects_end_action_cb, NULL, user_data); if (error) g_error_free(error); DLEYNA_LOG_DEBUG("Exit"); } static GUPnPServiceProxyAction *prv_get_child_count_begin_action_cb( dleyna_service_task_t *task, GUPnPServiceProxy *proxy, gboolean *failed) { GUPnPServiceProxyAction *action; dls_task_t *user_data; const gchar *path; gchar *root_path = NULL; gchar *id = NULL; dls_async_browse_objects_t *cb_task_data; GError *error = NULL; DLEYNA_LOG_DEBUG("Enter"); user_data = (dls_task_t *)dleyna_service_task_get_user_data(task); cb_task_data = &((dls_async_task_t *)user_data)->ut.browse_objects; path = cb_task_data->objects_id[cb_task_data->index - 1]; /* Should never fail. Error already managed in previous browse */ (void) dls_path_get_path_and_id(path, &root_path, &id, &error); if (error != NULL) goto exit; action = gupnp_service_proxy_begin_action( proxy, "Browse", dleyna_service_task_begin_action_cb, task, "ObjectID", G_TYPE_STRING, id, "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren", "Filter", G_TYPE_STRING, "", "StartingIndex", G_TYPE_INT, 0, "RequestedCount", G_TYPE_INT, 1, "SortCriteria", G_TYPE_STRING, "", NULL); g_free(root_path); g_free(id); exit: *failed = FALSE; if (error != NULL) { DLEYNA_LOG_WARNING("%s: %s", path, error->message); action = NULL; prv_browse_objects_add_error_result(cb_task_data, path, error); if (cb_task_data->get_all.vb != NULL) { g_variant_builder_unref(cb_task_data->get_all.vb); cb_task_data->get_all.vb = NULL; } if (cb_task_data->index < cb_task_data->object_count) dleyna_service_task_add( cb_task_data->queue_id, prv_browse_objects_begin_action_cb, proxy, prv_browse_objects_end_action_cb, NULL, user_data); g_error_free(error); } return action; } static void prv_browse_objects_end_action_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { GError *error = NULL; dls_async_task_t *cb_data = user_data; dls_async_browse_objects_t *cb_task_data = &cb_data->ut.browse_objects; dls_async_get_all_t *cb_all_data = &cb_data->ut.browse_objects.get_all; GUPnPDIDLLiteParser *parser = NULL; gchar *result = NULL; const gchar *message; gboolean end; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(proxy, action, &error, "Result", G_TYPE_STRING, &result, NULL); if (!end || (result == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("Browse Object operation failed: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Browse operation failed: %s", message); goto on_exit; } DLEYNA_LOG_DEBUG_NL(); DLEYNA_LOG_DEBUG("Result: %s", result); DLEYNA_LOG_DEBUG_NL(); cb_all_data->vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); parser = gupnp_didl_lite_parser_new(); g_signal_connect(parser, "object-available", G_CALLBACK(prv_get_all), cb_data); if (!gupnp_didl_lite_parser_parse_didl(parser, result, &error)) { if (error->code == GUPNP_XML_ERROR_EMPTY_NODE) { DLEYNA_LOG_WARNING("Property not defined for object"); cb_data->error = g_error_new( DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_PROPERTY, "Property not defined for object"); } else { DLEYNA_LOG_WARNING( "Unable to parse results of browse: %s", error->message); cb_data->error = g_error_new( DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Unable to parse results of browse: %s", error->message); } goto on_exit; } if (cb_all_data->need_child_count && (cb_all_data->filter_mask & DLS_UPNP_MASK_PROP_CHILD_COUNT)) { DLEYNA_LOG_DEBUG("Need Child Count"); dleyna_service_task_add(cb_task_data->queue_id, prv_get_child_count_begin_action_cb, cb_data->proxy, prv_get_child_count_end_action_cb, NULL, cb_data); goto no_complete; } g_variant_builder_add(cb_task_data->avb, "@a{sv}", g_variant_builder_end(cb_all_data->vb)); on_exit: if (cb_data->error != NULL) { message = cb_task_data->objects_id[cb_task_data->index - 1]; prv_browse_objects_add_error_result(cb_task_data, message, cb_data->error); g_error_free(cb_data->error); cb_data->error = NULL; } if (cb_all_data->vb != NULL) { g_variant_builder_unref(cb_all_data->vb); cb_all_data->vb = NULL; } if (cb_task_data->index < cb_task_data->object_count) dleyna_service_task_add(cb_task_data->queue_id, prv_browse_objects_begin_action_cb, cb_data->proxy, prv_browse_objects_end_action_cb, NULL, cb_data); no_complete: if (parser) g_object_unref(parser); if (error) g_error_free(error); g_free(result); DLEYNA_LOG_DEBUG("Exit"); } static GUPnPServiceProxyAction *prv_browse_objects_begin_action_cb( dleyna_service_task_t *task, GUPnPServiceProxy *proxy, gboolean *failed) { GUPnPServiceProxyAction *action; dls_task_t *user_data; dls_async_browse_objects_t *cb_task_data; const gchar *path; gchar *root_path = NULL; gchar *id = NULL; GError *error = NULL; DLEYNA_LOG_DEBUG("Enter called"); user_data = (dls_task_t *)dleyna_service_task_get_user_data(task); cb_task_data = &((dls_async_task_t *)user_data)->ut.browse_objects; path = cb_task_data->objects_id[cb_task_data->index]; /* Reset dynamic field. */ cb_task_data->get_all.need_child_count = FALSE; /* Process anyway. We will add an entry with error */ (void) dls_path_get_path_and_id(path, &root_path, &id, &error); if (error != NULL) goto exit; DLEYNA_LOG_DEBUG("Browse Metadata for path [id]: %s [%s]", path, id); action = gupnp_service_proxy_begin_action( proxy, "Browse", dleyna_service_task_begin_action_cb, task, "ObjectID", G_TYPE_STRING, id, "BrowseFlag", G_TYPE_STRING, "BrowseMetadata", "Filter", G_TYPE_STRING, cb_task_data->upnp_filter, "StartingIndex", G_TYPE_INT, 0, "RequestedCount", G_TYPE_INT, 0, "SortCriteria", G_TYPE_STRING, "", NULL); g_free(root_path); g_free(id); exit: *failed = FALSE; /* It's the ONLY place where index is incremented */ cb_task_data->index++; if (error != NULL) { DLEYNA_LOG_WARNING("%s: %s", path, error->message); prv_browse_objects_add_error_result(cb_task_data, path, error); action = NULL; g_error_free(error); if (cb_task_data->index < cb_task_data->object_count) dleyna_service_task_add( cb_task_data->queue_id, prv_browse_objects_begin_action_cb, proxy, prv_browse_objects_end_action_cb, NULL, user_data); } return action; } static void prv_browse_objects_chain_cancelled(GCancellable *cancellable, gpointer user_data) { dls_async_task_t *cb_data = user_data; DLEYNA_LOG_DEBUG("Enter"); dleyna_task_processor_cancel_queue(cb_data->ut.browse_objects.queue_id); dls_async_task_cancelled_cb(cancellable, user_data); DLEYNA_LOG_DEBUG("Exit"); } static void prv_browse_objects_chain_end(gboolean cancelled, gpointer data) { dls_task_t *task = (dls_task_t *)data; dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_async_browse_objects_t *cb_task_data = &cb_data->ut.browse_objects; DLEYNA_LOG_DEBUG("Enter"); if (!cancelled) cb_data->task.result = g_variant_ref_sink( g_variant_builder_end( cb_task_data->avb)); else if (cb_data->error == NULL) cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_CANCELLED, "Operation cancelled."); (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); DLEYNA_LOG_DEBUG("Exit"); } void dls_device_browse_objects(dls_client_t *client, dls_task_t *task) { const dleyna_task_queue_key_t *queue_id; dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_async_browse_objects_t *cb_task_data; dls_device_context_t *context; const gchar **objs; gsize length; guint i; gboolean path_ok; const char *message; DLEYNA_LOG_DEBUG("Root Path %s", task->target.root_path) objs = g_variant_get_objv(task->ut.browse_objects.objects, &length); path_ok = (length > 0); for (i = 0; (i < length) && path_ok; i++) path_ok = g_str_has_prefix(objs[i], task->target.root_path); if (!path_ok) { g_free(objs); message = (length > 0) ? "At least one root path is invalid." : "Object path array is empty"; cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_PATH, "%s", message); goto on_error; } queue_id = dleyna_task_processor_add_queue( dls_server_get_task_processor(), dleyna_service_task_create_source(), DLS_SERVER_SINK, DLEYNA_TASK_QUEUE_FLAG_AUTO_REMOVE, dleyna_service_task_process_cb, dleyna_service_task_cancel_cb, dleyna_service_task_delete_cb); dleyna_task_queue_set_finally(queue_id, prv_browse_objects_chain_end); dleyna_task_queue_set_user_data(queue_id, task); context = dls_device_get_context(task->target.device, client); cb_data->proxy = context->cds.proxy; cb_task_data = &cb_data->ut.browse_objects; cb_task_data->queue_id = queue_id; cb_task_data->avb = g_variant_builder_new(G_VARIANT_TYPE("aa{sv}")); cb_task_data->objects_id = objs; cb_task_data->object_count = length; g_object_add_weak_pointer((G_OBJECT(context->cds.proxy)), (gpointer *)&cb_data->proxy); cb_data->cancel_id = g_cancellable_connect( cb_data->cancellable, G_CALLBACK(prv_browse_objects_chain_cancelled), cb_data, NULL); dleyna_service_task_add(queue_id, prv_browse_objects_begin_action_cb, cb_data->proxy, prv_browse_objects_end_action_cb, NULL, cb_data); dleyna_task_queue_start(queue_id); on_error: if (cb_data->error != NULL) (void) g_idle_add(dls_async_task_complete, cb_data); } void dls_device_get_resource(dls_client_t *client, dls_task_t *task, const gchar *upnp_filter) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_async_get_all_t *cb_task_data; dls_device_context_t *context; context = dls_device_get_context(task->target.device, client); cb_task_data = &cb_data->ut.get_all; cb_task_data->vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); cb_task_data->prop_func = G_CALLBACK(prv_get_resource); cb_task_data->device_object = FALSE; cb_task_data->proxy = context->ems.proxy; cb_data->proxy = context->cds.proxy; g_object_add_weak_pointer((G_OBJECT(context->cds.proxy)), (gpointer *)&cb_data->proxy); cb_data->action = gupnp_service_proxy_begin_action( cb_data->proxy, "Browse", prv_get_all_ms2spec_props_cb, cb_data, "ObjectID", G_TYPE_STRING, task->target.id, "BrowseFlag", G_TYPE_STRING, "BrowseMetadata", "Filter", G_TYPE_STRING, upnp_filter, "StartingIndex", G_TYPE_INT, 0, "RequestedCount", G_TYPE_INT, 0, "SortCriteria", G_TYPE_STRING, "", NULL); cb_data->cancel_id = g_cancellable_connect( cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); DLEYNA_LOG_DEBUG("Exit"); } static gchar *prv_create_new_container_didl(const gchar *parent_id, dls_task_t *task) { GUPnPDIDLLiteWriter *writer; GUPnPDIDLLiteObject *item; GUPnPDIDLLiteContainer *container; GVariantIter iter; GVariant *child_type; gchar *actual_type; GUPnPOCMFlags flags; gchar *retval = NULL; actual_type = dls_props_media_spec_ex_to_upnp_class( task->ut.create_container.type); if (!actual_type) goto on_error; writer = gupnp_didl_lite_writer_new(NULL); item = GUPNP_DIDL_LITE_OBJECT( gupnp_didl_lite_writer_add_container(writer)); container = GUPNP_DIDL_LITE_CONTAINER(item); gupnp_didl_lite_object_set_id(item, ""); gupnp_didl_lite_object_set_title( item, task->ut.create_container.display_name); gupnp_didl_lite_object_set_parent_id(item, parent_id); gupnp_didl_lite_object_set_upnp_class(item, actual_type); g_free(actual_type); gupnp_didl_lite_object_set_restricted(item, FALSE); flags = GUPNP_OCM_FLAGS_UPLOAD | GUPNP_OCM_FLAGS_CREATE_CONTAINER | GUPNP_OCM_FLAGS_DESTROYABLE | GUPNP_OCM_FLAGS_UPLOAD_DESTROYABLE | GUPNP_OCM_FLAGS_CHANGE_METADATA; gupnp_didl_lite_object_set_dlna_managed(item, flags); g_variant_iter_init(&iter, task->ut.create_container.child_types); while ((child_type = g_variant_iter_next_value(&iter))) { actual_type = dls_props_media_spec_ex_to_upnp_class( g_variant_get_string(child_type, NULL)); if (actual_type != NULL) { gupnp_didl_lite_container_add_create_class(container, actual_type); g_free(actual_type); } g_variant_unref(child_type); } retval = gupnp_didl_lite_writer_get_string(writer); g_object_unref(item); g_object_unref(writer); on_error: return retval; } static const gchar *prv_get_dlna_profile_name(const gchar *filename) { gchar *uri; GError *error = NULL; const gchar *profile_name = NULL; GUPnPDLNAProfile *profile; GUPnPDLNAProfileGuesser *guesser; gboolean relaxed_mode = TRUE; gboolean extended_mode = TRUE; guesser = gupnp_dlna_profile_guesser_new(relaxed_mode, extended_mode); uri = g_filename_to_uri(filename, NULL, &error); if (uri == NULL) { DLEYNA_LOG_WARNING("Unable to convert filename: %s", filename); if (error) { DLEYNA_LOG_WARNING("Error: %s", error->message); g_error_free(error); } goto on_error; } profile = gupnp_dlna_profile_guesser_guess_profile_sync(guesser, uri, 5000, NULL, &error); if (profile == NULL) { DLEYNA_LOG_WARNING("Unable to guess profile for URI: %s", uri); if (error) { DLEYNA_LOG_WARNING("Error: %s", error->message); g_error_free(error); } goto on_error; } profile_name = gupnp_dlna_profile_get_name(profile); on_error: g_object_unref(guesser); g_free(uri); return profile_name; } static gchar *prv_create_upload_didl(const gchar *parent_id, dls_task_t *task, const gchar *object_class, const gchar *mime_type) { GUPnPDIDLLiteWriter *writer; GUPnPDIDLLiteObject *item; gchar *retval; GUPnPProtocolInfo *protocol_info; GUPnPDIDLLiteResource *res; const gchar *profile; writer = gupnp_didl_lite_writer_new(NULL); item = GUPNP_DIDL_LITE_OBJECT(gupnp_didl_lite_writer_add_item(writer)); gupnp_didl_lite_object_set_id(item, ""); gupnp_didl_lite_object_set_title(item, task->ut.upload.display_name); gupnp_didl_lite_object_set_parent_id(item, parent_id); gupnp_didl_lite_object_set_upnp_class(item, object_class); gupnp_didl_lite_object_set_restricted(item, FALSE); protocol_info = gupnp_protocol_info_new(); gupnp_protocol_info_set_mime_type(protocol_info, mime_type); gupnp_protocol_info_set_protocol(protocol_info, "*"); gupnp_protocol_info_set_network(protocol_info, "*"); profile = prv_get_dlna_profile_name(task->ut.upload.file_path); if (profile != NULL) gupnp_protocol_info_set_dlna_profile(protocol_info, profile); res = gupnp_didl_lite_object_add_resource(item); gupnp_didl_lite_resource_set_protocol_info(res, protocol_info); retval = gupnp_didl_lite_writer_get_string(writer); g_object_unref(res); g_object_unref(protocol_info); g_object_unref(item); g_object_unref(writer); return retval; } static void prv_extract_import_uri(GUPnPDIDLLiteParser *parser, GUPnPDIDLLiteObject *object, gpointer user_data) { gchar **import_uri = user_data; GList *resources; GList *ptr; GUPnPDIDLLiteResource *res; const gchar *uri; if (!*import_uri) { resources = gupnp_didl_lite_object_get_resources(object); ptr = resources; while (ptr) { res = ptr->data; if (!*import_uri) { uri = gupnp_didl_lite_resource_get_import_uri( res); if (uri) *import_uri = g_strdup(uri); } g_object_unref(res); ptr = ptr->next; } g_list_free(resources); } } static void prv_upload_delete_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { dls_async_task_t *cb_data = user_data; DLEYNA_LOG_DEBUG("Enter"); (void) gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, NULL, NULL); (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); DLEYNA_LOG_DEBUG("Exit"); } static void prv_upload_job_delete(gpointer up_job) { dls_device_upload_job_t *upload_job = up_job; if (up_job) { if (upload_job->remove_idle) (void) g_source_remove(upload_job->remove_idle); g_free(upload_job); } } static gboolean prv_remove_update_job(gpointer user_data) { dls_device_upload_job_t *upload_job = user_data; dls_device_upload_t *upload; upload = g_hash_table_lookup(upload_job->device->uploads, &upload_job->upload_id); if (upload) { g_hash_table_remove(upload_job->device->uploads, &upload_job->upload_id); DLEYNA_LOG_DEBUG("Removing Upload Object: %d", upload_job->upload_id); } upload_job->remove_idle = 0; g_hash_table_remove(upload_job->device->upload_jobs, &upload_job->upload_id); return FALSE; } static void prv_generate_upload_update(dls_device_upload_job_t *upload_job, dls_device_upload_t *upload) { GVariant *args; args = g_variant_new("(ustt)", upload_job->upload_id, upload->status, upload->bytes_uploaded, upload->bytes_to_upload); DLEYNA_LOG_DEBUG( "Emitting: %s (%u %s %"G_GUINT64_FORMAT" %"G_GUINT64_FORMAT")" " on %s", DLS_INTERFACE_UPLOAD_UPDATE, upload_job->upload_id, upload->status, upload->bytes_uploaded, upload->bytes_to_upload, upload_job->device->path); (void) dls_server_get_connector()->notify( upload_job->device->connection, upload_job->device->path, DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE, DLS_INTERFACE_UPLOAD_UPDATE, args, NULL); } static void prv_post_finished(SoupSession *session, SoupMessage *msg, gpointer user_data) { dls_device_upload_job_t *upload_job = user_data; dls_device_upload_t *upload; gint *upload_id; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Upload %u finished. Code %u Message %s", upload_job->upload_id, msg->status_code, msg->reason_phrase); /* This is clumsy but we need to distinguish between two cases: 1. We cancel because the process is exitting. 2. We cancel because a client has called CancelUpload. We could use custom SOUP error messages to distinguish the cases but device->shutting_down seemed less hacky. We need this check as if we are shutting down it is dangerous to manipulate uploads as we are being called from its destructor. */ if (upload_job->device->shutting_down) { DLEYNA_LOG_DEBUG("Device shutting down. Cancelling Upload"); goto on_error; } upload = g_hash_table_lookup(upload_job->device->uploads, &upload_job->upload_id); if (upload) { upload_job->remove_idle = g_timeout_add(30000, prv_remove_update_job, user_data); if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) { upload->status = DLS_UPLOAD_STATUS_COMPLETED; upload->bytes_uploaded = upload->bytes_to_upload; } else if (msg->status_code == SOUP_STATUS_CANCELLED) { upload->status = DLS_UPLOAD_STATUS_CANCELLED; } else { upload->status = DLS_UPLOAD_STATUS_ERROR; } DLEYNA_LOG_DEBUG("Upload Status: %s", upload->status); prv_generate_upload_update(upload_job, upload); g_object_unref(upload->msg); upload->msg = NULL; g_object_unref(upload->soup_session); upload->soup_session = NULL; g_mapped_file_unref(upload->mapped_file); upload->mapped_file = NULL; g_free(upload->body); upload->body = NULL; upload_id = g_new(gint, 1); *upload_id = upload_job->upload_id; g_hash_table_insert(upload_job->device->upload_jobs, upload_id, upload_job); upload_job = NULL; } on_error: prv_upload_job_delete(upload_job); DLEYNA_LOG_DEBUG("Exit"); } static void prv_upload_delete(gpointer up) { dls_device_upload_t *upload = up; DLEYNA_LOG_DEBUG("Enter"); if (upload) { if (upload->msg) { soup_session_cancel_message(upload->soup_session, upload->msg, SOUP_STATUS_CANCELLED); g_object_unref(upload->msg); } if (upload->soup_session) g_object_unref(upload->soup_session); if (upload->mapped_file) g_mapped_file_unref(upload->mapped_file); else if (upload->body) g_free(upload->body); g_free(upload); } DLEYNA_LOG_DEBUG("Exit"); } static void prv_post_bytes_written(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) { dls_device_upload_t *upload = user_data; upload->bytes_uploaded += chunk->length; if (upload->bytes_uploaded > upload->bytes_to_upload) upload->bytes_uploaded = upload->bytes_to_upload; } static dls_device_upload_t *prv_upload_data_new(const gchar *file_path, gchar *body, gsize body_length, const gchar *import_uri, const gchar *mime_type, GError **error) { dls_device_upload_t *upload = NULL; GMappedFile *mapped_file = NULL; gchar *up_body = body; gsize up_body_length = body_length; DLEYNA_LOG_DEBUG("Enter"); if (file_path) { mapped_file = g_mapped_file_new(file_path, FALSE, NULL); if (!mapped_file) { DLEYNA_LOG_WARNING("Unable to map %s into memory", file_path); *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_IO, "Unable to map %s into memory", file_path); goto on_error; } up_body = g_mapped_file_get_contents(mapped_file); up_body_length = g_mapped_file_get_length(mapped_file); } upload = g_new0(dls_device_upload_t, 1); upload->soup_session = soup_session_async_new(); upload->msg = soup_message_new("POST", import_uri); upload->mapped_file = mapped_file; upload->body = body; upload->body_length = body_length; if (!upload->msg) { DLEYNA_LOG_WARNING("Invalid URI %s", import_uri); *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_RESULT, "Invalid URI %s", import_uri); goto on_error; } upload->status = DLS_UPLOAD_STATUS_IN_PROGRESS; upload->bytes_to_upload = up_body_length; soup_message_headers_set_expectations(upload->msg->request_headers, SOUP_EXPECTATION_CONTINUE); soup_message_set_request(upload->msg, mime_type, SOUP_MEMORY_STATIC, up_body, up_body_length); g_signal_connect(upload->msg, "wrote-body-data", G_CALLBACK(prv_post_bytes_written), upload); DLEYNA_LOG_DEBUG("Exit with Success"); return upload; on_error: prv_upload_delete(upload); DLEYNA_LOG_WARNING("Exit with Fail"); return NULL; } static void prv_create_container_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { dls_async_task_t *cb_data = user_data; const gchar *message; GError *error = NULL; gchar *result = NULL; gchar *object_id = NULL; gchar *object_path; gboolean end; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &error, "ObjectID", G_TYPE_STRING, &object_id, "Result", G_TYPE_STRING, &result, NULL); if (!end || (object_id == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("Create Object operation failed: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Create Object operation failed: %s", message); goto on_error; } object_path = dls_path_from_id(cb_data->task.target.root_path, object_id); cb_data->task.result = g_variant_ref_sink(g_variant_new_object_path( object_path)); g_free(object_path); on_error: (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); g_free(object_id); g_free(result); if (error) g_error_free(error); DLEYNA_LOG_DEBUG("Exit"); } static void prv_generic_upload_cb(dls_async_task_t *cb_data, char *file_path, gchar *body, gsize body_length, const gchar *mime_type) { gchar *object_id = NULL; gchar *result = NULL; gchar *import_uri = NULL; gchar *object_path; const gchar *message; GError *error = NULL; gboolean delete_needed = FALSE; gboolean end; gint *upload_id; GUPnPDIDLLiteParser *parser = NULL; GVariant *out_p[2]; dls_device_upload_t *upload; dls_device_upload_job_t *upload_job; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &error, "ObjectID", G_TYPE_STRING, &object_id, "Result", G_TYPE_STRING, &result, NULL); if (!end || (object_id == NULL) || (result == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("Create Object operation failed: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Create Object operation failed: %s", message); goto on_error; } DLEYNA_LOG_DEBUG_NL(); DLEYNA_LOG_DEBUG("Create Object Result: %s", result); DLEYNA_LOG_DEBUG_NL(); delete_needed = TRUE; parser = gupnp_didl_lite_parser_new(); g_signal_connect(parser, "object-available" , G_CALLBACK(prv_extract_import_uri), &import_uri); if (!gupnp_didl_lite_parser_parse_didl(parser, result, &error) && error->code != GUPNP_XML_ERROR_EMPTY_NODE) { DLEYNA_LOG_WARNING( "Unable to parse results of CreateObject: %s", error->message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Unable to parse results of CreateObject: %s", error->message); goto on_error; } if (!import_uri) { DLEYNA_LOG_WARNING("Missing Import URI"); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Missing Import URI"); goto on_error; } DLEYNA_LOG_DEBUG("Import URI %s", import_uri); upload = prv_upload_data_new(file_path, body, body_length, import_uri, mime_type, &cb_data->error); if (!upload) goto on_error; upload_job = g_new0(dls_device_upload_job_t, 1); upload_job->device = cb_data->task.target.device; upload_job->upload_id = (gint) cb_data->task.target.device->upload_id; soup_session_queue_message(upload->soup_session, upload->msg, prv_post_finished, upload_job); g_object_ref(upload->msg); upload_id = g_new(gint, 1); *upload_id = upload_job->upload_id; g_hash_table_insert(cb_data->task.target.device->uploads, upload_id, upload); object_path = dls_path_from_id(cb_data->task.target.root_path, object_id); DLEYNA_LOG_DEBUG("Upload ID %u", *upload_id); DLEYNA_LOG_DEBUG("Object ID %s", object_id); DLEYNA_LOG_DEBUG("Object Path %s", object_path); out_p[0] = g_variant_new_uint32(*upload_id); out_p[1] = g_variant_new_object_path(object_path); cb_data->task.result = g_variant_ref_sink(g_variant_new_tuple(out_p, 2)); ++cb_data->task.target.device->upload_id; if (cb_data->task.target.device->upload_id > G_MAXINT) cb_data->task.target.device->upload_id = 0; g_free(object_path); on_error: if (cb_data->error && delete_needed) { DLEYNA_LOG_WARNING( "Upload failed deleting created object with id %s", object_id); cb_data->action = gupnp_service_proxy_begin_action( cb_data->proxy, "DestroyObject", prv_upload_delete_cb, cb_data, "ObjectID", G_TYPE_STRING, object_id, NULL); } else { (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); } g_free(object_id); g_free(import_uri); if (parser) g_object_unref(parser); g_free(result); if (error) g_error_free(error); DLEYNA_LOG_DEBUG("Exit"); } static void prv_create_object_upload_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { dls_async_task_t *cb_data = user_data; prv_generic_upload_cb(cb_data, cb_data->task.ut.upload.file_path, NULL, 0, cb_data->ut.upload.mime_type); } void dls_device_upload(dls_client_t *client, dls_task_t *task, const gchar *parent_id) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_device_context_t *context; gchar *didl; dls_async_upload_t *cb_task_data; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Uploading file to %s", parent_id); context = dls_device_get_context(task->target.device, client); cb_task_data = &cb_data->ut.upload; didl = prv_create_upload_didl(parent_id, task, cb_task_data->object_class, cb_task_data->mime_type); DLEYNA_LOG_DEBUG_NL(); DLEYNA_LOG_DEBUG("DIDL: %s", didl); DLEYNA_LOG_DEBUG_NL(); cb_data->proxy = context->cds.proxy; g_object_add_weak_pointer((G_OBJECT(context->cds.proxy)), (gpointer *)&cb_data->proxy); cb_data->action = gupnp_service_proxy_begin_action( cb_data->proxy, "CreateObject", prv_create_object_upload_cb, cb_data, "ContainerID", G_TYPE_STRING, parent_id, "Elements", G_TYPE_STRING, didl, NULL); cb_data->cancel_id = g_cancellable_connect( cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); g_free(didl); DLEYNA_LOG_DEBUG("Exit"); } gboolean dls_device_get_upload_status(dls_task_t *task, GError **error) { dls_device_upload_t *upload; gboolean retval = FALSE; GVariant *out_params[3]; guint upload_id; DLEYNA_LOG_DEBUG("Enter"); upload_id = task->ut.upload_action.upload_id; upload = g_hash_table_lookup(task->target.device->uploads, &upload_id); if (!upload) { *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Unknown Upload ID %u ", upload_id); goto on_error; } out_params[0] = g_variant_new_string(upload->status); out_params[1] = g_variant_new_uint64(upload->bytes_uploaded); out_params[2] = g_variant_new_uint64(upload->bytes_to_upload); DLEYNA_LOG_DEBUG( "Upload ( %s %"G_GUINT64_FORMAT" %"G_GUINT64_FORMAT" )", upload->status, upload->bytes_uploaded, upload->bytes_to_upload); task->result = g_variant_ref_sink(g_variant_new_tuple(out_params, 3)); retval = TRUE; on_error: DLEYNA_LOG_DEBUG("Exit"); return retval; } void dls_device_get_upload_ids(dls_task_t *task) { GVariantBuilder vb; GHashTableIter iter; gpointer key; DLEYNA_LOG_DEBUG("Enter"); g_variant_builder_init(&vb, G_VARIANT_TYPE("au")); g_hash_table_iter_init(&iter, task->target.device->uploads); while (g_hash_table_iter_next(&iter, &key, NULL)) g_variant_builder_add(&vb, "u", (guint32) (*((gint *)key))); task->result = g_variant_ref_sink(g_variant_builder_end(&vb)); DLEYNA_LOG_DEBUG("Exit"); } gboolean dls_device_cancel_upload(dls_task_t *task, GError **error) { dls_device_upload_t *upload; gboolean retval = FALSE; guint upload_id; DLEYNA_LOG_DEBUG("Enter"); upload_id = task->ut.upload_action.upload_id; upload = g_hash_table_lookup(task->target.device->uploads, &upload_id); if (!upload) { *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Unknown Upload ID %u ", upload_id); goto on_error; } if (upload->msg) { soup_session_cancel_message(upload->soup_session, upload->msg, SOUP_STATUS_CANCELLED); DLEYNA_LOG_DEBUG("Cancelling Upload %u ", upload_id); } retval = TRUE; on_error: DLEYNA_LOG_DEBUG("Exit"); return retval; } static void prv_destroy_object_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { GError *upnp_error = NULL; dls_async_task_t *cb_data = user_data; DLEYNA_LOG_DEBUG("Enter"); if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &upnp_error, NULL)) { DLEYNA_LOG_WARNING("Destroy Object operation failed: %s", upnp_error->message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Destroy Object operation failed: %s", upnp_error->message); } (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); if (upnp_error) g_error_free(upnp_error); DLEYNA_LOG_DEBUG("Exit"); } void dls_device_delete_object(dls_client_t *client, dls_task_t *task) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_device_context_t *context; DLEYNA_LOG_DEBUG("Enter"); context = dls_device_get_context(task->target.device, client); cb_data->proxy = context->cds.proxy; g_object_add_weak_pointer((G_OBJECT(context->cds.proxy)), (gpointer *)&cb_data->proxy); cb_data->action = gupnp_service_proxy_begin_action( cb_data->proxy, "DestroyObject", prv_destroy_object_cb, cb_data, "ObjectID", G_TYPE_STRING, task->target.id, NULL); cb_data->cancel_id = g_cancellable_connect(cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); DLEYNA_LOG_DEBUG("Exit"); } void dls_device_create_container(dls_client_t *client, dls_task_t *task, const gchar *parent_id) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_device_context_t *context; gchar *didl; DLEYNA_LOG_DEBUG("Enter"); context = dls_device_get_context(task->target.device, client); didl = prv_create_new_container_didl(parent_id, task); if (!didl) { DLEYNA_LOG_WARNING("Unable to create didl"); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Unable to create didl"); goto on_error; } DLEYNA_LOG_DEBUG("DIDL: %s", didl); cb_data->proxy = context->cds.proxy; g_object_add_weak_pointer((G_OBJECT(context->cds.proxy)), (gpointer *)&cb_data->proxy); cb_data->action = gupnp_service_proxy_begin_action( cb_data->proxy, "CreateObject", prv_create_container_cb, cb_data, "ContainerID", G_TYPE_STRING, parent_id, "Elements", G_TYPE_STRING, didl, NULL); cb_data->cancel_id = g_cancellable_connect( cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); g_free(didl); on_error: DLEYNA_LOG_DEBUG("Exit"); return; } static void prv_update_object_update_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { GError *upnp_error = NULL; dls_async_task_t *cb_data = user_data; DLEYNA_LOG_DEBUG("Enter"); if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &upnp_error, NULL)) { DLEYNA_LOG_WARNING("Update Object operation failed: %s", upnp_error->message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Update Object operation " " failed: %s", upnp_error->message); } (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); if (upnp_error) g_error_free(upnp_error); DLEYNA_LOG_DEBUG("Exit"); } static gchar *prv_get_current_xml_fragment(GUPnPDIDLLiteObject *object, dls_upnp_prop_mask mask) { gchar *retval = NULL; if (mask & DLS_UPNP_MASK_PROP_DISPLAY_NAME) retval = gupnp_didl_lite_object_get_title_xml_string(object); else if (mask & DLS_UPNP_MASK_PROP_ALBUM) retval = gupnp_didl_lite_object_get_album_xml_string(object); else if (mask & DLS_UPNP_MASK_PROP_DATE) retval = gupnp_didl_lite_object_get_date_xml_string(object); else if (mask & DLS_UPNP_MASK_PROP_TYPE_EX) retval = gupnp_didl_lite_object_get_upnp_class_xml_string( object); else if (mask & DLS_UPNP_MASK_PROP_TRACK_NUMBER) retval = gupnp_didl_lite_object_get_track_number_xml_string( object); else if (mask & DLS_UPNP_MASK_PROP_ARTISTS) retval = gupnp_didl_lite_object_get_artists_xml_string(object); return retval; } static gchar *prv_get_new_xml_fragment(GUPnPDIDLLiteObject *object, dls_upnp_prop_mask mask, GVariant *value) { GUPnPDIDLLiteContributor *artist; const gchar *artist_name; gchar *upnp_class; GVariantIter viter; gchar *retval = NULL; if (mask & DLS_UPNP_MASK_PROP_DISPLAY_NAME) { gupnp_didl_lite_object_set_title( object, g_variant_get_string(value, NULL)); retval = gupnp_didl_lite_object_get_title_xml_string(object); } else if (mask & DLS_UPNP_MASK_PROP_ALBUM) { gupnp_didl_lite_object_set_album( object, g_variant_get_string(value, NULL)); retval = gupnp_didl_lite_object_get_album_xml_string(object); } else if (mask & DLS_UPNP_MASK_PROP_DATE) { gupnp_didl_lite_object_set_date( object, g_variant_get_string(value, NULL)); retval = gupnp_didl_lite_object_get_date_xml_string(object); } else if (mask & DLS_UPNP_MASK_PROP_TYPE_EX) { upnp_class = dls_props_media_spec_ex_to_upnp_class( g_variant_get_string(value, NULL)); if (!upnp_class) goto on_error; gupnp_didl_lite_object_set_upnp_class(object, upnp_class); g_free(upnp_class); retval = gupnp_didl_lite_object_get_upnp_class_xml_string( object); } else if (mask & DLS_UPNP_MASK_PROP_TRACK_NUMBER) { gupnp_didl_lite_object_set_track_number( object, g_variant_get_int32(value)); retval = gupnp_didl_lite_object_get_track_number_xml_string( object); } else if (mask & DLS_UPNP_MASK_PROP_ARTISTS) { gupnp_didl_lite_object_unset_artists(object); (void) g_variant_iter_init(&viter, value); while (g_variant_iter_next(&viter, "&s", &artist_name)) { artist = gupnp_didl_lite_object_add_artist(object); gupnp_didl_lite_contributor_set_name(artist, artist_name); } retval = gupnp_didl_lite_object_get_artists_xml_string(object); } on_error: return retval; } static void prv_get_xml_fragments(GUPnPDIDLLiteParser *parser, GUPnPDIDLLiteObject *object, gpointer user_data) { GString *current_str; GString *new_str; gchar *frag1; gchar *frag2; GVariantIter viter; const gchar *prop; GVariant *value; dls_prop_map_t *prop_map; GUPnPDIDLLiteWriter *writer; GUPnPDIDLLiteObject *scratch_object; gboolean first = TRUE; dls_async_task_t *cb_data = user_data; dls_async_update_t *cb_task_data = &cb_data->ut.update; dls_task_t *task = &cb_data->task; dls_task_update_t *task_data = &task->ut.update; DLEYNA_LOG_DEBUG("Enter"); current_str = g_string_new(""); new_str = g_string_new(""); writer = gupnp_didl_lite_writer_new(NULL); if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) scratch_object = GUPNP_DIDL_LITE_OBJECT( gupnp_didl_lite_writer_add_container(writer)); else scratch_object = GUPNP_DIDL_LITE_OBJECT( gupnp_didl_lite_writer_add_item(writer)); (void) g_variant_iter_init(&viter, task_data->to_add_update); while (g_variant_iter_next(&viter, "{&sv}", &prop, &value)) { DLEYNA_LOG_DEBUG("to_add_update = %s", prop); prop_map = g_hash_table_lookup(cb_task_data->map, prop); frag1 = prv_get_current_xml_fragment(object, prop_map->type); frag2 = prv_get_new_xml_fragment(scratch_object, prop_map->type, value); if (!frag2) { DLEYNA_LOG_DEBUG("Unable to set %s. Skipping", prop); g_free(frag1); continue; } if (!first) { g_string_append(current_str, ","); g_string_append(new_str, ","); } else { first = FALSE; } if (frag1) { g_string_append(current_str, (const gchar *)frag1); g_free(frag1); } g_string_append(new_str, (const gchar *)frag2); g_free(frag2); } (void) g_variant_iter_init(&viter, task_data->to_delete); while (g_variant_iter_next(&viter, "&s", &prop)) { DLEYNA_LOG_DEBUG("to_delete = %s", prop); prop_map = g_hash_table_lookup(cb_task_data->map, prop); frag1 = prv_get_current_xml_fragment(object, prop_map->type); if (!frag1) continue; if (!first) g_string_append(current_str, ","); else first = FALSE; g_string_append(current_str, (const gchar *)frag1); g_free(frag1); } cb_task_data->current_tag_value = g_string_free(current_str, FALSE); DLEYNA_LOG_DEBUG("current_tag_value = %s", cb_task_data->current_tag_value); cb_task_data->new_tag_value = g_string_free(new_str, FALSE); DLEYNA_LOG_DEBUG("new_tag_value = %s", cb_task_data->new_tag_value); g_object_unref(scratch_object); g_object_unref(writer); DLEYNA_LOG_DEBUG("Exit"); } static void prv_update_object_browse_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { GError *error = NULL; dls_async_task_t *cb_data = user_data; dls_async_update_t *cb_task_data = &cb_data->ut.update; GUPnPDIDLLiteParser *parser = NULL; gchar *result = NULL; const gchar *message; gboolean end; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &error, "Result", G_TYPE_STRING, &result, NULL); if (!end || (result == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("Browse Object operation failed: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Browse operation failed: %s", message); goto on_error; } DLEYNA_LOG_DEBUG("dls_device_update_ex_object result: %s", result); parser = gupnp_didl_lite_parser_new(); g_signal_connect(parser, "object-available", G_CALLBACK(prv_get_xml_fragments), cb_data); if (!gupnp_didl_lite_parser_parse_didl(parser, result, &error)) { if (error->code == GUPNP_XML_ERROR_EMPTY_NODE) { DLEYNA_LOG_WARNING("Property not defined for object"); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_PROPERTY, "Property not defined for object"); } else { DLEYNA_LOG_WARNING( "Unable to parse results of browse: %s", error->message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Unable to parse results of browse: %s", error->message); } goto on_error; } cb_data->action = gupnp_service_proxy_begin_action( cb_data->proxy, "UpdateObject", prv_update_object_update_cb, cb_data, "ObjectID", G_TYPE_STRING, cb_data->task.target.id, "CurrentTagValue", G_TYPE_STRING, cb_task_data->current_tag_value, "NewTagValue", G_TYPE_STRING, cb_task_data->new_tag_value, NULL); goto no_complete; on_error: (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); no_complete: if (parser) g_object_unref(parser); g_free(result); if (error) g_error_free(error); DLEYNA_LOG_DEBUG("Exit"); } void dls_device_update_object(dls_client_t *client, dls_task_t *task, const gchar *upnp_filter) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_device_context_t *context; DLEYNA_LOG_DEBUG("Enter"); context = dls_device_get_context(task->target.device, client); cb_data->proxy = context->cds.proxy; g_object_add_weak_pointer((G_OBJECT(context->cds.proxy)), (gpointer *)&cb_data->proxy); cb_data->action = gupnp_service_proxy_begin_action( cb_data->proxy, "Browse", prv_update_object_browse_cb, cb_data, "ObjectID", G_TYPE_STRING, task->target.id, "BrowseFlag", G_TYPE_STRING, "BrowseMetadata", "Filter", G_TYPE_STRING, upnp_filter, "StartingIndex", G_TYPE_INT, 0, "RequestedCount", G_TYPE_INT, 0, "SortCriteria", G_TYPE_STRING, "", NULL); cb_data->cancel_id = g_cancellable_connect(cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); DLEYNA_LOG_DEBUG("Exit"); } static void prv_get_object_metadata_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { GError *error = NULL; dls_async_task_t *cb_data = user_data; gchar *result = NULL; const gchar *message; gboolean end; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &error, "Result", G_TYPE_STRING, &result, NULL); if (!end || (result == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("Browse Object operation failed: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Browse operation failed: %s", message); goto on_complete; } cb_data->task.result = g_variant_ref_sink(g_variant_new_string(result)); DLEYNA_LOG_DEBUG("prv_get_object_metadata_cb result: %s", result); g_free(result); on_complete: (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); if (error) g_error_free(error); DLEYNA_LOG_DEBUG("Exit"); } void dls_device_get_object_metadata(dls_client_t *client, dls_task_t *task, const gchar *upnp_filter) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_device_context_t *context; DLEYNA_LOG_DEBUG("Enter"); context = dls_device_get_context(task->target.device, client); cb_data->proxy = context->cds.proxy; g_object_add_weak_pointer((G_OBJECT(context->cds.proxy)), (gpointer *)&cb_data->proxy); cb_data->action = gupnp_service_proxy_begin_action( cb_data->proxy, "Browse", prv_get_object_metadata_cb, cb_data, "ObjectID", G_TYPE_STRING, task->target.id, "BrowseFlag", G_TYPE_STRING, "BrowseMetadata", "Filter", G_TYPE_STRING, "*", "StartingIndex", G_TYPE_INT, 0, "RequestedCount", G_TYPE_INT, 0, "SortCriteria", G_TYPE_STRING, "", NULL); cb_data->cancel_id = g_cancellable_connect(cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); DLEYNA_LOG_DEBUG("Exit"); } static void prv_create_reference_cb(GUPnPServiceProxy *proxy, GUPnPServiceProxyAction *action, gpointer user_data) { GError *error = NULL; dls_async_task_t *cb_data = user_data; const gchar *message; gchar *object_id = NULL; gchar *object_path; gboolean end; DLEYNA_LOG_DEBUG("Enter"); end = gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, &error, "NewID", G_TYPE_STRING, &object_id, NULL); if (!end || (object_id == NULL)) { message = (error != NULL) ? error->message : "Invalid result"; DLEYNA_LOG_WARNING("CreateReference operation failed: %s", message); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Update Object operation " " failed: %s", message); goto on_error; } object_path = dls_path_from_id(cb_data->task.target.root_path, object_id); DLEYNA_LOG_DEBUG("Result @id: %s - Path: %s", object_id, object_path); cb_data->task.result = g_variant_ref_sink(g_variant_new_object_path( object_path)); g_free(object_path); on_error: (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); g_free(object_id); if (error != NULL) g_error_free(error); DLEYNA_LOG_DEBUG("Exit"); } void dls_device_create_reference(dls_client_t *client, dls_task_t *task) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_device_context_t *context; gchar *i_root = NULL; gchar *i_id = NULL; gchar *path = cb_data->task.ut.create_reference.item_path; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Create Reference for path: %s", path); if (!dls_path_get_path_and_id(path, &i_root, &i_id, NULL)) { DLEYNA_LOG_DEBUG("Can't get id for path %s", path); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Unable to find object for path: %s", path); goto on_error; } DLEYNA_LOG_DEBUG("Create Reference for @id: %s", i_id); DLEYNA_LOG_DEBUG(" In Container @id: %s", task->target.id); context = dls_device_get_context(task->target.device, client); cb_data->proxy = context->cds.proxy; g_object_add_weak_pointer((G_OBJECT(context->cds.proxy)), (gpointer *)&cb_data->proxy); cb_data->action = gupnp_service_proxy_begin_action( cb_data->proxy, "CreateReference", prv_create_reference_cb, cb_data, "ContainerID", G_TYPE_STRING, task->target.id, "ObjectID", G_TYPE_STRING, i_id, NULL); cb_data->cancel_id = g_cancellable_connect( cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); on_error: g_free(i_root); g_free(i_id); DLEYNA_LOG_DEBUG("Exit"); } static void prv_build_icon_result(dls_device_t *device, dls_task_t *task) { GVariant *out_p[2]; out_p[0] = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, device->icon.bytes, device->icon.size, 1); out_p[1] = g_variant_new_string(device->icon.mime_type); task->result = g_variant_ref_sink(g_variant_new_tuple(out_p, 2)); } static void prv_get_icon_cancelled(GCancellable *cancellable, gpointer user_data) { dls_device_download_t *download = (dls_device_download_t *)user_data; dls_async_task_cancelled_cb(cancellable, download->task); if (download->msg) { soup_session_cancel_message(download->session, download->msg, SOUP_STATUS_CANCELLED); DLEYNA_LOG_DEBUG("Cancelling device icon download"); } } static void prv_free_download_info(dls_device_download_t *download) { if (download->msg) g_object_unref(download->msg); g_object_unref(download->session); g_free(download); } static void prv_get_icon_session_cb(SoupSession *session, SoupMessage *msg, gpointer user_data) { dls_device_download_t *download = (dls_device_download_t *)user_data; dls_async_task_t *cb_data = (dls_async_task_t *)download->task; dls_device_t *device = (dls_device_t *)cb_data->task.target.device; if (msg->status_code == SOUP_STATUS_CANCELLED) goto out; if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) { device->icon.size = msg->response_body->length; device->icon.bytes = g_malloc(device->icon.size); memcpy(device->icon.bytes, msg->response_body->data, device->icon.size); prv_build_icon_result(device, &cb_data->task); } else { DLEYNA_LOG_DEBUG("Failed to GET device icon: %s", msg->reason_phrase); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Failed to GET device icon"); } (void) g_idle_add(dls_async_task_complete, cb_data); g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); out: prv_free_download_info(download); } void dls_device_get_icon(dls_client_t *client, dls_task_t *task) { dls_device_context_t *context; dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_device_t *device = task->target.device; gchar *url; dls_device_download_t *download; if (device->icon.size != 0) { prv_build_icon_result(device, task); goto end; } context = dls_device_get_context(device, client); url = gupnp_device_info_get_icon_url(context->device_info, NULL, -1, -1, -1, FALSE, &device->icon.mime_type, NULL, NULL, NULL); if (url == NULL) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_NOT_SUPPORTED, "No icon available"); goto end; } download = g_new0(dls_device_download_t, 1); download->session = soup_session_async_new(); download->msg = soup_message_new(SOUP_METHOD_GET, url); download->task = cb_data; if (!download->msg) { DLEYNA_LOG_WARNING("Invalid URL %s", url); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_RESULT, "Invalid URL %s", url); prv_free_download_info(download); g_free(url); goto end; } cb_data->cancel_id = g_cancellable_connect(cb_data->cancellable, G_CALLBACK(prv_get_icon_cancelled), download, NULL); g_object_ref(download->msg); soup_session_queue_message(download->session, download->msg, prv_get_icon_session_cb, download); g_free(url); return; end: (void) g_idle_add(dls_async_task_complete, cb_data); } static void prv_free_tcp_data(dls_tcp_wake_t *tcp_data) { if (tcp_data != NULL) { g_free(tcp_data->buffer); g_object_unref(tcp_data->socket_connection); g_object_unref(tcp_data->output_stream); g_free(tcp_data); } } static gboolean prv_wake_on_timeout_elapsed(gpointer user_data) { dls_device_t *device = user_data; DLEYNA_LOG_DEBUG("WAKE-ON time-out ellapsed."); dls_server_delete_sleeping_device(device); return FALSE; } static void prv_start_wake_on_watcher(dls_device_t *device, guint timeout) { if (device->wake_on_timeout_id) (void) g_source_remove(device->wake_on_timeout_id); DLEYNA_LOG_DEBUG("Starting WAKE-ON watcher..."); device->wake_on_timeout_id = g_timeout_add_seconds( timeout, prv_wake_on_timeout_elapsed, device); } static void tcp_wake_cb(GObject *source, GAsyncResult *result, gpointer user_data) { dls_tcp_wake_t *tcp_data = (dls_tcp_wake_t *)user_data; dls_async_task_t *cb_data = (dls_async_task_t *)tcp_data->task; GError *tcp_error = NULL; gssize written; DLEYNA_LOG_DEBUG("Enter"); if (tcp_data->socket_connection == NULL) { tcp_data->socket_connection = g_socket_client_connect_to_host_finish( G_SOCKET_CLIENT(source), result, &tcp_error); g_object_unref(source); g_object_unref(result); if (tcp_data->socket_connection == NULL) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_IO, "Failed to connect"); if (tcp_error) { DLEYNA_LOG_WARNING("Failed to connect: %s", tcp_error->message); g_error_free(tcp_error); } goto on_complete; } tcp_data->output_stream = g_io_stream_get_output_stream( G_IO_STREAM(tcp_data->socket_connection)); goto on_write; } written = g_output_stream_write_finish(G_OUTPUT_STREAM(source), result, &tcp_error); if (written < 0) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_IO, "Failed to write"); if (tcp_error) { DLEYNA_LOG_WARNING("Failed to write: %s", tcp_error->message); g_error_free(tcp_error); } goto on_complete; } tcp_data->sent += written; if (tcp_data->sent == tcp_data->to_send) goto on_complete; on_write: g_output_stream_write_async(tcp_data->output_stream, tcp_data->buffer + tcp_data->sent, tcp_data->to_send - tcp_data->sent, G_PRIORITY_DEFAULT, cb_data->cancellable, tcp_wake_cb, tcp_data); goto on_exit; on_complete: prv_free_tcp_data(tcp_data); if (!g_cancellable_is_cancelled(cb_data->cancellable)) { (void) g_idle_add(dls_async_task_complete, cb_data); if (cb_data->task.target.device->sleeping_context != NULL) prv_start_wake_on_watcher(cb_data->task.target.device, tcp_data->max_wake_on_delay); } g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); on_exit: DLEYNA_LOG_DEBUG("Exit"); return; } static gboolean prv_hex_char_to_byte(const gchar hex_char, uint8_t *byte) { gchar ch; gboolean result = TRUE; if (!g_ascii_isxdigit(hex_char)) goto on_exit; ch = g_ascii_toupper(hex_char); if (ch >= '0' && ch <= '9') *byte = ch - '0'; else if (ch >= 'A' && ch <= 'F') *byte = 10 + ch - 'A'; on_exit: return result; } static uint8_t *prv_hex_str_to_bin(const gchar *hex_str, gsize *out_len) { gsize i; gsize j; uint8_t *buffer = NULL; uint8_t byte = 0; gsize len; len = strlen(hex_str); if (len % 2 != 0) { DLEYNA_LOG_WARNING("Invalid Hex String"); goto on_exit; } buffer = g_malloc(len / 2); for (i = 0, j = 0; i < len; i += 2, j++) { if (!prv_hex_char_to_byte(hex_str[i], &buffer[j])) goto on_error; if (!prv_hex_char_to_byte(hex_str[i+1], &byte)) goto on_error; buffer[j] = (buffer[j] << 4) + byte; } *out_len = j; goto on_exit; on_error: g_free(buffer); buffer = NULL; on_exit: return buffer; } static void prv_device_wake_tcp(guint8 *packet, gsize packet_len, const gchar *host, guint max_wake_on_delay, dls_async_task_t *cb_data) { GSocketClient *socket_client; dls_tcp_wake_t *tcp_data; socket_client = g_socket_client_new(); tcp_data = g_new0(dls_tcp_wake_t, 1); tcp_data->task = cb_data; tcp_data->buffer = packet; tcp_data->to_send = packet_len; tcp_data->max_wake_on_delay = max_wake_on_delay; cb_data->cancel_id = g_cancellable_connect(cb_data->cancellable, G_CALLBACK(dls_async_task_cancelled_cb), cb_data, NULL); g_socket_client_connect_to_host_async(socket_client, host, DLS_DEFAULT_WAKE_PORT, cb_data->cancellable, tcp_wake_cb, tcp_data); } static GError *prv_device_wake_udp(guint8 *packet, gsize packet_len, GInetAddress *host_inet_address, GSocketFamily socket_family, gboolean broadcast) { GSocket *socket; GError *send_error = NULL; GError *error = NULL; gssize bytes_sent; GSocketAddress *host_address = NULL; socket = g_socket_new(socket_family, G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_UDP, NULL); if (socket == NULL) { error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_IO, "Cannot create UDP socket"); goto on_complete; } host_address = g_inet_socket_address_new(host_inet_address, DLS_DEFAULT_WAKE_PORT); g_socket_set_blocking(socket, FALSE); if (broadcast) g_socket_set_broadcast(socket, broadcast); bytes_sent = g_socket_send_to(socket, host_address, (const gchar *)packet, packet_len, NULL, &send_error); if (bytes_sent == -1) { error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_IO, "Failed to send UDP packet"); DLEYNA_LOG_WARNING("Failed to send UDP packet: %s", send_error->message); g_error_free(send_error); } on_complete: if (socket) { g_socket_close(socket, NULL); g_object_unref(socket); } if (host_address) g_object_unref(host_address); return error; } static gboolean prv_get_interface_ip_address(struct sockaddr *sock_address, gchar *host) { socklen_t sock_len; gint family; gboolean res = FALSE; family = sock_address->sa_family; if (family != AF_INET && family != AF_INET6) goto on_exit; if (family == AF_INET) sock_len = sizeof(struct sockaddr_in); else sock_len = sizeof(struct sockaddr_in6); if (getnameinfo(sock_address, sock_len, host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0) { if (family == AF_INET6) inet_ntop(AF_INET6, &sock_address, host, NI_MAXHOST); res = TRUE; } on_exit: return res; } static gchar *prv_get_broadcast_ip_address(gchar *ip_address) { struct ifaddrs *ifaddr; struct ifaddrs *ifa; char host[NI_MAXHOST]; gchar *broadcast_ip_address = NULL; if (getifaddrs(&ifaddr) == -1) { DLEYNA_LOG_WARNING("Failed to call getifaddrs"); goto on_exit; } ifa = ifaddr; for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { if ((ifa->ifa_addr == NULL) || (!prv_get_interface_ip_address(ifa->ifa_addr, host)) || strcmp(host, ip_address)) continue; if ((ifa->ifa_flags & IFF_BROADCAST) && (ifa->ifa_ifu.ifu_broadaddr != NULL) && prv_get_interface_ip_address(ifa->ifa_ifu.ifu_broadaddr, host)) { broadcast_ip_address = g_strdup(host); break; } } freeifaddrs(ifaddr); on_exit: return broadcast_ip_address; } void dls_device_wake(dls_client_t *client, dls_task_t *task) { dls_device_context_t *context; dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_device_t *device = task->target.device; dls_network_if_info_t *info; GSocketFamily socket_family; GSocketProtocol socket_protocol; GInetAddress *host_inet_address = NULL; gboolean broadcast = FALSE; gsize packet_len; guint8 *packet = NULL; gchar *wake_on_ip_address; gchar *broadcast_ip_address = NULL; DLEYNA_LOG_DEBUG("Enter"); if (!device->sleeping) { DLEYNA_LOG_DEBUG("Device is not sleeping"); goto on_complete; } context = dls_device_get_context(device, client); if ((context->ems.proxy == NULL) || (device->network_if_info == NULL)) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_NOT_SUPPORTED, "Wake is not supported"); goto on_complete; } info = device->network_if_info; if ((info->wake_transport == NULL) || !strcmp(info->wake_transport, "UDP-Broadcast")) { socket_protocol = G_SOCKET_PROTOCOL_UDP; broadcast = TRUE; } else if (!strcmp(info->wake_transport, "UDP-Unicast")) { socket_protocol = G_SOCKET_PROTOCOL_UDP; } else if (!strcmp(info->wake_transport, "TCP-Unicast")) { socket_protocol = G_SOCKET_PROTOCOL_TCP; } else { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_NOT_SUPPORTED, "Unsupported wake transport"); goto on_complete; } DLEYNA_LOG_DEBUG("MacAddress = %s", info->mac_address); DLEYNA_LOG_DEBUG("DeviceUUID = %s", info->device_uuid); DLEYNA_LOG_DEBUG("NetworkInterfaceMode = %s", info->network_if_mode); DLEYNA_LOG_DEBUG("WakeOnPattern = %s", info->wake_on_pattern); DLEYNA_LOG_DEBUG("WakeSupportedTransport = %s", info->wake_transport); DLEYNA_LOG_DEBUG("WakeOnDelay = %u", info->max_wake_on_delay); wake_on_ip_address = (gchar *)g_list_nth_data(info->ip_addresses, info->ip_address_position); DLEYNA_LOG_DEBUG("Context IP Address = %s", context->ip_address); DLEYNA_LOG_DEBUG("Wake ON IP Address = %s", wake_on_ip_address); if (broadcast) { broadcast_ip_address = prv_get_broadcast_ip_address( context->ip_address); if (broadcast_ip_address != NULL) { wake_on_ip_address = broadcast_ip_address; DLEYNA_LOG_DEBUG("Use Broadcast IP Address = %s", broadcast_ip_address); } } host_inet_address = g_inet_address_new_from_string(wake_on_ip_address); if (host_inet_address == NULL) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_HOST_FAILED, "Invalid host address: %s", wake_on_ip_address); goto on_complete; } socket_family = g_inet_address_get_family(host_inet_address); if ((socket_family != G_SOCKET_FAMILY_IPV4) && (socket_family != G_SOCKET_FAMILY_IPV6)) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_HOST_FAILED, "Invalid host address family: %s", wake_on_ip_address); goto on_complete; } packet = prv_hex_str_to_bin(info->wake_on_pattern, &packet_len); if (packet == NULL) { cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_HOST_FAILED, "Invalid wake on pattern"); goto on_complete; } if (socket_protocol == G_SOCKET_PROTOCOL_UDP) { cb_data->error = prv_device_wake_udp(packet, packet_len, host_inet_address, socket_family, broadcast); if (device->sleeping_context != NULL) prv_start_wake_on_watcher(device, info->max_wake_on_delay); } else { prv_device_wake_tcp(packet, packet_len, wake_on_ip_address, info->max_wake_on_delay, cb_data); goto on_exit; } on_complete: if (host_inet_address != NULL) g_object_unref(host_inet_address); g_free(packet); (void) g_idle_add(dls_async_task_complete, cb_data); on_exit: g_free(broadcast_ip_address); DLEYNA_LOG_DEBUG("Exit"); return; } dleyna-server-0.6.0/libdleyna/server/device.h000077500000000000000000000120771305660312300211670ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #ifndef DLS_DEVICE_H__ #define DLS_DEVICE_H__ #include #include #include #include "async.h" #include "client.h" #include "props.h" typedef struct dls_network_if_info_t_ dls_network_if_info_t; struct dls_network_if_info_t_ { gchar *system_name; gchar *mac_address; gchar *device_uuid; gchar *network_if_mode; gchar *wake_on_pattern; gchar *wake_transport; guint max_wake_on_delay; GList *ip_addresses; guint ip_address_position; }; typedef struct dls_service_t_ dls_service_t; struct dls_service_t_ { GUPnPServiceProxy *proxy; gboolean subscribed; guint timeout_id; }; struct dls_device_context_t_ { gchar *ip_address; GUPnPDeviceProxy *device_proxy; GUPnPDeviceInfo *device_info; dls_device_t *device; dls_service_t cds; dls_service_t ems; }; typedef struct dls_device_icon_t_ dls_device_icon_t; struct dls_device_icon_t_ { gchar *mime_type; guchar *bytes; gsize size; }; struct dls_device_t_ { dleyna_connector_id_t connection; guint id; gchar *path; GPtrArray *contexts; dls_device_context_t *sleeping_context; guint wake_on_timeout_id; guint timeout_id; GHashTable *uploads; GHashTable *upload_jobs; guint upload_id; guint system_update_id; GVariant *search_caps; GVariant *sort_caps; GVariant *sort_ext_caps; GVariant *feature_list; gboolean shutting_down; gboolean has_last_change; guint construct_step; dls_device_icon_t icon; gboolean sleeping; dls_network_if_info_t *network_if_info; }; dls_device_context_t *dls_device_append_new_context(dls_device_t *device, const gchar *ip_address, GUPnPDeviceProxy *proxy, GUPnPDeviceInfo *device_info); void dls_device_delete(void *device); void dls_device_unsubscribe(void *device); void dls_device_construct( dls_device_t *dev, dls_device_context_t *context, dleyna_connector_id_t connection, const dleyna_connector_dispatch_cb_t *dispatch_table, GHashTable *property_map, const dleyna_task_queue_key_t *queue_id); dls_device_t *dls_device_new( dleyna_connector_id_t connection, GUPnPDeviceProxy *proxy, GUPnPDeviceInfo *device_info, const gchar *ip_address, const dleyna_connector_dispatch_cb_t *dispatch_table, GHashTable *filter_map, const char *udn, const dleyna_task_queue_key_t *queue_id); dls_device_t *dls_device_from_path(const gchar *path, GHashTable *device_list); dls_device_context_t *dls_device_get_context(const dls_device_t *device, dls_client_t *client); void dls_device_delete_context(dls_device_context_t *context); void dls_device_get_children(dls_client_t *client, dls_task_t *task, const gchar *upnp_filter, const gchar *sort_by); void dls_device_get_all_props(dls_client_t *client, dls_task_t *task, gboolean root_object); void dls_device_get_prop(dls_client_t *client, dls_task_t *task, dls_prop_map_t *prop_map, gboolean root_object); void dls_device_search(dls_client_t *client, dls_task_t *task, const gchar *upnp_filter, const gchar *upnp_query, const gchar *sort_by); void dls_device_browse_objects(dls_client_t *client, dls_task_t *task); void dls_device_get_resource(dls_client_t *client, dls_task_t *task, const gchar *upnp_filter); void dls_device_subscribe_to_service_changes(dls_device_t *device); void dls_device_upload(dls_client_t *client, dls_task_t *task, const gchar *parent_id); gboolean dls_device_get_upload_status(dls_task_t *task, GError **error); gboolean dls_device_cancel_upload(dls_task_t *task, GError **error); void dls_device_get_upload_ids(dls_task_t *task); void dls_device_delete_object(dls_client_t *client, dls_task_t *task); void dls_device_create_container(dls_client_t *client, dls_task_t *task, const gchar *parent_id); void dls_device_update_object(dls_client_t *client, dls_task_t *task, const gchar *upnp_filter); void dls_device_get_object_metadata(dls_client_t *client, dls_task_t *task, const gchar *parent_id); void dls_device_create_reference(dls_client_t *client, dls_task_t *task); void dls_device_get_icon(dls_client_t *client, dls_task_t *task); void dls_device_wake(dls_client_t *client, dls_task_t *task); #endif /* DLS_DEVICE_H__ */ dleyna-server-0.6.0/libdleyna/server/dleyna-server-service.conf.in000066400000000000000000000023131305660312300252360ustar00rootroot00000000000000# Configuration file for dleyna-server # # # # General configuration options [general] # true: Service always stay in memory running # false: Service quit when the last client disconnects. never-quit=@never_quit@ # IPC connector name connector-name=@with_connector_name@ # Source port for SSDP messages #port=4321 # Log configuration options [log] # Define the logging output method. 3 technologies are defined: # # 0=Syslog # 1=GLib # 2=File log-type=@with_log_type@ # Comma-separated list of logging level. # Log levels are: 1=critical, 2=error, 3=warning, 4=message, 5=info, 6=debug # # Allowed values for log-levels are # 0 = disabled # 7 = default (=1,2,5) # 8 = all (=1,2,3,4,5,6) # 1,..,6 = a comma separated list of log level # # IMPORTANT: This log level is a subset of the log level defined at compile time # You can't enable levels disabled at compile time # level=8 means all level flags defined at compile time. log-level=@with_log_level@ # Network filtering [netf] # true: Enable the network filtering. # false: Disable the network filtering. netf-enabled=false # Comma-separated list of interface name, SSID or IP address. # If netf is enabled but the list is empty, it behaves as disabled. netf-list= dleyna-server-0.6.0/libdleyna/server/ifaddrs.h000066400000000000000000000020771305660312300213400ustar00rootroot00000000000000/**************************************************************************** **************************************************************************** *** *** This header was generated from a glibc header of the same name. *** It contains only constants, structures, and macros generated from *** the original header, and thus, contains no copyrightable information. *** **************************************************************************** ****************************************************************************/ #ifndef _IFADDRS_H #define _IFADDRS_H #include struct ifaddrs { struct ifaddrs *ifa_next; char *ifa_name; unsigned int ifa_flags; struct sockaddr *ifa_addr; struct sockaddr *ifa_netmask; union { struct sockaddr *ifu_broadaddr; struct sockaddr *ifu_dstaddr; } ifa_ifu; #define ifa_broadaddr ifa_ifu.ifu_broadaddr #define ifa_dstaddr ifa_ifu.ifu_dstaddr void *ifa_data; }; extern int getifaddrs(struct ifaddrs **ifap); extern void freeifaddrs(struct ifaddrs *ifa); #endif dleyna-server-0.6.0/libdleyna/server/interface.h000077500000000000000000000223711305660312300216660ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Regis Merlino * */ #ifndef DLEYNA_SERVER_INTERFACE_H__ #define DLEYNA_SERVER_INTERFACE_H__ enum dls_manager_interface_type_ { DLS_MANAGER_INTERFACE_MANAGER, DLS_MANAGER_INTERFACE_INFO_PROPERTIES, DLS_MANAGER_INTERFACE_INFO_MAX }; enum dls_interface_type_ { DLS_INTERFACE_INFO_PROPERTIES, DLS_INTERFACE_INFO_OBJECT, DLS_INTERFACE_INFO_CONTAINER, DLS_INTERFACE_INFO_ITEM, DLS_INTERFACE_INFO_DEVICE, DLS_INTERFACE_INFO_MAX }; #define DLS_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties" #define DLS_INTERFACE_MEDIA_CONTAINER "org.gnome.UPnP.MediaContainer2" #define DLS_INTERFACE_MEDIA_OBJECT "org.gnome.UPnP.MediaObject2" #define DLS_INTERFACE_MEDIA_ITEM "org.gnome.UPnP.MediaItem2" #define DLS_INTERFACE_PROP_ERROR "Error" #define DLS_INTERFACE_PROP_ERROR_ID "ID" #define DLS_INTERFACE_PROP_ERROR_NAME "Name" #define DLS_INTERFACE_PROP_ERROR_MESSAGE "Message" /* Manager Properties */ #define DLS_INTERFACE_PROP_NEVER_QUIT "NeverQuit" #define DLS_INTERFACE_PROP_WHITE_LIST_ENTRIES "WhiteListEntries" #define DLS_INTERFACE_PROP_WHITE_LIST_ENABLED "WhiteListEnabled" /* Object Properties */ #define DLS_INTERFACE_PROP_PATH "Path" #define DLS_INTERFACE_PROP_PARENT "Parent" #define DLS_INTERFACE_PROP_RESTRICTED "Restricted" #define DLS_INTERFACE_PROP_DISPLAY_NAME "DisplayName" #define DLS_INTERFACE_PROP_TYPE "Type" #define DLS_INTERFACE_PROP_TYPE_EX "TypeEx" #define DLS_INTERFACE_PROP_CREATOR "Creator" #define DLS_INTERFACE_PROP_DLNA_MANAGED "DLNAManaged" #define DLS_INTERFACE_PROP_OBJECT_UPDATE_ID "ObjectUpdateID" /* Item Properties */ #define DLS_INTERFACE_PROP_REFPATH "RefPath" #define DLS_INTERFACE_PROP_ARTIST "Artist" #define DLS_INTERFACE_PROP_ARTISTS "Artists" #define DLS_INTERFACE_PROP_ALBUM "Album" #define DLS_INTERFACE_PROP_DATE "Date" #define DLS_INTERFACE_PROP_GENRE "Genre" #define DLS_INTERFACE_PROP_TRACK_NUMBER "TrackNumber" #define DLS_INTERFACE_PROP_ALBUM_ART_URL "AlbumArtURL" #define DLS_INTERFACE_PROP_RESOURCES "Resources" /* Container Properties */ #define DLS_INTERFACE_PROP_SEARCHABLE "Searchable" #define DLS_INTERFACE_PROP_CHILD_COUNT "ChildCount" #define DLS_INTERFACE_PROP_CREATE_CLASSES "CreateClasses" #define DLS_INTERFACE_PROP_CONTAINER_UPDATE_ID "ContainerUpdateID" #define DLS_INTERFACE_PROP_TOTAL_DELETED_CHILD_COUNT "TotalDeletedChildCount" /* Device Properties */ #define DLS_INTERFACE_PROP_LOCATION "Location" #define DLS_INTERFACE_PROP_UDN "UDN" #define DLS_INTERFACE_PROP_ROOT_UDN "RootUDN" #define DLS_INTERFACE_PROP_DEVICE_TYPE "DeviceType" #define DLS_INTERFACE_PROP_FRIENDLY_NAME "FriendlyName" #define DLS_INTERFACE_PROP_MANUFACTURER "Manufacturer" #define DLS_INTERFACE_PROP_MANUFACTURER_URL "ManufacturerUrl" #define DLS_INTERFACE_PROP_MODEL_DESCRIPTION "ModelDescription" #define DLS_INTERFACE_PROP_MODEL_NAME "ModelName" #define DLS_INTERFACE_PROP_MODEL_NUMBER "ModelNumber" #define DLS_INTERFACE_PROP_MODEL_URL "ModelURL" #define DLS_INTERFACE_PROP_SERIAL_NUMBER "SerialNumber" #define DLS_INTERFACE_PROP_PRESENTATION_URL "PresentationURL" #define DLS_INTERFACE_PROP_ICON_URL "IconURL" #define DLS_INTERFACE_PROP_SLEEPING "Sleeping" #define DLS_INTERFACE_PROP_SV_DLNA_CAPABILITIES "DLNACaps" #define DLS_INTERFACE_PROP_SV_SEARCH_CAPABILITIES "SearchCaps" #define DLS_INTERFACE_PROP_SV_SORT_CAPABILITIES "SortCaps" #define DLS_INTERFACE_PROP_SV_SORT_EXT_CAPABILITIES "SortExtCaps" #define DLS_INTERFACE_PROP_SV_FEATURE_LIST "FeatureList" #define DLS_INTERFACE_PROP_SV_SERVICE_RESET_TOKEN "ServiceResetToken" /* Resources Properties */ #define DLS_INTERFACE_PROP_MIME_TYPE "MIMEType" #define DLS_INTERFACE_PROP_DLNA_PROFILE "DLNAProfile" #define DLS_INTERFACE_PROP_DLNA_CONVERSION "DLNAConversion" #define DLS_INTERFACE_PROP_DLNA_OPERATION "DLNAOperation" #define DLS_INTERFACE_PROP_DLNA_FLAGS "DLNAFlags" #define DLS_INTERFACE_PROP_SIZE "Size" #define DLS_INTERFACE_PROP_DURATION "Duration" #define DLS_INTERFACE_PROP_BITRATE "Bitrate" #define DLS_INTERFACE_PROP_SAMPLE_RATE "SampleRate" #define DLS_INTERFACE_PROP_BITS_PER_SAMPLE "BitsPerSample" #define DLS_INTERFACE_PROP_WIDTH "Width" #define DLS_INTERFACE_PROP_HEIGHT "Height" #define DLS_INTERFACE_PROP_COLOR_DEPTH "ColorDepth" #define DLS_INTERFACE_PROP_URLS "URLs" #define DLS_INTERFACE_PROP_URL "URL" #define DLS_INTERFACE_PROP_UPDATE_COUNT "UpdateCount" /* Evented State Variable Properties */ #define DLS_INTERFACE_PROP_ESV_SYSTEM_UPDATE_ID "SystemUpdateID" /* Changed event properties */ #define DLS_INTERFACE_PROP_CHANGE_TYPE "ChangeType" #define DLS_INTERFACE_PROP_UPDATE_ID "UpdateID" #define DLS_INTERFACE_PROP_SUBTREE_UPDATE "SubTreeUpdate" #define DLS_INTERFACE_GET_VERSION "GetVersion" #define DLS_INTERFACE_GET_SERVERS "GetServers" #define DLS_INTERFACE_RESCAN "Rescan" #define DLS_INTERFACE_RELEASE "Release" #define DLS_INTERFACE_SET_PROTOCOL_INFO "SetProtocolInfo" #define DLS_INTERFACE_PREFER_LOCAL_ADDRESSES "PreferLocalAddresses" #define DLS_INTERFACE_WHITE_LIST_ENABLE "WhiteListEnable" #define DLS_INTERFACE_WHITE_LIST_ADD_ENTRIES "WhiteListAddEntries" #define DLS_INTERFACE_WHITE_LIST_REMOVE_ENTRIES "WhiteListRemoveEntries" #define DLS_INTERFACE_WHITE_LIST_CLEAR "WhiteListClear" #define DLS_INTERFACE_FOUND_SERVER "FoundServer" #define DLS_INTERFACE_LOST_SERVER "LostServer" #define DLS_INTERFACE_LIST_CHILDREN "ListChildren" #define DLS_INTERFACE_LIST_CHILDREN_EX "ListChildrenEx" #define DLS_INTERFACE_LIST_ITEMS "ListItems" #define DLS_INTERFACE_LIST_ITEMS_EX "ListItemsEx" #define DLS_INTERFACE_LIST_CONTAINERS "ListContainers" #define DLS_INTERFACE_LIST_CONTAINERS_EX "ListContainersEx" #define DLS_INTERFACE_SEARCH_OBJECTS "SearchObjects" #define DLS_INTERFACE_SEARCH_OBJECTS_EX "SearchObjectsEx" #define DLS_INTERFACE_UPDATE "Update" #define DLS_INTERFACE_GET_COMPATIBLE_RESOURCE "GetCompatibleResource" #define DLS_INTERFACE_GET "Get" #define DLS_INTERFACE_GET_ALL "GetAll" #define DLS_INTERFACE_SET "Set" #define DLS_INTERFACE_INTERFACE_NAME "InterfaceName" #define DLS_INTERFACE_PROPERTY_NAME "PropertyName" #define DLS_INTERFACE_PROPERTIES_VALUE "Properties" #define DLS_INTERFACE_VALUE "Value" #define DLS_INTERFACE_CHILD_TYPES "ChildTypes" #define DLS_INTERFACE_VERSION "Version" #define DLS_INTERFACE_SERVERS "Servers" #define DLS_INTERFACE_CRITERIA "Criteria" #define DLS_INTERFACE_DICT "Dictionary" #define DLS_INTERFACE_PATH "Path" #define DLS_INTERFACE_QUERY "Query" #define DLS_INTERFACE_PROTOCOL_INFO "ProtocolInfo" #define DLS_INTERFACE_PREFER "Prefer" #define DLS_INTERFACE_OFFSET "Offset" #define DLS_INTERFACE_MAX "Max" #define DLS_INTERFACE_FILTER "Filter" #define DLS_INTERFACE_CHILDREN "Children" #define DLS_INTERFACE_SORT_BY "SortBy" #define DLS_INTERFACE_TOTAL_ITEMS "TotalItems" #define DLS_INTERFACE_PROPERTIES_CHANGED "PropertiesChanged" #define DLS_INTERFACE_CHANGED_PROPERTIES "ChangedProperties" #define DLS_INTERFACE_INVALIDATED_PROPERTIES "InvalidatedProperties" #define DLS_INTERFACE_ESV_CONTAINER_UPDATE_IDS "ContainerUpdateIDs" #define DLS_INTERFACE_CONTAINER_PATHS_ID "ContainerPathsIDs" #define DLS_INTERFACE_CHANGED_EVENT "Changed" #define DLS_INTERFACE_CHANGED_OBJECTS "ChangedObjects" #define DLS_INTERFACE_DELETE "Delete" #define DLS_INTERFACE_CREATE_CONTAINER "CreateContainer" #define DLS_INTERFACE_CREATE_CONTAINER_IN_ANY "CreateContainerInAnyContainer" #define DLS_INTERFACE_UPLOAD "Upload" #define DLS_INTERFACE_UPLOAD_TO_ANY "UploadToAnyContainer" #define DLS_INTERFACE_GET_UPLOAD_STATUS "GetUploadStatus" #define DLS_INTERFACE_GET_UPLOAD_IDS "GetUploadIDs" #define DLS_INTERFACE_CANCEL_UPLOAD "CancelUpload" #define DLS_INTERFACE_TOTAL "Total" #define DLS_INTERFACE_LENGTH "Length" #define DLS_INTERFACE_FILE_PATH "FilePath" #define DLS_INTERFACE_UPLOAD_ID "UploadId" #define DLS_INTERFACE_UPLOAD_IDS "UploadIDs" #define DLS_INTERFACE_UPLOAD_STATUS "UploadStatus" #define DLS_INTERFACE_UPLOAD_UPDATE "UploadUpdate" #define DLS_INTERFACE_TO_ADD_UPDATE "ToAddUpdate" #define DLS_INTERFACE_TO_DELETE "ToDelete" #define DLS_INTERFACE_CANCEL "Cancel" #define DLS_INTERFACE_GET_ICON "GetIcon" #define DLS_INTERFACE_RESOLUTION "Resolution" #define DLS_INTERFACE_ICON_BYTES "Bytes" #define DLS_INTERFACE_MIME_TYPE "MimeType" #define DLS_INTERFACE_REQ_MIME_TYPE "RequestedMimeType" #define DLS_INTERFACE_WAKE "Wake" #define DLS_INTERFACE_GET_METADATA "GetMetaData" #define DLS_INTERFACE_METADATA "MetaData" #define DLS_INTERFACE_BROWSE_OBJECTS "BrowseObjects" #define DLS_INTERFACE_OBJECTS_PATH "Objects" #define DLS_INTERFACE_CREATE_REFERENCE "CreateReference" #define DLS_INTERFACE_REFPATH "RefPath" #define DLS_INTERFACE_ENTRY_LIST "EntryList" #define DLS_INTERFACE_IS_ENABLED "IsEnabled" #endif /* DLEYNA_SERVER_INTERFACE_H__ */ dleyna-server-0.6.0/libdleyna/server/manager.c000066400000000000000000000204671305660312300213340ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Ludovic Ferrandis * */ #include #include #include #include #include #include #include "interface.h" #include "manager.h" #include "props.h" struct dls_manager_t_ { dleyna_connector_id_t connection; GUPnPContextManager *cm; dleyna_white_list_t *wl; }; static void prv_wl_notify_prop(dls_manager_t *manager, const gchar *prop_name, GVariant *prop_val) { GVariant *val; GVariantBuilder array; g_variant_builder_init(&array, G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(&array, "{sv}", prop_name, prop_val); val = g_variant_new("(s@a{sv}as)", DLEYNA_SERVER_INTERFACE_MANAGER, g_variant_builder_end(&array), NULL); (void) dls_server_get_connector()->notify(manager->connection, DLEYNA_SERVER_OBJECT, DLS_INTERFACE_PROPERTIES, DLS_INTERFACE_PROPERTIES_CHANGED, val, NULL); } dls_manager_t *dls_manager_new(dleyna_connector_id_t connection, GUPnPContextManager *connection_manager) { dls_manager_t *manager = g_new0(dls_manager_t, 1); GUPnPWhiteList *gupnp_wl; gupnp_wl = gupnp_context_manager_get_white_list(connection_manager); manager->connection = connection; manager->cm = connection_manager; manager->wl = dleyna_white_list_new(gupnp_wl); return manager; } void dls_manager_delete(dls_manager_t *manager) { if (manager != NULL) { dleyna_white_list_delete(manager->wl); g_free(manager); } } dleyna_white_list_t *dls_manager_get_white_list(dls_manager_t *manager) { return manager->wl; } void dls_manager_get_all_props(dls_manager_t *manager, dleyna_settings_t *settings, dls_task_t *task, dls_manager_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_async_get_all_t *cb_task_data; dls_task_get_props_t *task_data = &task->ut.get_props; gchar *i_name = task_data->interface_name; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Path: %s", task->target.path); DLEYNA_LOG_DEBUG("Interface %s", i_name); cb_data->cb = cb; cb_task_data = &cb_data->ut.get_all; cb_task_data->vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); if (!strcmp(i_name, DLEYNA_SERVER_INTERFACE_MANAGER) || !strcmp(i_name, "")) { dls_props_add_manager(settings, cb_task_data->vb); cb_data->task.result = g_variant_ref_sink( g_variant_builder_end( cb_task_data->vb)); } else { DLEYNA_LOG_WARNING("Interface is unknown."); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_INTERFACE, "Interface is unknown."); } (void) g_idle_add(dls_async_task_complete, cb_data); DLEYNA_LOG_DEBUG("Exit"); } void dls_manager_get_prop(dls_manager_t *manager, dleyna_settings_t *settings, dls_task_t *task, dls_manager_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_task_get_prop_t *task_data = &task->ut.get_prop; gchar *i_name = task_data->interface_name; gchar *name = task_data->prop_name; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Path: %s", task->target.path); DLEYNA_LOG_DEBUG("Interface %s", i_name); DLEYNA_LOG_DEBUG("Prop.%s", name); cb_data->cb = cb; if (!strcmp(i_name, DLEYNA_SERVER_INTERFACE_MANAGER) || !strcmp(i_name, "")) { cb_data->task.result = dls_props_get_manager_prop(settings, name); if (!cb_data->task.result) cb_data->error = g_error_new( DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_PROPERTY, "Unknown property"); } else { DLEYNA_LOG_WARNING("Interface is unknown."); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_INTERFACE, "Interface is unknown."); } (void) g_idle_add(dls_async_task_complete, cb_data); DLEYNA_LOG_DEBUG("Exit"); } static void prv_set_prop_never_quit(dls_manager_t *manager, dleyna_settings_t *settings, gboolean never_quit, GError **error) { GVariant *prop_val; gboolean old_val; DLEYNA_LOG_DEBUG("Enter %d", never_quit); old_val = dleyna_settings_is_never_quit(settings); if (old_val == never_quit) goto exit; /* If no error, the white list will be updated in the reload callack */ dleyna_settings_set_never_quit(settings, never_quit, error); if (*error == NULL) { prop_val = g_variant_new_boolean(never_quit); prv_wl_notify_prop(manager, DLS_INTERFACE_PROP_NEVER_QUIT, prop_val); } exit: DLEYNA_LOG_DEBUG("Exit"); return; } static void prv_set_prop_wl_enabled(dls_manager_t *manager, dleyna_settings_t *settings, gboolean enabled, GError **error) { GVariant *prop_val; gboolean old_val; DLEYNA_LOG_DEBUG("Enter %d", enabled); old_val = dleyna_settings_is_white_list_enabled(settings); if (old_val == enabled) goto exit; /* If no error, the white list will be updated in the reload callack */ dleyna_settings_set_white_list_enabled(settings, enabled, error); if (*error == NULL) { dleyna_white_list_enable(manager->wl, enabled); prop_val = g_variant_new_boolean(enabled); prv_wl_notify_prop(manager, DLS_INTERFACE_PROP_WHITE_LIST_ENABLED, prop_val); } exit: DLEYNA_LOG_DEBUG("Exit"); return; } static void prv_set_prop_wl_entries(dls_manager_t *manager, dleyna_settings_t *settings, GVariant *entries, GError **error) { DLEYNA_LOG_DEBUG("Enter"); if (strcmp(g_variant_get_type_string(entries), "as")) { DLEYNA_LOG_WARNING("Invalid parameter type. 'as' expected."); *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_QUERY, "Invalid parameter type. 'as' expected."); goto exit; } /* If no error, the white list will be updated in the reload callack * callack */ dleyna_settings_set_white_list_entries(settings, entries, error); if (*error == NULL) { dleyna_white_list_clear(manager->wl); dleyna_white_list_add_entries(manager->wl, entries); prv_wl_notify_prop(manager, DLS_INTERFACE_PROP_WHITE_LIST_ENTRIES, entries); } exit: DLEYNA_LOG_DEBUG("Exit"); } void dls_manager_set_prop(dls_manager_t *manager, dleyna_settings_t *settings, dls_task_t *task, dls_manager_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_task_set_prop_t *task_data = &task->ut.set_prop; GVariant *param = task_data->params; gchar *name = task_data->prop_name; gchar *i_name = task_data->interface_name; GError *error = NULL; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Path: %s", task->target.path); DLEYNA_LOG_DEBUG("Interface %s", i_name); DLEYNA_LOG_DEBUG("Prop.%s", name); cb_data->cb = cb; if (strcmp(i_name, DLEYNA_SERVER_INTERFACE_MANAGER) && strcmp(i_name, "")) { DLEYNA_LOG_WARNING("Interface is unknown."); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_INTERFACE, "Interface is unknown."); goto exit; } if (!strcmp(name, DLS_INTERFACE_PROP_NEVER_QUIT)) prv_set_prop_never_quit(manager, settings, g_variant_get_boolean(param), &error); else if (!strcmp(name, DLS_INTERFACE_PROP_WHITE_LIST_ENABLED)) prv_set_prop_wl_enabled(manager, settings, g_variant_get_boolean(param), &error); else if (!strcmp(name, DLS_INTERFACE_PROP_WHITE_LIST_ENTRIES)) prv_set_prop_wl_entries(manager, settings, param, &error); else cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_UNKNOWN_PROPERTY, "Unknown property"); if (error != NULL) cb_data->error = error; exit: (void) g_idle_add(dls_async_task_complete, cb_data); DLEYNA_LOG_DEBUG("Exit"); } dleyna-server-0.6.0/libdleyna/server/manager.h000066400000000000000000000035211305660312300213310ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Ludovic Ferrandis * */ #ifndef DLS_MANAGER_H__ #define DLS_MANAGER_H__ #include #include #include #include "task.h" typedef struct dls_manager_t_ dls_manager_t; typedef void (*dls_manager_task_complete_t)(dls_task_t *task, GError *error); dls_manager_t *dls_manager_new(dleyna_connector_id_t connection, GUPnPContextManager *connection_manager); void dls_manager_delete(dls_manager_t *manager); dleyna_white_list_t *dls_manager_get_white_list(dls_manager_t *manager); void dls_manager_get_all_props(dls_manager_t *manager, dleyna_settings_t *settings, dls_task_t *task, dls_manager_task_complete_t cb); void dls_manager_get_prop(dls_manager_t *manager, dleyna_settings_t *settings, dls_task_t *task, dls_manager_task_complete_t cb); void dls_manager_set_prop(dls_manager_t *manager, dleyna_settings_t *settings, dls_task_t *task, dls_manager_task_complete_t cb); #endif /* DLS_MANAGER_H__ */ dleyna-server-0.6.0/libdleyna/server/path.c000077500000000000000000000062261305660312300206560ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #include #include #include #include "path.h" #include "server.h" gboolean dls_path_get_non_root_id(const gchar *object_path, const gchar **slash_before_id) { gboolean retval = FALSE; unsigned int offset = strlen(DLEYNA_SERVER_PATH) + 1; if (!g_str_has_prefix(object_path, DLEYNA_SERVER_PATH "/")) goto on_error; if (!object_path[offset]) goto on_error; *slash_before_id = strchr(&object_path[offset], '/'); retval = TRUE; on_error: return retval; } static gchar *prv_object_name_to_id(const gchar *object_name) { gchar *retval = NULL; unsigned int object_len = strlen(object_name); unsigned int i; gint hex; gchar byte; if (object_len & 1) goto on_error; retval = g_malloc((object_len >> 1) + 1); for (i = 0; i < object_len; i += 2) { hex = g_ascii_xdigit_value(object_name[i]); if (hex == -1) goto on_error; byte = hex << 4; hex = g_ascii_xdigit_value(object_name[i + 1]); if (hex == -1) goto on_error; byte |= hex; retval[i >> 1] = byte; } retval[i >> 1] = 0; return retval; on_error: g_free(retval); return NULL; } gboolean dls_path_get_path_and_id(const gchar *object_path, gchar **root_path, gchar **id, GError **error) { const gchar *slash; gchar *coded_id; if (!dls_path_get_non_root_id(object_path, &slash)) goto on_error; if (!slash) { *root_path = g_strdup(object_path); *id = g_strdup("0"); } else { if (!slash[1]) goto on_error; coded_id = prv_object_name_to_id(slash + 1); if (!coded_id) goto on_error; *root_path = g_strndup(object_path, slash - object_path); *id = coded_id; } return TRUE; on_error: if (error) *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_PATH, "object path is badly formed."); return FALSE; } static gchar *prv_id_to_object_name(const gchar *id) { gchar *retval; unsigned int i; unsigned int data_len = strlen(id); retval = g_malloc((data_len << 1) + 1); retval[0] = 0; for (i = 0; i < data_len; i++) sprintf(&retval[i << 1], "%0x", (guint8) id[i]); return retval; } gchar *dls_path_from_id(const gchar *root_path, const gchar *id) { gchar *coded_id; gchar *path; if (!strcmp(id, "0")) { path = g_strdup(root_path); } else { coded_id = prv_id_to_object_name(id); path = g_strdup_printf("%s/%s", root_path, coded_id); g_free(coded_id); } return path; } dleyna-server-0.6.0/libdleyna/server/path.h000077500000000000000000000022431305660312300206560ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #ifndef DLS_PATH_H__ #define DLS_PATH_H__ #include gboolean dls_path_get_non_root_id(const gchar *object_path, const gchar **slash_before_id); gboolean dls_path_get_path_and_id(const gchar *object_path, gchar **root_path, gchar **id, GError **error); gchar *dls_path_from_id(const gchar *root_path, const gchar *id); #endif /* DLS_PATH_H__ */ dleyna-server-0.6.0/libdleyna/server/props.c000077500000000000000000001766641305660312300211030ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #include #include #include #include #include #include "device.h" #include "interface.h" #include "path.h" #include "props.h" static const gchar gUPnPObject[] = "object"; static const gchar gUPnPContainer[] = "object.container"; static const gchar gUPnPAudioItem[] = "object.item.audioItem"; static const gchar gUPnPVideoItem[] = "object.item.videoItem"; static const gchar gUPnPImageItem[] = "object.item.imageItem"; static const gchar gUPnPItem[] = "object.item"; static const unsigned int gUPnPObjectLen = (sizeof(gUPnPObject) / sizeof(gchar)) - 1; static const unsigned int gUPnPContainerLen = (sizeof(gUPnPContainer) / sizeof(gchar)) - 1; static const unsigned int gUPnPAudioItemLen = (sizeof(gUPnPAudioItem) / sizeof(gchar)) - 1; static const unsigned int gUPnPVideoItemLen = (sizeof(gUPnPVideoItem) / sizeof(gchar)) - 1; static const unsigned int gUPnPImageItemLen = (sizeof(gUPnPImageItem) / sizeof(gchar)) - 1; static const unsigned int gUPnPItemLen = (sizeof(gUPnPItem) / sizeof(gchar)) - 1; static const gchar gUPnPMusicTrack[] = "object.item.audioItem.musicTrack"; static const gchar gUPnPMovie[] = "object.item.videoItem.movie"; static const gchar gUPnPPhoto[] = "object.item.imageItem.photo"; static const gchar gMediaSpec2Container[] = "container"; static const gchar gMediaSpec2AudioMusic[] = "music"; static const gchar gMediaSpec2Audio[] = "audio"; static const gchar gMediaSpec2Video[] = "video"; static const gchar gMediaSpec2VideoMovie[] = "video.movie"; static const gchar gMediaSpec2Image[] = "image"; static const gchar gMediaSpec2ImagePhoto[] = "image.photo"; static const gchar gMediaSpec2ItemUnclassified[] = "item.unclassified"; static const gchar gMediaSpec2ExItem[] = "item"; static const unsigned int gMediaSpec2ExItemLen = (sizeof(gMediaSpec2ExItem) / sizeof(gchar)) - 1; static const unsigned int gMediaSpec2ContainerLen = (sizeof(gMediaSpec2Container) / sizeof(gchar)) - 1; typedef struct dls_prop_dlna_t_ dls_prop_dlna_t; struct dls_prop_dlna_t_ { guint dlna_flag; const gchar *prop_name; }; static const dls_prop_dlna_t g_prop_dlna_ci[] = { {GUPNP_DLNA_CONVERSION_TRANSCODED, "Transcoded"}, {0, NULL} }; static const dls_prop_dlna_t g_prop_dlna_op[] = { {GUPNP_DLNA_OPERATION_RANGE, "RangeSeek"}, {GUPNP_DLNA_OPERATION_TIMESEEK, "TimeSeek"}, {0, NULL} }; static const dls_prop_dlna_t g_prop_dlna_flags[] = { {GUPNP_DLNA_FLAGS_SENDER_PACED, "SenderPaced"}, {GUPNP_DLNA_FLAGS_TIME_BASED_SEEK, "TimeBased"}, {GUPNP_DLNA_FLAGS_BYTE_BASED_SEEK, "ByteBased"}, {GUPNP_DLNA_FLAGS_PLAY_CONTAINER, "PlayContainer"}, {GUPNP_DLNA_FLAGS_S0_INCREASE, "S0Increase"}, {GUPNP_DLNA_FLAGS_SN_INCREASE, "SNIncrease"}, {GUPNP_DLNA_FLAGS_RTSP_PAUSE, "RTSPPause"}, {GUPNP_DLNA_FLAGS_STREAMING_TRANSFER_MODE, "StreamingTM"}, {GUPNP_DLNA_FLAGS_INTERACTIVE_TRANSFER_MODE, "InteractiveTM"}, {GUPNP_DLNA_FLAGS_BACKGROUND_TRANSFER_MODE, "BackgroundTM"}, {GUPNP_DLNA_FLAGS_CONNECTION_STALL, "ConnectionStall"}, {GUPNP_DLNA_FLAGS_DLNA_V15, "DLNA_V15"}, {0, NULL} }; static const dls_prop_dlna_t g_prop_dlna_ocm[] = { {GUPNP_OCM_FLAGS_UPLOAD, "Upload"}, {GUPNP_OCM_FLAGS_CREATE_CONTAINER, "CreateContainer"}, {GUPNP_OCM_FLAGS_DESTROYABLE, "Delete"}, {GUPNP_OCM_FLAGS_UPLOAD_DESTROYABLE, "UploadDelete"}, {GUPNP_OCM_FLAGS_CHANGE_METADATA, "ChangeMeta"}, {0, NULL} }; static dls_prop_map_t *prv_prop_map_new(const gchar *prop_name, dls_upnp_prop_mask type, gboolean filter, gboolean searchable, gboolean updateable) { dls_prop_map_t *retval = g_new(dls_prop_map_t, 1); retval->upnp_prop_name = prop_name; retval->type = type; retval->filter = filter; retval->searchable = searchable; retval->updateable = updateable; return retval; } void dls_prop_maps_new(GHashTable **property_map, GHashTable **filter_map) { dls_prop_map_t *prop_t; GHashTable *p_map; GHashTable *f_map; p_map = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); f_map = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); /* @childCount */ prop_t = prv_prop_map_new("@childCount", DLS_UPNP_MASK_PROP_CHILD_COUNT, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_CHILD_COUNT, prop_t); g_hash_table_insert(p_map, "@childCount", DLS_INTERFACE_PROP_CHILD_COUNT); /* @id */ prop_t = prv_prop_map_new("@id", DLS_UPNP_MASK_PROP_PATH, FALSE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_PATH, prop_t); g_hash_table_insert(p_map, "@id", DLS_INTERFACE_PROP_PATH); /* @parentID */ prop_t = prv_prop_map_new("@parentID", DLS_UPNP_MASK_PROP_PARENT, FALSE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_PARENT, prop_t); g_hash_table_insert(p_map, "@parentID", DLS_INTERFACE_PROP_PARENT); /* @refID */ prop_t = prv_prop_map_new("@refID", DLS_UPNP_MASK_PROP_REFPATH, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_REFPATH, prop_t); g_hash_table_insert(p_map, "@refID", DLS_INTERFACE_PROP_REFPATH); /* @restricted */ prop_t = prv_prop_map_new("@restricted", DLS_UPNP_MASK_PROP_RESTRICTED, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_RESTRICTED, prop_t); g_hash_table_insert(p_map, "@restricted", DLS_INTERFACE_PROP_RESTRICTED); /* @searchable */ prop_t = prv_prop_map_new("@searchable", DLS_UPNP_MASK_PROP_SEARCHABLE, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_SEARCHABLE, prop_t); g_hash_table_insert(p_map, "@searchable", DLS_INTERFACE_PROP_SEARCHABLE); /* dc:creator */ prop_t = prv_prop_map_new("dc:creator", DLS_UPNP_MASK_PROP_CREATOR, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_CREATOR, prop_t); g_hash_table_insert(p_map, "dc:creator", DLS_INTERFACE_PROP_CREATOR); /* dc:date */ prop_t = prv_prop_map_new("dc:date", DLS_UPNP_MASK_PROP_DATE, TRUE, TRUE, TRUE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_DATE, prop_t); g_hash_table_insert(p_map, "dc:date", DLS_INTERFACE_PROP_DATE); /* dc:title */ prop_t = prv_prop_map_new("dc:title", DLS_UPNP_MASK_PROP_DISPLAY_NAME, FALSE, TRUE, TRUE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_DISPLAY_NAME, prop_t); g_hash_table_insert(p_map, "dc:title", DLS_INTERFACE_PROP_DISPLAY_NAME); /* dlna:dlnaManaged */ prop_t = prv_prop_map_new("dlna:dlnaManaged", DLS_UPNP_MASK_PROP_DLNA_MANAGED, TRUE, FALSE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_DLNA_MANAGED, prop_t); g_hash_table_insert(p_map, "dlna:dlnaManaged", DLS_INTERFACE_PROP_DLNA_MANAGED); /* res */ /* res - RES */ prop_t = prv_prop_map_new("res", DLS_UPNP_MASK_PROP_RESOURCES, TRUE, FALSE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_RESOURCES, prop_t); /* res - URL */ prop_t = prv_prop_map_new("res", DLS_UPNP_MASK_PROP_URL, TRUE, FALSE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_URL, prop_t); /* res - URLS */ prop_t = prv_prop_map_new("res", DLS_UPNP_MASK_PROP_URLS, TRUE, FALSE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_URLS, prop_t); /* res@bitrate */ prop_t = prv_prop_map_new("res@bitrate", DLS_UPNP_MASK_PROP_BITRATE, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_BITRATE, prop_t); g_hash_table_insert(p_map, "res@bitrate", DLS_INTERFACE_PROP_BITRATE); /* res@bitsPerSample */ prop_t = prv_prop_map_new("res@bitsPerSample", DLS_UPNP_MASK_PROP_BITS_PER_SAMPLE, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_BITS_PER_SAMPLE, prop_t); g_hash_table_insert(p_map, "res@bitsPerSample", DLS_INTERFACE_PROP_BITS_PER_SAMPLE); /* res@colorDepth */ prop_t = prv_prop_map_new("res@colorDepth", DLS_UPNP_MASK_PROP_COLOR_DEPTH, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_COLOR_DEPTH, prop_t); g_hash_table_insert(p_map, "res@colorDepth", DLS_INTERFACE_PROP_COLOR_DEPTH); /* res@duration */ prop_t = prv_prop_map_new("res@duration", DLS_UPNP_MASK_PROP_DURATION, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_DURATION, prop_t); g_hash_table_insert(p_map, "res@duration", DLS_INTERFACE_PROP_DURATION); /* res@protocolInfo */ /* res@protocolInfo - DLNA PROFILE*/ prop_t = prv_prop_map_new("res@protocolInfo", DLS_UPNP_MASK_PROP_DLNA_PROFILE, TRUE, FALSE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_DLNA_PROFILE, prop_t); /* res@protocolInfo - DLNA CONVERSION*/ prop_t = prv_prop_map_new("res@protocolInfo", DLS_UPNP_MASK_PROP_DLNA_CONVERSION, TRUE, FALSE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_DLNA_CONVERSION, prop_t); /* res@protocolInfo - DLNA OPERATION*/ prop_t = prv_prop_map_new("res@protocolInfo", DLS_UPNP_MASK_PROP_DLNA_OPERATION, TRUE, FALSE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_DLNA_OPERATION, prop_t); /* res@protocolInfo - DLNA FLAGS*/ prop_t = prv_prop_map_new("res@protocolInfo", DLS_UPNP_MASK_PROP_DLNA_FLAGS, TRUE, FALSE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_DLNA_FLAGS, prop_t); /* res@protocolInfo - MIME TYPES*/ prop_t = prv_prop_map_new("res@protocolInfo", DLS_UPNP_MASK_PROP_MIME_TYPE, TRUE, FALSE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_MIME_TYPE, prop_t); /* res@resolution */ /* res@resolution - HEIGH */ prop_t = prv_prop_map_new("res@resolution", DLS_UPNP_MASK_PROP_HEIGHT, TRUE, FALSE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_HEIGHT, prop_t); /* res@resolution - WIDTH */ prop_t = prv_prop_map_new("res@resolution", DLS_UPNP_MASK_PROP_WIDTH, TRUE, FALSE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_WIDTH, prop_t); /* res@sampleFrequency */ prop_t = prv_prop_map_new("res@sampleFrequency", DLS_UPNP_MASK_PROP_SAMPLE_RATE, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_SAMPLE_RATE, prop_t); g_hash_table_insert(p_map, "res@sampleFrequency", DLS_INTERFACE_PROP_SAMPLE_RATE); /* res@size */ prop_t = prv_prop_map_new("res@size", DLS_UPNP_MASK_PROP_SIZE, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_SIZE, prop_t); g_hash_table_insert(p_map, "res@size", DLS_INTERFACE_PROP_SIZE); /* res@updateCount */ prop_t = prv_prop_map_new("res@updateCount", DLS_UPNP_MASK_PROP_UPDATE_COUNT, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_UPDATE_COUNT, prop_t); g_hash_table_insert(p_map, "res@updateCount", DLS_INTERFACE_PROP_UPDATE_COUNT); /* upnp:album */ prop_t = prv_prop_map_new("upnp:album", DLS_UPNP_MASK_PROP_ALBUM, TRUE, TRUE, TRUE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_ALBUM, prop_t); g_hash_table_insert(p_map, "upnp:album", DLS_INTERFACE_PROP_ALBUM); /* upnp:albumArtURI */ prop_t = prv_prop_map_new("upnp:albumArtURI", DLS_UPNP_MASK_PROP_ALBUM_ART_URL, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_ALBUM_ART_URL, prop_t); g_hash_table_insert(p_map, "upnp:albumArtURI", DLS_INTERFACE_PROP_ALBUM_ART_URL); /* upnp:artist */ /* upnp:artist - ARTIST*/ prop_t = prv_prop_map_new("upnp:artist", DLS_UPNP_MASK_PROP_ARTIST, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_ARTIST, prop_t); g_hash_table_insert(p_map, "upnp:artist", DLS_INTERFACE_PROP_ARTIST); /* upnp:artist - ARTISTS*/ prop_t = prv_prop_map_new("upnp:artist", DLS_UPNP_MASK_PROP_ARTISTS, TRUE, FALSE, TRUE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_ARTISTS, prop_t); /* upnp:class */ prop_t = prv_prop_map_new("upnp:class", DLS_UPNP_MASK_PROP_TYPE, FALSE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_TYPE, prop_t); g_hash_table_insert(p_map, "upnp:class", DLS_INTERFACE_PROP_TYPE); prop_t = prv_prop_map_new("upnp:class", DLS_UPNP_MASK_PROP_TYPE_EX, FALSE, TRUE, TRUE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_TYPE_EX, prop_t); /* upnp:containerUpdateID */ prop_t = prv_prop_map_new("upnp:containerUpdateID", DLS_UPNP_MASK_PROP_CONTAINER_UPDATE_ID, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_CONTAINER_UPDATE_ID, prop_t); g_hash_table_insert(p_map, "upnp:containerUpdateID", DLS_INTERFACE_PROP_CONTAINER_UPDATE_ID); /* upnp:createClass */ prop_t = prv_prop_map_new("upnp:createClass", DLS_UPNP_MASK_PROP_CREATE_CLASSES, TRUE, FALSE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_CREATE_CLASSES, prop_t); /* upnp:genre */ prop_t = prv_prop_map_new("upnp:genre", DLS_UPNP_MASK_PROP_GENRE, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_GENRE, prop_t); g_hash_table_insert(p_map, "upnp:genre", DLS_INTERFACE_PROP_GENRE); /* upnp:objectUpdateID */ prop_t = prv_prop_map_new("upnp:objectUpdateID", DLS_UPNP_MASK_PROP_OBJECT_UPDATE_ID, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_OBJECT_UPDATE_ID, prop_t); g_hash_table_insert(p_map, "upnp:objectUpdateID", DLS_INTERFACE_PROP_OBJECT_UPDATE_ID); /* upnp:originalTrackNumber */ prop_t = prv_prop_map_new("upnp:originalTrackNumber", DLS_UPNP_MASK_PROP_TRACK_NUMBER, TRUE, TRUE, TRUE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_TRACK_NUMBER, prop_t); g_hash_table_insert(p_map, "upnp:originalTrackNumber", DLS_INTERFACE_PROP_TRACK_NUMBER); /* upnp:totalDeletedChildCount */ prop_t = prv_prop_map_new("upnp:totalDeletedChildCount", DLS_UPNP_MASK_PROP_TOTAL_DELETED_CHILD_COUNT, TRUE, TRUE, FALSE); g_hash_table_insert(f_map, DLS_INTERFACE_PROP_TOTAL_DELETED_CHILD_COUNT, prop_t); g_hash_table_insert(p_map, "upnp:totalDeletedChildCount", DLS_INTERFACE_PROP_TOTAL_DELETED_CHILD_COUNT); *filter_map = f_map; *property_map = p_map; } static gchar *prv_compute_upnp_filter(GHashTable *upnp_props) { gpointer key; GString *str; GHashTableIter iter; str = g_string_new(""); g_hash_table_iter_init(&iter, upnp_props); if (g_hash_table_iter_next(&iter, &key, NULL)) { g_string_append(str, (const gchar *)key); while (g_hash_table_iter_next(&iter, &key, NULL)) { g_string_append(str, ","); g_string_append(str, (const gchar *)key); } } return g_string_free(str, FALSE); } static dls_upnp_prop_mask prv_parse_filter_list(GHashTable *filter_map, GVariant *filter, gchar **upnp_filter) { GVariantIter viter; const gchar *prop; dls_prop_map_t *prop_map; GHashTable *upnp_props; dls_upnp_prop_mask mask = 0; upnp_props = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); (void) g_variant_iter_init(&viter, filter); while (g_variant_iter_next(&viter, "&s", &prop)) { prop_map = g_hash_table_lookup(filter_map, prop); if (!prop_map) continue; mask |= prop_map->type; if (!prop_map->filter) continue; g_hash_table_insert(upnp_props, (gpointer) prop_map->upnp_prop_name, NULL); } *upnp_filter = prv_compute_upnp_filter(upnp_props); g_hash_table_unref(upnp_props); return mask; } dls_upnp_prop_mask dls_props_parse_filter(GHashTable *filter_map, GVariant *filter, gchar **upnp_filter) { gchar *str; gboolean parse_filter = TRUE; dls_upnp_prop_mask mask; if (g_variant_n_children(filter) == 1) { g_variant_get_child(filter, 0, "&s", &str); if (!strcmp(str, "*")) parse_filter = FALSE; } if (parse_filter) { mask = prv_parse_filter_list(filter_map, filter, upnp_filter); } else { mask = DLS_UPNP_MASK_ALL_PROPS; *upnp_filter = g_strdup("*"); } return mask; } gboolean dls_props_parse_update_filter(GHashTable *filter_map, GVariant *to_add_update, GVariant *to_delete, dls_upnp_prop_mask *mask, gchar **upnp_filter) { GVariantIter viter; const gchar *prop; GVariant *value; dls_prop_map_t *prop_map; GHashTable *upnp_props; gboolean retval = FALSE; *mask = 0; upnp_props = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); (void) g_variant_iter_init(&viter, to_add_update); while (g_variant_iter_next(&viter, "{&sv}", &prop, &value)) { DLEYNA_LOG_DEBUG("to_add_update = %s", prop); prop_map = g_hash_table_lookup(filter_map, prop); if ((!prop_map) || (!prop_map->updateable)) goto on_error; *mask |= prop_map->type; if (!prop_map->filter) continue; g_hash_table_insert(upnp_props, (gpointer) prop_map->upnp_prop_name, NULL); } (void) g_variant_iter_init(&viter, to_delete); while (g_variant_iter_next(&viter, "&s", &prop)) { DLEYNA_LOG_DEBUG("to_delete = %s", prop); prop_map = g_hash_table_lookup(filter_map, prop); if ((!prop_map) || (!prop_map->updateable) || (*mask & prop_map->type) != 0) goto on_error; *mask |= prop_map->type; if (!prop_map->filter) continue; g_hash_table_insert(upnp_props, (gpointer) prop_map->upnp_prop_name, NULL); } *upnp_filter = prv_compute_upnp_filter(upnp_props); retval = TRUE; on_error: g_hash_table_unref(upnp_props); return retval; } static void prv_add_string_prop(GVariantBuilder *vb, const gchar *key, const gchar *value) { if (value) { DLEYNA_LOG_DEBUG("Prop %s = %s", key, value); g_variant_builder_add(vb, "{sv}", key, g_variant_new_string(value)); } } static void prv_add_strv_prop(GVariantBuilder *vb, const gchar *key, const gchar **value, unsigned int len) { if (value && *value && len > 0) g_variant_builder_add(vb, "{sv}", key, g_variant_new_strv(value, len)); } static void prv_add_path_prop(GVariantBuilder *vb, const gchar *key, const gchar *value) { if (value) { DLEYNA_LOG_DEBUG("Prop %s = %s", key, value); g_variant_builder_add(vb, "{sv}", key, g_variant_new_object_path(value)); } } static void prv_add_uint_prop(GVariantBuilder *vb, const gchar *key, unsigned int value) { DLEYNA_LOG_DEBUG("Prop %s = %u", key, value); g_variant_builder_add(vb, "{sv}", key, g_variant_new_uint32(value)); } static void prv_add_int_prop(GVariantBuilder *vb, const gchar *key, int value) { if (value != -1) g_variant_builder_add(vb, "{sv}", key, g_variant_new_int32(value)); } static void prv_add_variant_prop(GVariantBuilder *vb, const gchar *key, GVariant *prop) { if (prop) g_variant_builder_add(vb, "{sv}", key, prop); } void dls_props_add_child_count(GVariantBuilder *item_vb, gint value) { prv_add_uint_prop(item_vb, DLS_INTERFACE_PROP_CHILD_COUNT, value); } static void prv_add_bool_prop(GVariantBuilder *vb, const gchar *key, gboolean value) { DLEYNA_LOG_DEBUG("Prop %s = %u", key, value); g_variant_builder_add(vb, "{sv}", key, g_variant_new_boolean(value)); } static void prv_add_int64_prop(GVariantBuilder *vb, const gchar *key, gint64 value) { if (value != -1) { DLEYNA_LOG_DEBUG("Prop %s = %"G_GINT64_FORMAT, key, value); g_variant_builder_add(vb, "{sv}", key, g_variant_new_int64(value)); } } static void prv_add_list_dlna_str(gpointer data, gpointer user_data) { GVariantBuilder *vb = (GVariantBuilder *)user_data; gchar *cap_str = (gchar *)data; gchar *str; int value = 0; if (g_str_has_prefix(cap_str, "srs-rt-retention-period-")) { str = cap_str + strlen("srs-rt-retention-period-"); cap_str = "srs-rt-retention-period"; if (*str) { if (!g_strcmp0(str, "infinity")) value = -1; else value = atoi(str); } } prv_add_uint_prop(vb, cap_str, value); } static GVariant *prv_add_list_dlna_prop(GList *list) { GVariantBuilder vb; g_variant_builder_init(&vb, G_VARIANT_TYPE("a{sv}")); g_list_foreach(list, prv_add_list_dlna_str, &vb); return g_variant_builder_end(&vb); } static GVariant *prv_props_get_dlna_info_dict(guint flags, const dls_prop_dlna_t *dfa) { GVariantBuilder builder; gboolean set; gint i = 0; g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sb}")); while (dfa[i].dlna_flag) { set = (flags & dfa[i].dlna_flag) != 0; g_variant_builder_add(&builder, "{sb}", dfa[i].prop_name, set); i++; } return g_variant_builder_end(&builder); } static void prv_add_list_artists_str(gpointer data, gpointer user_data) { GVariantBuilder *vb = (GVariantBuilder *)user_data; GUPnPDIDLLiteContributor *contributor = data; const char *str; str = gupnp_didl_lite_contributor_get_name(contributor); g_variant_builder_add(vb, "s", str); } static GVariant *prv_get_artists_prop(GList *list) { GVariantBuilder vb; g_variant_builder_init(&vb, G_VARIANT_TYPE("as")); g_list_foreach(list, prv_add_list_artists_str, &vb); return g_variant_builder_end(&vb); } void dls_props_add_device(GUPnPDeviceInfo *root_proxy, GUPnPDeviceInfo *proxy, GUPnPServiceProxy *ems_proxy, const dls_device_t *device, GVariantBuilder *vb) { gchar *str; GList *list; GVariant *dlna_caps; prv_add_string_prop(vb, DLS_INTERFACE_PROP_LOCATION, gupnp_device_info_get_location(proxy)); prv_add_string_prop(vb, DLS_INTERFACE_PROP_UDN, gupnp_device_info_get_udn(proxy)); if (proxy != root_proxy) prv_add_string_prop(vb, DLS_INTERFACE_PROP_ROOT_UDN, gupnp_device_info_get_udn(root_proxy)); prv_add_string_prop(vb, DLS_INTERFACE_PROP_DEVICE_TYPE, gupnp_device_info_get_device_type(proxy)); str = gupnp_device_info_get_friendly_name(proxy); prv_add_string_prop(vb, DLS_INTERFACE_PROP_FRIENDLY_NAME, str); g_free(str); str = gupnp_device_info_get_manufacturer(proxy); prv_add_string_prop(vb, DLS_INTERFACE_PROP_MANUFACTURER, str); g_free(str); str = gupnp_device_info_get_manufacturer_url(proxy); prv_add_string_prop(vb, DLS_INTERFACE_PROP_MANUFACTURER_URL, str); g_free(str); str = gupnp_device_info_get_model_description(proxy); prv_add_string_prop(vb, DLS_INTERFACE_PROP_MODEL_DESCRIPTION, str); g_free(str); str = gupnp_device_info_get_model_name(proxy); prv_add_string_prop(vb, DLS_INTERFACE_PROP_MODEL_NAME, str); g_free(str); str = gupnp_device_info_get_model_number(proxy); prv_add_string_prop(vb, DLS_INTERFACE_PROP_MODEL_NUMBER, str); g_free(str); str = gupnp_device_info_get_model_url(proxy); prv_add_string_prop(vb, DLS_INTERFACE_PROP_MODEL_URL, str); g_free(str); str = gupnp_device_info_get_serial_number(proxy); prv_add_string_prop(vb, DLS_INTERFACE_PROP_SERIAL_NUMBER, str); g_free(str); str = gupnp_device_info_get_presentation_url(proxy); prv_add_string_prop(vb, DLS_INTERFACE_PROP_PRESENTATION_URL, str); g_free(str); str = gupnp_device_info_get_icon_url(proxy, NULL, -1, -1, -1, FALSE, NULL, NULL, NULL, NULL); prv_add_string_prop(vb, DLS_INTERFACE_PROP_ICON_URL, str); g_free(str); list = gupnp_device_info_list_dlna_capabilities(proxy); if (list != NULL) { dlna_caps = prv_add_list_dlna_prop(list); g_variant_builder_add(vb, "{sv}", DLS_INTERFACE_PROP_SV_DLNA_CAPABILITIES, dlna_caps); g_list_free_full(list, g_free); } if (device->search_caps != NULL) g_variant_builder_add(vb, "{sv}", DLS_INTERFACE_PROP_SV_SEARCH_CAPABILITIES, device->search_caps); if (device->sort_caps != NULL) g_variant_builder_add(vb, "{sv}", DLS_INTERFACE_PROP_SV_SORT_CAPABILITIES, device->sort_caps); if (device->sort_ext_caps != NULL) g_variant_builder_add( vb, "{sv}", DLS_INTERFACE_PROP_SV_SORT_EXT_CAPABILITIES, device->sort_ext_caps); if (device->feature_list != NULL) g_variant_builder_add(vb, "{sv}", DLS_INTERFACE_PROP_SV_FEATURE_LIST, device->feature_list); } GVariant *dls_props_get_device_prop(GUPnPDeviceInfo *root_proxy, GUPnPDeviceInfo *proxy, const dls_device_t *device, const gchar *prop) { GVariant *dlna_caps = NULL; GVariant *retval = NULL; const gchar *str = NULL; gchar *copy = NULL; GList *list; if (!strcmp(DLS_INTERFACE_PROP_LOCATION, prop)) { str = gupnp_device_info_get_location(proxy); } else if (!strcmp(DLS_INTERFACE_PROP_UDN, prop)) { str = gupnp_device_info_get_udn(proxy); } else if (!strcmp(DLS_INTERFACE_PROP_ROOT_UDN, prop)) { if (proxy != root_proxy) str = gupnp_device_info_get_udn(root_proxy); } else if (!strcmp(DLS_INTERFACE_PROP_DEVICE_TYPE, prop)) { str = gupnp_device_info_get_device_type(proxy); } else if (!strcmp(DLS_INTERFACE_PROP_FRIENDLY_NAME, prop)) { copy = gupnp_device_info_get_friendly_name(proxy); str = copy; } else if (!strcmp(DLS_INTERFACE_PROP_MANUFACTURER, prop)) { copy = gupnp_device_info_get_manufacturer(proxy); str = copy; } else if (!strcmp(DLS_INTERFACE_PROP_MANUFACTURER_URL, prop)) { copy = gupnp_device_info_get_manufacturer_url(proxy); str = copy; } else if (!strcmp(DLS_INTERFACE_PROP_MODEL_DESCRIPTION, prop)) { copy = gupnp_device_info_get_model_description(proxy); str = copy; } else if (!strcmp(DLS_INTERFACE_PROP_MODEL_NAME, prop)) { copy = gupnp_device_info_get_model_name(proxy); str = copy; } else if (!strcmp(DLS_INTERFACE_PROP_MODEL_NUMBER, prop)) { copy = gupnp_device_info_get_model_number(proxy); str = copy; } else if (!strcmp(DLS_INTERFACE_PROP_MODEL_URL, prop)) { copy = gupnp_device_info_get_model_url(proxy); str = copy; } else if (!strcmp(DLS_INTERFACE_PROP_SERIAL_NUMBER, prop)) { copy = gupnp_device_info_get_serial_number(proxy); str = copy; } else if (!strcmp(DLS_INTERFACE_PROP_PRESENTATION_URL, prop)) { copy = gupnp_device_info_get_presentation_url(proxy); str = copy; } else if (!strcmp(DLS_INTERFACE_PROP_ICON_URL, prop)) { copy = gupnp_device_info_get_icon_url(proxy, NULL, -1, -1, -1, FALSE, NULL, NULL, NULL, NULL); str = copy; } else if (!strcmp(DLS_INTERFACE_PROP_SV_DLNA_CAPABILITIES, prop)) { list = gupnp_device_info_list_dlna_capabilities(proxy); if (list != NULL) { dlna_caps = prv_add_list_dlna_prop(list); g_list_free_full(list, g_free); retval = g_variant_ref_sink(dlna_caps); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG copy = g_variant_print(dlna_caps, FALSE); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, copy); #endif } } else if (!strcmp(DLS_INTERFACE_PROP_SV_SEARCH_CAPABILITIES, prop)) { if (device->search_caps != NULL) { retval = g_variant_ref(device->search_caps); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG copy = g_variant_print(device->search_caps, FALSE); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, copy); #endif } } else if (!strcmp(DLS_INTERFACE_PROP_SV_SORT_CAPABILITIES, prop)) { if (device->sort_caps != NULL) { retval = g_variant_ref(device->sort_caps); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG copy = g_variant_print(device->sort_caps, FALSE); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, copy); #endif } } else if (!strcmp(DLS_INTERFACE_PROP_SV_SORT_EXT_CAPABILITIES, prop)) { if (device->sort_ext_caps != NULL) { retval = g_variant_ref(device->sort_ext_caps); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG copy = g_variant_print(device->sort_ext_caps, FALSE); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, copy); #endif } } else if (!strcmp(DLS_INTERFACE_PROP_SV_FEATURE_LIST, prop)) { if (device->feature_list != NULL) { retval = g_variant_ref(device->feature_list); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG copy = g_variant_print(device->feature_list, FALSE); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, copy); #endif } } if (!retval) { if (str) { DLEYNA_LOG_DEBUG("Prop %s = %s", prop, str); retval = g_variant_ref_sink(g_variant_new_string(str)); } #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_WARNING else DLEYNA_LOG_WARNING("Property %s not defined", prop); #endif } g_free(copy); return retval; } static GUPnPDIDLLiteResource *prv_match_resource(GUPnPDIDLLiteResource *res, gchar **pi_str_array) { GUPnPDIDLLiteResource *retval = NULL; GUPnPProtocolInfo *res_pi; GUPnPProtocolInfo *pi; unsigned int i; gboolean match; if (!pi_str_array) { retval = res; goto done; } res_pi = gupnp_didl_lite_resource_get_protocol_info(res); if (!res_pi) goto done; for (i = 0; pi_str_array[i]; ++i) { pi = gupnp_protocol_info_new_from_string(pi_str_array[i], NULL); if (!pi) continue; match = gupnp_protocol_info_is_compatible(pi, res_pi); g_object_unref(pi); if (match) { retval = res; break; } } done: return retval; } static GUPnPDIDLLiteResource *prv_get_matching_resource (GUPnPDIDLLiteObject *object, const gchar *protocol_info) { GUPnPDIDLLiteResource *retval = NULL; GUPnPDIDLLiteResource *res; GList *resources; GList *ptr; gchar **pi_str_array = NULL; if (protocol_info) pi_str_array = g_strsplit(protocol_info, ",", 0); resources = gupnp_didl_lite_object_get_resources(object); ptr = resources; while (ptr) { res = ptr->data; if (!retval) { retval = prv_match_resource(res, pi_str_array); if (!retval) g_object_unref(res); } else { g_object_unref(res); } ptr = ptr->next; } g_list_free(resources); if (pi_str_array) g_strfreev(pi_str_array); return retval; } static void prv_parse_common_resources(GVariantBuilder *item_vb, GUPnPDIDLLiteResource *res, dls_upnp_prop_mask filter_mask) { GUPnPProtocolInfo *protocol_info; gint64 int64_val; guint uint_val; const char *str_val; GUPnPDLNAConversion conv; GUPnPDLNAOperation ope; GUPnPDLNAFlags flags; if (filter_mask & DLS_UPNP_MASK_PROP_SIZE) { int64_val = gupnp_didl_lite_resource_get_size64(res); prv_add_int64_prop(item_vb, DLS_INTERFACE_PROP_SIZE, int64_val); } if ((filter_mask & DLS_UPNP_MASK_PROP_UPDATE_COUNT) && gupnp_didl_lite_resource_update_count_is_set(res)) { uint_val = gupnp_didl_lite_resource_get_update_count(res); prv_add_uint_prop(item_vb, DLS_INTERFACE_PROP_UPDATE_COUNT, uint_val); } protocol_info = gupnp_didl_lite_resource_get_protocol_info(res); if (filter_mask & DLS_UPNP_MASK_PROP_DLNA_PROFILE) { str_val = gupnp_protocol_info_get_dlna_profile(protocol_info); prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_DLNA_PROFILE, str_val); } if (filter_mask & DLS_UPNP_MASK_PROP_DLNA_CONVERSION) { conv = gupnp_protocol_info_get_dlna_conversion(protocol_info); prv_add_variant_prop(item_vb, DLS_INTERFACE_PROP_DLNA_CONVERSION, prv_props_get_dlna_info_dict( conv, g_prop_dlna_ci)); } if (filter_mask & DLS_UPNP_MASK_PROP_DLNA_OPERATION) { ope = gupnp_protocol_info_get_dlna_operation(protocol_info); prv_add_variant_prop(item_vb, DLS_INTERFACE_PROP_DLNA_OPERATION, prv_props_get_dlna_info_dict( ope, g_prop_dlna_op)); } if (filter_mask & DLS_UPNP_MASK_PROP_DLNA_FLAGS) { flags = gupnp_protocol_info_get_dlna_flags(protocol_info); prv_add_variant_prop(item_vb, DLS_INTERFACE_PROP_DLNA_FLAGS, prv_props_get_dlna_info_dict( flags, g_prop_dlna_flags)); } if (filter_mask & DLS_UPNP_MASK_PROP_MIME_TYPE) { str_val = gupnp_protocol_info_get_mime_type(protocol_info); prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_MIME_TYPE, str_val); } } static void prv_parse_all_resources(GVariantBuilder *item_vb, GUPnPDIDLLiteResource *res, dls_upnp_prop_mask filter_mask) { int int_val; prv_parse_common_resources(item_vb, res, filter_mask); if (filter_mask & DLS_UPNP_MASK_PROP_BITRATE) { int_val = gupnp_didl_lite_resource_get_bitrate(res); prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_BITRATE, int_val); } if (filter_mask & DLS_UPNP_MASK_PROP_SAMPLE_RATE) { int_val = gupnp_didl_lite_resource_get_sample_freq(res); prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_SAMPLE_RATE, int_val); } if (filter_mask & DLS_UPNP_MASK_PROP_BITS_PER_SAMPLE) { int_val = gupnp_didl_lite_resource_get_bits_per_sample(res); prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_BITS_PER_SAMPLE, int_val); } if (filter_mask & DLS_UPNP_MASK_PROP_DURATION) { int_val = (int) gupnp_didl_lite_resource_get_duration(res); prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_DURATION, int_val); } if (filter_mask & DLS_UPNP_MASK_PROP_WIDTH) { int_val = (int) gupnp_didl_lite_resource_get_width(res); prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_WIDTH, int_val); } if (filter_mask & DLS_UPNP_MASK_PROP_HEIGHT) { int_val = (int) gupnp_didl_lite_resource_get_height(res); prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_HEIGHT, int_val); } if (filter_mask & DLS_UPNP_MASK_PROP_COLOR_DEPTH) { int_val = (int) gupnp_didl_lite_resource_get_color_depth(res); prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_COLOR_DEPTH, int_val); } } static GVariant *prv_compute_create_classes(GUPnPDIDLLiteContainer *container) { GVariantBuilder create_classes_vb; GList *create_classes; GList *ptr; GUPnPDIDLLiteCreateClass *create_class; const char *content; const char *ms2_class; gboolean inc_derived; g_variant_builder_init(&create_classes_vb, G_VARIANT_TYPE("a(sb)")); create_classes = gupnp_didl_lite_container_get_create_classes_full( container); ptr = create_classes; while (ptr) { create_class = ptr->data; content = gupnp_didl_lite_create_class_get_content( create_class); ms2_class = dls_props_upnp_class_to_media_spec_ex(content); inc_derived = gupnp_didl_lite_create_class_get_include_derived( create_class); g_variant_builder_add(&create_classes_vb, "(sb)", ms2_class, inc_derived); g_object_unref(ptr->data); ptr = g_list_next(ptr); } g_list_free(create_classes); return g_variant_builder_end(&create_classes_vb); } static const gchar *prv_media_spec_to_upnp_class(const gchar *m2spec_class) { const gchar *retval = NULL; if (!strcmp(m2spec_class, gMediaSpec2Container)) retval = gUPnPContainer; else if (!strcmp(m2spec_class, gMediaSpec2AudioMusic)) retval = gUPnPMusicTrack; else if (!strcmp(m2spec_class, gMediaSpec2Audio)) retval = gUPnPAudioItem; else if (!strcmp(m2spec_class, gMediaSpec2VideoMovie)) retval = gUPnPMovie; else if (!strcmp(m2spec_class, gMediaSpec2Video)) retval = gUPnPVideoItem; else if (!strcmp(m2spec_class, gMediaSpec2ImagePhoto)) retval = gUPnPPhoto; else if (!strcmp(m2spec_class, gMediaSpec2Image)) retval = gUPnPImageItem; return retval; } const gchar *dls_props_media_spec_to_upnp_class(const gchar *m2spec_class) { const gchar *retval = NULL; if (!m2spec_class) goto on_error; retval = prv_media_spec_to_upnp_class(m2spec_class); if (!retval && !strcmp(m2spec_class, gMediaSpec2ItemUnclassified)) retval = gUPnPItem; on_error: return retval; } gchar *dls_props_media_spec_ex_to_upnp_class(const gchar *m2spec_class) { gchar *retval = NULL; const gchar *basic_type; const gchar *ptr = NULL; if (!m2spec_class) goto on_error; basic_type = prv_media_spec_to_upnp_class(m2spec_class); if (basic_type) { retval = g_strdup(basic_type); } else { if (!strncmp(m2spec_class, gMediaSpec2ExItem, gMediaSpec2ExItemLen)) ptr = m2spec_class + gMediaSpec2ExItemLen; else if (!strncmp(m2spec_class, gMediaSpec2Container, gMediaSpec2ContainerLen)) ptr = m2spec_class + gMediaSpec2ContainerLen; if (ptr && (!*ptr || *ptr == '.')) retval = g_strdup_printf("object.%s", m2spec_class); } on_error: return retval; } static const gchar *prv_upnp_class_to_media_spec(const gchar *upnp_class, gboolean *exact) { const gchar *retval = NULL; const gchar *ptr; if (!upnp_class) goto on_error; if (!strncmp(upnp_class, gUPnPContainer, gUPnPContainerLen)) { ptr = upnp_class + gUPnPContainerLen; if (!*ptr || *ptr == '.') { retval = gMediaSpec2Container; *exact = *ptr == 0; } } else if (!strncmp(upnp_class, gUPnPAudioItem, gUPnPAudioItemLen)) { ptr = upnp_class + gUPnPAudioItemLen; if (!strcmp(ptr, ".musicTrack")) { retval = gMediaSpec2AudioMusic; *exact = TRUE; } else if (!*ptr || *ptr == '.') { retval = gMediaSpec2Audio; *exact = *ptr == 0; } } else if (!strncmp(upnp_class, gUPnPVideoItem, gUPnPVideoItemLen)) { ptr = upnp_class + gUPnPVideoItemLen; if (!strcmp(ptr, ".movie")) { retval = gMediaSpec2VideoMovie; *exact = TRUE; } else if (!*ptr || *ptr == '.') { retval = gMediaSpec2Video; *exact = *ptr == 0; } } else if (!strncmp(upnp_class, gUPnPImageItem, gUPnPImageItemLen)) { ptr = upnp_class + gUPnPImageItemLen; if (!strcmp(ptr, ".photo")) { retval = gMediaSpec2ImagePhoto; *exact = TRUE; } else if (!*ptr || *ptr == '.') { retval = gMediaSpec2Image; *exact = *ptr == 0; } } else if (!strncmp(upnp_class, gUPnPItem, gUPnPItemLen)) { ptr = upnp_class + gUPnPItemLen; if (!*ptr || *ptr == '.') { retval = gMediaSpec2ItemUnclassified; *exact = *ptr == 0; } } on_error: return retval; } const gchar *dls_props_upnp_class_to_media_spec(const gchar *upnp_class) { gboolean exact; return prv_upnp_class_to_media_spec(upnp_class, &exact); } const gchar *dls_props_upnp_class_to_media_spec_ex(const gchar *upnp_class) { const gchar *retval; gboolean exact; retval = prv_upnp_class_to_media_spec(upnp_class, &exact); if (!retval) goto on_error; if (exact) { if (retval == gMediaSpec2ItemUnclassified) retval = gMediaSpec2ExItem; } else { retval = upnp_class + gUPnPObjectLen + 1; } on_error: return retval; } static GVariant *prv_compute_resources(GUPnPDIDLLiteObject *object, dls_upnp_prop_mask filter_mask, gboolean all_res) { GUPnPDIDLLiteResource *res = NULL; GList *resources; GList *ptr; GVariantBuilder *res_array_vb; GVariantBuilder *res_vb; const char *str_val; GVariant *retval; res_array_vb = g_variant_builder_new(G_VARIANT_TYPE("aa{sv}")); resources = gupnp_didl_lite_object_get_resources(object); ptr = resources; while (ptr) { res = ptr->data; res_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); if (filter_mask & DLS_UPNP_MASK_PROP_URL) { str_val = gupnp_didl_lite_resource_get_uri(res); prv_add_string_prop(res_vb, DLS_INTERFACE_PROP_URL, str_val); } if (all_res) prv_parse_all_resources(res_vb, res, filter_mask); else prv_parse_common_resources(res_vb, res, filter_mask); g_variant_builder_add(res_array_vb, "@a{sv}", g_variant_builder_end(res_vb)); g_variant_builder_unref(res_vb); g_object_unref(ptr->data); ptr = g_list_next(ptr); } retval = g_variant_builder_end(res_array_vb); g_variant_builder_unref(res_array_vb); g_list_free(resources); return retval; } static void prv_add_resources(GVariantBuilder *item_vb, GUPnPDIDLLiteObject *object, dls_upnp_prop_mask filter_mask, gboolean all_res) { GVariant *val; val = prv_compute_resources(object, filter_mask, all_res); if (g_variant_n_children(val)) g_variant_builder_add(item_vb, "{sv}", DLS_INTERFACE_PROP_RESOURCES, val); else g_variant_unref(val); } gboolean dls_props_add_object(GVariantBuilder *item_vb, GUPnPDIDLLiteObject *object, const char *root_path, const gchar *parent_path, dls_upnp_prop_mask filter_mask) { gchar *path = NULL; const char *id; const char *title; const char *creator; const char *upnp_class; const char *media_spec_type; const char *media_spec_type_ex; gboolean retval = FALSE; gboolean rest; GUPnPOCMFlags flags; guint uint_val; id = gupnp_didl_lite_object_get_id(object); if (!id) goto on_error; upnp_class = gupnp_didl_lite_object_get_upnp_class(object); media_spec_type = dls_props_upnp_class_to_media_spec(upnp_class); if (!media_spec_type) goto on_error; media_spec_type_ex = dls_props_upnp_class_to_media_spec_ex(upnp_class); title = gupnp_didl_lite_object_get_title(object); creator = gupnp_didl_lite_object_get_creator(object); rest = gupnp_didl_lite_object_get_restricted(object); path = dls_path_from_id(root_path, id); if (filter_mask & DLS_UPNP_MASK_PROP_DISPLAY_NAME) prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_DISPLAY_NAME, title); if (filter_mask & DLS_UPNP_MASK_PROP_CREATOR) prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_CREATOR, creator); if (filter_mask & DLS_UPNP_MASK_PROP_PATH) prv_add_path_prop(item_vb, DLS_INTERFACE_PROP_PATH, path); if (filter_mask & DLS_UPNP_MASK_PROP_PARENT) prv_add_path_prop(item_vb, DLS_INTERFACE_PROP_PARENT, parent_path); if (filter_mask & DLS_UPNP_MASK_PROP_TYPE) prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_TYPE, media_spec_type); if (filter_mask & DLS_UPNP_MASK_PROP_TYPE_EX) prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_TYPE_EX, media_spec_type_ex); if (filter_mask & DLS_UPNP_MASK_PROP_RESTRICTED) prv_add_bool_prop(item_vb, DLS_INTERFACE_PROP_RESTRICTED, rest); if (filter_mask & DLS_UPNP_MASK_PROP_DLNA_MANAGED) { flags = gupnp_didl_lite_object_get_dlna_managed(object); if (flags != GUPNP_OCM_FLAGS_NONE) prv_add_variant_prop(item_vb, DLS_INTERFACE_PROP_DLNA_MANAGED, prv_props_get_dlna_info_dict( flags, g_prop_dlna_ocm)); } if ((filter_mask & DLS_UPNP_MASK_PROP_OBJECT_UPDATE_ID) && gupnp_didl_lite_object_update_id_is_set(object)) { uint_val = gupnp_didl_lite_object_get_update_id(object); prv_add_uint_prop(item_vb, DLS_INTERFACE_PROP_OBJECT_UPDATE_ID, uint_val); } retval = TRUE; on_error: g_free(path); return retval; } void dls_props_add_container(GVariantBuilder *item_vb, GUPnPDIDLLiteContainer *object, dls_upnp_prop_mask filter_mask, const gchar *protocol_info, gboolean *have_child_count) { int child_count; gboolean searchable; guint uint_val; GUPnPDIDLLiteResource *res; GVariant *val; const char *str_val; *have_child_count = FALSE; if (filter_mask & DLS_UPNP_MASK_PROP_CHILD_COUNT) { child_count = gupnp_didl_lite_container_get_child_count(object); if (child_count >= 0) { prv_add_uint_prop(item_vb, DLS_INTERFACE_PROP_CHILD_COUNT, (unsigned int) child_count); *have_child_count = TRUE; } } if (filter_mask & DLS_UPNP_MASK_PROP_SEARCHABLE) { searchable = gupnp_didl_lite_container_get_searchable(object); prv_add_bool_prop(item_vb, DLS_INTERFACE_PROP_SEARCHABLE, searchable); } if (filter_mask & DLS_UPNP_MASK_PROP_CREATE_CLASSES) { val = prv_compute_create_classes(object); if (g_variant_n_children(val)) prv_add_variant_prop(item_vb, DLS_INTERFACE_PROP_CREATE_CLASSES, val); else g_variant_unref(val); } if ((filter_mask & DLS_UPNP_MASK_PROP_CONTAINER_UPDATE_ID) && gupnp_didl_lite_container_container_update_id_is_set(object)) { uint_val = gupnp_didl_lite_container_get_container_update_id( object); prv_add_uint_prop(item_vb, DLS_INTERFACE_PROP_CONTAINER_UPDATE_ID, uint_val); } if ((filter_mask & DLS_UPNP_MASK_PROP_TOTAL_DELETED_CHILD_COUNT) && gupnp_didl_lite_container_total_deleted_child_count_is_set( object)) { uint_val = gupnp_didl_lite_container_get_total_deleted_child_count(object); prv_add_uint_prop(item_vb, DLS_INTERFACE_PROP_TOTAL_DELETED_CHILD_COUNT, uint_val); } if (filter_mask & DLS_UPNP_MASK_PROP_RESOURCES) prv_add_resources(item_vb, GUPNP_DIDL_LITE_OBJECT(object), filter_mask, FALSE); if (filter_mask & DLS_UPNP_MASK_PROP_ARTIST) { GUPnPDIDLLiteObject *obj = GUPNP_DIDL_LITE_OBJECT(object); const char *artist = gupnp_didl_lite_object_get_artist(obj); prv_add_string_prop (item_vb, DLS_INTERFACE_PROP_ARTIST, artist); } if (filter_mask & DLS_UPNP_MASK_PROP_ALBUM_ART_URL) { GUPnPDIDLLiteObject *obj = GUPNP_DIDL_LITE_OBJECT(object); const char *url = gupnp_didl_lite_object_get_album_art(obj); prv_add_string_prop (item_vb, DLS_INTERFACE_PROP_ALBUM_ART_URL, url); } res = prv_get_matching_resource(GUPNP_DIDL_LITE_OBJECT(object), protocol_info); if (res) { if (filter_mask & DLS_UPNP_MASK_PROP_URLS) { str_val = gupnp_didl_lite_resource_get_uri(res); prv_add_strv_prop(item_vb, DLS_INTERFACE_PROP_URLS, &str_val, 1); } prv_parse_common_resources(item_vb, res, filter_mask); g_object_unref(res); } } void dls_props_add_item(GVariantBuilder *item_vb, GUPnPDIDLLiteObject *object, const gchar *root_path, dls_upnp_prop_mask filter_mask, const gchar *protocol_info) { int track_number; GUPnPDIDLLiteResource *res; const char *str_val; char *path; GList *list; if (filter_mask & DLS_UPNP_MASK_PROP_ARTIST) prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_ARTIST, gupnp_didl_lite_object_get_artist(object)); if (filter_mask & DLS_UPNP_MASK_PROP_ARTISTS) { list = gupnp_didl_lite_object_get_artists(object); if (list != NULL) { prv_add_variant_prop(item_vb, DLS_INTERFACE_PROP_ARTISTS, prv_get_artists_prop(list)); g_list_free_full(list, g_object_unref); } } if (filter_mask & DLS_UPNP_MASK_PROP_ALBUM) prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_ALBUM, gupnp_didl_lite_object_get_album(object)); if (filter_mask & DLS_UPNP_MASK_PROP_DATE) prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_DATE, gupnp_didl_lite_object_get_date(object)); if (filter_mask & DLS_UPNP_MASK_PROP_GENRE) prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_GENRE, gupnp_didl_lite_object_get_genre(object)); if (filter_mask & DLS_UPNP_MASK_PROP_TRACK_NUMBER) { track_number = gupnp_didl_lite_object_get_track_number(object); if (track_number >= 0) prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_TRACK_NUMBER, track_number); } if (filter_mask & DLS_UPNP_MASK_PROP_ALBUM_ART_URL) prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_ALBUM_ART_URL, gupnp_didl_lite_object_get_album_art( object)); if (filter_mask & DLS_UPNP_MASK_PROP_REFPATH) { str_val = gupnp_didl_lite_item_get_ref_id( GUPNP_DIDL_LITE_ITEM(object)); if (str_val != NULL) { path = dls_path_from_id(root_path, str_val); prv_add_path_prop(item_vb, DLS_INTERFACE_PROP_REFPATH, path); g_free(path); } } res = prv_get_matching_resource(object, protocol_info); if (res) { if (filter_mask & DLS_UPNP_MASK_PROP_URLS) { str_val = gupnp_didl_lite_resource_get_uri(res); prv_add_strv_prop(item_vb, DLS_INTERFACE_PROP_URLS, &str_val, 1); } prv_parse_all_resources(item_vb, res, filter_mask); g_object_unref(res); } if (filter_mask & DLS_UPNP_MASK_PROP_RESOURCES) prv_add_resources(item_vb, object, filter_mask, TRUE); } void dls_props_add_resource(GVariantBuilder *item_vb, GUPnPDIDLLiteObject *object, dls_upnp_prop_mask filter_mask, const gchar *protocol_info) { GUPnPDIDLLiteResource *res; const char *str_val; res = prv_get_matching_resource(object, protocol_info); if (res) { if (filter_mask & DLS_UPNP_MASK_PROP_URL) { str_val = gupnp_didl_lite_resource_get_uri(res); prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_URL, str_val); } if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) prv_parse_common_resources(item_vb, res, filter_mask); else prv_parse_all_resources(item_vb, res, filter_mask); g_object_unref(res); } } static GVariant *prv_get_common_resource_property(const gchar *prop, GUPnPDIDLLiteResource *res) { gint64 int64_val; guint uint_val; const char *str_val; GVariant *retval = NULL; GUPnPProtocolInfo *protocol_info; GUPnPDLNAConversion conv; GUPnPDLNAOperation ope; GUPnPDLNAFlags flags; if (!strcmp(prop, DLS_INTERFACE_PROP_DLNA_PROFILE)) { protocol_info = gupnp_didl_lite_resource_get_protocol_info(res); if (!protocol_info) goto on_error; str_val = gupnp_protocol_info_get_dlna_profile(protocol_info); if (!str_val) goto on_error; retval = g_variant_ref_sink(g_variant_new_string(str_val)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_DLNA_CONVERSION)) { protocol_info = gupnp_didl_lite_resource_get_protocol_info(res); conv = gupnp_protocol_info_get_dlna_conversion(protocol_info); retval = prv_props_get_dlna_info_dict(conv, g_prop_dlna_ci); if (retval) retval = g_variant_ref_sink(retval); } else if (!strcmp(prop, DLS_INTERFACE_PROP_DLNA_OPERATION)) { protocol_info = gupnp_didl_lite_resource_get_protocol_info(res); ope = gupnp_protocol_info_get_dlna_operation(protocol_info); retval = prv_props_get_dlna_info_dict(ope, g_prop_dlna_op); if (retval) retval = g_variant_ref_sink(retval); } else if (!strcmp(prop, DLS_INTERFACE_PROP_DLNA_FLAGS)) { protocol_info = gupnp_didl_lite_resource_get_protocol_info(res); flags = gupnp_protocol_info_get_dlna_flags(protocol_info); retval = prv_props_get_dlna_info_dict(flags, g_prop_dlna_flags); if (retval) retval = g_variant_ref_sink(retval); } else if (!strcmp(prop, DLS_INTERFACE_PROP_MIME_TYPE)) { protocol_info = gupnp_didl_lite_resource_get_protocol_info(res); if (!protocol_info) goto on_error; str_val = gupnp_protocol_info_get_mime_type(protocol_info); if (!str_val) goto on_error; retval = g_variant_ref_sink(g_variant_new_string(str_val)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_SIZE)) { int64_val = gupnp_didl_lite_resource_get_size64(res); if (int64_val == -1) goto on_error; retval = g_variant_ref_sink(g_variant_new_int64(int64_val)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_URLS)) { str_val = gupnp_didl_lite_resource_get_uri(res); if (str_val) retval = g_variant_ref_sink(g_variant_new_strv(&str_val, 1)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_UPDATE_COUNT) && gupnp_didl_lite_resource_update_count_is_set(res)) { uint_val = gupnp_didl_lite_resource_get_update_count(res); retval = g_variant_ref_sink(g_variant_new_uint32(uint_val)); } on_error: return retval; } static GVariant *prv_get_item_resource_property(const gchar *prop, GUPnPDIDLLiteResource *res) { int int_val; GVariant *retval; retval = prv_get_common_resource_property(prop, res); if (retval) goto on_exit; if (!strcmp(prop, DLS_INTERFACE_PROP_DURATION)) { int_val = (int) gupnp_didl_lite_resource_get_duration(res); if (int_val == -1) goto on_exit; retval = g_variant_ref_sink(g_variant_new_int32(int_val)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_BITRATE)) { int_val = gupnp_didl_lite_resource_get_bitrate(res); if (int_val == -1) goto on_exit; retval = g_variant_ref_sink(g_variant_new_int32(int_val)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_SAMPLE_RATE)) { int_val = gupnp_didl_lite_resource_get_sample_freq(res); if (int_val == -1) goto on_exit; retval = g_variant_ref_sink(g_variant_new_int32(int_val)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_BITS_PER_SAMPLE)) { int_val = gupnp_didl_lite_resource_get_bits_per_sample(res); if (int_val == -1) goto on_exit; retval = g_variant_ref_sink(g_variant_new_int32(int_val)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_WIDTH)) { int_val = (int) gupnp_didl_lite_resource_get_width(res); if (int_val == -1) goto on_exit; retval = g_variant_ref_sink(g_variant_new_int32(int_val)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_HEIGHT)) { int_val = (int) gupnp_didl_lite_resource_get_height(res); if (int_val == -1) goto on_exit; retval = g_variant_ref_sink(g_variant_new_int32(int_val)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_COLOR_DEPTH)) { int_val = (int) gupnp_didl_lite_resource_get_color_depth(res); if (int_val == -1) goto on_exit; retval = g_variant_ref_sink(g_variant_new_int32(int_val)); } on_exit: return retval; } GVariant *dls_props_get_object_prop(const gchar *prop, const gchar *root_path, GUPnPDIDLLiteObject *object) { const char *object_id; const char *parent_id; gchar *path; const char *upnp_class; const char *media_spec_type; const char *title; gboolean rest; GVariant *retval = NULL; GUPnPOCMFlags dlna_managed; guint uint_val; if (!strcmp(prop, DLS_INTERFACE_PROP_PARENT)) { object_id = gupnp_didl_lite_object_get_id(object); if (!object_id) goto on_error; parent_id = gupnp_didl_lite_object_get_parent_id(object); if (!parent_id) goto on_error; if (!strcmp(object_id, "0") || !strcmp(parent_id, "-1")) { DLEYNA_LOG_DEBUG("Prop %s = %s", prop, root_path); retval = g_variant_ref_sink(g_variant_new_string( root_path)); } else { path = dls_path_from_id(root_path, parent_id); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, path); retval = g_variant_ref_sink(g_variant_new_string( path)); g_free(path); } } else if (!strcmp(prop, DLS_INTERFACE_PROP_PATH)) { object_id = gupnp_didl_lite_object_get_id(object); if (!object_id) goto on_error; path = dls_path_from_id(root_path, object_id); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, path); retval = g_variant_ref_sink(g_variant_new_string(path)); g_free(path); } else if (!strcmp(prop, DLS_INTERFACE_PROP_TYPE)) { upnp_class = gupnp_didl_lite_object_get_upnp_class(object); media_spec_type = dls_props_upnp_class_to_media_spec(upnp_class); if (!media_spec_type) goto on_error; DLEYNA_LOG_DEBUG("Prop %s = %s", prop, media_spec_type); retval = g_variant_ref_sink(g_variant_new_string( media_spec_type)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_TYPE_EX)) { upnp_class = gupnp_didl_lite_object_get_upnp_class(object); media_spec_type = dls_props_upnp_class_to_media_spec_ex(upnp_class); if (!media_spec_type) goto on_error; DLEYNA_LOG_DEBUG("Prop %s = %s", prop, media_spec_type); retval = g_variant_ref_sink(g_variant_new_string( media_spec_type)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_DISPLAY_NAME)) { title = gupnp_didl_lite_object_get_title(object); if (!title) goto on_error; DLEYNA_LOG_DEBUG("Prop %s = %s", prop, title); retval = g_variant_ref_sink(g_variant_new_string(title)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_CREATOR)) { title = gupnp_didl_lite_object_get_creator(object); if (!title) goto on_error; DLEYNA_LOG_DEBUG("Prop %s = %s", prop, title); retval = g_variant_ref_sink(g_variant_new_string(title)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_RESTRICTED)) { rest = gupnp_didl_lite_object_get_restricted(object); DLEYNA_LOG_DEBUG("Prop %s = %d", prop, rest); retval = g_variant_ref_sink(g_variant_new_boolean(rest)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_DLNA_MANAGED)) { dlna_managed = gupnp_didl_lite_object_get_dlna_managed(object); DLEYNA_LOG_DEBUG("Prop %s = %0x", prop, dlna_managed); retval = g_variant_ref_sink(prv_props_get_dlna_info_dict( dlna_managed, g_prop_dlna_ocm)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_OBJECT_UPDATE_ID) && gupnp_didl_lite_object_update_id_is_set(object)) { uint_val = gupnp_didl_lite_object_get_update_id(object); DLEYNA_LOG_DEBUG("Prop %s = %u", prop, uint_val); retval = g_variant_ref_sink(g_variant_new_uint32(uint_val)); } on_error: return retval; } GVariant *dls_props_get_item_prop(const gchar *prop, const gchar *root_path, GUPnPDIDLLiteObject *object, const gchar *protocol_info) { const gchar *str; gchar *path; gint track_number; GUPnPDIDLLiteResource *res; GVariant *retval = NULL; GList *list; #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG gchar *prop_str; #endif if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) goto on_error; if (!strcmp(prop, DLS_INTERFACE_PROP_ARTIST)) { str = gupnp_didl_lite_object_get_artist(object); if (!str) goto on_error; DLEYNA_LOG_DEBUG("Prop %s = %s", prop, str); retval = g_variant_ref_sink(g_variant_new_string(str)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_ARTISTS)) { list = gupnp_didl_lite_object_get_artists(object); if (!list) goto on_error; retval = g_variant_ref_sink(prv_get_artists_prop(list)); g_list_free_full(list, g_object_unref); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG prop_str = g_variant_print(retval, FALSE); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, prop_str); g_free(prop_str); #endif } else if (!strcmp(prop, DLS_INTERFACE_PROP_ALBUM)) { str = gupnp_didl_lite_object_get_album(object); if (!str) goto on_error; DLEYNA_LOG_DEBUG("Prop %s = %s", prop, str); retval = g_variant_ref_sink(g_variant_new_string(str)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_DATE)) { str = gupnp_didl_lite_object_get_date(object); if (!str) goto on_error; DLEYNA_LOG_DEBUG("Prop %s = %s", prop, str); retval = g_variant_ref_sink(g_variant_new_string(str)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_GENRE)) { str = gupnp_didl_lite_object_get_genre(object); if (!str) goto on_error; DLEYNA_LOG_DEBUG("Prop %s = %s", prop, str); retval = g_variant_ref_sink(g_variant_new_string(str)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_TRACK_NUMBER)) { track_number = gupnp_didl_lite_object_get_track_number(object); if (track_number < 0) goto on_error; DLEYNA_LOG_DEBUG("Prop %s = %d", prop, track_number); retval = g_variant_ref_sink( g_variant_new_int32(track_number)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_ALBUM_ART_URL)) { str = gupnp_didl_lite_object_get_album_art(object); if (!str) goto on_error; DLEYNA_LOG_DEBUG("Prop %s = %s", prop, str); retval = g_variant_ref_sink(g_variant_new_string(str)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_REFPATH)) { str = gupnp_didl_lite_item_get_ref_id( GUPNP_DIDL_LITE_ITEM(object)); if (!str) goto on_error; DLEYNA_LOG_DEBUG("Prop %s = %s", prop, str); path = dls_path_from_id(root_path, str); retval = g_variant_ref_sink(g_variant_new_string(path)); g_free(path); } else if (!strcmp(prop, DLS_INTERFACE_PROP_RESOURCES)) { retval = g_variant_ref_sink( prv_compute_resources(object, DLS_UPNP_MASK_ALL_PROPS, TRUE)); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG prop_str = g_variant_print(retval, FALSE); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, prop_str); g_free(prop_str); #endif } else { res = prv_get_matching_resource(object, protocol_info); if (!res) goto on_error; retval = prv_get_item_resource_property(prop, res); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG if (retval) { prop_str = g_variant_print(retval, FALSE); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, prop_str); g_free(prop_str); } #endif g_object_unref(res); } on_error: return retval; } GVariant *dls_props_get_container_prop(const gchar *prop, GUPnPDIDLLiteObject *object, const gchar *protocol_info) { gint child_count; gboolean searchable; GUPnPDIDLLiteContainer *container; GVariant *retval = NULL; guint uint_val; GUPnPDIDLLiteResource *res; #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG gchar *prop_str; #endif if (!GUPNP_IS_DIDL_LITE_CONTAINER(object)) goto on_error; container = (GUPnPDIDLLiteContainer *)object; if (!strcmp(prop, DLS_INTERFACE_PROP_CHILD_COUNT)) { child_count = gupnp_didl_lite_container_get_child_count(container); DLEYNA_LOG_DEBUG("Prop %s = %d", prop, child_count); if (child_count >= 0) { retval = g_variant_new_uint32((guint) child_count); retval = g_variant_ref_sink(retval); } } else if (!strcmp(prop, DLS_INTERFACE_PROP_SEARCHABLE)) { searchable = gupnp_didl_lite_container_get_searchable(container); DLEYNA_LOG_DEBUG("Prop %s = %d", prop, searchable); retval = g_variant_ref_sink( g_variant_new_boolean(searchable)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_CREATE_CLASSES)) { retval = g_variant_ref_sink( prv_compute_create_classes(container)); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG prop_str = g_variant_print(retval, FALSE); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, prop_str); g_free(prop_str); #endif } else if (!strcmp(prop, DLS_INTERFACE_PROP_CONTAINER_UPDATE_ID) && gupnp_didl_lite_container_container_update_id_is_set( container)) { uint_val = gupnp_didl_lite_container_get_container_update_id( container); DLEYNA_LOG_DEBUG("Prop %s = %u", prop, uint_val); retval = g_variant_ref_sink(g_variant_new_uint32(uint_val)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_TOTAL_DELETED_CHILD_COUNT) && gupnp_didl_lite_container_total_deleted_child_count_is_set( container)) { uint_val = gupnp_didl_lite_container_get_total_deleted_child_count( container); DLEYNA_LOG_DEBUG("Prop %s = %u", prop, uint_val); retval = g_variant_ref_sink(g_variant_new_uint32(uint_val)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_ARTIST)) { const char *strval = gupnp_didl_lite_object_get_artist( GUPNP_DIDL_LITE_OBJECT(object)); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, strval); retval = g_variant_ref_sink(g_variant_new_string(strval)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_ALBUM_ART_URL)) { const char *strval = gupnp_didl_lite_object_get_album_art( GUPNP_DIDL_LITE_OBJECT(object)); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, strval); retval = g_variant_ref_sink(g_variant_new_string(strval)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_RESOURCES)) { retval = g_variant_ref_sink( prv_compute_resources(object, DLS_UPNP_MASK_ALL_PROPS, FALSE)); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG prop_str = g_variant_print(retval, FALSE); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, prop_str); g_free(prop_str); #endif } else { res = prv_get_matching_resource(object, protocol_info); if (!res) goto on_error; retval = prv_get_common_resource_property(prop, res); #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG if (retval) { prop_str = g_variant_print(retval, FALSE); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, prop_str); g_free(prop_str); } #endif g_object_unref(res); } on_error: return retval; } static GVariant *prv_build_wl_entries(dleyna_settings_t *settings) { GVariant *result; result = dleyna_settings_white_list_entries(settings); if (result == NULL) result = g_variant_new("as", NULL); return result; } void dls_props_add_manager(dleyna_settings_t *settings, GVariantBuilder *vb) { prv_add_bool_prop(vb, DLS_INTERFACE_PROP_NEVER_QUIT, dleyna_settings_is_never_quit(settings)); prv_add_bool_prop(vb, DLS_INTERFACE_PROP_WHITE_LIST_ENABLED, dleyna_settings_is_white_list_enabled(settings)); g_variant_builder_add(vb, "{sv}", DLS_INTERFACE_PROP_WHITE_LIST_ENTRIES, prv_build_wl_entries(settings)); } GVariant *dls_props_get_manager_prop(dleyna_settings_t *settings, const gchar *prop) { GVariant *retval = NULL; gboolean b_value; #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG gchar *prop_str; #endif if (!strcmp(prop, DLS_INTERFACE_PROP_NEVER_QUIT)) { b_value = dleyna_settings_is_never_quit(settings); retval = g_variant_ref_sink(g_variant_new_boolean(b_value)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_WHITE_LIST_ENABLED)) { b_value = dleyna_settings_is_white_list_enabled(settings); retval = g_variant_ref_sink(g_variant_new_boolean(b_value)); } else if (!strcmp(prop, DLS_INTERFACE_PROP_WHITE_LIST_ENTRIES)) { retval = g_variant_ref_sink(prv_build_wl_entries(settings)); } #if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG if (retval) { prop_str = g_variant_print(retval, FALSE); DLEYNA_LOG_DEBUG("Prop %s = %s", prop, prop_str); g_free(prop_str); } #endif return retval; } GVariant *dls_props_get_error_prop(GError *error) { GVariantBuilder gvb; g_variant_builder_init(&gvb, G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(&gvb, "{sv}", DLS_INTERFACE_PROP_ERROR_ID, g_variant_new_int32(error->code)); /* NOTE: We should modify the dleyna-connector API to add a new method to * retrieve the error string. Direct call to DBUS are forbidden. * * g_variant_builder_add(&gvb, "{sv}", * DLS_INTERFACE_PROP_ERROR_NAME, * g_variant_new_string( * g_dbus_error_get_remote_error(cb_data->error))); */ g_variant_builder_add(&gvb, "{sv}", DLS_INTERFACE_PROP_ERROR_MESSAGE, g_variant_new_string(error->message)); return g_variant_builder_end(&gvb); } dleyna-server-0.6.0/libdleyna/server/props.h000077500000000000000000000131771305660312300210750ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #ifndef DLS_PROPS_H__ #define DLS_PROPS_H__ #include #include #include #include "async.h" #define DLS_UPNP_MASK_PROP_PARENT (1LL << 0) #define DLS_UPNP_MASK_PROP_TYPE (1LL << 1) #define DLS_UPNP_MASK_PROP_PATH (1LL << 2) #define DLS_UPNP_MASK_PROP_DISPLAY_NAME (1LL << 3) #define DLS_UPNP_MASK_PROP_CHILD_COUNT (1LL << 4) #define DLS_UPNP_MASK_PROP_SEARCHABLE (1LL << 5) #define DLS_UPNP_MASK_PROP_URLS (1LL << 6) #define DLS_UPNP_MASK_PROP_MIME_TYPE (1LL << 7) #define DLS_UPNP_MASK_PROP_ARTIST (1LL << 8) #define DLS_UPNP_MASK_PROP_ALBUM (1LL << 9) #define DLS_UPNP_MASK_PROP_DATE (1LL << 10) #define DLS_UPNP_MASK_PROP_GENRE (1LL << 11) #define DLS_UPNP_MASK_PROP_DLNA_PROFILE (1LL << 12) #define DLS_UPNP_MASK_PROP_TRACK_NUMBER (1LL << 13) #define DLS_UPNP_MASK_PROP_SIZE (1LL << 14) #define DLS_UPNP_MASK_PROP_DURATION (1LL << 15) #define DLS_UPNP_MASK_PROP_BITRATE (1LL << 16) #define DLS_UPNP_MASK_PROP_SAMPLE_RATE (1LL << 17) #define DLS_UPNP_MASK_PROP_BITS_PER_SAMPLE (1LL << 18) #define DLS_UPNP_MASK_PROP_WIDTH (1LL << 19) #define DLS_UPNP_MASK_PROP_HEIGHT (1LL << 20) #define DLS_UPNP_MASK_PROP_COLOR_DEPTH (1LL << 21) #define DLS_UPNP_MASK_PROP_ALBUM_ART_URL (1LL << 22) #define DLS_UPNP_MASK_PROP_RESOURCES (1LL << 23) #define DLS_UPNP_MASK_PROP_URL (1LL << 24) #define DLS_UPNP_MASK_PROP_REFPATH (1LL << 25) #define DLS_UPNP_MASK_PROP_RESTRICTED (1LL << 26) #define DLS_UPNP_MASK_PROP_DLNA_MANAGED (1LL << 27) #define DLS_UPNP_MASK_PROP_CREATOR (1LL << 28) #define DLS_UPNP_MASK_PROP_ARTISTS (1LL << 29) #define DLS_UPNP_MASK_PROP_CREATE_CLASSES (1LL << 30) #define DLS_UPNP_MASK_PROP_OBJECT_UPDATE_ID (1LL << 31) #define DLS_UPNP_MASK_PROP_UPDATE_COUNT (1LL << 32) #define DLS_UPNP_MASK_PROP_CONTAINER_UPDATE_ID (1LL << 33) #define DLS_UPNP_MASK_PROP_TOTAL_DELETED_CHILD_COUNT (1LL << 34) #define DLS_UPNP_MASK_PROP_DLNA_CONVERSION (1LL << 35) #define DLS_UPNP_MASK_PROP_DLNA_OPERATION (1LL << 36) #define DLS_UPNP_MASK_PROP_DLNA_FLAGS (1LL << 37) #define DLS_UPNP_MASK_PROP_TYPE_EX (1LL << 38) #define DLS_UPNP_MASK_ALL_PROPS 0xffffffffffffffff typedef struct dls_prop_map_t_ dls_prop_map_t; struct dls_prop_map_t_ { const gchar *upnp_prop_name; dls_upnp_prop_mask type; gboolean filter; gboolean searchable; gboolean updateable; }; void dls_prop_maps_new(GHashTable **property_map, GHashTable **filter_map); dls_upnp_prop_mask dls_props_parse_filter(GHashTable *filter_map, GVariant *filter, gchar **upnp_filter); gboolean dls_props_parse_update_filter(GHashTable *filter_map, GVariant *to_add_update, GVariant *to_delete, dls_upnp_prop_mask *mask, gchar **upnp_filter); void dls_props_add_device(GUPnPDeviceInfo *root_proxy, GUPnPDeviceInfo *proxy, GUPnPServiceProxy *ems_proxy, const dls_device_t *device, GVariantBuilder *vb); GVariant *dls_props_get_device_prop(GUPnPDeviceInfo *root_proxy, GUPnPDeviceInfo *proxy, const dls_device_t *device, const gchar *prop); gboolean dls_props_add_object(GVariantBuilder *item_vb, GUPnPDIDLLiteObject *object, const char *root_path, const gchar *parent_path, dls_upnp_prop_mask filter_mask); GVariant *dls_props_get_object_prop(const gchar *prop, const gchar *root_path, GUPnPDIDLLiteObject *object); void dls_props_add_container(GVariantBuilder *item_vb, GUPnPDIDLLiteContainer *object, dls_upnp_prop_mask filter_mask, const gchar *protocol_info, gboolean *have_child_count); void dls_props_add_child_count(GVariantBuilder *item_vb, gint value); GVariant *dls_props_get_container_prop(const gchar *prop, GUPnPDIDLLiteObject *object, const gchar *protocol_info); void dls_props_add_resource(GVariantBuilder *item_vb, GUPnPDIDLLiteObject *object, dls_upnp_prop_mask filter_mask, const gchar *protocol_info); void dls_props_add_item(GVariantBuilder *item_vb, GUPnPDIDLLiteObject *object, const gchar *root_path, dls_upnp_prop_mask filter_mask, const gchar *protocol_info); GVariant *dls_props_get_item_prop(const gchar *prop, const gchar *root_path, GUPnPDIDLLiteObject *object, const gchar *protocol_info); const gchar *dls_props_media_spec_to_upnp_class(const gchar *m2spec_class); gchar *dls_props_media_spec_ex_to_upnp_class(const gchar *m2spec_class); const gchar *dls_props_upnp_class_to_media_spec(const gchar *upnp_class); const gchar *dls_props_upnp_class_to_media_spec_ex(const gchar *upnp_class); void dls_props_add_manager(dleyna_settings_t *settings, GVariantBuilder *vb); GVariant *dls_props_get_manager_prop(dleyna_settings_t *settings, const gchar *prop); GVariant *dls_props_get_error_prop(GError *error); #endif /* DLS_PROPS_H__ */ dleyna-server-0.6.0/libdleyna/server/search.c000077500000000000000000000076431305660312300211730ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #include #include "interface.h" #include "path.h" #include "props.h" #include "search.h" gchar *dls_search_translate_search_string(GHashTable *filter_map, const gchar *search_string) { GRegex *reg; gchar *retval = NULL; GMatchInfo *match_info = NULL; gchar *prop = NULL; gchar *op = NULL; gchar *value = NULL; const gchar *translated_value; gchar *translated_type_ex; dls_prop_map_t *prop_map; GString *str; gint start_pos; gint end_pos; gint old_end_pos = 0; unsigned int skipped; unsigned int search_string_len = strlen(search_string); gchar *root_path; gchar *id; reg = g_regex_new("(\\w+)\\s+(=|!=|<|<=|>|>|contains|doesNotContain|"\ "derivedfrom|exists)\\s+"\ "(\"[^\"]*\"|true|false)", 0, 0, NULL); str = g_string_new(""); g_regex_match(reg, search_string, 0, &match_info); while (g_match_info_matches(match_info)) { prop = g_match_info_fetch(match_info, 1); if (!prop) goto on_error; op = g_match_info_fetch(match_info, 2); if (!op) goto on_error; value = g_match_info_fetch(match_info, 3); if (!value) goto on_error; /* Handle special cases where we need to translate value as well as property name */ if (!strcmp(prop, DLS_INTERFACE_PROP_TYPE)) { /* Skip the quotes */ value[strlen(value) - 1] = 0; translated_value = dls_props_media_spec_to_upnp_class( value + 1); if (!translated_value) goto on_error; g_free(value); value = g_strdup_printf("\"%s\"", translated_value); } else if (!strcmp(prop, DLS_INTERFACE_PROP_TYPE_EX)) { /* Skip the quotes */ value[strlen(value) - 1] = 0; translated_type_ex = dls_props_media_spec_ex_to_upnp_class( value + 1); if (!translated_type_ex) goto on_error; g_free(value); value = g_strdup_printf("\"%s\"", translated_type_ex); g_free(translated_type_ex); } else if (!strcmp(prop, DLS_INTERFACE_PROP_PARENT) || !strcmp(prop, DLS_INTERFACE_PROP_PATH)) { value[strlen(value) - 1] = 0; if (!dls_path_get_path_and_id(value + 1, &root_path, &id, NULL)) goto on_error; g_free(root_path); g_free(value); value = g_strdup_printf("\"%s\"", id); g_free(id); } prop_map = g_hash_table_lookup(filter_map, prop); if (!prop_map) goto on_error; if (!prop_map->searchable) goto on_error; if (!g_match_info_fetch_pos(match_info, 0, &start_pos, &end_pos)) goto on_error; skipped = start_pos - old_end_pos; if (skipped > 0) g_string_append_len(str, &search_string[old_end_pos], skipped); g_string_append_printf(str, "%s %s %s", prop_map->upnp_prop_name, op, value); old_end_pos = end_pos; g_free(value); g_free(prop); g_free(op); value = NULL; prop = NULL; op = NULL; g_match_info_next(match_info, NULL); } skipped = search_string_len - old_end_pos; if (skipped > 0) g_string_append_len(str, &search_string[old_end_pos], skipped); retval = g_string_free(str, FALSE); str = NULL; on_error: g_free(value); g_free(prop); g_free(op); if (match_info) g_match_info_free(match_info); if (str) g_string_free(str, TRUE); g_regex_unref(reg); return retval; } dleyna-server-0.6.0/libdleyna/server/search.h000077500000000000000000000017661305660312300212000ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #ifndef DLS_SEARCH_H__ #define DLS_SEARCH_H__ #include gchar *dls_search_translate_search_string(GHashTable *filter_map, const gchar *search_string); #endif /* DLS_PROPS_H__ */ dleyna-server-0.6.0/libdleyna/server/server.c000066400000000000000000001302441305660312300212230ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * Regis Merlino * */ #include #include #include #include #include #include #include #include #include "async.h" #include "client.h" #include "control-point-server.h" #include "device.h" #include "interface.h" #include "manager.h" #include "path.h" #include "server.h" #include "upnp.h" #ifdef UA_PREFIX #define DLS_PRG_NAME UA_PREFIX " dLeyna/" VERSION #else #define DLS_PRG_NAME "dLeyna/" VERSION #endif typedef struct dls_server_context_t_ dls_server_context_t; struct dls_server_context_t_ { dleyna_connector_id_t connection; dleyna_task_processor_t *processor; const dleyna_connector_t *connector; dleyna_settings_t *settings; guint dls_id[DLS_MANAGER_INTERFACE_INFO_MAX]; GHashTable *watchers; dls_upnp_t *upnp; dls_manager_t *manager; }; static dls_server_context_t g_context; static const gchar g_root_introspection[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; static const gchar g_server_introspection[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; static const gchar *g_manager_interfaces[DLS_MANAGER_INTERFACE_INFO_MAX] = { /* MUST be in the exact same order as g_root_introspection */ DLEYNA_SERVER_INTERFACE_MANAGER, DLS_INTERFACE_PROPERTIES }; const dleyna_connector_t *dls_server_get_connector(void) { return g_context.connector; } dleyna_task_processor_t *dls_server_get_task_processor(void) { return g_context.processor; } static void prv_process_sync_task(dls_task_t *task) { dls_client_t *client; const gchar *client_name; switch (task->type) { case DLS_TASK_GET_VERSION: task->result = g_variant_ref_sink(g_variant_new_string( VERSION)); dls_task_complete(task); break; case DLS_TASK_GET_SERVERS: task->result = dls_upnp_get_device_ids(g_context.upnp); dls_task_complete(task); break; case DLS_TASK_RESCAN: dls_upnp_rescan(g_context.upnp); dls_task_complete(task); break; case DLS_TASK_SET_PROTOCOL_INFO: client_name = dleyna_task_queue_get_source(task->atom.queue_id); client = g_hash_table_lookup(g_context.watchers, client_name); if (client) { g_free(client->protocol_info); if (task->ut.protocol_info.protocol_info[0]) { client->protocol_info = task->ut.protocol_info.protocol_info; task->ut.protocol_info.protocol_info = NULL; } else { client->protocol_info = NULL; } } dls_task_complete(task); break; case DLS_TASK_SET_PREFER_LOCAL_ADDRESSES: client_name = dleyna_task_queue_get_source(task->atom.queue_id); client = g_hash_table_lookup(g_context.watchers, client_name); if (client) { client->prefer_local_addresses = task->ut.prefer_local_addresses.prefer; } dls_task_complete(task); break; case DLS_TASK_GET_UPLOAD_STATUS: dls_upnp_get_upload_status(g_context.upnp, task); break; case DLS_TASK_GET_UPLOAD_IDS: dls_upnp_get_upload_ids(g_context.upnp, task); break; case DLS_TASK_CANCEL_UPLOAD: dls_upnp_cancel_upload(g_context.upnp, task); break; default: goto finished; break; } dleyna_task_queue_task_completed(task->atom.queue_id); finished: return; } static void prv_async_task_complete(dls_task_t *task, GError *error) { DLEYNA_LOG_DEBUG("Enter"); if (!error) { dls_task_complete(task); } else { dls_task_fail(task, error); g_error_free(error); } dleyna_task_queue_task_completed(task->atom.queue_id); DLEYNA_LOG_DEBUG("Exit"); } static void prv_process_async_task(dls_task_t *task) { dls_async_task_t *async_task = (dls_async_task_t *)task; dls_client_t *client; const gchar *client_name; DLEYNA_LOG_DEBUG("Enter"); async_task->cancellable = g_cancellable_new(); client_name = dleyna_task_queue_get_source(task->atom.queue_id); client = g_hash_table_lookup(g_context.watchers, client_name); switch (task->type) { case DLS_TASK_MANAGER_GET_PROP: dls_manager_get_prop(g_context.manager, g_context.settings, task, prv_async_task_complete); break; case DLS_TASK_MANAGER_GET_ALL_PROPS: dls_manager_get_all_props(g_context.manager, g_context.settings, task, prv_async_task_complete); break; case DLS_TASK_MANAGER_SET_PROP: dls_manager_set_prop(g_context.manager, g_context.settings, task, prv_async_task_complete); break; case DLS_TASK_GET_CHILDREN: dls_upnp_get_children(g_context.upnp, client, task, prv_async_task_complete); break; case DLS_TASK_GET_PROP: dls_upnp_get_prop(g_context.upnp, client, task, prv_async_task_complete); break; case DLS_TASK_GET_ALL_PROPS: dls_upnp_get_all_props(g_context.upnp, client, task, prv_async_task_complete); break; case DLS_TASK_SEARCH: dls_upnp_search(g_context.upnp, client, task, prv_async_task_complete); break; case DLS_TASK_BROWSE_OBJECTS: dls_upnp_browse_objects(g_context.upnp, client, task, prv_async_task_complete); break; case DLS_TASK_GET_RESOURCE: dls_upnp_get_resource(g_context.upnp, client, task, prv_async_task_complete); break; case DLS_TASK_UPLOAD_TO_ANY: dls_upnp_upload_to_any(g_context.upnp, client, task, prv_async_task_complete); break; case DLS_TASK_UPLOAD: dls_upnp_upload(g_context.upnp, client, task, prv_async_task_complete); break; case DLS_TASK_DELETE_OBJECT: dls_upnp_delete_object(g_context.upnp, client, task, prv_async_task_complete); break; case DLS_TASK_CREATE_CONTAINER: dls_upnp_create_container(g_context.upnp, client, task, prv_async_task_complete); break; case DLS_TASK_CREATE_CONTAINER_IN_ANY: dls_upnp_create_container_in_any(g_context.upnp, client, task, prv_async_task_complete); break; case DLS_TASK_UPDATE_OBJECT: dls_upnp_update_object(g_context.upnp, client, task, prv_async_task_complete); break; case DLS_TASK_GET_OBJECT_METADATA: dls_upnp_get_object_metadata(g_context.upnp, client, task, prv_async_task_complete); break; case DLS_TASK_CREATE_REFERENCE: dls_upnp_create_reference(g_context.upnp, client, task, prv_async_task_complete); break; case DLS_TASK_GET_ICON: dls_upnp_get_icon(g_context.upnp, client, task, prv_async_task_complete); break; case DLS_TASK_WAKE: dls_upnp_wake(g_context.upnp, client, task, prv_async_task_complete); break; default: break; } DLEYNA_LOG_DEBUG("Exit"); } static void prv_process_task(dleyna_task_atom_t *task, gpointer user_data) { dls_task_t *client_task = (dls_task_t *)task; if (client_task->synchronous) prv_process_sync_task(client_task); else prv_process_async_task(client_task); } static void prv_cancel_task(dleyna_task_atom_t *task, gpointer user_data) { dls_task_cancel((dls_task_t *)task); } static void prv_delete_task(dleyna_task_atom_t *task, gpointer user_data) { dls_task_delete((dls_task_t *)task); } static void prv_manager_root_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation); static void prv_manager_props_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation); static void prv_object_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation); static void prv_item_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation); static void prv_con_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation); static void prv_props_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation); static void prv_device_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation); static const dleyna_connector_dispatch_cb_t g_root_vtables[DLS_MANAGER_INTERFACE_INFO_MAX] = { /* MUST be in the exact same order as g_root_introspection */ prv_manager_root_method_call, prv_manager_props_method_call }; static const dleyna_connector_dispatch_cb_t g_server_vtables[DLS_INTERFACE_INFO_MAX] = { /* MUST be in the exact same order as g_server_introspection */ prv_props_method_call, prv_object_method_call, prv_con_method_call, prv_item_method_call, prv_device_method_call }; static void prv_remove_client(const gchar *name) { dleyna_task_processor_remove_queues_for_source(g_context.processor, name); (void) g_hash_table_remove(g_context.watchers, name); if (g_hash_table_size(g_context.watchers) == 0) if (!dleyna_settings_is_never_quit(g_context.settings)) dleyna_task_processor_set_quitting(g_context.processor); } static void prv_lost_client(const gchar *name) { DLEYNA_LOG_DEBUG("Lost Client %s", name); prv_remove_client(name); } static void prv_add_task(dls_task_t *task, const gchar *source, const gchar *sink) { dls_client_t *client; const dleyna_task_queue_key_t *queue_id; if (!g_hash_table_lookup(g_context.watchers, source)) { client = g_new0(dls_client_t, 1); client->prefer_local_addresses = TRUE; g_context.connector->watch_client(source); g_hash_table_insert(g_context.watchers, g_strdup(source), client); } queue_id = dleyna_task_processor_lookup_queue(g_context.processor, source, sink); if (!queue_id) queue_id = dleyna_task_processor_add_queue( g_context.processor, source, sink, DLEYNA_TASK_QUEUE_FLAG_AUTO_START, prv_process_task, prv_cancel_task, prv_delete_task); dleyna_task_queue_add_task(queue_id, &task->atom); } static void prv_manager_root_method_call( dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation) { dls_task_t *task; if (!strcmp(method, DLS_INTERFACE_RELEASE)) { prv_remove_client(sender); g_context.connector->return_response(invocation, NULL); goto finished; } else if (!strcmp(method, DLS_INTERFACE_RESCAN)) { task = dls_task_rescan_new(invocation); } else if (!strcmp(method, DLS_INTERFACE_GET_VERSION)) { task = dls_task_get_version_new(invocation); } else if (!strcmp(method, DLS_INTERFACE_GET_SERVERS)) { task = dls_task_get_servers_new(invocation); } else if (!strcmp(method, DLS_INTERFACE_SET_PROTOCOL_INFO)) { task = dls_task_set_protocol_info_new(invocation, parameters); } else if (!strcmp(method, DLS_INTERFACE_PREFER_LOCAL_ADDRESSES)) { task = dls_task_prefer_local_addresses_new(invocation, parameters); } else { goto finished; } prv_add_task(task, sender, DLS_SERVER_SINK); finished: return; } static void prv_manager_props_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation) { dls_task_t *task; GError *error = NULL; if (!strcmp(method, DLS_INTERFACE_GET_ALL)) task = dls_task_manager_get_props_new(invocation, object, parameters, &error); else if (!strcmp(method, DLS_INTERFACE_GET)) task = dls_task_manager_get_prop_new(invocation, object, parameters, &error); else if (!strcmp(method, DLS_INTERFACE_SET)) task = dls_task_manager_set_prop_new(invocation, object, parameters, &error); else goto finished; if (!task) { g_context.connector->return_error(invocation, error); g_error_free(error); goto finished; } prv_add_task(task, sender, task->target.path); finished: return; } gboolean dls_server_get_object_info(const gchar *object_path, gchar **root_path, gchar **object_id, dls_device_t **device, GError **error) { if (!dls_path_get_path_and_id(object_path, root_path, object_id, error)) { DLEYNA_LOG_WARNING("Bad object %s", object_path); goto on_error; } *device = dls_device_from_path(*root_path, dls_upnp_get_device_udn_map(g_context.upnp)); if (*device == NULL) { *device = dls_device_from_path(*root_path, dls_upnp_get_sleeping_device_udn_map( g_context.upnp)); } if (*device == NULL) { DLEYNA_LOG_WARNING("Cannot locate device for %s", *root_path); *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "Cannot locate device corresponding to" " the specified path"); g_free(*root_path); g_free(*object_id); goto on_error; } return TRUE; on_error: return FALSE; } gboolean dls_server_is_device_sleeping(dls_device_t *dev) { if (dev->sleeping_context != NULL) return TRUE; else return dev->sleeping; } void dls_server_delete_sleeping_device(dls_device_t *dev) { if (dev->sleeping_context != NULL) dls_upnp_delete_sleeping_device(g_context.upnp, dev); } static const gchar *prv_get_device_id(const gchar *object, GError **error) { dls_device_t *device; gchar *root_path; gchar *id; if (!dls_server_get_object_info(object, &root_path, &id, &device, error)) goto on_error; g_free(id); g_free(root_path); return device->path; on_error: return NULL; } static void prv_object_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation) { dls_task_t *task; GError *error = NULL; if (!strcmp(method, DLS_INTERFACE_DELETE)) task = dls_task_delete_new(invocation, object, &error); else if (!strcmp(method, DLS_INTERFACE_UPDATE)) task = dls_task_update_new(invocation, object, parameters, &error); else if (!strcmp(method, DLS_INTERFACE_GET_METADATA)) task = dls_task_get_metadata_new(invocation, object, &error); else goto finished; if (!task) { g_context.connector->return_error(invocation, error); g_error_free(error); goto finished; } prv_add_task(task, sender, task->target.device->path); finished: return; } static void prv_item_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation) { dls_task_t *task; GError *error = NULL; if (!strcmp(method, DLS_INTERFACE_GET_COMPATIBLE_RESOURCE)) { task = dls_task_get_resource_new(invocation, object, parameters, &error); if (!task) { g_context.connector->return_error(invocation, error); g_error_free(error); goto finished; } prv_add_task(task, sender, task->target.device->path); } finished: return; } static void prv_con_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation) { dls_task_t *task; GError *error = NULL; if (!strcmp(method, DLS_INTERFACE_LIST_CHILDREN)) task = dls_task_get_children_new(invocation, object, parameters, TRUE, TRUE, &error); else if (!strcmp(method, DLS_INTERFACE_LIST_CHILDREN_EX)) task = dls_task_get_children_ex_new(invocation, object, parameters, TRUE, TRUE, &error); else if (!strcmp(method, DLS_INTERFACE_LIST_ITEMS)) task = dls_task_get_children_new(invocation, object, parameters, TRUE, FALSE, &error); else if (!strcmp(method, DLS_INTERFACE_LIST_ITEMS_EX)) task = dls_task_get_children_ex_new(invocation, object, parameters, TRUE, FALSE, &error); else if (!strcmp(method, DLS_INTERFACE_LIST_CONTAINERS)) task = dls_task_get_children_new(invocation, object, parameters, FALSE, TRUE, &error); else if (!strcmp(method, DLS_INTERFACE_LIST_CONTAINERS_EX)) task = dls_task_get_children_ex_new(invocation, object, parameters, FALSE, TRUE, &error); else if (!strcmp(method, DLS_INTERFACE_SEARCH_OBJECTS)) task = dls_task_search_new(invocation, object, parameters, &error); else if (!strcmp(method, DLS_INTERFACE_SEARCH_OBJECTS_EX)) task = dls_task_search_ex_new(invocation, object, parameters, &error); else if (!strcmp(method, DLS_INTERFACE_UPLOAD)) task = dls_task_upload_new(invocation, object, parameters, &error); else if (!strcmp(method, DLS_INTERFACE_CREATE_CONTAINER)) task = dls_task_create_container_new_generic(invocation, DLS_TASK_CREATE_CONTAINER, object, parameters, &error); else if (!strcmp(method, DLS_INTERFACE_GET_COMPATIBLE_RESOURCE)) task = dls_task_get_resource_new(invocation, object, parameters, &error); else if (!strcmp(method, DLS_INTERFACE_CREATE_REFERENCE)) task = dls_task_create_reference_new(invocation, DLS_TASK_CREATE_REFERENCE, object, parameters, &error); else goto finished; if (!task) { g_context.connector->return_error(invocation, error); g_error_free(error); goto finished; } prv_add_task(task, sender, task->target.device->path); finished: return; } static void prv_props_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation) { dls_task_t *task; GError *error = NULL; if (!strcmp(method, DLS_INTERFACE_GET_ALL)) task = dls_task_get_props_new(invocation, object, parameters, &error); else if (!strcmp(method, DLS_INTERFACE_GET)) task = dls_task_get_prop_new(invocation, object, parameters, &error); else goto finished; if (!task) { g_context.connector->return_error(invocation, error); g_error_free(error); goto finished; } prv_add_task(task, sender, task->target.device->path); finished: return; } static void prv_device_method_call(dleyna_connector_id_t conn, const gchar *sender, const gchar *object, const gchar *interface, const gchar *method, GVariant *parameters, dleyna_connector_msg_id_t invocation) { dls_task_t *task; GError *error = NULL; const gchar *device_id; const dleyna_task_queue_key_t *queue_id; if (!strcmp(method, DLS_INTERFACE_UPLOAD_TO_ANY)) { task = dls_task_upload_to_any_new(invocation, object, parameters, &error); } else if (!strcmp(method, DLS_INTERFACE_CREATE_CONTAINER_IN_ANY)) { task = dls_task_create_container_new_generic( invocation, DLS_TASK_CREATE_CONTAINER_IN_ANY, object, parameters, &error); } else if (!strcmp(method, DLS_INTERFACE_GET_UPLOAD_STATUS)) { task = dls_task_get_upload_status_new(invocation, object, parameters, &error); } else if (!strcmp(method, DLS_INTERFACE_GET_UPLOAD_IDS)) { task = dls_task_get_upload_ids_new(invocation, object, &error); } else if (!strcmp(method, DLS_INTERFACE_CANCEL_UPLOAD)) { task = dls_task_cancel_upload_new(invocation, object, parameters, &error); } else if (!strcmp(method, DLS_INTERFACE_GET_ICON)) { task = dls_task_get_icon_new(invocation, object, parameters, &error); } else if (!strcmp(method, DLS_INTERFACE_BROWSE_OBJECTS)) { task = dls_task_browse_objects_new(invocation, object, parameters, &error); } else if (!strcmp(method, DLS_INTERFACE_WAKE)) { task = dls_task_wake_new(invocation, object, &error); } else if (!strcmp(method, DLS_INTERFACE_CANCEL)) { task = NULL; device_id = prv_get_device_id(object, &error); if (!device_id) goto on_error; queue_id = dleyna_task_processor_lookup_queue( g_context.processor, sender, device_id); if (queue_id) dleyna_task_processor_cancel_queue(queue_id); g_context.connector->return_response(invocation, NULL); goto finished; } else { goto finished; } on_error: if (!task) { g_context.connector->return_error(invocation, error); g_error_free(error); goto finished; } prv_add_task(task, sender, task->target.device->path); finished: return; } static void prv_found_media_server(const gchar *path, void *user_data) { (void) g_context.connector->notify(g_context.connection, DLEYNA_SERVER_OBJECT, DLEYNA_SERVER_INTERFACE_MANAGER, DLS_INTERFACE_FOUND_SERVER, g_variant_new("(o)", path), NULL); } static void prv_lost_media_server(const gchar *path, void *user_data) { (void) g_context.connector->notify(g_context.connection, DLEYNA_SERVER_OBJECT, DLEYNA_SERVER_INTERFACE_MANAGER, DLS_INTERFACE_LOST_SERVER, g_variant_new("(o)", path), NULL); dleyna_task_processor_remove_queues_for_sink(g_context.processor, path); } static void prv_unregister_client(gpointer user_data) { dls_client_t *client = user_data; if (client) { g_free(client->protocol_info); g_free(client); } } dls_upnp_t *dls_server_get_upnp(void) { return g_context.upnp; } static void prv_white_list_init(void) { gboolean enabled; GVariant *entries; dleyna_white_list_t *wl; DLEYNA_LOG_DEBUG("Enter"); enabled = dleyna_settings_is_white_list_enabled(g_context.settings); entries = dleyna_settings_white_list_entries(g_context.settings); wl = dls_manager_get_white_list(g_context.manager); dleyna_white_list_enable(wl, enabled); dleyna_white_list_add_entries(wl, entries); DLEYNA_LOG_DEBUG("Exit"); } static gboolean prv_control_point_start_service( dleyna_connector_id_t connection) { gboolean retval = TRUE; uint i; g_context.connection = connection; for (i = 0; i < DLS_MANAGER_INTERFACE_INFO_MAX; i++) g_context.dls_id[i] = g_context.connector->publish_object( connection, DLEYNA_SERVER_OBJECT, TRUE, g_manager_interfaces[i], g_root_vtables + i); if (g_context.dls_id[DLS_MANAGER_INTERFACE_MANAGER]) { g_context.upnp = dls_upnp_new(connection, dleyna_settings_port(g_context.settings), g_server_vtables, prv_found_media_server, prv_lost_media_server, NULL); g_context.manager = dls_manager_new(connection, dls_upnp_get_context_manager( g_context.upnp)); prv_white_list_init(); } else { retval = FALSE; } return retval; } static void prv_control_point_stop_service(void) { uint i; if (g_context.manager) { dls_manager_delete(g_context.manager); g_context.manager = NULL; } if (g_context.upnp) { dls_upnp_unsubscribe(g_context.upnp); dls_upnp_delete(g_context.upnp); g_context.upnp = NULL; } if (g_context.connection) { for (i = 0; i < DLS_MANAGER_INTERFACE_INFO_MAX; i++) if (g_context.dls_id[i]) g_context.connector->unpublish_object( g_context.connection, g_context.dls_id[i]); } } static void prv_control_point_initialize(const dleyna_connector_t *connector, dleyna_task_processor_t *processor, dleyna_settings_t *settings) { memset(&g_context, 0, sizeof(g_context)); g_context.connector = connector; g_context.processor = processor; g_context.settings = settings; g_context.connector->set_client_lost_cb(prv_lost_client); g_context.watchers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, prv_unregister_client); g_set_prgname(DLS_PRG_NAME); } static void prv_control_point_free(void) { if (g_context.watchers) g_hash_table_unref(g_context.watchers); } static const gchar *prv_control_point_server_name(void) { return DLEYNA_SERVER_NAME; } static const gchar *prv_control_point_server_introspection(void) { return g_server_introspection; } static const gchar *prv_control_point_root_introspection(void) { return g_root_introspection; } static const gchar *prv_control_point_get_version(void) { return VERSION; } static const dleyna_control_point_t g_control_point = { prv_control_point_initialize, prv_control_point_free, prv_control_point_server_name, prv_control_point_server_introspection, prv_control_point_root_introspection, prv_control_point_start_service, prv_control_point_stop_service, prv_control_point_get_version }; const dleyna_control_point_t *dleyna_control_point_get_server(void) { return &g_control_point; } dleyna-server-0.6.0/libdleyna/server/server.h000066400000000000000000000031231305660312300212230ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Regis Merlino * */ #ifndef DLS_SERVER_H__ #define DLS_SERVER_H__ #include #include #define DLS_SERVER_SINK "dleyna-server" typedef struct dls_device_t_ dls_device_t; typedef struct dls_device_context_t_ dls_device_context_t; typedef struct dls_upnp_t_ dls_upnp_t; gboolean dls_server_get_object_info(const gchar *object_path, gchar **root_path, gchar **object_id, dls_device_t **device, GError **error); dls_upnp_t *dls_server_get_upnp(void); gboolean dls_server_is_device_sleeping(dls_device_t *dev); void dls_server_delete_sleeping_device(dls_device_t *dev); dleyna_task_processor_t *dls_server_get_task_processor(void); const dleyna_connector_t *dls_server_get_connector(void); #endif /* DLS_SERVER_H__ */ dleyna-server-0.6.0/libdleyna/server/sort.c000077500000000000000000000042301305660312300207020ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #include #include "props.h" #include "sort.h" gchar *dls_sort_translate_sort_string(GHashTable *filter_map, const gchar *sort_string) { GRegex *reg; gchar *retval = NULL; GMatchInfo *match_info = NULL; gchar *prop = NULL; gchar *op = NULL; dls_prop_map_t *prop_map; GString *str; if (!g_regex_match_simple( "^((\\+|\\-)([^,\\+\\-]+))?(,(\\+|\\-)([^,\\+\\-]+))*$", sort_string, 0, 0)) goto no_free; reg = g_regex_new("(\\+|\\-)(\\w+)", 0, 0, NULL); str = g_string_new(""); g_regex_match(reg, sort_string, 0, &match_info); while (g_match_info_matches(match_info)) { op = g_match_info_fetch(match_info, 1); if (!op) goto on_error; prop = g_match_info_fetch(match_info, 2); if (!prop) goto on_error; prop_map = g_hash_table_lookup(filter_map, prop); if (!prop_map) goto on_error; if (!prop_map->searchable) goto on_error; g_string_append_printf(str, "%s%s,", op, prop_map->upnp_prop_name); g_free(prop); g_free(op); prop = NULL; op = NULL; g_match_info_next(match_info, NULL); } if (str->len > 0) str = g_string_truncate(str, str->len - 1); retval = g_string_free(str, FALSE); str = NULL; on_error: g_free(prop); g_free(op); if (match_info) g_match_info_free(match_info); if (str) g_string_free(str, TRUE); g_regex_unref(reg); no_free: return retval; } dleyna-server-0.6.0/libdleyna/server/sort.h000077500000000000000000000017561305660312300207210ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #ifndef DLS_SORT_H__ #define DLS_SORT_H__ #include gchar *dls_sort_translate_sort_string(GHashTable *filter_map, const gchar *sort_string); #endif /* DLS_SORT_H__ */ dleyna-server-0.6.0/libdleyna/server/task.c000077500000000000000000000443331305660312300206650ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #include #include #include "async.h" #include "path.h" static void prv_delete(dls_task_t *task) { if (!task->synchronous) dls_async_task_delete((dls_async_task_t *)task); switch (task->type) { case DLS_TASK_GET_CHILDREN: if (task->ut.get_children.filter) g_variant_unref(task->ut.get_children.filter); g_free(task->ut.get_children.sort_by); break; case DLS_TASK_MANAGER_GET_ALL_PROPS: case DLS_TASK_GET_ALL_PROPS: g_free(task->ut.get_props.interface_name); break; case DLS_TASK_MANAGER_GET_PROP: case DLS_TASK_GET_PROP: g_free(task->ut.get_prop.interface_name); g_free(task->ut.get_prop.prop_name); break; case DLS_TASK_MANAGER_SET_PROP: g_free(task->ut.set_prop.interface_name); g_free(task->ut.set_prop.prop_name); g_variant_unref(task->ut.set_prop.params); break; case DLS_TASK_SEARCH: g_free(task->ut.search.query); if (task->ut.search.filter) g_variant_unref(task->ut.search.filter); g_free(task->ut.search.sort_by); break; case DLS_TASK_BROWSE_OBJECTS: if (task->ut.browse_objects.objects) g_variant_unref(task->ut.browse_objects.objects); if (task->ut.browse_objects.filter) g_variant_unref(task->ut.browse_objects.filter); break; case DLS_TASK_GET_RESOURCE: if (task->ut.resource.filter) g_variant_unref(task->ut.resource.filter); g_free(task->ut.resource.protocol_info); break; case DLS_TASK_SET_PROTOCOL_INFO: if (task->ut.protocol_info.protocol_info) g_free(task->ut.protocol_info.protocol_info); break; case DLS_TASK_UPLOAD_TO_ANY: case DLS_TASK_UPLOAD: g_free(task->ut.upload.display_name); g_free(task->ut.upload.file_path); break; case DLS_TASK_CREATE_CONTAINER: case DLS_TASK_CREATE_CONTAINER_IN_ANY: g_free(task->ut.create_container.display_name); g_free(task->ut.create_container.type); if (task->ut.create_container.child_types) g_variant_unref(task->ut.create_container.child_types); break; case DLS_TASK_UPDATE_OBJECT: if (task->ut.update.to_add_update) g_variant_unref(task->ut.update.to_add_update); if (task->ut.update.to_delete) g_variant_unref(task->ut.update.to_delete); break; case DLS_TASK_CREATE_REFERENCE: g_free(task->ut.create_reference.item_path); break; case DLS_TASK_GET_ICON: g_free(task->ut.get_icon.resolution); g_free(task->ut.get_icon.mime_type); break; case DLS_TASK_WAKE: break; default: break; } g_free(task->target.path); g_free(task->target.root_path); g_free(task->target.id); if (task->result) g_variant_unref(task->result); g_free(task); } dls_task_t *dls_task_rescan_new(dleyna_connector_msg_id_t invocation) { dls_task_t *task = g_new0(dls_task_t, 1); task->type = DLS_TASK_RESCAN; task->invocation = invocation; task->synchronous = TRUE; return task; } dls_task_t *dls_task_get_version_new(dleyna_connector_msg_id_t invocation) { dls_task_t *task = g_new0(dls_task_t, 1); task->type = DLS_TASK_GET_VERSION; task->invocation = invocation; task->result_format = "(@s)"; task->synchronous = TRUE; return task; } dls_task_t *dls_task_get_servers_new(dleyna_connector_msg_id_t invocation) { dls_task_t *task = g_new0(dls_task_t, 1); task->type = DLS_TASK_GET_SERVERS; task->invocation = invocation; task->result_format = "(@ao)"; task->synchronous = TRUE; return task; } dls_task_t *dls_task_manager_get_prop_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task = (dls_task_t *)g_new0(dls_async_task_t, 1); g_variant_get(parameters, "(ss)", &task->ut.get_prop.interface_name, &task->ut.get_prop.prop_name); g_strstrip(task->ut.get_prop.interface_name); g_strstrip(task->ut.get_prop.prop_name); task->target.path = g_strstrip(g_strdup(path)); task->type = DLS_TASK_MANAGER_GET_PROP; task->invocation = invocation; task->result_format = "(v)"; return task; } dls_task_t *dls_task_manager_get_props_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task = (dls_task_t *)g_new0(dls_async_task_t, 1); g_variant_get(parameters, "(s)", &task->ut.get_props.interface_name); g_strstrip(task->ut.get_props.interface_name); task->target.path = g_strstrip(g_strdup(path)); task->type = DLS_TASK_MANAGER_GET_ALL_PROPS; task->invocation = invocation; task->result_format = "(@a{sv})"; return task; } dls_task_t *dls_task_manager_set_prop_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task = (dls_task_t *)g_new0(dls_async_task_t, 1); g_variant_get(parameters, "(ssv)", &task->ut.set_prop.interface_name, &task->ut.set_prop.prop_name, &task->ut.set_prop.params); g_strstrip(task->ut.set_prop.interface_name); g_strstrip(task->ut.set_prop.prop_name); task->target.path = g_strstrip(g_strdup(path)); task->type = DLS_TASK_MANAGER_SET_PROP; task->invocation = invocation; return task; } static gboolean prv_set_task_target_info(dls_task_t *task, const gchar *path, GError **error) { task->target.path = g_strdup(path); g_strstrip(task->target.path); return dls_server_get_object_info(path, &task->target.root_path, &task->target.id, &task->target.device, error); } static gboolean prv_is_task_allowed(dls_task_t *task, GError **error) { if (dls_server_is_device_sleeping(task->target.device)) { if (task->type != DLS_TASK_WAKE && task->type != DLS_TASK_GET_PROP) goto on_error; } return TRUE; on_error: *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Target device is sleeping"); return FALSE; } static dls_task_t *prv_m2spec_task_new(dls_task_type_t type, dleyna_connector_msg_id_t invocation, const gchar *path, const gchar *result_format, GError **error, gboolean synchronous) { dls_task_t *task; if (synchronous) { task = g_new0(dls_task_t, 1); task->synchronous = TRUE; } else { task = (dls_task_t *)g_new0(dls_async_task_t, 1); } task->type = type; if (!prv_set_task_target_info(task, path, error) || !prv_is_task_allowed(task, error)) { prv_delete(task); task = NULL; goto finished; } task->invocation = invocation; task->result_format = result_format; finished: return task; } dls_task_t *dls_task_get_children_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, gboolean items, gboolean containers, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_GET_CHILDREN, invocation, path, "(@aa{sv})", error, FALSE); if (!task) goto finished; task->ut.get_children.containers = containers; task->ut.get_children.items = items; g_variant_get(parameters, "(uu@as)", &task->ut.get_children.start, &task->ut.get_children.count, &task->ut.get_children.filter); task->ut.get_children.sort_by = g_strdup(""); finished: return task; } dls_task_t *dls_task_get_children_ex_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, gboolean items, gboolean containers, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_GET_CHILDREN, invocation, path, "(@aa{sv})", error, FALSE); if (!task) goto finished; task->ut.get_children.containers = containers; task->ut.get_children.items = items; g_variant_get(parameters, "(uu@ass)", &task->ut.get_children.start, &task->ut.get_children.count, &task->ut.get_children.filter, &task->ut.get_children.sort_by); finished: return task; } dls_task_t *dls_task_get_prop_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_GET_PROP, invocation, path, "(v)", error, FALSE); if (!task) goto finished; g_variant_get(parameters, "(ss)", &task->ut.get_prop.interface_name, &task->ut.get_prop.prop_name); g_strstrip(task->ut.get_prop.interface_name); g_strstrip(task->ut.get_prop.prop_name); finished: return task; } dls_task_t *dls_task_get_props_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_GET_ALL_PROPS, invocation, path, "(@a{sv})", error, FALSE); if (!task) goto finished; g_variant_get(parameters, "(s)", &task->ut.get_props.interface_name); g_strstrip(task->ut.get_props.interface_name); finished: return task; } dls_task_t *dls_task_search_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_SEARCH, invocation, path, "(@aa{sv})", error, FALSE); if (!task) goto finished; g_variant_get(parameters, "(suu@as)", &task->ut.search.query, &task->ut.search.start, &task->ut.search.count, &task->ut.search.filter); task->ut.search.sort_by = g_strdup(""); finished: return task; } dls_task_t *dls_task_search_ex_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_SEARCH, invocation, path, "(@aa{sv}u)", error, FALSE); if (!task) goto finished; g_variant_get(parameters, "(suu@ass)", &task->ut.search.query, &task->ut.search.start, &task->ut.search.count, &task->ut.search.filter, &task->ut.search.sort_by); task->multiple_retvals = TRUE; finished: return task; } dls_task_t *dls_task_browse_objects_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_BROWSE_OBJECTS, invocation, path, "(@aa{sv})", error, FALSE); if (!task) goto finished; g_variant_get(parameters, "(@ao@as)", &task->ut.browse_objects.objects, &task->ut.browse_objects.filter); finished: return task; } dls_task_t *dls_task_get_resource_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_GET_RESOURCE, invocation, path, "(@a{sv})", error, FALSE); if (!task) goto finished; g_variant_get(parameters, "(s@as)", &task->ut.resource.protocol_info, &task->ut.resource.filter); finished: return task; } dls_task_t *dls_task_set_protocol_info_new(dleyna_connector_msg_id_t invocation, GVariant *parameters) { dls_task_t *task = g_new0(dls_task_t, 1); task->type = DLS_TASK_SET_PROTOCOL_INFO; task->invocation = invocation; task->synchronous = TRUE; g_variant_get(parameters, "(s)", &task->ut.protocol_info.protocol_info); return task; } static dls_task_t *prv_upload_new_generic(dls_task_type_t type, dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(type, invocation, path, "(uo)", error, FALSE); if (!task) goto finished; g_variant_get(parameters, "(ss)", &task->ut.upload.display_name, &task->ut.upload.file_path); g_strstrip(task->ut.upload.file_path); task->multiple_retvals = TRUE; finished: return task; } dls_task_t *dls_task_prefer_local_addresses_new( dleyna_connector_msg_id_t invocation, GVariant *parameters) { dls_task_t *task = g_new0(dls_task_t, 1); task->type = DLS_TASK_SET_PREFER_LOCAL_ADDRESSES; task->invocation = invocation; task->synchronous = TRUE; g_variant_get(parameters, "(b)", &task->ut.prefer_local_addresses.prefer); return task; } dls_task_t *dls_task_upload_to_any_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { return prv_upload_new_generic(DLS_TASK_UPLOAD_TO_ANY, invocation, path, parameters, error); } dls_task_t *dls_task_upload_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { return prv_upload_new_generic(DLS_TASK_UPLOAD, invocation, path, parameters, error); } dls_task_t *dls_task_get_upload_status_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_GET_UPLOAD_STATUS, invocation, path, "(stt)", error, TRUE); if (!task) goto finished; g_variant_get(parameters, "(u)", &task->ut.upload_action.upload_id); task->multiple_retvals = TRUE; finished: return task; } dls_task_t *dls_task_get_upload_ids_new(dleyna_connector_msg_id_t invocation, const gchar *path, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_GET_UPLOAD_IDS, invocation, path, "(@au)", error, TRUE); return task; } dls_task_t *dls_task_cancel_upload_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_CANCEL_UPLOAD, invocation, path, NULL, error, TRUE); if (!task) goto finished; g_variant_get(parameters, "(u)", &task->ut.upload_action.upload_id); finished: return task; } dls_task_t *dls_task_delete_new(dleyna_connector_msg_id_t invocation, const gchar *path, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_DELETE_OBJECT, invocation, path, NULL, error, FALSE); return task; } dls_task_t *dls_task_create_container_new_generic( dleyna_connector_msg_id_t invocation, dls_task_type_t type, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(type, invocation, path, "(@o)", error, FALSE); if (!task) goto finished; g_variant_get(parameters, "(ss@as)", &task->ut.create_container.display_name, &task->ut.create_container.type, &task->ut.create_container.child_types); finished: return task; } dls_task_t *dls_task_create_reference_new(dleyna_connector_msg_id_t invocation, dls_task_type_t type, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(type, invocation, path, "(@o)", error, FALSE); if (!task) goto finished; g_variant_get(parameters, "(o)", &task->ut.create_reference.item_path); (void) g_strstrip(task->ut.create_reference.item_path); finished: return task; } dls_task_t *dls_task_update_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_UPDATE_OBJECT, invocation, path, NULL, error, FALSE); if (!task) goto finished; g_variant_get(parameters, "(@a{sv}@as)", &task->ut.update.to_add_update, &task->ut.update.to_delete); finished: return task; } dls_task_t *dls_task_get_metadata_new(dleyna_connector_msg_id_t invocation, const gchar *path, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_GET_OBJECT_METADATA, invocation, path, "(@s)", error, FALSE); return task; } dls_task_t *dls_task_get_icon_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_GET_ICON, invocation, path, "(@ays)", error, FALSE); if (!task) goto finished; task->multiple_retvals = TRUE; g_variant_get(parameters, "(ss)", &task->ut.get_icon.mime_type, &task->ut.get_icon.resolution); finished: return task; } dls_task_t *dls_task_wake_new(dleyna_connector_msg_id_t invocation, const gchar *path, GError **error) { dls_task_t *task; task = prv_m2spec_task_new(DLS_TASK_WAKE, invocation, path, NULL, error, FALSE); return task; } void dls_task_complete(dls_task_t *task) { GVariant *variant = NULL; if (!task) goto finished; if (task->invocation) { if (task->result_format) { if (task->multiple_retvals) variant = g_variant_ref(task->result); else variant = g_variant_ref_sink( g_variant_new(task->result_format, task->result)); } dls_server_get_connector()->return_response(task->invocation, variant); if (variant) g_variant_unref(variant); task->invocation = NULL; } finished: return; } void dls_task_fail(dls_task_t *task, GError *error) { if (!task) goto finished; if (task->invocation) { dls_server_get_connector()->return_error(task->invocation, error); task->invocation = NULL; } finished: return; } void dls_task_cancel(dls_task_t *task) { GError *error; if (!task) goto finished; if (task->invocation) { error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_CANCELLED, "Operation cancelled."); dls_server_get_connector()->return_error(task->invocation, error); task->invocation = NULL; g_error_free(error); } if (!task->synchronous) dls_async_task_cancel((dls_async_task_t *)task); finished: return; } void dls_task_delete(dls_task_t *task) { GError *error; if (!task) goto finished; if (task->invocation) { error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_DIED, "Unable to complete command."); dls_server_get_connector()->return_error(task->invocation, error); g_error_free(error); } prv_delete(task); finished: return; } dleyna-server-0.6.0/libdleyna/server/task.h000077500000000000000000000221461305660312300206700ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #ifndef DLS_TASK_H__ #define DLS_TASK_H__ #include #include #include #include #include "server.h" enum dls_task_type_t_ { DLS_TASK_GET_VERSION, DLS_TASK_GET_SERVERS, DLS_TASK_RESCAN, DLS_TASK_GET_CHILDREN, DLS_TASK_GET_ALL_PROPS, DLS_TASK_GET_PROP, DLS_TASK_SEARCH, DLS_TASK_BROWSE_OBJECTS, DLS_TASK_GET_RESOURCE, DLS_TASK_SET_PREFER_LOCAL_ADDRESSES, DLS_TASK_SET_PROTOCOL_INFO, DLS_TASK_UPLOAD_TO_ANY, DLS_TASK_UPLOAD, DLS_TASK_GET_UPLOAD_STATUS, DLS_TASK_GET_UPLOAD_IDS, DLS_TASK_CANCEL_UPLOAD, DLS_TASK_DELETE_OBJECT, DLS_TASK_CREATE_CONTAINER, DLS_TASK_CREATE_CONTAINER_IN_ANY, DLS_TASK_UPDATE_OBJECT, DLS_TASK_GET_OBJECT_METADATA, DLS_TASK_CREATE_REFERENCE, DLS_TASK_GET_ICON, DLS_TASK_MANAGER_GET_ALL_PROPS, DLS_TASK_MANAGER_GET_PROP, DLS_TASK_MANAGER_SET_PROP, DLS_TASK_WAKE }; typedef enum dls_task_type_t_ dls_task_type_t; typedef void (*dls_cancel_task_t)(void *handle); typedef struct dls_task_get_children_t_ dls_task_get_children_t; struct dls_task_get_children_t_ { gboolean containers; gboolean items; guint start; guint count; GVariant *filter; gchar *sort_by; }; typedef struct dls_task_get_props_t_ dls_task_get_props_t; struct dls_task_get_props_t_ { gchar *interface_name; }; typedef struct dls_task_get_prop_t_ dls_task_get_prop_t; struct dls_task_get_prop_t_ { gchar *prop_name; gchar *interface_name; }; typedef struct dls_task_set_prop_t_ dls_task_set_prop_t; struct dls_task_set_prop_t_ { gchar *prop_name; gchar *interface_name; GVariant *params; }; typedef struct dls_task_search_t_ dls_task_search_t; struct dls_task_search_t_ { gchar *query; guint start; guint count; gchar *sort_by; GVariant *filter; }; typedef struct dls_task_browse_objects_t_ dls_task_browse_objects_t; struct dls_task_browse_objects_t_ { GVariant *objects; GVariant *filter; }; typedef struct dls_task_get_resource_t_ dls_task_get_resource_t; struct dls_task_get_resource_t_ { gchar *protocol_info; GVariant *filter; }; typedef struct dls_task_set_prefer_local_addresses_t_ dls_task_set_prefer_local_addresses_t; struct dls_task_set_prefer_local_addresses_t_ { gboolean prefer; }; typedef struct dls_task_set_protocol_info_t_ dls_task_set_protocol_info_t; struct dls_task_set_protocol_info_t_ { gchar *protocol_info; }; typedef struct dls_task_upload_t_ dls_task_upload_t; struct dls_task_upload_t_ { gchar *display_name; gchar *file_path; }; typedef struct dls_task_upload_action_t_ dls_task_upload_action_t; struct dls_task_upload_action_t_ { guint upload_id; }; typedef struct dls_task_create_container_t_ dls_task_create_container_t; struct dls_task_create_container_t_ { gchar *display_name; gchar *type; GVariant *child_types; }; typedef struct dls_task_update_t_ dls_task_update_t; struct dls_task_update_t_ { GVariant *to_add_update; GVariant *to_delete; }; typedef struct dls_task_create_reference_t_ dls_task_create_reference_t; struct dls_task_create_reference_t_ { gchar *item_path; }; typedef struct dls_task_target_info_t_ dls_task_target_info_t; struct dls_task_target_info_t_ { gchar *path; gchar *root_path; gchar *id; dls_device_t *device; }; typedef struct dls_task_get_icon_t_ dls_task_get_icon_t; struct dls_task_get_icon_t_ { gchar *mime_type; gchar *resolution; }; typedef struct dls_task_t_ dls_task_t; struct dls_task_t_ { dleyna_task_atom_t atom; /* pseudo inheritance - MUST be first field */ dls_task_type_t type; dls_task_target_info_t target; const gchar *result_format; GVariant *result; dleyna_connector_msg_id_t invocation; gboolean synchronous; gboolean multiple_retvals; union { dls_task_get_children_t get_children; dls_task_get_props_t get_props; dls_task_get_prop_t get_prop; dls_task_set_prop_t set_prop; dls_task_search_t search; dls_task_get_resource_t resource; dls_task_set_prefer_local_addresses_t prefer_local_addresses; dls_task_set_protocol_info_t protocol_info; dls_task_upload_t upload; dls_task_upload_action_t upload_action; dls_task_create_container_t create_container; dls_task_update_t update; dls_task_create_reference_t create_reference; dls_task_get_icon_t get_icon; dls_task_browse_objects_t browse_objects; } ut; }; dls_task_t *dls_task_rescan_new(dleyna_connector_msg_id_t invocation); dls_task_t *dls_task_get_version_new(dleyna_connector_msg_id_t invocation); dls_task_t *dls_task_get_servers_new(dleyna_connector_msg_id_t invocation); dls_task_t *dls_task_get_children_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, gboolean items, gboolean containers, GError **error); dls_task_t *dls_task_get_children_ex_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, gboolean items, gboolean containers, GError **error); dls_task_t *dls_task_get_prop_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_get_props_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_search_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_search_ex_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_browse_objects_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_get_resource_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_set_protocol_info_new(dleyna_connector_msg_id_t invocation, GVariant *parameters); dls_task_t *dls_task_prefer_local_addresses_new( dleyna_connector_msg_id_t invocation, GVariant *parameters); dls_task_t *dls_task_upload_to_any_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_upload_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_get_upload_status_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_get_upload_ids_new(dleyna_connector_msg_id_t invocation, const gchar *path, GError **error); dls_task_t *dls_task_cancel_upload_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_delete_new(dleyna_connector_msg_id_t invocation, const gchar *path, GError **error); dls_task_t *dls_task_create_container_new_generic( dleyna_connector_msg_id_t invocation, dls_task_type_t type, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_create_reference_new(dleyna_connector_msg_id_t invocation, dls_task_type_t type, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_update_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_get_metadata_new(dleyna_connector_msg_id_t invocation, const gchar *path, GError **error); dls_task_t *dls_task_get_icon_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_manager_get_prop_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_manager_get_props_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_manager_set_prop_new(dleyna_connector_msg_id_t invocation, const gchar *path, GVariant *parameters, GError **error); dls_task_t *dls_task_wake_new(dleyna_connector_msg_id_t invocation, const gchar *path, GError **error); void dls_task_cancel(dls_task_t *task); void dls_task_complete(dls_task_t *task); void dls_task_fail(dls_task_t *task, GError *error); void dls_task_delete(dls_task_t *task); #endif /* DLS_TASK_H__ */ dleyna-server-0.6.0/libdleyna/server/upnp.c000077500000000000000000001021501305660312300206750ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #include #include #include #include #include #include #include #include "async.h" #include "device.h" #include "interface.h" #include "path.h" #include "search.h" #include "sort.h" #include "upnp.h" #define DLS_DMS_DEVICE_TYPE "urn:schemas-upnp-org:device:MediaServer:" struct dls_upnp_t_ { dleyna_connector_id_t connection; const dleyna_connector_dispatch_cb_t *interface_info; GHashTable *filter_map; GHashTable *property_map; dls_upnp_callback_t found_server; dls_upnp_callback_t lost_server; GUPnPContextManager *context_manager; void *user_data; GHashTable *device_udn_map; GHashTable *sleeping_device_udn_map; GHashTable *device_uc_map; }; /* Private structure used in service task */ typedef struct prv_device_new_ct_t_ prv_device_new_ct_t; struct prv_device_new_ct_t_ { dls_upnp_t *upnp; char *udn; gchar *ip_address; dls_device_t *device; const dleyna_task_queue_key_t *queue_id; }; static void prv_device_new_free(prv_device_new_ct_t *priv_t) { if (priv_t) { g_free(priv_t->udn); g_free(priv_t->ip_address); g_free(priv_t); } } static void prv_device_chain_end(gboolean cancelled, gpointer data) { dls_device_t *device; prv_device_new_ct_t *priv_t = (prv_device_new_ct_t *)data; DLEYNA_LOG_DEBUG("Enter"); device = priv_t->device; if (cancelled) goto on_clear; DLEYNA_LOG_DEBUG("Notify new server available: %s", device->path); g_hash_table_insert(priv_t->upnp->device_udn_map, g_strdup(priv_t->udn), device); priv_t->upnp->found_server(device->path, priv_t->upnp->user_data); on_clear: g_hash_table_remove(priv_t->upnp->device_uc_map, priv_t->udn); if (cancelled) dls_device_delete(device); prv_device_new_free(priv_t); DLEYNA_LOG_DEBUG_NL(); } static void prv_device_context_switch_end(gboolean cancelled, gpointer data) { prv_device_new_ct_t *priv_t = (prv_device_new_ct_t *)data; DLEYNA_LOG_DEBUG("Enter"); g_hash_table_remove(priv_t->upnp->device_uc_map, priv_t->udn); prv_device_new_free(priv_t); DLEYNA_LOG_DEBUG("Exit"); } static const dleyna_task_queue_key_t *prv_create_device_queue( prv_device_new_ct_t **priv_t) { const dleyna_task_queue_key_t *queue_id; *priv_t = g_new0(prv_device_new_ct_t, 1); queue_id = dleyna_task_processor_add_queue( dls_server_get_task_processor(), dleyna_service_task_create_source(), DLS_SERVER_SINK, DLEYNA_TASK_QUEUE_FLAG_AUTO_REMOVE, dleyna_service_task_process_cb, dleyna_service_task_cancel_cb, dleyna_service_task_delete_cb); dleyna_task_queue_set_finally(queue_id, prv_device_chain_end); dleyna_task_queue_set_user_data(queue_id, *priv_t); return queue_id; } static void prv_update_device_context(prv_device_new_ct_t *priv_t, dls_upnp_t *upnp, const char *udn, dls_device_t *device, const gchar *ip_address, const dleyna_task_queue_key_t *queue_id) { priv_t->upnp = upnp; priv_t->udn = g_strdup(udn); priv_t->ip_address = g_strdup(ip_address); priv_t->queue_id = queue_id; priv_t->device = device; g_hash_table_insert(upnp->device_uc_map, g_strdup(udn), priv_t); } static GUPnPDeviceInfo *prv_lookup_dms_child_device(GUPnPDeviceInfo *proxy) { GList *child_devices; GList *next; const gchar *device_type; GUPnPDeviceInfo *info = NULL; GUPnPDeviceInfo *child_info = NULL; child_devices = gupnp_device_info_list_device_types(proxy); next = child_devices; while (next != NULL) { device_type = (gchar *)next->data; child_info = gupnp_device_info_get_device(proxy, device_type); if (g_str_has_prefix(device_type, DLS_DMS_DEVICE_TYPE)) { break; } else { info = prv_lookup_dms_child_device(child_info); g_object_unref(child_info); child_info = NULL; if (info != NULL) { child_info = info; break; } } next = g_list_next(next); } g_list_free_full(child_devices, (GDestroyNotify)g_free); return child_info; } static void prv_device_available_cb(GUPnPControlPoint *cp, GUPnPDeviceProxy *proxy, gpointer user_data) { dls_upnp_t *upnp = user_data; const char *udn; dls_device_t *device; const gchar *ip_address; dls_device_context_t *context; const dleyna_task_queue_key_t *queue_id; unsigned int i; prv_device_new_ct_t *priv_t; GUPnPDeviceInfo *device_proxy = (GUPnPDeviceInfo *)proxy; GUPnPDeviceInfo *device_info = NULL; const gchar *device_type; gboolean subscribe = FALSE; gpointer key; gpointer val; udn = gupnp_device_info_get_udn(device_proxy); ip_address = gssdp_client_get_host_ip( GSSDP_CLIENT(gupnp_control_point_get_context(cp))); if (!udn || !ip_address) goto on_error; DLEYNA_LOG_DEBUG("UDN %s", udn); DLEYNA_LOG_DEBUG("IP Address %s", ip_address); device_type = gupnp_device_info_get_device_type(device_proxy); if (!g_str_has_prefix(device_type, DLS_DMS_DEVICE_TYPE)) { device_info = prv_lookup_dms_child_device(device_proxy); if (device_info == NULL) goto on_error; } else { device_info = device_proxy; } device = g_hash_table_lookup(upnp->device_udn_map, udn); if (!device) { device = g_hash_table_lookup(upnp->sleeping_device_udn_map, udn); if (device != NULL) { if (g_hash_table_lookup_extended( upnp->sleeping_device_udn_map, udn, &key, &val)) { g_hash_table_steal( upnp->sleeping_device_udn_map, udn); g_free(key); } g_hash_table_insert(upnp->device_udn_map, g_strdup(udn), device); if (device->wake_on_timeout_id) { DLEYNA_LOG_DEBUG("Stop WAKE-ON watcher..."); (void) g_source_remove( device->wake_on_timeout_id); device->wake_on_timeout_id = 0; } dls_device_delete_context(device->sleeping_context); device->sleeping_context = NULL; device->sleeping = FALSE; subscribe = TRUE; } } if (!device) { priv_t = g_hash_table_lookup(upnp->device_uc_map, udn); if (priv_t) device = priv_t->device; } if (!device) { DLEYNA_LOG_DEBUG("Device not found. Adding"); DLEYNA_LOG_DEBUG_NL(); queue_id = prv_create_device_queue(&priv_t); device = dls_device_new(upnp->connection, proxy, device_info, ip_address, upnp->interface_info, upnp->property_map, udn, queue_id); prv_update_device_context(priv_t, upnp, udn, device, ip_address, queue_id); } else { DLEYNA_LOG_DEBUG("Device Found"); for (i = 0; i < device->contexts->len; ++i) { context = g_ptr_array_index(device->contexts, i); if (!strcmp(context->ip_address, ip_address)) break; } if (i == device->contexts->len) { DLEYNA_LOG_DEBUG("Adding Context"); (void) dls_device_append_new_context(device, ip_address, proxy, device_info); if (subscribe) dls_device_subscribe_to_service_changes(device); } DLEYNA_LOG_DEBUG_NL(); } on_error: return; } static gboolean prv_subscribe_to_service_changes(gpointer user_data) { dls_device_t *device = user_data; device->timeout_id = 0; dls_device_subscribe_to_service_changes(device); return FALSE; } static void prv_device_unavailable_cb(GUPnPControlPoint *cp, GUPnPDeviceProxy *proxy, gpointer user_data) { dls_upnp_t *upnp = user_data; const char *udn; dls_device_t *device; const gchar *ip_address; unsigned int i; dls_device_context_t *context; gboolean subscribed; gboolean construction_ctx = FALSE; gboolean under_construction = FALSE; prv_device_new_ct_t *priv_t; const dleyna_task_queue_key_t *queue_id; dls_device_context_t *lost_context; gpointer key; gpointer val; DLEYNA_LOG_DEBUG("Enter"); udn = gupnp_device_info_get_udn((GUPnPDeviceInfo *)proxy); ip_address = gupnp_context_get_host_ip( gupnp_control_point_get_context(cp)); if (!udn || !ip_address) goto on_error; DLEYNA_LOG_DEBUG("UDN %s", udn); DLEYNA_LOG_DEBUG("IP Address %s", ip_address); device = g_hash_table_lookup(upnp->device_udn_map, udn); if (!device) { priv_t = g_hash_table_lookup(upnp->device_uc_map, udn); if (priv_t) { device = priv_t->device; under_construction = TRUE; } } if (!device) { DLEYNA_LOG_WARNING("Device not found. Ignoring"); goto on_error; } for (i = 0; i < device->contexts->len; ++i) { context = g_ptr_array_index(device->contexts, i); if (!strcmp(context->ip_address, ip_address)) break; } if (i >= device->contexts->len) goto on_error; subscribed = (context->cds.subscribed || context->ems.subscribed); if (under_construction) construction_ctx = !strcmp(context->ip_address, priv_t->ip_address); g_ptr_array_set_free_func(device->contexts, NULL); lost_context = g_ptr_array_remove_index(device->contexts, i); g_ptr_array_set_free_func(device->contexts, (GDestroyNotify)dls_device_delete_context); if (device->contexts->len == 0) { if (!under_construction) { DLEYNA_LOG_DEBUG("Last Context lost."); if (!device->sleeping) { DLEYNA_LOG_DEBUG("Delete device."); upnp->lost_server(device->path, upnp->user_data); g_hash_table_remove(upnp->device_udn_map, udn); } else { DLEYNA_LOG_DEBUG("Persist sleeping device."); dleyna_task_processor_remove_queues_for_sink( dls_server_get_task_processor(), device->path); g_hash_table_insert( upnp->sleeping_device_udn_map, g_strdup(udn), device); if (g_hash_table_lookup_extended( upnp->device_udn_map, udn, &key, &val)) { g_hash_table_steal(upnp->device_udn_map, udn); g_free(key); } device->sleeping_context = lost_context; lost_context = NULL; } } else { DLEYNA_LOG_WARNING( "Device under construction. Cancelling"); dleyna_task_processor_cancel_queue(priv_t->queue_id); } } else if (under_construction && construction_ctx) { DLEYNA_LOG_WARNING( "Device under construction. Switching context"); /* Cancel previous contruction task chain */ g_hash_table_remove(priv_t->upnp->device_uc_map, priv_t->udn); dleyna_task_queue_set_finally(priv_t->queue_id, prv_device_context_switch_end); dleyna_task_processor_cancel_queue(priv_t->queue_id); /* Create a new construction task chain */ context = dls_device_get_context(device, NULL); queue_id = prv_create_device_queue(&priv_t); prv_update_device_context(priv_t, upnp, udn, device, context->ip_address, queue_id); /* Start tasks from current construction step */ dls_device_construct(device, context, upnp->connection, upnp->interface_info, upnp->property_map, queue_id); } else if (subscribed && !device->timeout_id) { DLEYNA_LOG_DEBUG("Subscribe on new context"); device->timeout_id = g_timeout_add_seconds(1, prv_subscribe_to_service_changes, device); } if (lost_context != NULL) dls_device_delete_context(lost_context); on_error: DLEYNA_LOG_DEBUG("Exit"); DLEYNA_LOG_DEBUG_NL(); return; } static void prv_on_context_available(GUPnPContextManager *context_manager, GUPnPContext *context, gpointer user_data) { dls_upnp_t *upnp = user_data; GUPnPControlPoint *cp; cp = gupnp_control_point_new( context, "upnp:rootdevice"); g_signal_connect(cp, "device-proxy-available", G_CALLBACK(prv_device_available_cb), upnp); g_signal_connect(cp, "device-proxy-unavailable", G_CALLBACK(prv_device_unavailable_cb), upnp); gssdp_resource_browser_set_active(GSSDP_RESOURCE_BROWSER(cp), TRUE); gupnp_context_manager_manage_control_point(upnp->context_manager, cp); g_object_unref(cp); } dls_upnp_t *dls_upnp_new(dleyna_connector_id_t connection, guint port, const dleyna_connector_dispatch_cb_t *dispatch_table, dls_upnp_callback_t found_server, dls_upnp_callback_t lost_server, void *user_data) { dls_upnp_t *upnp = g_new0(dls_upnp_t, 1); upnp->connection = connection; upnp->interface_info = dispatch_table; upnp->user_data = user_data; upnp->found_server = found_server; upnp->lost_server = lost_server; upnp->device_udn_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, dls_device_delete); upnp->sleeping_device_udn_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, dls_device_delete); upnp->device_uc_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); dls_prop_maps_new(&upnp->property_map, &upnp->filter_map); upnp->context_manager = gupnp_context_manager_create(port); g_signal_connect(upnp->context_manager, "context-available", G_CALLBACK(prv_on_context_available), upnp); return upnp; } void dls_upnp_delete(dls_upnp_t *upnp) { if (upnp) { g_signal_handlers_disconnect_by_func (G_OBJECT (upnp->context_manager), G_CALLBACK(prv_on_context_available), upnp); g_object_unref(upnp->context_manager); g_hash_table_unref(upnp->property_map); g_hash_table_unref(upnp->filter_map); g_hash_table_unref(upnp->device_udn_map); g_hash_table_unref(upnp->sleeping_device_udn_map); g_hash_table_unref(upnp->device_uc_map); g_free(upnp); } } GVariant *dls_upnp_get_device_ids(dls_upnp_t *upnp) { GVariantBuilder vb; GHashTableIter iter; gpointer value; dls_device_t *device; GVariant *retval; DLEYNA_LOG_DEBUG("Enter"); g_variant_builder_init(&vb, G_VARIANT_TYPE("ao")); g_hash_table_iter_init(&iter, upnp->device_udn_map); while (g_hash_table_iter_next(&iter, NULL, &value)) { device = value; DLEYNA_LOG_DEBUG("Have device %s", device->path); g_variant_builder_add(&vb, "o", device->path); } g_hash_table_iter_init(&iter, upnp->sleeping_device_udn_map); while (g_hash_table_iter_next(&iter, NULL, &value)) { device = value; DLEYNA_LOG_DEBUG("Have sleeping device %s", device->path); g_variant_builder_add(&vb, "o", device->path); } retval = g_variant_ref_sink(g_variant_builder_end(&vb)); DLEYNA_LOG_DEBUG("Exit"); return retval; } GHashTable *dls_upnp_get_device_udn_map(dls_upnp_t *upnp) { return upnp->device_udn_map; } GHashTable *dls_upnp_get_sleeping_device_udn_map(dls_upnp_t *upnp) { return upnp->sleeping_device_udn_map; } void dls_upnp_delete_sleeping_device(dls_upnp_t *upnp, dls_device_t *device) { const char *udn; dls_device_context_t *ctx = device->sleeping_context; udn = gupnp_device_info_get_udn((GUPnPDeviceInfo *)ctx->device_proxy); upnp->lost_server(device->path, upnp->user_data); g_hash_table_remove(upnp->sleeping_device_udn_map, udn); } void dls_upnp_get_children(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_async_bas_t *cb_task_data; gchar *upnp_filter = NULL; gchar *sort_by = NULL; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Path: %s", task->target.path); DLEYNA_LOG_DEBUG("Start: %u", task->ut.get_children.start); DLEYNA_LOG_DEBUG("Count: %u", task->ut.get_children.count); cb_data->cb = cb; cb_task_data = &cb_data->ut.bas; cb_task_data->filter_mask = dls_props_parse_filter(upnp->filter_map, task->ut.get_children.filter, &upnp_filter); DLEYNA_LOG_DEBUG("Filter Mask 0x%"G_GUINT64_FORMAT"x", cb_task_data->filter_mask); sort_by = dls_sort_translate_sort_string(upnp->filter_map, task->ut.get_children.sort_by); if (!sort_by) { DLEYNA_LOG_WARNING("Invalid Sort Criteria"); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_QUERY, "Sort Criteria are not valid"); goto on_error; } DLEYNA_LOG_DEBUG("Sort By %s", sort_by); cb_task_data->protocol_info = client->protocol_info; dls_device_get_children(client, task, upnp_filter, sort_by); on_error: if (!cb_data->action) (void) g_idle_add(dls_async_task_complete, cb_data); g_free(sort_by); g_free(upnp_filter); DLEYNA_LOG_DEBUG("Exit with %s", !cb_data->action ? "FAIL" : "SUCCESS"); } void dls_upnp_get_all_props(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { gboolean root_object; dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_async_get_all_t *cb_task_data; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Path: %s", task->target.path); DLEYNA_LOG_DEBUG("Interface %s", task->ut.get_prop.interface_name); cb_data->cb = cb; cb_task_data = &cb_data->ut.get_all; root_object = task->target.id[0] == '0' && task->target.id[1] == 0; DLEYNA_LOG_DEBUG("Root Object = %d", root_object); cb_task_data->protocol_info = client->protocol_info; cb_task_data->filter_mask = DLS_UPNP_MASK_ALL_PROPS; dls_device_get_all_props(client, task, root_object); DLEYNA_LOG_DEBUG("Exit with SUCCESS"); } void dls_upnp_get_prop(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { gboolean root_object; dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_async_get_prop_t *cb_task_data; dls_prop_map_t *prop_map; dls_task_get_prop_t *task_data; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Path: %s", task->target.path); DLEYNA_LOG_DEBUG("Interface %s", task->ut.get_prop.interface_name); DLEYNA_LOG_DEBUG("Prop.%s", task->ut.get_prop.prop_name); task_data = &task->ut.get_prop; cb_data->cb = cb; cb_task_data = &cb_data->ut.get_prop; root_object = task->target.id[0] == '0' && task->target.id[1] == 0; DLEYNA_LOG_DEBUG("Root Object = %d", root_object); cb_task_data->protocol_info = client->protocol_info; prop_map = g_hash_table_lookup(upnp->filter_map, task_data->prop_name); dls_device_get_prop(client, task, prop_map, root_object); DLEYNA_LOG_DEBUG("Exit with SUCCESS"); } void dls_upnp_search(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { gchar *upnp_filter = NULL; gchar *upnp_query = NULL; gchar *sort_by = NULL; dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_async_bas_t *cb_task_data; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Path: %s", task->target.path); DLEYNA_LOG_DEBUG("Query: %s", task->ut.search.query); DLEYNA_LOG_DEBUG("Start: %u", task->ut.search.start); DLEYNA_LOG_DEBUG("Count: %u", task->ut.search.count); cb_data->cb = cb; cb_task_data = &cb_data->ut.bas; cb_task_data->filter_mask = dls_props_parse_filter(upnp->filter_map, task->ut.search.filter, &upnp_filter); DLEYNA_LOG_DEBUG("Filter Mask 0x%"G_GUINT64_FORMAT"x", cb_task_data->filter_mask); upnp_query = dls_search_translate_search_string(upnp->filter_map, task->ut.search.query); if (!upnp_query) { DLEYNA_LOG_WARNING("Query string is not valid:%s", task->ut.search.query); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_QUERY, "Query string is not valid."); goto on_error; } DLEYNA_LOG_DEBUG("UPnP Query %s", upnp_query); sort_by = dls_sort_translate_sort_string(upnp->filter_map, task->ut.search.sort_by); if (!sort_by) { DLEYNA_LOG_WARNING("Invalid Sort Criteria"); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_QUERY, "Sort Criteria are not valid"); goto on_error; } DLEYNA_LOG_DEBUG("Sort By %s", sort_by); cb_task_data->protocol_info = client->protocol_info; dls_device_search(client, task, upnp_filter, upnp_query, sort_by); on_error: if (!cb_data->action) (void) g_idle_add(dls_async_task_complete, cb_data); g_free(sort_by); g_free(upnp_query); g_free(upnp_filter); DLEYNA_LOG_DEBUG("Exit with %s", !cb_data->action ? "FAIL" : "SUCCESS"); } void dls_upnp_browse_objects(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_async_browse_objects_t *cb_task_data; DLEYNA_LOG_DEBUG("Enter"); cb_data->cb = cb; cb_task_data = &cb_data->ut.browse_objects; cb_task_data->get_all.protocol_info = client->protocol_info; cb_task_data->get_all.filter_mask = dls_props_parse_filter(upnp->filter_map, task->ut.browse_objects.filter, &cb_task_data->upnp_filter); DLEYNA_LOG_DEBUG("Filter Mask 0x%"G_GUINT64_FORMAT"x", cb_task_data->get_all.filter_mask); dls_device_browse_objects(client, task); DLEYNA_LOG_DEBUG("Exit"); } void dls_upnp_get_resource(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_async_get_all_t *cb_task_data; gchar *upnp_filter = NULL; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Protocol Info: %s ", task->ut.resource.protocol_info); cb_data->cb = cb; cb_task_data = &cb_data->ut.get_all; DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, task->target.id); cb_task_data->filter_mask = dls_props_parse_filter(upnp->filter_map, task->ut.resource.filter, &upnp_filter); DLEYNA_LOG_DEBUG("Filter Mask 0x%"G_GUINT64_FORMAT"x", cb_task_data->filter_mask); dls_device_get_resource(client, task, upnp_filter); g_free(upnp_filter); DLEYNA_LOG_DEBUG("Exit"); } static gboolean prv_compute_mime_and_class(dls_task_t *task, dls_async_upload_t *cb_task_data, GError **error) { gchar *content_type = NULL; if (!g_file_test(task->ut.upload.file_path, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS)) { DLEYNA_LOG_WARNING( "File %s does not exist or is not a regular file", task->ut.upload.file_path); *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OBJECT_NOT_FOUND, "File %s does not exist or is not a regular file", task->ut.upload.file_path); goto on_error; } content_type = g_content_type_guess(task->ut.upload.file_path, NULL, 0, NULL); if (!content_type) { DLEYNA_LOG_WARNING("Unable to determine Content Type for %s", task->ut.upload.file_path); *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_MIME, "Unable to determine Content Type for %s", task->ut.upload.file_path); goto on_error; } cb_task_data->mime_type = g_content_type_get_mime_type(content_type); g_free(content_type); if (!cb_task_data->mime_type) { DLEYNA_LOG_WARNING("Unable to determine MIME Type for %s", task->ut.upload.file_path); *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_MIME, "Unable to determine MIME Type for %s", task->ut.upload.file_path); goto on_error; } if (g_content_type_is_a(cb_task_data->mime_type, "image/*")) { cb_task_data->object_class = "object.item.imageItem"; } else if (g_content_type_is_a(cb_task_data->mime_type, "audio/*")) { cb_task_data->object_class = "object.item.audioItem"; } else if (g_content_type_is_a(cb_task_data->mime_type, "video/*")) { cb_task_data->object_class = "object.item.videoItem"; } else { DLEYNA_LOG_WARNING("Unsupported MIME Type %s", cb_task_data->mime_type); *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_MIME, "Unsupported MIME Type %s", cb_task_data->mime_type); goto on_error; } return TRUE; on_error: return FALSE; } void dls_upnp_upload_to_any(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_async_upload_t *cb_task_data; DLEYNA_LOG_DEBUG("Enter"); cb_data->cb = cb; cb_task_data = &cb_data->ut.upload; DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, task->target.id); if (strcmp(task->target.id, "0")) { DLEYNA_LOG_WARNING("Bad path %s", task->target.path); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_PATH, "UploadToAnyContainer must be executed on a root path"); goto on_error; } if (!prv_compute_mime_and_class(task, cb_task_data, &cb_data->error)) goto on_error; DLEYNA_LOG_DEBUG("MIME Type %s", cb_task_data->mime_type); DLEYNA_LOG_DEBUG("Object class %s", cb_task_data->object_class); dls_device_upload(client, task, "DLNA.ORG_AnyContainer"); on_error: if (!cb_data->action) (void) g_idle_add(dls_async_task_complete, cb_data); DLEYNA_LOG_DEBUG("Exit"); } void dls_upnp_upload(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_async_upload_t *cb_task_data; DLEYNA_LOG_DEBUG("Enter"); cb_data->cb = cb; cb_task_data = &cb_data->ut.upload; if (!prv_compute_mime_and_class(task, cb_task_data, &cb_data->error)) goto on_error; DLEYNA_LOG_DEBUG("MIME Type %s", cb_task_data->mime_type); DLEYNA_LOG_DEBUG("Object class %s", cb_task_data->object_class); dls_device_upload(client, task, task->target.id); on_error: if (!cb_data->action) (void) g_idle_add(dls_async_task_complete, cb_data); DLEYNA_LOG_DEBUG("Exit"); } void dls_upnp_get_upload_status(dls_upnp_t *upnp, dls_task_t *task) { GError *error = NULL; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, task->target.id); if (strcmp(task->target.id, "0")) { DLEYNA_LOG_WARNING("Bad path %s", task->target.path); error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_PATH, "GetUploadStatus must be executed on a root path"); goto on_error; } (void) dls_device_get_upload_status(task, &error); on_error: if (error) { dls_task_fail(task, error); g_error_free(error); } else { dls_task_complete(task); } DLEYNA_LOG_DEBUG("Exit"); } void dls_upnp_get_upload_ids(dls_upnp_t *upnp, dls_task_t *task) { GError *error = NULL; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, task->target.id); if (strcmp(task->target.id, "0")) { DLEYNA_LOG_WARNING("Bad path %s", task->target.path); error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_PATH, "GetUploadIDs must be executed on a root path"); goto on_error; } dls_device_get_upload_ids(task); on_error: if (error) { dls_task_fail(task, error); g_error_free(error); } else { dls_task_complete(task); } DLEYNA_LOG_DEBUG("Exit"); } void dls_upnp_cancel_upload(dls_upnp_t *upnp, dls_task_t *task) { GError *error = NULL; DLEYNA_LOG_DEBUG("Enter"); DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, task->target.id); if (strcmp(task->target.id, "0")) { DLEYNA_LOG_WARNING("Bad path %s", task->target.path); error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_PATH, "CancelUpload must be executed on a root path"); goto on_error; } (void) dls_device_cancel_upload(task, &error); on_error: if (error) { dls_task_fail(task, error); g_error_free(error); } else { dls_task_complete(task); } DLEYNA_LOG_DEBUG("Exit"); } void dls_upnp_delete_object(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); cb_data->cb = cb; DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, task->target.id); dls_device_delete_object(client, task); DLEYNA_LOG_DEBUG("Exit"); } void dls_upnp_create_container(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); cb_data->cb = cb; DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, task->target.id); dls_device_create_container(client, task, task->target.id); if (!cb_data->action) (void) g_idle_add(dls_async_task_complete, cb_data); DLEYNA_LOG_DEBUG("Exit"); } void dls_upnp_create_container_in_any(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); cb_data->cb = cb; DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, task->target.id); if (strcmp(task->target.id, "0")) { DLEYNA_LOG_WARNING("Bad path %s", task->target.path); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_PATH, "CreateContainerInAnyContainer must be executed on a root path"); goto on_error; } dls_device_create_container(client, task, "DLNA.ORG_AnyContainer"); on_error: if (!cb_data->action) (void) g_idle_add(dls_async_task_complete, cb_data); DLEYNA_LOG_DEBUG("Exit"); } void dls_upnp_update_object(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; dls_async_update_t *cb_task_data; dls_upnp_prop_mask mask; gchar *upnp_filter = NULL; dls_task_update_t *task_data; DLEYNA_LOG_DEBUG("Enter"); cb_data->cb = cb; cb_task_data = &cb_data->ut.update; task_data = &task->ut.update; DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, task->target.id); if (!dls_props_parse_update_filter(upnp->filter_map, task_data->to_add_update, task_data->to_delete, &mask, &upnp_filter)) { DLEYNA_LOG_WARNING("Invalid Parameter"); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Invalid Parameter"); goto on_error; } cb_task_data->map = upnp->filter_map; DLEYNA_LOG_DEBUG("Filter = %s", upnp_filter); DLEYNA_LOG_DEBUG("Mask = 0x%"G_GUINT64_FORMAT"x", mask); if (mask == 0) { DLEYNA_LOG_WARNING("Empty Parameters"); cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_OPERATION_FAILED, "Empty Parameters"); goto on_error; } dls_device_update_object(client, task, upnp_filter); on_error: g_free(upnp_filter); if (!cb_data->action) (void) g_idle_add(dls_async_task_complete, cb_data); DLEYNA_LOG_DEBUG("Exit"); } void dls_upnp_get_object_metadata(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); cb_data->cb = cb; DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, task->target.id); dls_device_get_object_metadata(client, task, task->target.id); DLEYNA_LOG_DEBUG("Exit"); } void dls_upnp_create_reference(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); cb_data->cb = cb; DLEYNA_LOG_DEBUG("Root Path: %s - Id: %s", task->target.root_path, task->target.id); dls_device_create_reference(client, task); DLEYNA_LOG_DEBUG("Exit"); return; } void dls_upnp_get_icon(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); cb_data->cb = cb; dls_device_get_icon(client, task); DLEYNA_LOG_DEBUG("Exit"); } void dls_upnp_wake(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb) { dls_async_task_t *cb_data = (dls_async_task_t *)task; DLEYNA_LOG_DEBUG("Enter"); cb_data->cb = cb; dls_device_wake(client, task); DLEYNA_LOG_DEBUG("Exit"); } void dls_upnp_unsubscribe(dls_upnp_t *upnp) { GHashTableIter iter; gpointer value; dls_device_t *device; DLEYNA_LOG_DEBUG("Enter"); g_hash_table_iter_init(&iter, upnp->device_udn_map); while (g_hash_table_iter_next(&iter, NULL, &value)) { device = value; dls_device_unsubscribe(device); } DLEYNA_LOG_DEBUG("Exit"); } static gboolean prv_device_uc_find(gpointer key, gpointer value, gpointer user_data) { prv_device_new_ct_t *priv_t = (prv_device_new_ct_t *)value; return (priv_t->device == user_data) ? TRUE : FALSE; } static gboolean prv_device_find(gpointer key, gpointer value, gpointer user_data) { return (value == user_data) ? TRUE : FALSE; } gboolean dls_upnp_device_context_exist(dls_device_t *device, dls_device_context_t *context) { gpointer result; guint i; gboolean found = FALSE; dls_upnp_t *upnp = dls_server_get_upnp(); if (upnp == NULL) goto on_exit; /* Check if the device still exist */ result = g_hash_table_find(upnp->device_udn_map, prv_device_find, device); if (result == NULL) if (g_hash_table_find(upnp->device_uc_map, prv_device_uc_find, device) == NULL) goto on_exit; /* Search if the context still exist in the device */ for (i = 0; i < device->contexts->len; ++i) { if (g_ptr_array_index(device->contexts, i) == context) { found = TRUE; break; } } on_exit: return found; } void dls_upnp_rescan(dls_upnp_t *upnp) { DLEYNA_LOG_DEBUG("re-scanning control points"); gupnp_context_manager_rescan_control_points(upnp->context_manager); } GUPnPContextManager *dls_upnp_get_context_manager(dls_upnp_t *upnp) { return upnp->context_manager; } dleyna-server-0.6.0/libdleyna/server/upnp.h000077500000000000000000000102441305660312300207040ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * */ #ifndef DLS_UPNP_H__ #define DLS_UPNP_H__ #include #include #include "client.h" #include "async.h" typedef void (*dls_upnp_callback_t)(const gchar *path, void *user_data); typedef void (*dls_upnp_task_complete_t)(dls_task_t *task, GError *error); dls_upnp_t *dls_upnp_new(dleyna_connector_id_t connection, guint port, const dleyna_connector_dispatch_cb_t *dispatch_table, dls_upnp_callback_t found_server, dls_upnp_callback_t lost_server, void *user_data); void dls_upnp_delete(dls_upnp_t *upnp); GVariant *dls_upnp_get_device_ids(dls_upnp_t *upnp); GHashTable *dls_upnp_get_device_udn_map(dls_upnp_t *upnp); GHashTable *dls_upnp_get_sleeping_device_udn_map(dls_upnp_t *upnp); void dls_upnp_delete_sleeping_device(dls_upnp_t *upnp, dls_device_t *device); void dls_upnp_get_children(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_get_all_props(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_get_prop(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_search(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_browse_objects(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_get_resource(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_upload_to_any(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_upload(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_get_upload_status(dls_upnp_t *upnp, dls_task_t *task); void dls_upnp_get_upload_ids(dls_upnp_t *upnp, dls_task_t *task); void dls_upnp_cancel_upload(dls_upnp_t *upnp, dls_task_t *task); void dls_upnp_delete_object(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_create_container(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_create_container_in_any(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_update_object(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_get_object_metadata(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_create_reference(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_get_icon(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_wake(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, dls_upnp_task_complete_t cb); void dls_upnp_unsubscribe(dls_upnp_t *upnp); gboolean dls_upnp_device_context_exist(dls_device_t *device, dls_device_context_t *context); void dls_upnp_rescan(dls_upnp_t *upnp); GUPnPContextManager *dls_upnp_get_context_manager(dls_upnp_t *upnp); #endif /* DLS_UPNP_H__ */ dleyna-server-0.6.0/libdleyna/server/xml-util.c000077500000000000000000000053741305660312300215000ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Christophe Guiraud * */ #include #include #include "xml-util.h" static xmlNode *prv_get_child_node(xmlNode *node, va_list args) { const gchar *name; name = va_arg(args, const gchar *); while (name != NULL) { node = node->children; while (node != NULL) { if (node->name != NULL && !strcmp(name, (char *)node->name)) break; node = node->next; } if (node == NULL) break; name = va_arg(args, const gchar *); } return node; } static GList *prv_get_children_list(xmlNode *node, const gchar *name) { GList *child_list = NULL; node = node->children; while (node != NULL) { if (node->name != NULL && !strcmp(name, (char *)node->name)) child_list = g_list_prepend(child_list, node); node = node->next; } return child_list; } GList *xml_util_get_child_string_list_content_by_name(xmlNode *node, ...) { xmlChar *content; va_list args; GList *child_list = NULL; GList *next; GList *str_list = NULL; xmlNode *child_list_node; xmlNode *child_node; va_start(args, node); child_node = prv_get_child_node(node, args); va_end(args); if (child_node != NULL) { child_list = prv_get_children_list(child_node->parent, (const gchar *)child_node->name); next = child_list; while (next) { child_list_node = (xmlNode *)next->data; content = xmlNodeGetContent(child_list_node); if (content != NULL) { str_list = g_list_prepend(str_list, g_strdup((gchar *)content)); xmlFree(content); } next = g_list_next(next); } g_list_free(child_list); } return str_list; } gchar *xml_util_get_child_string_content_by_name(xmlNode *node, ...) { xmlChar *content; va_list args; gchar *str = NULL; xmlNode *child_node; va_start(args, node); child_node = prv_get_child_node(node, args); va_end(args); if (child_node != NULL) { content = xmlNodeGetContent(child_node); if (content != NULL) { str = g_strdup((gchar *)content); xmlFree(content); } } return str; } dleyna-server-0.6.0/libdleyna/server/xml-util.h000077500000000000000000000021471305660312300215000ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Christophe Guiraud * */ #ifndef DLS_XML_UTIL_H__ #define DLS_XML_UTIL_H__ #include #include #include GList *xml_util_get_child_string_list_content_by_name(xmlNode *node, ...); gchar *xml_util_get_child_string_content_by_name(xmlNode *node, ...); #endif /* DLS_XML_UTIL_H__ */ dleyna-server-0.6.0/m4/000077500000000000000000000000001305660312300146145ustar00rootroot00000000000000dleyna-server-0.6.0/m4/compiler-flags.m4000077500000000000000000000041631305660312300177710ustar00rootroot00000000000000dnl dnl dLeyna dnl dnl Copyright (C) 2012-2015 Intel Corporation. All rights reserved. dnl dnl This program is free software; you can redistribute it and/or modify it dnl under the terms and conditions of the GNU Lesser General Public License, dnl version 2.1, as published by the Free Software Foundation. dnl dnl This program is distributed in the hope it will be useful, but WITHOUT dnl ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or dnl FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License dnl for more details. dnl dnl You should have received a copy of the GNU Lesser General Public License dnl along with this program; if not, write to the Free Software Foundation, Inc., dnl 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. dnl dnl Ludovic Ferrandis dnl Regis Merlino dnl AC_DEFUN_ONCE([DLEYNA_SERVER_COMPILER_FLAGS], [ if test x"${CFLAGS}" = x""; then CFLAGS="-Wall" AS_VAR_APPEND([CFLAGS], [" -O2"]) AS_VAR_APPEND([CFLAGS], [" -D_FORTIFY_SOURCE=2"]) fi if test x"$USE_MAINTAINER_MODE" = x"yes"; then AS_VAR_APPEND([CFLAGS], [" -Wextra"]) AS_VAR_APPEND([CFLAGS], [" -Wno-unused-parameter"]) AS_VAR_APPEND([CFLAGS], [" -Wno-missing-field-initializers"]) AS_VAR_APPEND([CFLAGS], [" -Wdeclaration-after-statement"]) AS_VAR_APPEND([CFLAGS], [" -Wmissing-declarations"]) AS_VAR_APPEND([CFLAGS], [" -Wredundant-decls"]) AS_VAR_APPEND([CFLAGS], [" -Wcast-align"]) AS_VAR_APPEND([CFLAGS], [" -Wstrict-prototypes"]) AS_VAR_APPEND([CFLAGS], [" -Wmissing-prototypes"]) AS_VAR_APPEND([CFLAGS], [" -Wnested-externs"]) AS_VAR_APPEND([CFLAGS], [" -Wshadow"]) AS_VAR_APPEND([CFLAGS], [" -Wformat=2"]) AS_VAR_APPEND([CFLAGS], [" -Winit-self"]) AS_VAR_APPEND([CFLAGS], [" -std=gnu99"]) AS_VAR_APPEND([CFLAGS], [" -pedantic"]) AS_VAR_APPEND([CFLAGS], [" -Wno-overlength-strings"]) AS_VAR_APPEND([CFLAGS], [" -DG_DISABLE_DEPRECATED"]) AS_VAR_APPEND([CFLAGS], [" -DGLIB_DISABLE_DEPRECATION_WARNINGS"]) fi AS_VAR_APPEND([CFLAGS], [" -Wno-format-extra-args"]) AS_VAR_APPEND([CFLAGS], [" -Wl,--no-undefined"]) ]) dleyna-server-0.6.0/m4/log.m4000077500000000000000000000037311305660312300156460ustar00rootroot00000000000000dnl dnl dLeyna dnl dnl Copyright (C) 2012-2015 Intel Corporation. All rights reserved. dnl dnl This program is free software; you can redistribute it and/or modify it dnl under the terms and conditions of the GNU Lesser General Public License, dnl version 2.1, as published by the Free Software Foundation. dnl dnl This program is distributed in the hope it will be useful, but WITHOUT dnl ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or dnl FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License dnl for more details. dnl dnl You should have received a copy of the GNU Lesser General Public License dnl along with this program; if not, write to the Free Software Foundation, Inc., dnl 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. dnl dnl Ludovic Ferrandis dnl AC_DEFUN([_DLEYNA_LOG_LEVEL_CHECK_VALUE], [ AS_CASE($1, [[[1-6]]], [AS_IF([test "x${log_unique}" = xyes], [ AC_MSG_ERROR(["Log levels 0, 7 and 8 cannot be combined with other values"], 1) ]) : $((log_level_count++)) ], [0|7|8], [AS_IF([test ${log_level_count} -ne 0], [ AC_MSG_ERROR(["Log level $1 cannot be combined with other values"], 1) ]) log_unique=yes ], [AC_MSG_ERROR(["$1 is not a valid value"], 1)] ) ] ) AC_DEFUN([DLEYNA_LOG_LEVEL_CHECK], [ AC_MSG_CHECKING([for --with-log-level=$1]) old_IFS=${IFS} IFS="," log_ok=yes log_unique=no log_level_count=0 LOG_LEVEL=0 for log_level in $1 do IFS=${old_IFS} _DLEYNA_LOG_LEVEL_CHECK_VALUE([$log_level]) IFS="," log_name=LOG_LEVEL_${log_level} eval log_value=\$${log_name} : $((LOG_LEVEL |= ${log_value})) done IFS=${old_IFS} AC_DEFINE_UNQUOTED([DLEYNA_LOG_LEVEL], [${LOG_LEVEL}], [Log level flag for debug messages]) AC_MSG_RESULT([ok]) ] ) AC_DEFUN([DLEYNA_LOG_TYPE_CHECK], [ AC_MSG_CHECKING([for --with-log-type=$1]) AS_CASE($1, [0|1], [], [AC_MSG_ERROR(["$1 is not a valid value"], 1)] ) AC_MSG_RESULT([ok]) ] ) dleyna-server-0.6.0/server/000077500000000000000000000000001305660312300156025ustar00rootroot00000000000000dleyna-server-0.6.0/server/Makefile.am000066400000000000000000000015661305660312300176460ustar00rootroot00000000000000AM_CFLAGS = $(GLIB_CFLAGS) \ $(GIO_CFLAGS) \ $(DLEYNA_CORE_CFLAGS) \ -I$(top_srcdir) \ -include config.h libexec_PROGRAMS = dleyna-server-service dleyna_server_service_SOURCES = daemon.c dleyna_server_service_LDADD = $(GLIB_LIBS) \ $(GIO_LIBS) \ $(DLEYNA_CORE_LIBS) \ $(top_builddir)/libdleyna/server/libdleyna-server-1.0.la dbusservicedir = $(DBUS_SERVICE_DIR) dbusservice_in_files = com.intel.dleyna-server.service.in dbusservice_DATA = com.intel.dleyna-server.service # Replace the 'libexecdir' marker with its fully expanded value %.service: %.service.in Makefile $(AM_V_GEN) $(SED) -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@ pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = dleyna-server-service-1.0.pc EXTRA_DIST = $(dbusservice_in_files) CLEANFILES = $(dbusservice_DATA) DISTCLEANFILES = $(dbusservice_DATA) dleyna-server-0.6.0/server/com.intel.dleyna-server.service.in000066400000000000000000000001261305660312300242370ustar00rootroot00000000000000[D-BUS Service] Name=com.intel.dleyna-server Exec=@libexecdir@/dleyna-server-service dleyna-server-0.6.0/server/daemon.c000066400000000000000000000044731305660312300172210ustar00rootroot00000000000000/* * dLeyna * * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Mark Ryan * Regis Merlino * */ #include #include #include #include #define DLS_SERVER_SERVICE_NAME "dleyna-server-service" static guint g_sig_id; static gboolean prv_quit_handler(GIOChannel *source, GIOCondition condition, gpointer user_data) { dleyna_main_loop_quit(); g_sig_id = 0; return FALSE; } static gboolean prv_init_signal_handler(sigset_t mask) { gboolean retval = FALSE; int fd = -1; GIOChannel *channel = NULL; fd = signalfd(-1, &mask, SFD_NONBLOCK); if (fd == -1) goto on_error; channel = g_io_channel_unix_new(fd); g_io_channel_set_close_on_unref(channel, TRUE); if (g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, NULL) != G_IO_STATUS_NORMAL) goto on_error; if (g_io_channel_set_encoding(channel, NULL, NULL) != G_IO_STATUS_NORMAL) goto on_error; g_sig_id = g_io_add_watch(channel, G_IO_IN | G_IO_PRI, prv_quit_handler, NULL); retval = TRUE; on_error: if (channel) g_io_channel_unref(channel); return retval; } int main(int argc, char *argv[]) { sigset_t mask; int retval = 1; sigemptyset(&mask); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGINT); if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) goto out; if (!prv_init_signal_handler(mask)) goto out; retval = dleyna_main_loop_start(DLS_SERVER_SERVICE_NAME, dleyna_control_point_get_server(), NULL); out: if (g_sig_id) (void) g_source_remove(g_sig_id); return retval; } dleyna-server-0.6.0/server/dleyna-server-service-1.0.pc.in000066400000000000000000000003231305660312300232430ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libexecdir=@libexecdir@ Name: @PACKAGE@-service Description: UPnP & DLNA media content management Requires.private: glib-2.0 gio-2.0 dleyna-core-1.0 Version: @VERSION@ dleyna-server-0.6.0/test/000077500000000000000000000000001305660312300152535ustar00rootroot00000000000000dleyna-server-0.6.0/test/dbus/000077500000000000000000000000001305660312300162105ustar00rootroot00000000000000dleyna-server-0.6.0/test/dbus/Makefile.am000066400000000000000000000003651305660312300202500ustar00rootroot00000000000000AM_CFLAGS = $(GLIB_CFLAGS) \ $(GIO_CFLAGS) dms_info_sources = dms-info.c noinst_PROGRAMS = dms-info dms_info_SOURCES = $(dms_info_sources) dms_info_CFLAGS = $(GLIB_CFLAGS) \ $(GIO_CFLAGS) dms_info_LDADD = $(GLIB_LIBS) \ $(GIO_LIBS) dleyna-server-0.6.0/test/dbus/dms-info.c000077500000000000000000000243311305660312300200760ustar00rootroot00000000000000/* * dms-info * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright (C) 2012-2015 Intel Corporation. All rights reserved. * * Mark Ryan * ******************************************************************************/ #include #include #include #include #include #include #include #define DMS_INFO_SERVICE "com.intel.dleyna-server" #define DMS_INFO_MANAGER_IF "com.intel.dLeynaServer.Manager" #define DMS_INFO_MANAGER_OBJ "/com/intel/dLeynaServer" #define DMS_INFO_GET_SERVERS "GetServers" #define DMS_INFO_GET_ALL "GetAll" #define DMS_INFO_PROPERTIES_IF "org.freedesktop.DBus.Properties" typedef struct dms_info_t_ dms_info_t; struct dms_info_t_ { guint sig_id; GMainLoop *main_loop; GDBusProxy *manager_proxy; GCancellable *cancellable; GHashTable *dmss; unsigned int async; }; typedef struct dms_server_data_t_ dms_server_data_t; struct dms_server_data_t_ { GCancellable *cancellable; GDBusProxy *proxy; }; static dms_server_data_t *prv_dms_server_data_new(GDBusProxy *proxy) { dms_server_data_t *data = g_new(dms_server_data_t, 1); data->proxy = proxy; data->cancellable = g_cancellable_new(); return data; } static void prv_dms_server_data_delete(gpointer user_data) { dms_server_data_t *data = user_data; g_object_unref(data->cancellable); g_object_unref(data->proxy); g_free(data); } static void prv_dms_info_free(dms_info_t *info) { if (info->manager_proxy) g_object_unref(info->manager_proxy); if (info->sig_id) (void) g_source_remove(info->sig_id); if (info->main_loop) g_main_loop_unref(info->main_loop); if (info->cancellable) g_object_unref(info->cancellable); if (info->dmss) g_hash_table_unref(info->dmss); } static void prv_dump_container_props(GVariant *props) { GVariantIter iter; GVariant *dictionary; gchar *key; GVariant *value; gchar *formatted_value; dictionary = g_variant_get_child_value(props, 0); (void) g_variant_iter_init(&iter, dictionary); printf("\n"); while (g_variant_iter_next(&iter, "{&sv}", &key, &value)) { formatted_value = g_variant_print(value, FALSE); printf("%s: %s\n", key, formatted_value); g_free(formatted_value); g_variant_unref(value); } } static void prv_get_props_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { GVariant *variant; dms_info_t *info = user_data; dms_server_data_t *data; const gchar *obj_path; obj_path = g_dbus_proxy_get_object_path((GDBusProxy *) source_object); data = g_hash_table_lookup(info->dmss, obj_path); --info->async; if (g_cancellable_is_cancelled(data->cancellable)) { if (info->async == 0) g_main_loop_quit(info->main_loop); printf("Get Properties cancelled.\n"); } else { variant = g_dbus_proxy_call_finish(info->manager_proxy, res, NULL); if (!variant) { printf("Get Properties failed.\n"); } else { prv_dump_container_props(variant); g_variant_unref(variant); } } } static void prv_get_container_props(dms_info_t *info, const gchar *container) { dms_server_data_t *data; GDBusProxy *proxy; GDBusProxyFlags flags; if (!g_hash_table_lookup_extended(info->dmss, container, NULL, NULL)) { printf("Container Object Found: %s\n", container); /* We'll create these proxies synchronously. The server is already started and we don't want to retrieve any properties so it should be fast. Okay, we do want to retrieve properties but we want to do so for all interfaces and not just org.gnome.UPnP.MediaContainer2. To do this we will need to call GetAll(""). */ flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES; proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION, flags, NULL, DMS_INFO_SERVICE, container, DMS_INFO_PROPERTIES_IF, NULL, NULL); if (!proxy) { printf("Unable to create Container Proxy for %s\n", container); } else { data = prv_dms_server_data_new(proxy); g_hash_table_insert(info->dmss, g_strdup(container), data); ++info->async; g_dbus_proxy_call(proxy, DMS_INFO_GET_ALL, g_variant_new("(s)", ""), G_DBUS_CALL_FLAGS_NONE, -1, data->cancellable, prv_get_props_cb, info); } } } static void prv_get_root_folders(dms_info_t *info, GVariant *servers) { GVariantIter iter; GVariant *array; const gchar *container; gsize count; /* Results always seem to be packed inside a tuple. We need to extract our array from the tuple */ array = g_variant_get_child_value(servers, 0); count = g_variant_iter_init(&iter, array); printf("Found %"G_GSIZE_FORMAT" DMS Root Containers\n", count); while (g_variant_iter_next(&iter, "o", &container)) prv_get_container_props(info, container); } static void prv_get_servers_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { GVariant *variant; dms_info_t *info = user_data; --info->async; if (g_cancellable_is_cancelled(info->cancellable)) { if (info->async == 0) g_main_loop_quit(info->main_loop); printf("Get Servers cancelled\n"); } else { variant = g_dbus_proxy_call_finish(info->manager_proxy, res, NULL); if (!variant) { printf("Get Servers failed.\n"); } else { prv_get_root_folders(info, variant); g_variant_unref(variant); } } } static void prv_on_signal(GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, gpointer user_data) { gchar *container; dms_info_t *info = user_data; if (!strcmp(signal_name, "FoundServer")) { g_variant_get(parameters, "(&o)", &container); if (!g_hash_table_lookup_extended(info->dmss, container, NULL, NULL)) { printf("\nFound DMS %s\n", container); prv_get_container_props(info, container); } } else if (!strcmp(signal_name, "LostServer")) { g_variant_get(parameters, "(&o)", &container); printf("\nLost DMS %s\n", container); (void) g_hash_table_remove(info->dmss, container); } } static void prv_manager_proxy_created(GObject *source_object, GAsyncResult *result, gpointer user_data) { dms_info_t *info = user_data; GDBusProxy *proxy; --info->async; if (g_cancellable_is_cancelled(info->cancellable)) { printf("Manager proxy creation cancelled.\n"); if (info->async == 0) g_main_loop_quit(info->main_loop); } else { proxy = g_dbus_proxy_new_finish(result, NULL); if (!proxy) { printf("Unable to create manager proxy.\n"); } else { info->manager_proxy = proxy; /* Set up signals to be notified when servers appear and dissapear */ g_signal_connect(proxy, "g-signal", G_CALLBACK (prv_on_signal), info); /* Now we need to retrieve a list of the DMSes on the network. This involves IPC so we will do this asynchronously.*/ ++info->async; g_cancellable_reset(info->cancellable); g_dbus_proxy_call(proxy, DMS_INFO_GET_SERVERS, NULL, G_DBUS_CALL_FLAGS_NONE, -1, info->cancellable, prv_get_servers_cb, info); } } } static gboolean prv_quit_handler(GIOChannel *source, GIOCondition condition, gpointer user_data) { dms_info_t *info = user_data; GHashTableIter iter; gpointer value; dms_server_data_t *data; /* We cannot quit straight away if asynchronous calls our outstanding. First we need to cancel them. Each time one is cancel info->async will drop by 1. Once it reaches 0 the callback function associated with the command being cancelled will quit the mainloop. */ if (info->async == 0) { g_main_loop_quit(info->main_loop); } else { g_cancellable_cancel(info->cancellable); g_hash_table_iter_init(&iter, info->dmss); while (g_hash_table_iter_next(&iter, NULL, &value)) { data = value; g_cancellable_cancel(data->cancellable); } } info->sig_id = 0; return FALSE; } static bool prv_init_signal_handler(sigset_t mask, dms_info_t *info) { bool retval = false; int fd = -1; GIOChannel *channel = NULL; fd = signalfd(-1, &mask, SFD_NONBLOCK); if (fd == -1) goto on_error; channel = g_io_channel_unix_new(fd); g_io_channel_set_close_on_unref(channel, TRUE); if (g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, NULL) != G_IO_STATUS_NORMAL) goto on_error; if (g_io_channel_set_encoding(channel, NULL, NULL) != G_IO_STATUS_NORMAL) goto on_error; info->sig_id = g_io_add_watch(channel, G_IO_IN | G_IO_PRI, prv_quit_handler, info); retval = true; on_error: if (channel) g_io_channel_unref(channel); return retval; } int main(int argc, char *argv[]) { dms_info_t info; sigset_t mask; memset(&info, 0, sizeof(info)); sigemptyset(&mask); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGINT); if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) goto on_error; g_type_init(); /* Create proxy for com.intel.dLeynaServer.Manager. The Manager object has no properties. We will create the proxy asynchronously and use G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES to ensure that gio does not contact the server to retrieve remote properties. Creating the proxy will force dleyna-media-service be to launched if it is not already running. */ info.cancellable = g_cancellable_new(); info.async = 1; g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NULL, DMS_INFO_SERVICE, DMS_INFO_MANAGER_OBJ, DMS_INFO_MANAGER_IF, info.cancellable, prv_manager_proxy_created, &info); info.dmss = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, prv_dms_server_data_delete); info.main_loop = g_main_loop_new(NULL, FALSE); if (!prv_init_signal_handler(mask, &info)) goto on_error; g_main_loop_run(info.main_loop); on_error: prv_dms_info_free(&info); return 0; } dleyna-server-0.6.0/test/dbus/download_sync_controller.py000077500000000000000000000455401305660312300237030ustar00rootroot00000000000000# download_sync_controller # # Copyright (C) 2012-2017 Intel Corporation. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms and conditions of the GNU Lesser General Public License, # version 2.1, as published by the Free Software Foundation. # # This program is distributed in the hope it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. # # Regis Merlino # import ConfigParser import os import shutil import urllib import dbus from mediaconsole import UPNP, Container, Device class _DscUpnp(UPNP): def __init__(self): UPNP.__init__(self) def get_servers(self): return self._manager.GetServers() class _DscContainer(Container): def __init__(self, path): Container.__init__(self, path) self.__path = path def find_containers(self): return self._containerIF.SearchObjectsEx( 'Type derivedfrom "container"', 0, 0, ['DisplayName', 'Path', 'Type'], '')[0] def find_updates(self, upd_id): return self._containerIF.SearchObjectsEx( 'ObjectUpdateID > "{0}"'.format(upd_id), 0, 0, ['DisplayName', 'Path', 'RefPath', 'URLs', 'Type', 'Parent'], '')[0] def find_children(self): return self._containerIF.ListChildrenEx(0, 0, ['DisplayName', 'Path', 'RefPath', 'URLs', 'Parent', 'Type'], '') class DscError(Exception): """A Download Sync Controller error.""" def __init__(self, message): """ message: description of the error """ Exception.__init__(self, message) self.message = message def __str__(self): return 'DscError: ' + self.message class _DscDownloader(object): def __init__(self, url, path): self.url = url self.path = path def download(self): urllib.urlretrieve(self.url, self.path) class _DscStore(object): SYNC_SECTION = 'sync_info' SYNC_OPTION = 'sync_contents' CUR_ID_OPTION = 'current_id' MEDIA_SECTION = 'media_info' NAME_SUFFIX = '-name' ITEM_NEW = 1 ITEM_UPDATE = 2 CONTAINER_NEW = 3 def __init__(self, root_path, server_id): self.__root_path = root_path + '/' + server_id self.__config_path = self.__root_path + '/' + 'tracking.conf' self.__config = ConfigParser.ConfigParser() self.__cur_id = 0 self.__sync = False def initialize(self, sync): if not os.path.exists(self.__root_path): os.makedirs(self.__root_path) self.__config.read(self.__config_path) if not self.__config.has_section(_DscStore.SYNC_SECTION): self.__config.add_section(_DscStore.SYNC_SECTION) self.__config.set(_DscStore.SYNC_SECTION, _DscStore.CUR_ID_OPTION, '0') if sync: self.__config.set(_DscStore.SYNC_SECTION, _DscStore.SYNC_OPTION, 'yes') else: self.__config.set(_DscStore.SYNC_SECTION, _DscStore.SYNC_OPTION, 'no') if not self.__config.has_section(_DscStore.MEDIA_SECTION): self.__config.add_section(_DscStore.MEDIA_SECTION) self.__cur_id = self.__config.getint(_DscStore.SYNC_SECTION, _DscStore.CUR_ID_OPTION) self.__sync = self.__config.getboolean(_DscStore.SYNC_SECTION, _DscStore.SYNC_OPTION) def __write_config(self): with open(self.__config_path, 'wb') as configfile: self.__config.write(configfile) def __id_from_path(self, path): return os.path.basename(path) def __orig_id(self, media_object): try: return self.__id_from_path(media_object['RefPath']) except KeyError: return self.__id_from_path(media_object['Path']) def __removed_items(self, local_ids, remote_items): for local_id in local_ids: found = False for remote in remote_items: remote_id = self.__id_from_path(remote['Path']) if local_id.endswith(_DscStore.NAME_SUFFIX) or \ local_id == remote_id: found = True if not found: yield local_id def __sync_item(self, obj, obj_id, parent_id, status, write_conf): orig = self.__orig_id(obj) if status == _DscStore.ITEM_UPDATE: old_path = self.__config.get(_DscStore.MEDIA_SECTION, orig) new_path = self.__create_path_for_name(obj['DisplayName']) print u'\tMedia "{0}" updated'.format(obj['DisplayName']) print u'\t\tto "{0}"'.format(new_path) self.__config.set(_DscStore.MEDIA_SECTION, orig, new_path) os.rename(old_path, new_path) elif status == _DscStore.ITEM_NEW: print u'\tNew media "{0}" tracked'.format(obj['DisplayName']) self.__config.set(parent_id, obj_id, orig) self.__config.set(parent_id, obj_id + _DscStore.NAME_SUFFIX, obj['DisplayName']) if not self.__config.has_option(_DscStore.MEDIA_SECTION, orig) and \ self.__sync: local_path = self.__create_path_for_name(obj['DisplayName']) self.__config.set(_DscStore.MEDIA_SECTION, orig, local_path) print u'\tDownloading contents from "{0}"'.format(obj['URLs'][0]) print u'\t\tinto "{0}"...'.format(local_path) downloader = _DscDownloader(obj['URLs'][0], local_path) downloader.download() else: pass if write_conf: self.__write_config() def __create_path_for_name(self, file_name): new_path = self.__root_path + '/' + str(self.__cur_id) + '-' + file_name self.__cur_id += 1 self.__config.set(_DscStore.SYNC_SECTION, _DscStore.CUR_ID_OPTION, str(self.__cur_id)) return new_path def remove(self): if os.path.exists(self.__root_path): shutil.rmtree(self.__root_path) def sync_container(self, container, items): print u'Syncing container "{0}"...'.format(container['DisplayName']) container_id = self.__id_from_path(container['Path']) if not self.__config.has_section(container_id): self.__config.add_section(container_id) for remote in items: remote_id = self.__id_from_path(remote['Path']) if not self.__config.has_option(container_id, remote_id): if remote['Type'] == 'container': status = _DscStore.CONTAINER_NEW else: status = _DscStore.ITEM_NEW self.__sync_item(remote, remote_id, container_id, status, False) for local in self.__removed_items( self.__config.options(container_id), items): if self.__config.has_section(local): print u'\tRemoved a container' self.__config.remove_option(container_id, local) self.__config.remove_section(local) else: orig = self.__config.get(container_id, local) name = self.__config.get(container_id, local + _DscStore.NAME_SUFFIX) print u'\tRemoved media "{0}"'.format(name) self.__config.remove_option(container_id, local) self.__config.remove_option(container_id, local + _DscStore.NAME_SUFFIX) if local == orig: orig_name = self.__config.get(_DscStore.MEDIA_SECTION, orig) self.__config.remove_option(_DscStore.MEDIA_SECTION, orig) if self.__sync: print u'\tRemoved local downloaded contents "{0}"' \ .format(orig_name) if os.path.exists(orig_name): os.remove(orig_name) self.__write_config() def sync_item(self, obj): print u'Syncing item "{0}"...'.format(obj['DisplayName']) obj_id = self.__id_from_path(obj['Path']) parent_id = self.__id_from_path(obj['Parent']) if self.__config.has_option(parent_id, obj_id): status = _DscStore.ITEM_UPDATE else: status = _DscStore.ITEM_NEW self.__sync_item(obj, obj_id, parent_id, status, True) class DscController(object): """A Download Sync Controller. The Download Sync Controller receive changes in the content or metadata stored on media servers (DMS/M-DMS) and apply those changes to the local storage. Media servers must expose the 'content-synchronization' capability to be tracked by this controller. The three main methods are servers(), track() and sync(). * servers() lists the media servers available on the network * track() is used to add a media server to the list of servers that are to be synchronized. * sync() launches the servers synchronisation to a local storage Sample usage: >>> controller.servers() >>> controller.track('/com/intel/dLeynaServer/server/0') >>> controller.sync() """ CONFIG_PATH = os.environ['HOME'] + '/.config/download-sync-controller.conf' SUID_OPTION = 'system_update_id' SRT_OPTION = 'service_reset_token' SYNC_OPTION = 'sync_contents' DATA_PATH_SECTION = '__data_path__' DATA_PATH_OPTION = 'path' def __init__(self, rel_path = None): """ rel_path: if provided, contains the relative local storage path, from the user's HOME directory. If not provided, the local storage path will be '$HOME/download-sync-controller' """ self.__upnp = _DscUpnp() self.__config = ConfigParser.ConfigParser() self.__config.read(DscController.CONFIG_PATH) if rel_path: self.__set_data_path(rel_path) elif not self.__config.has_section(DscController.DATA_PATH_SECTION): self.__set_data_path('download-sync-controller') self.__store_path = self.__config.get(DscController.DATA_PATH_SECTION, DscController.DATA_PATH_OPTION) def __write_config(self): with open(DscController.CONFIG_PATH, 'wb') as configfile: self.__config.write(configfile) def __set_data_path(self, rel_path): data_path = os.environ['HOME'] + '/' + rel_path if not self.__config.has_section(DscController.DATA_PATH_SECTION): self.__config.add_section(DscController.DATA_PATH_SECTION) self.__config.set(DscController.DATA_PATH_SECTION, DscController.DATA_PATH_OPTION, data_path) self.__write_config() def __need_sync(self, servers): for item in servers: device = Device(item) uuid = device.get_prop('UDN') if self.__config.has_section(uuid): new_id = device.get_prop('SystemUpdateID') new_srt = device.get_prop('ServiceResetToken') cur_id = self.__config.getint(uuid, DscController.SUID_OPTION) cur_srt = self.__config.get(uuid, DscController.SRT_OPTION) if cur_id == -1 or cur_srt != new_srt: print print u'Server {0} needs *full* sync:'.format(uuid) yield item, uuid, 0, new_id, new_srt, True elif cur_id < new_id: print print u'Server {0} needs sync:'.format(uuid) yield item, uuid, cur_id, new_id, new_srt, False def __check_trackable(self, server): try: try: srt = server.get_prop('ServiceResetToken') except: raise DscError("'ServiceResetToken' variable not supported") try: dlna_caps = server.get_prop('DLNACaps') if not 'content-synchronization' in dlna_caps: raise except: raise DscError("'content-synchronization' cap not supported") try: search_caps = server.get_prop('SearchCaps') if not [x for x in search_caps if 'ObjectUpdateID' in x]: raise if not [x for x in search_caps if 'ContainerUpdateID' in x]: raise except: raise DscError("'objectUpdateID' search cap not supported") return srt except DscError as err: print err return None def track(self, server_path, track = True, sync_contents = True): """Adds or removes a media server to/from the controller's list. server_path: d-bus path for the media server track: when 'True', adds a server to the list when 'False' removes a server from the list sync_contents: when 'True', downloads media contents to the local storage upon synchronization. """ server = Device(server_path) server_uuid = server.get_prop('UDN') if track and not self.__config.has_section(server_uuid): srt = self.__check_trackable(server) if srt != None: self.__config.add_section(server_uuid) self.__config.set(server_uuid, DscController.SUID_OPTION, '-1') self.__config.set(server_uuid, DscController.SRT_OPTION, srt) if sync_contents: self.__config.set(server_uuid, DscController.SYNC_OPTION, 'yes') else: self.__config.set(server_uuid, DscController.SYNC_OPTION, 'no') self.__write_config() else: print u"Sorry, the server {0} has no such capability and " \ "will not be tracked.".format(server_path) elif not track and self.__config.has_section(server_uuid): self.__config.remove_section(server_uuid) self.__write_config() store = _DscStore(self.__store_path, server_uuid) store.remove() def track_reset(self, server_path, sync_contents = True): """Removes local contents and meta data for a media server. The next synchronization will be a *full* synchronization. server_path: d-bus path for the media server sync_contents: when 'True', downloads media contents to the local storage upon synchronization. """ self.track(server_path, False, sync_contents) self.track(server_path, True, sync_contents) def servers(self): """Displays media servers available on the network. Displays media servers information as well as the tracked status. """ print u'Running servers:' for item in self.__upnp.get_servers(): try: server = Container(item) try: folder_name = server.get_prop('FriendlyName') except Exception: folder_name = server.get_prop('DisplayName') device = Device(item) dev_uuid = device.get_prop('UDN') dev_path = device.get_prop('Path') print u'{0:<25} Tracked({2}) {3} {1}'.format(folder_name, dev_path, self.__config.has_option(dev_uuid, DscController.SUID_OPTION), dev_uuid) except dbus.exceptions.DBusException as err: print u'Cannot retrieve properties for ' + item print str(err).strip()[:-1] def tracked_servers(self): """Displays the list of servers currently tracked by the controller.""" print u'Tracked servers:' for name in self.__config.sections(): if name != DscController.DATA_PATH_SECTION: print u'{0:<30}'.format(name) def sync(self): """Performs a synchronization for all the tracked media servers. Displays some progress information during the process. """ print u'Syncing...' for item, uuid, cur, new_id, new_srt, full_sync in \ self.__need_sync(self.__upnp.get_servers()): sync = self.__config.getboolean(uuid, DscController.SYNC_OPTION) if full_sync: print u'Resetting local contents for server {0}'.format(uuid) self.track_reset(item) objects = _DscContainer(item).find_containers() else: objects = _DscContainer(item).find_updates(cur) store = _DscStore(self.__store_path, uuid) store.initialize(sync) for obj in objects: if obj['Type'] == 'container': children = _DscContainer(obj['Path']).find_children() store.sync_container(obj, children) else: store.sync_item(obj) self.__config.set(uuid, DscController.SUID_OPTION, str(new_id)) if full_sync: self.__config.set(uuid, DscController.SRT_OPTION, str(new_srt)) self.__write_config() print print u'Done.' def reset(self): """Removes local contents and meta data for all the tracked servers. After the call, the list of tracked servers will be empty. """ for name in self.__config.sections(): if name != DscController.DATA_PATH_SECTION: self.__config.remove_section(name) store = _DscStore(self.__store_path, name) store.remove() self.__write_config() if __name__ == '__main__': controller = DscController() controller.servers() print print u'"controller" instance is ready for use.' print u'Type "help(DscController)" for more details and usage samples.' dleyna-server-0.6.0/test/dbus/dsc.sh000077500000000000000000000000461305660312300173200ustar00rootroot00000000000000python -i -m download_sync_controller dleyna-server-0.6.0/test/dbus/lost_client_test.py000077500000000000000000000052771305660312300221560ustar00rootroot00000000000000# test_lost_client # # Copyright (C) 2012-2017 Intel Corporation. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms and conditions of the GNU Lesser General Public License, # version 2.1, as published by the Free Software Foundation. # # This program is distributed in the hope it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. # # Regis Merlino # import gobject import dbus import dbus.mainloop.glib def handle_browse_reply(objects): print "Total Items: " + str(len(objects)) print loop.quit() def handle_error(e): print "An error occured" loop.quit() def make_async_call(): root.ListChildrenEx(0, 5, ["DisplayName", "Type"], "-DisplayName", reply_handler=handle_browse_reply, error_handler=handle_error) root.ListChildrenEx(0, 5, ["DisplayName", "Type"], "-DisplayName", reply_handler=handle_browse_reply, error_handler=handle_error) root.ListChildrenEx(0, 5, ["DisplayName", "Type"], "-DisplayName", reply_handler=handle_browse_reply, error_handler=handle_error) root.ListChildrenEx(0, 5, ["DisplayName", "Type"], "-DisplayName", reply_handler=handle_browse_reply, error_handler=handle_error) root.ListChildrenEx(0, 5, ["DisplayName", "Type"], "-DisplayName", reply_handler=handle_browse_reply, error_handler=handle_error) # Test: force quit - this should cancel the search on server side loop.quit() dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() root = dbus.Interface(bus.get_object( 'com.intel.dleyna-server', '/com/intel/dLeynaServer/server/0'), 'org.gnome.UPnP.MediaContainer2') gobject.timeout_add(1000, make_async_call) loop = gobject.MainLoop() loop.run() dleyna-server-0.6.0/test/dbus/mc.sh000077500000000000000000000000321305660312300171410ustar00rootroot00000000000000python -i -m mediaconsole dleyna-server-0.6.0/test/dbus/mediaconsole.py000066400000000000000000000232721305660312300212320ustar00rootroot00000000000000# mediaconsole # # Copyright (C) 2012-2017 Intel Corporation. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms and conditions of the GNU Lesser General Public License, # version 2.1, as published by the Free Software Foundation. # # This program is distributed in the hope it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. # # Mark Ryan # import dbus import sys import json from xml.dom import minidom def print_properties(props): print json.dumps(props, indent=4, sort_keys=True) class MediaObject(object): def __init__(self, path): bus = dbus.SessionBus() self._propsIF = dbus.Interface(bus.get_object( 'com.intel.dleyna-server', path), 'org.freedesktop.DBus.Properties') self.__objIF = dbus.Interface(bus.get_object( 'com.intel.dleyna-server', path), 'org.gnome.UPnP.MediaObject2') def get_props(self, iface = ""): return self._propsIF.GetAll(iface) def get_prop(self, prop_name, iface = ""): return self._propsIF.Get(iface, prop_name) def print_prop(self, prop_name, iface = ""): print_properties(self._propsIF.Get(iface, prop_name)) def print_props(self, iface = ""): print_properties(self._propsIF.GetAll(iface)) def print_dms_id(self): path = self._propsIF.Get("", "Path") dms_id = path[path.rfind("/") + 1:] i = 0 while i+1 < len(dms_id): num = dms_id[i] + dms_id[i+1] sys.stdout.write(unichr(int(num, 16))) i = i + 2 print def print_metadata(self): metadata = self.__objIF.GetMetaData() print minidom.parseString(metadata).toprettyxml(indent=' '*4) def delete(self): return self.__objIF.Delete() def update(self, to_add_update, to_delete): return self.__objIF.Update(to_add_update, to_delete) def get_metadata(self): return self.__objIF.GetMetaData() class Item(MediaObject): def __init__(self, path): MediaObject.__init__(self, path) bus = dbus.SessionBus() self._itemIF = dbus.Interface(bus.get_object( 'com.intel.dleyna-server', path), 'org.gnome.UPnP.MediaItem2') def print_compatible_resource(self, protocol_info, fltr): print_properties(self._itemIF.GetCompatibleResource(protocol_info, fltr)) class Container(MediaObject): def __init__(self, path): MediaObject.__init__(self, path) bus = dbus.SessionBus() self._containerIF = dbus.Interface(bus.get_object( 'com.intel.dleyna-server', path), 'org.gnome.UPnP.MediaContainer2') def list_children(self, offset, count, fltr, sort=""): objects = self._containerIF.ListChildrenEx(offset, count, fltr, sort) for item in objects: print_properties(item) print "" def list_containers(self, offset, count, fltr, sort=""): objects = self._containerIF.ListContainersEx(offset, count, fltr, sort) for item in objects: print_properties(item) print "" def list_items(self, offset, count, fltr, sort=""): objects = self._containerIF.ListItemsEx(offset, count, fltr, sort) for item in objects: print_properties(item) print "" def search(self, query, offset, count, fltr, sort=""): objects, total = self._containerIF.SearchObjectsEx(query, offset, count, fltr, sort) print "Total Items: " + str(total) print for item in objects: print_properties(item) print "" def tree(self, level=0): objects = self._containerIF.ListChildren( 0, 0, ["DisplayName", "Path", "Type"]) for props in objects: print (" " * (level * 4) + props["DisplayName"] + " : (" + props["Path"]+ ")") if props["Type"] == "container": Container(props["Path"]).tree(level + 1) def upload(self, name, file_path): (tid, path) = self._containerIF.Upload(name, file_path) print "Transfer ID: " + str(tid) print u"Path: " + path def create_container(self, name, type, child_types): path = self._containerIF.CreateContainer(name, type, child_types) print u"New container path: " + path def print_compatible_resource(self, protocol_info, fltr): print_properties(self._containerIF.GetCompatibleResource(protocol_info, fltr)) def create_reference(self, file_path): path = self._containerIF.CreateReference(file_path) print u"Reference Path: " + path class Device(Container): def __init__(self, path): Container.__init__(self, path) bus = dbus.SessionBus() self._deviceIF = dbus.Interface(bus.get_object( 'com.intel.dleyna-server', path), 'com.intel.dLeynaServer.MediaDevice') def browse_objects(self, paths, fltr=['*']): objects = self._deviceIF.BrowseObjects(paths, fltr) for item in objects: print_properties(item) print "" def upload_to_any(self, name, file_path): (tid, path) = self._deviceIF.UploadToAnyContainer(name, file_path) print "Transfer ID: " + str(tid) print u"Path: " + path def create_container_in_any(self, name, type, child_types): path = self._deviceIF.CreateContainerInAnyContainer(name, type, child_types) print u"New container path: " + path def get_upload_status(self, id): (status, length, total) = self._deviceIF.GetUploadStatus(id) print "Status: " + status print "Length: " + str(length) print "Total: " + str(total) def get_upload_ids(self): upload_ids = self._deviceIF.GetUploadIDs() print_properties(upload_ids) def cancel_upload(self, id): self._deviceIF.CancelUpload(id) def cancel(self): return self._deviceIF.Cancel() def print_icon(self, mime_type, resolution): bytes, mime = self._deviceIF.GetIcon(mime_type, resolution) print "Icon mime type: " + mime def wake(self): return self._deviceIF.Wake() class UPNP(object): def __init__(self): bus = dbus.SessionBus() self._manager = dbus.Interface(bus.get_object( 'com.intel.dleyna-server', '/com/intel/dLeynaServer'), 'com.intel.dLeynaServer.Manager') self._propsIF = dbus.Interface(bus.get_object( 'com.intel.dleyna-server', '/com/intel/dLeynaServer'), 'org.freedesktop.DBus.Properties') def get_props(self, iface = ""): return self._propsIF.GetAll(iface) def get_prop(self, prop_name, iface = ""): return self._propsIF.Get(iface, prop_name) def set_prop(self, prop_name, val, iface = ""): return self._propsIF.Set(iface, prop_name, val) def print_prop(self, prop_name, iface = ""): print_properties(self._propsIF.Get(iface, prop_name)) def print_props(self, iface = ""): print_properties(self._propsIF.GetAll(iface)) def server_from_name(self, friendly_name): retval = None for i in self._manager.GetServers(): server = Device(i) server_name = server.get_prop("FriendlyName").lower() if server_name.find(friendly_name.lower()) != -1: retval = server break return retval def server_from_udn(self, udn): retval = None for i in self._manager.GetServers(): server = Device(i) if server.get_prop("UDN") == udn: retval = server break return retval def servers(self): for i in self._manager.GetServers(): try: server = Container(i) try: folderName = server.get_prop("FriendlyName"); except Exception: folderName = server.get_prop("DisplayName"); print u'{0:<30}{1:<30}'.format(folderName , i) except dbus.exceptions.DBusException, err: print u"Cannot retrieve properties for " + i print str(err).strip()[:-1] def version(self): print self._manager.GetVersion() def set_protocol_info(self, protocol_info): self._manager.SetProtocolInfo(protocol_info) def prefer_local_addresses(self, prefer): self._manager.PreferLocalAddresses(prefer) def rescan(self): self._manager.Rescan() def white_list_enable(self, enable): self.set_prop("WhiteListEnabled", enable) def white_list_add(self, entries): white_list = set(self.get_prop('WhiteListEntries')) white_list = white_list | set(entries) self.set_prop("WhiteListEntries", list(white_list)) def white_list_remove(self, entries): white_list = set(self.get_prop('WhiteListEntries')) white_list = white_list - set(entries) self.set_prop("WhiteListEntries", list(white_list)) def white_list_clear(self): self.set_prop("WhiteListEntries", ['']) dleyna-server-0.6.0/test/dbus/monitor_changed.py000077500000000000000000000027541305660312300217350ustar00rootroot00000000000000#!/usr/bin/env python # monitor_last_change # # Copyright (C) 2012-2017 Intel Corporation. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms and conditions of the GNU Lesser General Public License, # version 2.1, as published by the Free Software Foundation. # # This program is distributed in the hope it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. # # Regis Merlino # Mark Ryan # import gobject import dbus import dbus.mainloop.glib import json def print_properties(props): print json.dumps(props, indent=4, sort_keys=True) def changed(objects, path): print "Changed signal from [%s]" % path print "Objects:" print_properties(objects) if __name__ == '__main__': dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() bus.add_signal_receiver(changed, bus_name="com.intel.dleyna-server", signal_name = "Changed", path_keyword="path") mainloop = gobject.MainLoop() mainloop.run() dleyna-server-0.6.0/test/dbus/monitor_contents_changes.py000077500000000000000000000041261305660312300236640ustar00rootroot00000000000000# monitor_contents_changes # # Copyright (C) 2012-2017 Intel Corporation. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms and conditions of the GNU Lesser General Public License, # version 2.1, as published by the Free Software Foundation. # # This program is distributed in the hope it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. # # Regis Merlino # import gobject import dbus import dbus.mainloop.glib import json def print_properties(props): print json.dumps(props, indent=4, sort_keys=True) def properties_changed(iface, changed, invalidated, path): print "PropertiesChanged signal from {%s} [%s]" % (iface, path) print "Changed:" print_properties(changed) print "Invalidated:" print_properties(invalidated) def container_update(id_list, path, interface): iface = interface[interface.rfind(".") + 1:] print "ContainerUpdateIDs signal from {%s} [%s]" % (iface, path) for (id_path, sys_id) in id_list: print "-->\t %s : %u" % (id_path, sys_id) if __name__ == '__main__': dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() bus.add_signal_receiver(properties_changed, bus_name="com.intel.dleyna-server", signal_name = "PropertiesChanged", path_keyword="path") bus.add_signal_receiver(container_update, bus_name="com.intel.dleyna-server", signal_name = "ContainerUpdateIDs", path_keyword="path", interface_keyword="interface") mainloop = gobject.MainLoop() mainloop.run() dleyna-server-0.6.0/test/dbus/monitor_upload_update.py000077500000000000000000000030511305660312300231610ustar00rootroot00000000000000#!/usr/bin/env python # monitor_upload_update # # Copyright (C) 2012-2017 Intel Corporation. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms and conditions of the GNU Lesser General Public License, # version 2.1, as published by the Free Software Foundation. # # This program is distributed in the hope it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. # # Regis Merlino # Mark Ryan # import gobject import dbus import dbus.mainloop.glib def upload_update(upload_id, status, uploaded, to_upload, path): print "UploadUpdate signal from [%s]" % (path) print "Upload ID: " + str(upload_id) print "Status: " + status print "Uploaded: " + str(uploaded) print "To Upload: " + str(to_upload) if __name__ == '__main__': dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() bus.add_signal_receiver(upload_update, bus_name="com.intel.dleyna-server", signal_name = "UploadUpdate", path_keyword="path") mainloop = gobject.MainLoop() mainloop.run() dleyna-server-0.6.0/test/dbus/stress_test.py000066400000000000000000000040171305660312300211460ustar00rootroot00000000000000# stress-test # # Copyright (C) 2012-2017 Intel Corporation. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms and conditions of the GNU Lesser General Public License, # version 2.1, as published by the Free Software Foundation. # # This program is distributed in the hope it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. # # Regis Merlino # import gobject import dbus import dbus.mainloop.glib def handle_browse_reply(objects): print "Total Items: " + str(len(objects)) def handle_error(err): if err.get_dbus_name() == 'com.intel.dleyna.Cancelled': print "Cancelled..." else: print "An error occured" print err loop.quit() def make_async_calls(): i = 0 while i < 5: root.ListChildrenEx(0, 5, ["DisplayName", "Type"], "-DisplayName", reply_handler=handle_browse_reply, error_handler=handle_error) i += 1 device.Cancel() gobject.timeout_add(1000, make_async_calls) dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() root = dbus.Interface(bus.get_object( 'com.intel.dleyna-server', '/com/intel/dLeynaServer/server/0'), 'org.gnome.UPnP.MediaContainer2') device = dbus.Interface(bus.get_object( 'com.intel.dleyna-server', '/com/intel/dLeynaServer/server/0'), 'com.intel.dLeynaServer.MediaDevice') gobject.timeout_add(1000, make_async_calls) loop = gobject.MainLoop() loop.run() dleyna-server-0.6.0/test/dbus/upload_sync_controller.py000066400000000000000000000450241305660312300233520ustar00rootroot00000000000000# upload_sync_controller # # Copyright (C) 2012-2017 Intel Corporation. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms and conditions of the GNU Lesser General Public License, # version 2.1, as published by the Free Software Foundation. # # This program is distributed in the hope it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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., # 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. # # Regis Merlino # import ConfigParser import os import shutil import urllib import dbus import gio import glib from mediaconsole import UPNP, MediaObject, Container, Device class _UscUpnp(UPNP): def __init__(self): UPNP.__init__(self) def get_servers(self): return self._manager.GetServers() class _UscDevice(Device): def __init__(self, path): Device.__init__(self, path) def create_container_in_any(self, name, type, child_types): return self._deviceIF.CreateContainerInAnyContainer(name, type, child_types) class _UscContainer(Container): def __init__(self, path): Container.__init__(self, path) def create_container(self, name, type, child_types): return self._containerIF.CreateContainer(name, type, child_types) def upload(self, name, file_path): return self._containerIF.Upload(name, file_path) class UscError(Exception): """An Upload Sync Controller error.""" def __init__(self, message): """ message: description of the error """ Exception.__init__(self, message) self.message = message def __str__(self): return 'UscError: ' + self.message class _UscStore(object): REMOTE_ID_OPTION = 'remote_id' TYPE_OPTION = 'type' def __init__(self, root_path, server_id): if not os.path.exists(root_path): os.makedirs(root_path) self.__config_path = os.path.join(root_path, server_id + '.conf') self.__config = ConfigParser.ConfigParser() def initialize(self): self.__config.read(self.__config_path) def __write_config(self): with open(self.__config_path, 'wb') as configfile: self.__config.write(configfile) def remove(self): if os.path.exists(self.__config_path): os.remove(self.__config_path) def __parent_deleted(self, path, deleted_containers): for del_path in deleted_containers: if path.startswith(del_path): return True return False def remove_file(self, path): id = self.__config.get(path, self.REMOTE_ID_OPTION) print u'\tDeleting server object {0}'.format(id) try: obj = MediaObject(id) obj.delete() except: pass print u'\tDeleting local cache {0}'.format(path) self.__config.remove_section(path) def sync_deleted_files(self, path): deleted_containers = [] sections = self.__config.sections() if not sections: return for path in sections: if self.__parent_deleted(path, deleted_containers): print u'\tDeleting local cache {0}'.format(path) self.__config.remove_section(path) elif not os.path.exists(path): if self.__config.get(path, self.TYPE_OPTION) == 'container': deleted_containers.append(path + os.sep) self.remove_file(path) self.__write_config() def add_file(self, container, name, parent): new_path = os.path.join(parent, name) if os.path.isdir(new_path): if self.__config.has_section(new_path): id = self.__config.get(new_path, self.REMOTE_ID_OPTION) else: print u'\tCreating server container for {0}'.format(new_path) id = container.create_container(name, 'container', ['*']) print u'\tStoring cached data for {0}'.format(id) self.__config.add_section(new_path) self.__config.set(new_path, self.REMOTE_ID_OPTION, id) self.__config.set(new_path, self.TYPE_OPTION, 'container') new_container = _UscContainer(id) self.sync_added_files(new_container, new_path) elif not self.__config.has_section(new_path): print u'\tUploading file {0}'.format(new_path) (up, id) = container.upload(name, new_path) print u'\tStoring cached data for {0}'.format(id) self.__config.add_section(new_path) self.__config.set(new_path, self.REMOTE_ID_OPTION, id) self.__config.set(new_path, self.TYPE_OPTION, 'item') def sync_added_files(self, container, path): children = os.listdir(path) for child in children: self.add_file(container, child, path) self.__write_config() def set_root(self, path, id): self.__config.add_section(path) self.__config.set(path, self.REMOTE_ID_OPTION, id) self.__config.set(path, self.TYPE_OPTION, 'container') self.__write_config() def object_id_from_path(self, path): return self.__config.get(path, self.REMOTE_ID_OPTION) def rename_file(self, path1, path2): self.__config.add_section(path2) id = self.__config.get(path1, self.REMOTE_ID_OPTION) self.__config.set(path2, self.REMOTE_ID_OPTION, id) type = self.__config.get(path1, self.TYPE_OPTION) self.__config.set(path2, self.TYPE_OPTION, type) self.__config.remove_section(path1) self.__write_config() class UscController(object): """An Upload Sync Controller sample app. The Upload Sync Controller propagates changes in a local folder to media servers (DMS/M-DMS) to be added to their list of available content. Media servers must expose the 'content-synchronization' capability to be managed by this controller. The four main methods are servers(), track(), add_server() and start_sync(). * servers() lists the media servers available on the network * track() is used to set the local folder that is to be synchronized * add_server() is used to add a media server to the list of servers that are to be synchronized * start_sync() launches the servers synchronisation from the local storage Sample usage: >>> controller.servers() >>> controller.track('/home/user/local_folder') >>> controller.add_server('/com/intel/dLeynaServer/server/0') >>> controller.start_sync() """ CONFIG_PATH = os.path.join(os.environ['HOME'], '.config/upload-sync-controller.conf') ROOT_CONTAINER_ID_OPTION = 'root_container_id' DATA_PATH_SECTION = '__paths__' DATA_PATH_OPTION = 'data_path' TRACK_PATH_OPTION = 'track_path' def __init__(self, rel_path = None): """ rel_path: if provided, contains the relative local database path, from the user's HOME directory. If not provided, the local database path will be '$HOME/upload-sync-controller' """ self.__upnp = _UscUpnp() self.__config = ConfigParser.ConfigParser() self.__config.read(UscController.CONFIG_PATH) if rel_path: self.__set_data_path(rel_path) elif not self.__config.has_section(UscController.DATA_PATH_SECTION): self.__set_data_path('upload-sync-controller') self.__store_path = self.__config.get(UscController.DATA_PATH_SECTION, UscController.DATA_PATH_OPTION) try: self.__track_path = self.__config.get( UscController.DATA_PATH_SECTION, UscController.TRACK_PATH_OPTION) except: self.__track_path = None def __write_config(self): with open(UscController.CONFIG_PATH, 'wb') as configfile: self.__config.write(configfile) def __set_data_path(self, rel_path): data_path = os.environ['HOME'] + os.sep + rel_path if not self.__config.has_section(UscController.DATA_PATH_SECTION): self.__config.add_section(UscController.DATA_PATH_SECTION) self.__config.set(UscController.DATA_PATH_SECTION, UscController.DATA_PATH_OPTION, data_path) self.__write_config() def __check_trackable(self, server): try: try: srt = server.get_prop('ServiceResetToken') except: raise UscError("'ServiceResetToken' variable not supported") try: dlna_caps = server.get_prop('DLNACaps') if not 'content-synchronization' in dlna_caps: raise except: raise UscError("'content-synchronization' cap not supported") try: search_caps = server.get_prop('SearchCaps') if not [x for x in search_caps if 'ObjectUpdateID' in x]: raise if not [x for x in search_caps if 'ContainerUpdateID' in x]: raise except: raise UscError("'objectUpdateID' search cap not supported") return srt except UscError as err: print err return None def __need_sync(self, servers): for item in servers: device = _UscDevice(item) uuid = device.get_prop('UDN') if self.__config.has_section(uuid): yield device, uuid, self.__config.has_option(uuid, UscController.ROOT_CONTAINER_ID_OPTION) def __remove_monitor(self, path): for item in self.__monitor_list.keys(): if item.startswith(path): print u'\tStop monitoring {0}'.format(path) del self.__monitor_list[item] def __add_monitor(self, path): print u'\tStart monitoring {0}'.format(path) gfile = gio.File(path) monitor = gfile.monitor_directory(gio.FILE_MONITOR_SEND_MOVED, None) monitor.connect("changed", self.__directory_changed) self.__monitor_list[path] = monitor def __monitor_directory(self, path): self.__add_monitor(path) children = os.listdir(path) for child in children: new_path = os.path.join(path, child) if os.path.isdir(new_path): self.__monitor_directory(new_path) def __start_monitoring(self): self.__monitor_list = {} self.__monitor_directory(self.__track_path) print u'Type ctrl-c to stop.' try: main_loop = glib.MainLoop() main_loop.run() except: print u'Monitoring stopped.' def __directory_changed(self, monitor, file1, file2, evt_type): print if evt_type == gio.FILE_MONITOR_EVENT_CREATED: (parent, name) = os.path.split(file1.get_path()) for store in self.__store_list: parent_id = store.object_id_from_path(parent) container = _UscContainer(parent_id) store.add_file(container, name, parent) if os.path.isdir(file1.get_path()): self.__monitor_directory(file1.get_path()) elif evt_type == gio.FILE_MONITOR_EVENT_MOVED: (parent1, name1) = os.path.split(file1.get_path()) (parent2, name2) = os.path.split(file2.get_path()) renamed = (parent1 == parent2) if file1.get_path() in self.__monitor_list: self.__remove_monitor(file1.get_path()) for store in self.__store_list: object_id = store.object_id_from_path(file1.get_path()) obj = MediaObject(object_id) dlna_managed = obj.get_prop('DLNAManaged') if renamed and dlna_managed['ChangeMeta']: print u'\tRenaming {0} to {1}'.format(name1, name2) props = {'DisplayName' : name2} obj.update(props, []) store.rename_file(file1.get_path(), file2.get_path()) else: store.remove_file(file1.get_path()) parent_id = store.object_id_from_path(parent2) container = _UscContainer(parent_id) store.add_file(container, name2, parent2) if os.path.isdir(file2.get_path()): self.__monitor_directory(file2.get_path()) elif evt_type == gio.FILE_MONITOR_EVENT_DELETED: if file1.get_path() in self.__monitor_list: self.__remove_monitor(file1.get_path()) for store in self.__store_list: store.remove_file(file1.get_path()) else: return print u'Type ctrl-c to stop.' def track(self, track_path): """Sets the local folder that is to be synchronized.""" self.__track_path = track_path self.__config.set(UscController.DATA_PATH_SECTION, UscController.TRACK_PATH_OPTION, track_path) self.__write_config() def start_sync(self): """Performs a synchronization for all the media servers. Displays some progress information during the process. """ if not self.__track_path: print u'Error: track path is not set' return self.__store_list = [] print u'Syncing...' for device, uuid, init in self.__need_sync(self.__upnp.get_servers()): store = _UscStore(self.__store_path, uuid) store.initialize() self.__store_list.append(store) if not init: print u'Performing initial sync for server {0}:'.format(uuid) root = device.create_container_in_any('usc_root', 'container', ['image']) self.__config.set(uuid, UscController.ROOT_CONTAINER_ID_OPTION, root) self.__write_config() store.set_root(self.__track_path, root) else: print u'Performing normal sync for server {0}:'.format(uuid) root = self.__config.get(uuid, UscController.ROOT_CONTAINER_ID_OPTION) print u'\tRoot container is {0}'.format(root) root_container = _UscContainer(root) store.sync_deleted_files(self.__track_path) store.sync_added_files(root_container, self.__track_path) print u'Done.' if len(self.__store_list) == 0: print u'Nothing to do, stopping.' return print u'Now monitoring local changes...' self.__start_monitoring() def servers(self): """Displays media servers available on the network. Displays media servers information as well as the synchronized status. """ print u'Running servers:' for item in self.__upnp.get_servers(): try: server = Container(item) try: folder_name = server.get_prop('FriendlyName') except Exception: folder_name = server.get_prop('DisplayName') device = Device(item) dev_uuid = device.get_prop('UDN') dev_path = device.get_prop('Path') print u'{0:<25} Synchronized({2}) {3} {1}'.format( folder_name, dev_path, self.__config.has_section(dev_uuid), dev_uuid) except dbus.exceptions.DBusException as err: print u'Cannot retrieve properties for ' + item print str(err).strip()[:-1] def synchronized(self): """Displays the list of servers currently synchronized.""" print u'Synchronized servers:' for name in self.__config.sections(): if name != UscController.DATA_PATH_SECTION: print u'{0:<30}'.format(name) def add_server(self, server_path): """Adds a media server to the controller's list. server_path: d-bus path for the media server """ server = Device(server_path) server_uuid = server.get_prop('UDN') if not self.__config.has_section(server_uuid): srt = self.__check_trackable(server) if srt != None: self.__config.add_section(server_uuid) self.__write_config() else: print u"Sorry, the server {0} has no such capability and " \ "will not be synchronized.".format(server_path) def __remove_server(self, server_uuid): try: root = self.__config.get(server_uuid, UscController.ROOT_CONTAINER_ID_OPTION) MediaObject(root).delete() except: pass self.__config.remove_section(server_uuid) store = _UscStore(self.__store_path, server_uuid) store.remove() def remove_server(self, server_path): """Removes a media server from the controller's list. Also removes the server side synchronized file and folders. server_path: d-bus path for the media server """ server = Device(server_path) server_uuid = server.get_prop('UDN') if self.__config.has_section(server_uuid): self.__remove_server(server_uuid) self.__write_config() def reset(self): """Removes all media servers from the controller's list Also removes the server side synchronized file and folders. """ for name in self.__config.sections(): if name != UscController.DATA_PATH_SECTION: self.__remove_server(name) self.__write_config() if __name__ == '__main__': print u'An Upload Sync Controller sample app.' print controller = UscController() controller.servers() print print u'"controller" instance is ready for use.' print u'Type "help(UscController)" for more details and usage samples.' dleyna-server-0.6.0/test/dbus/usc.sh000077500000000000000000000000441305660312300173370ustar00rootroot00000000000000python -i -m upload_sync_controller